Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nextcloud-based User and Auth managers #2087

Merged
merged 1 commit into from
Sep 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changelog/unreleased/nextcloud-user-backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Enhancement: More unit tests for the Nextcloud auth and user managers

Adds more unit tests for the Nextcloud auth manager and the Nextcloud user manager

https://github.com/cs3org/reva/pull/2087
66 changes: 44 additions & 22 deletions pkg/auth/manager/nextcloud/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ type mgr struct {
endPoint string
}

type config struct {
// AuthManagerConfig contains config for a Nextcloud-based AuthManager
type AuthManagerConfig struct {
EndPoint string `mapstructure:"endpoint" docs:";The Nextcloud backend endpoint for user check"`
}

Expand All @@ -55,11 +56,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 +76,14 @@ func New(m map[string]interface{}) (auth.Manager, error) {
}
c.init()

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

// NewAuthManager returns a new Nextcloud-based AuthManager
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 +98,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 +123,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 +155,20 @@ 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 := range result.Scopes {
scope := result.Scopes[k]
pointersMap[k] = &scope
}
return user, nil, nil
return &result.User, pointersMap, nil
}
107 changes: 107 additions & 0 deletions pkg/auth/manager/nextcloud/nextcloud_server_mock.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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"

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