Skip to content

Commit

Permalink
Nextcloud-based User and Auth managers
Browse files Browse the repository at this point in the history
  • Loading branch information
michielbdejong committed Sep 20, 2021
1 parent 1b81695 commit 10ff742
Show file tree
Hide file tree
Showing 9 changed files with 780 additions and 67 deletions.
1 change: 1 addition & 0 deletions internal/grpc/services/storageprovider/storageprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,7 @@ func (s *service) ListContainer(ctx context.Context, req *provider.ListContainer

var infos = make([]*provider.ResourceInfo, 0, len(mds))
prefixMountpoint := utils.IsAbsoluteReference(req.Ref)

for _, md := range mds {
if err := s.wrap(ctx, md, prefixMountpoint); err != nil {
return &provider.ListContainerResponse{
Expand Down
63 changes: 41 additions & 22 deletions pkg/auth/manager/nextcloud/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ type mgr struct {
endPoint string
}

type config struct {
type AuthManagerConfig struct {
EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"`
}

Expand All @@ -55,11 +55,11 @@ type Action struct {
argS string
}

func (c *config) init() {
func (c *AuthManagerConfig) init() {
}

func parseConfig(m map[string]interface{}) (*config, error) {
c := &config{}
func parseConfig(m map[string]interface{}) (*AuthManagerConfig, error) {
c := &AuthManagerConfig{}
if err := mapstructure.Decode(m, c); err != nil {
err = errors.Wrap(err, "error decoding conf")
return nil, err
Expand All @@ -75,9 +75,13 @@ func New(m map[string]interface{}) (auth.Manager, error) {
}
c.init()

return NewAuthManager(c, &http.Client{})
}

func NewAuthManager(c *AuthManagerConfig, hc *http.Client) (auth.Manager, error) {
return &mgr{
endPoint: c.EndPoint, // e.g. "http://nc/apps/sciencemesh/"
client: &http.Client{},
client: hc,
}, nil
}

Expand All @@ -92,7 +96,7 @@ func (am *mgr) do(ctx context.Context, a Action) (int, []byte, error) {
// return 0, nil, err
// }
// url := am.endPoint + "~" + a.username + "/api/" + a.verb
url := "http://localhost/apps/sciencemesh/~" + a.username + "/api/" + a.verb
url := "http://localhost/apps/sciencemesh/~" + a.username + "/api/auth/" + a.verb
log.Info().Msgf("am.do %s %s", url, a.argS)
req, err := http.NewRequest(http.MethodPost, url, strings.NewReader(a.argS))
if err != nil {
Expand All @@ -117,18 +121,30 @@ func (am *mgr) do(ctx context.Context, a Action) (int, []byte, error) {

// Authenticate method as defined in https://github.com/cs3org/reva/blob/28500a8/pkg/auth/auth.go#L31-L33
func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string) (*user.User, map[string]*authpb.Scope, error) {
var params = map[string]string{
"password": clientSecret,
// "username": clientID,
type paramsObj struct {
ClientID string `json:"clientID"`
ClientSecret string `json:"clientSecret"`
// Scope authpb.Scope
}
bodyStr, err := json.Marshal(params)
bodyObj := &paramsObj{
ClientID: clientID,
ClientSecret: clientSecret,
// Scope: authpb.Scope{
// Resource: &types.OpaqueEntry{
// Decoder: "json",
// Value: []byte(`{"resource_id":{"storage_id":"storage-id","opaque_id":"opaque-id"},"path":"some/file/path.txt"}`),
// },
// Role: authpb.Role_ROLE_OWNER,
// },
}
bodyStr, err := json.Marshal(bodyObj)
if err != nil {
return nil, nil, err
}
log := appctx.GetLogger(ctx)
log.Info().Msgf("Authenticate %s %s", clientID, bodyStr)

statusCode, _, err := am.do(ctx, Action{"Authenticate", clientID, string(bodyStr)})
statusCode, body, err := am.do(ctx, Action{"Authenticate", clientID, string(bodyStr)})

if err != nil {
return nil, nil, err
Expand All @@ -137,16 +153,19 @@ func (am *mgr) Authenticate(ctx context.Context, clientID, clientSecret string)
if statusCode != 200 {
return nil, nil, errors.New("Username/password not recognized by Nextcloud backend")
}
user := &user.User{
Username: clientID,
Id: &user.UserId{
OpaqueId: clientID,
Idp: "localhost",
Type: 1,
},
Mail: clientID,
DisplayName: clientID,
Groups: nil,

type resultsObj struct {
User user.User `json:"user"`
Scopes map[string]authpb.Scope `json:"scopes"`
}
result := &resultsObj{}
err = json.Unmarshal(body, &result)
if err != nil {
return nil, nil, err
}
var pointersMap = make(map[string]*authpb.Scope)
for k, v := range result.Scopes {
pointersMap[k] = &v
}
return user, nil, nil
return &result.User, pointersMap, nil
}
117 changes: 117 additions & 0 deletions pkg/auth/manager/nextcloud/nextcloud_server_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package nextcloud

import (
"context"
"fmt"
"io"
"net"
"net/http"
"net/http/httptest"
"strings"
)

// Response contains data for the Nextcloud mock server to respond
// and to switch to a new server state
type Response struct {
code int
body string
newServerState string
}

const serverStateError = "ERROR"
const serverStateEmpty = "EMPTY"
const serverStateHome = "HOME"
const serverStateSubdir = "SUBDIR"
const serverStateNewdir = "NEWDIR"
const serverStateSubdirNewdir = "SUBDIR-NEWDIR"
const serverStateFileRestored = "FILE-RESTORED"
const serverStateGrantAdded = "GRANT-ADDED"
const serverStateGrantUpdated = "GRANT-UPDATED"
const serverStateGrantRemoved = "GRANT-REMOVED"
const serverStateRecycle = "RECYCLE"
const serverStateReference = "REFERENCE"
const serverStateMetadata = "METADATA"

var serverState = serverStateEmpty

var responses = map[string]Response{
`POST /apps/sciencemesh/~einstein/api/auth/Authenticate {"clientID":"einstein","clientSecret":"relativity"}`: {200, `{"user":{"id":{"idp":"some-idp","opaque_id":"some-opaque-user-id","type":1}},"scopes":{"user":{"resource":{"decoder":"json","value":"eyJyZXNvdXJjZV9pZCI6eyJzdG9yYWdlX2lkIjoic3RvcmFnZS1pZCIsIm9wYXF1ZV9pZCI6Im9wYXF1ZS1pZCJ9LCJwYXRoIjoic29tZS9maWxlL3BhdGgudHh0In0="},"role":1}}}`, serverStateHome},
}

// GetNextcloudServerMock returns a handler that pretends to be a remote Nextcloud server
func GetNextcloudServerMock(called *[]string) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
buf := new(strings.Builder)
_, err := io.Copy(buf, r.Body)
if err != nil {
panic("Error reading response into buffer")
}
var key = fmt.Sprintf("%s %s %s", r.Method, r.URL, buf.String())
fmt.Printf("Nextcloud Server Mock key components %s %d %s %d %s %d\n", r.Method, len(r.Method), r.URL.String(), len(r.URL.String()), buf.String(), len(buf.String()))
fmt.Printf("Nextcloud Server Mock key %s\n", key)
*called = append(*called, key)
response := responses[key]
if (response == Response{}) {
key = fmt.Sprintf("%s %s %s %s", r.Method, r.URL, buf.String(), serverState)
fmt.Printf("Nextcloud Server Mock key with State %s\n", key)
// *called = append(*called, key)
response = responses[key]
}
if (response == Response{}) {
fmt.Println("ERROR!!")
fmt.Println("ERROR!!")
fmt.Printf("Nextcloud Server Mock key not found! %s\n", key)
fmt.Println("ERROR!!")
fmt.Println("ERROR!!")
response = Response{200, fmt.Sprintf("response not defined! %s", key), serverStateEmpty}
}
serverState = responses[key].newServerState
if serverState == `` {
serverState = serverStateError
}
w.WriteHeader(response.code)
// w.Header().Set("Etag", "mocker-etag")
_, err = w.Write([]byte(responses[key].body))
if err != nil {
panic(err)
}
})
}

// TestingHTTPClient thanks to https://itnext.io/how-to-stub-requests-to-remote-hosts-with-go-6c2c1db32bf2
// Ideally, this function would live in tests/helpers, but
// if we put it there, it gets excluded by .dockerignore, and the
// Docker build fails (see https://github.com/cs3org/reva/issues/1999)
// So putting it here for now - open to suggestions if someone knows
// a better way to inject this.
func TestingHTTPClient(handler http.Handler) (*http.Client, func()) {
s := httptest.NewServer(handler)

cli := &http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, network, _ string) (net.Conn, error) {
return net.Dial(network, s.Listener.Addr().String())
},
},
}

return cli, s.Close
}
31 changes: 31 additions & 0 deletions pkg/auth/manager/nextcloud/nextcloud_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

package nextcloud_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestNextcloud(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Nextcloud Suite")
}
Loading

0 comments on commit 10ff742

Please sign in to comment.