From 1b29e56d1290dffa29fea4aa280806203617ac66 Mon Sep 17 00:00:00 2001 From: Ilja Neumann Date: Fri, 18 Dec 2020 00:05:12 +0100 Subject: [PATCH] account_resolver_test.go --- proxy/go.mod | 1 + .../pkg/middleware/_account_resolver_test.go | 213 --------------- proxy/pkg/middleware/account_resolver.go | 2 +- proxy/pkg/middleware/account_resolver_test.go | 135 ++++++++++ proxy/pkg/user/backend/backend_mock.go | 247 ++++++++++++++++++ 5 files changed, 384 insertions(+), 214 deletions(-) delete mode 100644 proxy/pkg/middleware/_account_resolver_test.go create mode 100644 proxy/pkg/middleware/account_resolver_test.go create mode 100644 proxy/pkg/user/backend/backend_mock.go diff --git a/proxy/go.mod b/proxy/go.mod index a0da92f11..077644803 100644 --- a/proxy/go.mod +++ b/proxy/go.mod @@ -24,6 +24,7 @@ require ( github.com/prometheus/client_golang v1.7.1 github.com/restic/calens v0.2.0 github.com/spf13/viper v1.7.0 + github.com/stretchr/testify v1.6.1 go.opencensus.io v0.22.5 golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d diff --git a/proxy/pkg/middleware/_account_resolver_test.go b/proxy/pkg/middleware/_account_resolver_test.go deleted file mode 100644 index 484e1664b..000000000 --- a/proxy/pkg/middleware/_account_resolver_test.go +++ /dev/null @@ -1,213 +0,0 @@ -package middleware - -/* - -Temporarily disabled - - -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/micro/go-micro/v2/client" - "github.com/owncloud/ocis/accounts/pkg/proto/v0" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/oidc" - "github.com/owncloud/ocis/proxy/pkg/config" - settings "github.com/owncloud/ocis/settings/pkg/proto/v0" -) - -func TestGetAccountSuccess(t *testing.T) { - if _, status := getAccount(log.NewLogger(), mockAccountResolverMiddlewareAccSvc(false, true), "mail eq 'success'"); status != 0 { - t.Errorf("expected an account") - } -} - -func TestGetAccountInternalError(t *testing.T) { - if _, status := getAccount(log.NewLogger(), mockAccountResolverMiddlewareAccSvc(true, false), "mail eq 'failure'"); status != http.StatusInternalServerError { - t.Errorf("expected an internal server error") - } -} - -func TestAccountResolverMiddleware(t *testing.T) { - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - m := AccountResolver( - Logger(log.NewLogger()), - TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}), - AccountsClient(mockAccountResolverMiddlewareAccSvc(false, true)), - SettingsRoleService(mockAccountResolverMiddlewareRolesSvc(false)), - )(next) - - r := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil) - w := httptest.NewRecorder() - ctx := oidc.NewContext(r.Context(), &oidc.StandardClaims{Email: "success"}) - r = r.WithContext(ctx) - m.ServeHTTP(w, r) - - if r.Header.Get("x-access-token") == "" { - t.Errorf("expected a token") - } -} - -func TestAccountResolverMiddlewareWithDisabledAccount(t *testing.T) { - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - m := AccountResolver( - Logger(log.NewLogger()), - TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}), - AccountsClient(mockAccountResolverMiddlewareAccSvc(false, false)), - SettingsRoleService(mockAccountResolverMiddlewareRolesSvc(false)), - )(next) - - r := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil) - w := httptest.NewRecorder() - ctx := oidc.NewContext(r.Context(), &oidc.StandardClaims{Email: "failure"}) - r = r.WithContext(ctx) - m.ServeHTTP(w, r) - - rsp := w.Result() - defer rsp.Body.Close() - - if rsp.StatusCode != http.StatusUnauthorized { - t.Errorf("expected a disabled account to be unauthorized, got: %d", rsp.StatusCode) - } -} - -func mockAccountResolverMiddlewareAccSvc(retErr, accEnabled bool) proto.AccountsService { - return &proto.MockAccountsService{ - ListFunc: func(ctx context.Context, in *proto.ListAccountsRequest, opts ...client.CallOption) (out *proto.ListAccountsResponse, err error) { - if retErr { - return nil, fmt.Errorf("error returned by mockAccountsService LIST") - } - return &proto.ListAccountsResponse{ - Accounts: []*proto.Account{ - { - Id: "yay", - AccountEnabled: accEnabled, - }, - }, - }, nil - }, - } -} - -func mockAccountResolverMiddlewareRolesSvc(returnError bool) settings.RoleService { - return &settings.MockRoleService{ - ListRoleAssignmentsFunc: func(ctx context.Context, req *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) (res *settings.ListRoleAssignmentsResponse, err error) { - if returnError { - return nil, fmt.Errorf("error returned by mockRoleService.ListRoleAssignments") - } - return &settings.ListRoleAssignmentsResponse{ - Assignments: []*settings.UserRoleAssignment{}, - }, nil - }, - } -} - -/* -import ( - "context" - "fmt" - "net/http" - "net/http/httptest" - "testing" - - "github.com/micro/go-micro/v2/client" - "github.com/owncloud/ocis/accounts/pkg/proto/v0" - "github.com/owncloud/ocis/ocis-pkg/log" - "github.com/owncloud/ocis/ocis-pkg/oidc" - "github.com/owncloud/ocis/proxy/pkg/config" - settings "github.com/owncloud/ocis/settings/pkg/proto/v0" -) - -// TODO testing the getAccount method should inject a cache -func TestGetAccountSuccess(t *testing.T) { - svcCache.Invalidate(AccountsKey, "success") - if _, status := getAccount(log.NewLogger(), mockAccountUUIDMiddlewareAccSvc(false, true), "mail eq 'success'"); status != 0 { - t.Errorf("expected an account") - } -} -func TestGetAccountInternalError(t *testing.T) { - svcCache.Invalidate(AccountsKey, "failure") - if _, status := getAccount(log.NewLogger(), mockAccountUUIDMiddlewareAccSvc(true, false), "mail eq 'failure'"); status != http.StatusInternalServerError { - t.Errorf("expected an internal server error") - } -} - -func TestAccountUUIDMiddleware(t *testing.T) { - svcCache.Invalidate(AccountsKey, "success") - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - m := AccountUUID( - Logger(log.NewLogger()), - TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}), - AccountsClient(mockAccountUUIDMiddlewareAccSvc(false, true)), - SettingsRoleService(mockAccountUUIDMiddlewareRolesSvc(false)), - )(next) - - r := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil) - w := httptest.NewRecorder() - ctx := oidc.NewContext(r.Context(), &oidc.StandardClaims{Email: "success"}) - r = r.WithContext(ctx) - m.ServeHTTP(w, r) - - if r.Header.Get("x-access-token") == "" { - t.Errorf("expected a token") - } -} - -func TestAccountUUIDMiddlewareWithDisabledAccount(t *testing.T) { - svcCache.Invalidate(AccountsKey, "failure") - next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}) - m := AccountUUID( - Logger(log.NewLogger()), - TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}), - AccountsClient(mockAccountUUIDMiddlewareAccSvc(false, false)), - SettingsRoleService(mockAccountUUIDMiddlewareRolesSvc(false)), - )(next) - - r := httptest.NewRequest(http.MethodGet, "http://www.example.com", nil) - w := httptest.NewRecorder() - ctx := oidc.NewContext(r.Context(), &oidc.StandardClaims{Email: "failure"}) - r = r.WithContext(ctx) - m.ServeHTTP(w, r) - - rsp := w.Result() - defer rsp.Body.Close() - - if rsp.StatusCode != http.StatusUnauthorized { - t.Errorf("expected a disabled account to be unauthorized, got: %d", rsp.StatusCode) - } -} - -func mockAccountUUIDMiddlewareAccSvc(retErr, accEnabled bool) proto.AccountsService { - return &proto.MockAccountsService{ - ListFunc: func(ctx context.Context, in *proto.ListAccountsRequest, opts ...client.CallOption) (out *proto.ListAccountsResponse, err error) { - if retErr { - return nil, fmt.Errorf("error returned by mockAccountsService LIST") - } - return &proto.ListAccountsResponse{ - Accounts: []*proto.Account{ - { - Id: "yay", - AccountEnabled: accEnabled, - }, - }, - }, nil - }, - } -} - -func mockAccountUUIDMiddlewareRolesSvc(returnError bool) settings.RoleService { - return &settings.MockRoleService{ - ListRoleAssignmentsFunc: func(ctx context.Context, req *settings.ListRoleAssignmentsRequest, opts ...client.CallOption) (res *settings.ListRoleAssignmentsResponse, err error) { - if returnError { - return nil, fmt.Errorf("error returned by mockRoleService.ListRoleAssignments") - } - return &settings.ListRoleAssignmentsResponse{ - Assignments: []*settings.UserRoleAssignment{}, - }, nil - }, - } -}*/ diff --git a/proxy/pkg/middleware/account_resolver.go b/proxy/pkg/middleware/account_resolver.go index 50e424102..b8379564f 100644 --- a/proxy/pkg/middleware/account_resolver.go +++ b/proxy/pkg/middleware/account_resolver.go @@ -99,7 +99,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - req.Header.Set("x-access-token", token) + req.Header.Set(tokenPkg.TokenHeader, token) m.next.ServeHTTP(w, req) } diff --git a/proxy/pkg/middleware/account_resolver_test.go b/proxy/pkg/middleware/account_resolver_test.go new file mode 100644 index 000000000..c30364230 --- /dev/null +++ b/proxy/pkg/middleware/account_resolver_test.go @@ -0,0 +1,135 @@ +package middleware + +import ( + "context" + "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/token" + "github.com/owncloud/ocis/ocis-pkg/log" + "github.com/owncloud/ocis/ocis-pkg/oidc" + "github.com/owncloud/ocis/proxy/pkg/config" + "github.com/owncloud/ocis/proxy/pkg/user/backend" + "github.com/stretchr/testify/assert" + "net/http" + "net/http/httptest" + "testing" +) + +func TestTokenIsAddedWithMailClaim(t *testing.T) { + sut := newMockAccountResolver(&userv1beta1.User{ + Id: &userv1beta1.UserId{Idp: "https://idx.example.com", OpaqueId: "123"}, + Mail: "foo@example.com", + }, nil) + + req, rw := mockRequest(&oidc.StandardClaims{ + Iss: "https://idx.example.com", + Email: "foo@example.com", + }) + + sut.ServeHTTP(rw, req) + + token := req.Header.Get(token.TokenHeader) + assert.NotEmpty(t, token) + assert.Contains(t, token, "eyJ") +} + +func TestTokenIsAddedWithUsernameClaim(t *testing.T) { + sut := newMockAccountResolver(&userv1beta1.User{ + Id: &userv1beta1.UserId{Idp: "https://idx.example.com", OpaqueId: "123"}, + Mail: "foo@example.com", + }, nil) + + req, rw := mockRequest(&oidc.StandardClaims{ + Iss: "https://idx.example.com", + PreferredUsername: "foo", + }) + + sut.ServeHTTP(rw, req) + + token := req.Header.Get(token.TokenHeader) + assert.NotEmpty(t, token) + + assert.Contains(t, token, "eyJ") +} + +func TestNSkipOnNoClaims(t *testing.T) { + sut := newMockAccountResolver(nil, backend.ErrAccountDisabled) + req, rw := mockRequest(nil) + + sut.ServeHTTP(rw, req) + + token := req.Header.Get("x-access-token") + assert.Empty(t, token) + assert.Equal(t, http.StatusOK, rw.Code) +} + +func TestUnauthorizedOnUserNotFound(t *testing.T) { + sut := newMockAccountResolver(nil, backend.ErrAccountNotFound) + req, rw := mockRequest(&oidc.StandardClaims{ + Iss: "https://idx.example.com", + PreferredUsername: "foo", + }) + + sut.ServeHTTP(rw, req) + + token := req.Header.Get(token.TokenHeader) + assert.Empty(t, token) + assert.Equal(t, http.StatusUnauthorized, rw.Code) +} + +func TestUnauthorizedOnUserDisabled(t *testing.T) { + sut := newMockAccountResolver(nil, backend.ErrAccountDisabled) + req, rw := mockRequest(&oidc.StandardClaims{ + Iss: "https://idx.example.com", + PreferredUsername: "foo", + }) + + sut.ServeHTTP(rw, req) + + token := req.Header.Get(token.TokenHeader) + assert.Empty(t, token) + assert.Equal(t, http.StatusUnauthorized, rw.Code) +} + +func TestInternalServerErrorOnMissingMailAndUsername(t *testing.T) { + sut := newMockAccountResolver(nil, backend.ErrAccountDisabled) + req, rw := mockRequest(&oidc.StandardClaims{ + Iss: "https://idx.example.com", + }) + + sut.ServeHTTP(rw, req) + + token := req.Header.Get(token.TokenHeader) + assert.Empty(t, token) + assert.Equal(t, http.StatusInternalServerError, rw.Code) +} + +func newMockAccountResolver(userBackendResult *userv1beta1.User, userBackendErr error) http.Handler { + mock := &backend.UserBackendMock{ + GetUserByClaimsFunc: func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) { + return userBackendResult, userBackendErr + }, + } + + return AccountResolver( + Logger(log.NewLogger()), + UserProvider(mock), + TokenManagerConfig(config.TokenManager{JWTSecret: "secret"}), + AutoprovisionAccounts(false), + )(mockHandler{}) +} + +func mockRequest(claims *oidc.StandardClaims) (*http.Request, *httptest.ResponseRecorder) { + if claims == nil { + return httptest.NewRequest("GET", "http://example.com/foo", nil), httptest.NewRecorder() + } + + ctx := oidc.NewContext(context.Background(), claims) + req := httptest.NewRequest("GET", "http://example.com/foo", nil).WithContext(ctx) + rw := httptest.NewRecorder() + + return req, rw +} + +type mockHandler struct{} + +func (m mockHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {} diff --git a/proxy/pkg/user/backend/backend_mock.go b/proxy/pkg/user/backend/backend_mock.go new file mode 100644 index 000000000..a1c21370c --- /dev/null +++ b/proxy/pkg/user/backend/backend_mock.go @@ -0,0 +1,247 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package backend + +import ( + "context" + "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/owncloud/ocis/ocis-pkg/oidc" + "sync" +) + +// Ensure, that UserBackendMock does implement UserBackend. +// If this is not the case, regenerate this file with moq. +var _ UserBackend = &UserBackendMock{} + +// UserBackendMock is a mock implementation of UserBackend. +// +// func TestSomethingThatUsesUserBackend(t *testing.T) { +// +// // make and configure a mocked UserBackend +// mockedUserBackend := &UserBackendMock{ +// AuthenticateFunc: func(ctx context.Context, username string, password string) (*userv1beta1.User, error) { +// panic("mock out the Authenticate method") +// }, +// CreateUserFromClaimsFunc: func(ctx context.Context, claims *oidc.StandardClaims) (*userv1beta1.User, error) { +// panic("mock out the CreateUserFromClaims method") +// }, +// GetUserByClaimsFunc: func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) { +// panic("mock out the GetUserByClaims method") +// }, +// GetUserGroupsFunc: func(ctx context.Context, userID string) { +// panic("mock out the GetUserGroups method") +// }, +// } +// +// // use mockedUserBackend in code that requires UserBackend +// // and then make assertions. +// +// } +type UserBackendMock struct { + // AuthenticateFunc mocks the Authenticate method. + AuthenticateFunc func(ctx context.Context, username string, password string) (*userv1beta1.User, error) + + // CreateUserFromClaimsFunc mocks the CreateUserFromClaims method. + CreateUserFromClaimsFunc func(ctx context.Context, claims *oidc.StandardClaims) (*userv1beta1.User, error) + + // GetUserByClaimsFunc mocks the GetUserByClaims method. + GetUserByClaimsFunc func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) + + // GetUserGroupsFunc mocks the GetUserGroups method. + GetUserGroupsFunc func(ctx context.Context, userID string) + + // calls tracks calls to the methods. + calls struct { + // Authenticate holds details about calls to the Authenticate method. + Authenticate []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Username is the username argument value. + Username string + // Password is the password argument value. + Password string + } + // CreateUserFromClaims holds details about calls to the CreateUserFromClaims method. + CreateUserFromClaims []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Claims is the claims argument value. + Claims *oidc.StandardClaims + } + // GetUserByClaims holds details about calls to the GetUserByClaims method. + GetUserByClaims []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // Claim is the claim argument value. + Claim string + // Value is the value argument value. + Value string + // WithRoles is the withRoles argument value. + WithRoles bool + } + // GetUserGroups holds details about calls to the GetUserGroups method. + GetUserGroups []struct { + // Ctx is the ctx argument value. + Ctx context.Context + // UserID is the userID argument value. + UserID string + } + } + lockAuthenticate sync.RWMutex + lockCreateUserFromClaims sync.RWMutex + lockGetUserByClaims sync.RWMutex + lockGetUserGroups sync.RWMutex +} + +// Authenticate calls AuthenticateFunc. +func (mock *UserBackendMock) Authenticate(ctx context.Context, username string, password string) (*userv1beta1.User, error) { + if mock.AuthenticateFunc == nil { + panic("UserBackendMock.AuthenticateFunc: method is nil but UserBackend.Authenticate was just called") + } + callInfo := struct { + Ctx context.Context + Username string + Password string + }{ + Ctx: ctx, + Username: username, + Password: password, + } + mock.lockAuthenticate.Lock() + mock.calls.Authenticate = append(mock.calls.Authenticate, callInfo) + mock.lockAuthenticate.Unlock() + return mock.AuthenticateFunc(ctx, username, password) +} + +// AuthenticateCalls gets all the calls that were made to Authenticate. +// Check the length with: +// len(mockedUserBackend.AuthenticateCalls()) +func (mock *UserBackendMock) AuthenticateCalls() []struct { + Ctx context.Context + Username string + Password string +} { + var calls []struct { + Ctx context.Context + Username string + Password string + } + mock.lockAuthenticate.RLock() + calls = mock.calls.Authenticate + mock.lockAuthenticate.RUnlock() + return calls +} + +// CreateUserFromClaims calls CreateUserFromClaimsFunc. +func (mock *UserBackendMock) CreateUserFromClaims(ctx context.Context, claims *oidc.StandardClaims) (*userv1beta1.User, error) { + if mock.CreateUserFromClaimsFunc == nil { + panic("UserBackendMock.CreateUserFromClaimsFunc: method is nil but UserBackend.CreateUserFromClaims was just called") + } + callInfo := struct { + Ctx context.Context + Claims *oidc.StandardClaims + }{ + Ctx: ctx, + Claims: claims, + } + mock.lockCreateUserFromClaims.Lock() + mock.calls.CreateUserFromClaims = append(mock.calls.CreateUserFromClaims, callInfo) + mock.lockCreateUserFromClaims.Unlock() + return mock.CreateUserFromClaimsFunc(ctx, claims) +} + +// CreateUserFromClaimsCalls gets all the calls that were made to CreateUserFromClaims. +// Check the length with: +// len(mockedUserBackend.CreateUserFromClaimsCalls()) +func (mock *UserBackendMock) CreateUserFromClaimsCalls() []struct { + Ctx context.Context + Claims *oidc.StandardClaims +} { + var calls []struct { + Ctx context.Context + Claims *oidc.StandardClaims + } + mock.lockCreateUserFromClaims.RLock() + calls = mock.calls.CreateUserFromClaims + mock.lockCreateUserFromClaims.RUnlock() + return calls +} + +// GetUserByClaims calls GetUserByClaimsFunc. +func (mock *UserBackendMock) GetUserByClaims(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) { + if mock.GetUserByClaimsFunc == nil { + panic("UserBackendMock.GetUserByClaimsFunc: method is nil but UserBackend.GetUserByClaims was just called") + } + callInfo := struct { + Ctx context.Context + Claim string + Value string + WithRoles bool + }{ + Ctx: ctx, + Claim: claim, + Value: value, + WithRoles: withRoles, + } + mock.lockGetUserByClaims.Lock() + mock.calls.GetUserByClaims = append(mock.calls.GetUserByClaims, callInfo) + mock.lockGetUserByClaims.Unlock() + return mock.GetUserByClaimsFunc(ctx, claim, value, withRoles) +} + +// GetUserByClaimsCalls gets all the calls that were made to GetUserByClaims. +// Check the length with: +// len(mockedUserBackend.GetUserByClaimsCalls()) +func (mock *UserBackendMock) GetUserByClaimsCalls() []struct { + Ctx context.Context + Claim string + Value string + WithRoles bool +} { + var calls []struct { + Ctx context.Context + Claim string + Value string + WithRoles bool + } + mock.lockGetUserByClaims.RLock() + calls = mock.calls.GetUserByClaims + mock.lockGetUserByClaims.RUnlock() + return calls +} + +// GetUserGroups calls GetUserGroupsFunc. +func (mock *UserBackendMock) GetUserGroups(ctx context.Context, userID string) { + if mock.GetUserGroupsFunc == nil { + panic("UserBackendMock.GetUserGroupsFunc: method is nil but UserBackend.GetUserGroups was just called") + } + callInfo := struct { + Ctx context.Context + UserID string + }{ + Ctx: ctx, + UserID: userID, + } + mock.lockGetUserGroups.Lock() + mock.calls.GetUserGroups = append(mock.calls.GetUserGroups, callInfo) + mock.lockGetUserGroups.Unlock() + mock.GetUserGroupsFunc(ctx, userID) +} + +// GetUserGroupsCalls gets all the calls that were made to GetUserGroups. +// Check the length with: +// len(mockedUserBackend.GetUserGroupsCalls()) +func (mock *UserBackendMock) GetUserGroupsCalls() []struct { + Ctx context.Context + UserID string +} { + var calls []struct { + Ctx context.Context + UserID string + } + mock.lockGetUserGroups.RLock() + calls = mock.calls.GetUserGroups + mock.lockGetUserGroups.RUnlock() + return calls +}