diff --git a/changelog/unreleased/reva-auth-tokens.md b/changelog/unreleased/reva-auth-tokens.md new file mode 100644 index 000000000..075479cc9 --- /dev/null +++ b/changelog/unreleased/reva-auth-tokens.md @@ -0,0 +1,11 @@ +Enhancement: Use reva's Authenticate method instead of spawning token managers + +When using the CS3 proxy backend, we previously obtained the user from reva's +userprovider service and minted the token ourselves. This required maintaining +a shared JWT secret between ocis and reva, as well duplication of logic. This +PR delegates this logic by using the `Authenticate` method provided by the reva +gateway service to obtain this token, making it an arbitrary, indestructible +entry. Currently, the changes have been made to the proxy service but will be +extended to others as well. + +https://github.com/owncloud/ocis/pull/2528 diff --git a/proxy/pkg/command/server.go b/proxy/pkg/command/server.go index d9c49da24..fb6f7aaa5 100644 --- a/proxy/pkg/command/server.go +++ b/proxy/pkg/command/server.go @@ -9,6 +9,7 @@ import ( "time" "github.com/coreos/go-oidc/v3/oidc" + "github.com/cs3org/reva/pkg/token/manager/jwt" chimiddleware "github.com/go-chi/chi/v5/middleware" "github.com/justinas/alice" "github.com/micro/cli/v2" @@ -148,14 +149,23 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic var userProvider backend.UserBackend switch cfg.AccountBackend { case "accounts": + tokenManager, err := jwt.New(map[string]interface{}{ + "secret": cfg.TokenManager.JWTSecret, + "expires": int64(24 * 60 * 60), + }) + if err != nil { + l.Error().Err(err). + Msg("Failed to create token manager") + } userProvider = backend.NewAccountsServiceUserBackend( acc.NewAccountsService("com.owncloud.api.accounts", grpc.DefaultClient), rolesClient, cfg.OIDC.Issuer, + tokenManager, l, ) case "cs3": - userProvider = backend.NewCS3UserBackend(revaClient, rolesClient, revaClient, l) + userProvider = backend.NewCS3UserBackend(rolesClient, revaClient, cfg.MachineAuthAPIKey, l) default: l.Fatal().Msgf("Invalid accounts backend type '%s'", cfg.AccountBackend) } @@ -217,7 +227,6 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic middleware.AccountResolver( middleware.Logger(l), middleware.UserProvider(userProvider), - middleware.TokenManagerConfig(cfg.TokenManager), middleware.UserOIDCClaim(cfg.UserOIDCClaim), middleware.UserCS3Claim(cfg.UserCS3Claim), middleware.AutoprovisionAccounts(cfg.AutoprovisionAccounts), @@ -232,7 +241,6 @@ func loadMiddlewares(ctx context.Context, l log.Logger, cfg *config.Config) alic // finally, trigger home creation when a user logs in middleware.CreateHome( middleware.Logger(l), - middleware.TokenManagerConfig(cfg.TokenManager), middleware.RevaGatewayClient(revaClient), ), ) diff --git a/proxy/pkg/config/config.go b/proxy/pkg/config/config.go index 3af176389..9983cf9aa 100644 --- a/proxy/pkg/config/config.go +++ b/proxy/pkg/config/config.go @@ -121,8 +121,9 @@ type Config struct { Reva Reva PreSignedURL PreSignedURL AccountBackend string - UserOIDCClaim string - UserCS3Claim string + UserOIDCClaim string + UserCS3Claim string + MachineAuthAPIKey string AutoprovisionAccounts bool EnableBasicAuth bool InsecureBackends bool diff --git a/proxy/pkg/flagset/flagset.go b/proxy/pkg/flagset/flagset.go index 22960d5b4..1bad97812 100644 --- a/proxy/pkg/flagset/flagset.go +++ b/proxy/pkg/flagset/flagset.go @@ -291,6 +291,14 @@ func ServerWithConfig(cfg *config.Config) []cli.Flag { Destination: &cfg.AccountBackend, }, + &cli.StringFlag{ + Name: "machine-auth-api-key", + Value: flags.OverrideDefaultString(cfg.MachineAuthAPIKey, "change-me-please"), + Usage: "the API key to be used for the machine auth driver in reva", + EnvVars: []string{"PROXY_MACHINE_AUTH_API_KEY"}, + Destination: &cfg.MachineAuthAPIKey, + }, + // Reva Middlewares Config &cli.StringSliceFlag{ Name: "proxy-user-agent-lock-in", diff --git a/proxy/pkg/middleware/account_resolver.go b/proxy/pkg/middleware/account_resolver.go index fe791ec0f..ec259b50c 100644 --- a/proxy/pkg/middleware/account_resolver.go +++ b/proxy/pkg/middleware/account_resolver.go @@ -4,12 +4,9 @@ import ( "errors" "net/http" - "github.com/cs3org/reva/pkg/auth/scope" "github.com/owncloud/ocis/proxy/pkg/user/backend" revactx "github.com/cs3org/reva/pkg/ctx" - "github.com/cs3org/reva/pkg/token" - "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/oidc" ) @@ -21,18 +18,9 @@ func AccountResolver(optionSetters ...Option) func(next http.Handler) http.Handl logger := options.Logger return func(next http.Handler) http.Handler { - tokenManager, err := jwt.New(map[string]interface{}{ - "secret": options.TokenManagerConfig.JWTSecret, - "expires": int64(60), - }) - if err != nil { - logger.Fatal().Err(err).Msg("Could not initialize token-manager") - } - return &accountResolver{ next: next, logger: logger, - tokenManager: tokenManager, userProvider: options.UserProvider, userOIDCClaim: options.UserOIDCClaim, userCS3Claim: options.UserCS3Claim, @@ -44,7 +32,6 @@ func AccountResolver(optionSetters ...Option) func(next http.Handler) http.Handl type accountResolver struct { next http.Handler logger log.Logger - tokenManager token.Manager userProvider backend.UserBackend autoProvisionAccounts bool userOIDCClaim string @@ -56,6 +43,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) { ctx := req.Context() claims := oidc.FromContext(ctx) u, ok := revactx.ContextGetUser(ctx) + token := "" // TODO what if an X-Access-Token is set? happens eg for download requests to the /data endpoint in the reva frontend if claims == nil && !ok { @@ -74,7 +62,7 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - u, err = m.userProvider.GetUserByClaims(req.Context(), m.userCS3Claim, value, true) + u, token, err = m.userProvider.GetUserByClaims(req.Context(), m.userCS3Claim, value, true) if errors.Is(err, backend.ErrAccountNotFound) { m.logger.Debug().Str("claim", m.userOIDCClaim).Str("value", value).Msg("User by claim not found") @@ -108,18 +96,6 @@ func (m accountResolver) ServeHTTP(w http.ResponseWriter, req *http.Request) { m.logger.Debug().Interface("claims", claims).Interface("user", u).Msg("associated claims with user") } - s, err := scope.AddOwnerScope(nil) - if err != nil { - m.logger.Error().Err(err).Msg("could not get owner scope") - return - } - token, err := m.tokenManager.MintToken(ctx, u, s) - if err != nil { - m.logger.Error().Err(err).Msg("could not mint token") - w.WriteHeader(http.StatusInternalServerError) - return - } - req.Header.Set(revactx.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 index 9674c1303..2846c892a 100644 --- a/proxy/pkg/middleware/account_resolver_test.go +++ b/proxy/pkg/middleware/account_resolver_test.go @@ -7,7 +7,9 @@ import ( "testing" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/auth/scope" revactx "github.com/cs3org/reva/pkg/ctx" + "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/oidc" "github.com/owncloud/ocis/proxy/pkg/config" @@ -106,9 +108,20 @@ func TestInternalServerErrorOnMissingMailAndUsername(t *testing.T) { } func newMockAccountResolver(userBackendResult *userv1beta1.User, userBackendErr error, oidcclaim, cs3claim string) http.Handler { + tokenManager, _ := jwt.New(map[string]interface{}{ + "secret": "change-me", + "expires": int64(60), + }) + + token := "" + if userBackendResult != nil { + s, _ := scope.AddOwnerScope(nil) + token, _ = tokenManager.MintToken(context.Background(), userBackendResult, s) + } + mock := &test.UserBackendMock{ - GetUserByClaimsFunc: func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) { - return userBackendResult, userBackendErr + GetUserByClaimsFunc: func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, string, error) { + return userBackendResult, token, userBackendErr }, } diff --git a/proxy/pkg/middleware/basic_auth.go b/proxy/pkg/middleware/basic_auth.go index e6f000ac6..b778c092c 100644 --- a/proxy/pkg/middleware/basic_auth.go +++ b/proxy/pkg/middleware/basic_auth.go @@ -41,7 +41,7 @@ func BasicAuth(optionSetters ...Option) func(next http.Handler) http.Handler { removeSuperfluousAuthenticate(w) login, password, _ := req.BasicAuth() - user, err := h.userProvider.Authenticate(req.Context(), login, password) + user, _, err := h.userProvider.Authenticate(req.Context(), login, password) // touch is a user agent locking guard, when touched changes to true it indicates the User-Agent on the // request is configured to support only one challenge, it it remains untouched, there are no considera- diff --git a/proxy/pkg/middleware/create_home.go b/proxy/pkg/middleware/create_home.go index 84ff08910..9de555653 100644 --- a/proxy/pkg/middleware/create_home.go +++ b/proxy/pkg/middleware/create_home.go @@ -8,8 +8,6 @@ import ( provider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" revactx "github.com/cs3org/reva/pkg/ctx" "github.com/cs3org/reva/pkg/rgrpc/status" - "github.com/cs3org/reva/pkg/token" - "github.com/cs3org/reva/pkg/token/manager/jwt" "github.com/owncloud/ocis/ocis-pkg/log" "google.golang.org/grpc/metadata" ) @@ -20,17 +18,9 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler { logger := options.Logger return func(next http.Handler) http.Handler { - tokenManager, err := jwt.New(map[string]interface{}{ - "secret": options.TokenManagerConfig.JWTSecret, - }) - if err != nil { - logger.Fatal().Err(err).Msgf("Could not initialize token-manager") - } - return &createHome{ next: next, logger: logger, - tokenManager: tokenManager, revaGatewayClient: options.RevaGatewayClient, } } @@ -39,7 +29,6 @@ func CreateHome(optionSetters ...Option) func(next http.Handler) http.Handler { type createHome struct { next http.Handler logger log.Logger - tokenManager token.Manager revaGatewayClient gateway.GatewayAPIClient } diff --git a/proxy/pkg/middleware/options.go b/proxy/pkg/middleware/options.go index 3f91cf1c2..f9b7afd78 100644 --- a/proxy/pkg/middleware/options.go +++ b/proxy/pkg/middleware/options.go @@ -45,9 +45,9 @@ type Options struct { // PreSignedURLConfig to configure the middleware PreSignedURLConfig config.PreSignedURL // UserOIDCClaim to read from the oidc claims - UserOIDCClaim string + UserOIDCClaim string // UserCS3Claim to use when looking up a user in the CS3 API - UserCS3Claim string + UserCS3Claim string // AutoprovisionAccounts when an accountResolver does not exist. AutoprovisionAccounts bool // EnableBasicAuth to allow basic auth diff --git a/proxy/pkg/middleware/signed_url_auth.go b/proxy/pkg/middleware/signed_url_auth.go index 76f0dff50..6eccbab15 100644 --- a/proxy/pkg/middleware/signed_url_auth.go +++ b/proxy/pkg/middleware/signed_url_auth.go @@ -48,7 +48,7 @@ func (m signedURLAuth) ServeHTTP(w http.ResponseWriter, req *http.Request) { return } - user, err := m.userProvider.GetUserByClaims(req.Context(), "username", req.URL.Query().Get("OC-Credential"), true) + user, _, err := m.userProvider.GetUserByClaims(req.Context(), "username", req.URL.Query().Get("OC-Credential"), true) if err != nil { m.logger.Error().Err(err).Msg("Could not get user by claim") w.WriteHeader(http.StatusInternalServerError) diff --git a/proxy/pkg/user/backend/accounts.go b/proxy/pkg/user/backend/accounts.go index 96b57fb5a..34d93d390 100644 --- a/proxy/pkg/user/backend/accounts.go +++ b/proxy/pkg/user/backend/accounts.go @@ -8,6 +8,8 @@ import ( cs3 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" types "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" + "github.com/cs3org/reva/pkg/auth/scope" + "github.com/cs3org/reva/pkg/token" accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/oidc" @@ -15,11 +17,12 @@ import ( ) // NewAccountsServiceUserBackend creates a user-provider which fetches users from the ocis accounts-service -func NewAccountsServiceUserBackend(ac accounts.AccountsService, rs settings.RoleService, oidcISS string, logger log.Logger) UserBackend { +func NewAccountsServiceUserBackend(ac accounts.AccountsService, rs settings.RoleService, oidcISS string, tokenManager token.Manager, logger log.Logger) UserBackend { return &accountsServiceBackend{ accountsClient: ac, settingsRoleService: rs, OIDCIss: oidcISS, + tokenManager: tokenManager, logger: logger, } } @@ -29,9 +32,10 @@ type accountsServiceBackend struct { settingsRoleService settings.RoleService OIDCIss string logger log.Logger + tokenManager token.Manager } -func (a accountsServiceBackend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, error) { +func (a accountsServiceBackend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) { var account *accounts.Account var status int var query string @@ -44,38 +48,43 @@ func (a accountsServiceBackend) GetUserByClaims(ctx context.Context, claim, valu case "id": query = fmt.Sprintf("id eq '%s'", strings.ReplaceAll(value, "'", "''")) default: - return nil, fmt.Errorf("invalid user by claim lookup must be 'mail', 'username' or 'id") + return nil, "", fmt.Errorf("invalid user by claim lookup must be 'mail', 'username' or 'id") } account, status = a.getAccount(ctx, query) if status == http.StatusNotFound { - return nil, ErrAccountNotFound + return nil, "", ErrAccountNotFound } if status != 0 || account == nil { - return nil, fmt.Errorf("could not get account, got status: %d", status) + return nil, "", fmt.Errorf("could not get account, got status: %d", status) } if !account.AccountEnabled { - return nil, ErrAccountDisabled + return nil, "", ErrAccountDisabled } user := a.accountToUser(account) + token, err := a.generateToken(ctx, user) + if err != nil { + return nil, "", err + } + if !withRoles { - return user, nil + return user, token, nil } if err := injectRoles(ctx, user, a.settingsRoleService); err != nil { a.logger.Warn().Err(err).Msgf("Could not load roles... continuing without") } - return user, nil + return user, token, nil } // Authenticate authenticates against the accounts services and returns the user on success -func (a *accountsServiceBackend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, error) { +func (a *accountsServiceBackend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) { query := fmt.Sprintf( "login eq '%s' and password eq '%s'", strings.ReplaceAll(username, "'", "''"), @@ -84,23 +93,28 @@ func (a *accountsServiceBackend) Authenticate(ctx context.Context, username stri account, status := a.getAccount(ctx, query) if status != 0 { - return nil, fmt.Errorf("could not authenticate with username, password for user %s. Status: %d", username, status) + return nil, "", fmt.Errorf("could not authenticate with username, password for user %s. Status: %d", username, status) } user := a.accountToUser(account) + token, err := a.generateToken(ctx, user) + if err != nil { + return nil, "", err + } + if err := injectRoles(ctx, user, a.settingsRoleService); err != nil { a.logger.Warn().Err(err).Msgf("Could not load roles... continuing without") } - return user, nil + return user, token, nil } func (a accountsServiceBackend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) { req := &accounts.CreateAccountRequest{ Account: &accounts.Account{ - CreationType: "LocalAccount", - AccountEnabled: true, + CreationType: "LocalAccount", + AccountEnabled: true, }, } var ok bool @@ -144,7 +158,7 @@ func (a *accountsServiceBackend) accountToUser(account *accounts.Account) *cs3.U Id: &cs3.UserId{ OpaqueId: account.Id, Idp: a.OIDCIss, - Type: cs3.UserType_USER_TYPE_PRIMARY, // TODO: once we have support for other user types, this needs to be inferred + Type: cs3.UserType_USER_TYPE_PRIMARY, // TODO: once we have support for other user types, this needs to be inferred }, Username: account.OnPremisesSamAccountName, DisplayName: account.DisplayName, @@ -185,6 +199,21 @@ func (a *accountsServiceBackend) getAccount(ctx context.Context, query string) ( return } +func (a *accountsServiceBackend) generateToken(ctx context.Context, u *cs3.User) (string, error) { + s, err := scope.AddOwnerScope(nil) + if err != nil { + a.logger.Error().Err(err).Msg("could not get owner scope") + return "", err + } + + token, err := a.tokenManager.MintToken(ctx, u, s) + if err != nil { + a.logger.Error().Err(err).Msg("could not mint token") + return "", err + } + return token, nil +} + func expandGroups(account *accounts.Account) []string { groups := make([]string, len(account.MemberOf)) for i := range account.MemberOf { diff --git a/proxy/pkg/user/backend/accounts_test.go b/proxy/pkg/user/backend/accounts_test.go index 5f57d3e67..57e652def 100644 --- a/proxy/pkg/user/backend/accounts_test.go +++ b/proxy/pkg/user/backend/accounts_test.go @@ -6,6 +6,7 @@ import ( "github.com/asim/go-micro/v3/client" userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/cs3org/reva/pkg/token/manager/jwt" accounts "github.com/owncloud/ocis/accounts/pkg/proto/v0" "github.com/owncloud/ocis/ocis-pkg/log" "github.com/owncloud/ocis/ocis-pkg/oidc" @@ -51,7 +52,7 @@ func TestGetUserByClaimsFound(t *testing.T) { for k := range tests { t.Run(tests[k].id, func(t *testing.T) { - u, err := accBackend.GetUserByClaims(context.Background(), tests[k].claim, tests[k].value, true) + u, _, err := accBackend.GetUserByClaims(context.Background(), tests[k].claim, tests[k].value, true) assert.NoError(t, err) assert.NotNil(t, u) @@ -64,7 +65,7 @@ func TestGetUserByClaimsFound(t *testing.T) { func TestGetUserByClaimsNotFound(t *testing.T) { accBackend := newAccountsBackend([]*accounts.Account{}, expectedRoles) - u, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true) + u, _, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true) assert.Error(t, err) assert.Nil(t, u) @@ -73,7 +74,7 @@ func TestGetUserByClaimsNotFound(t *testing.T) { func TestGetUserByClaimsInvalidClaim(t *testing.T) { accBackend := newAccountsBackend([]*accounts.Account{}, expectedRoles) - u, err := accBackend.GetUserByClaims(context.Background(), "invalidClaimName", "efwfwfwfe", true) + u, _, err := accBackend.GetUserByClaims(context.Background(), "invalidClaimName", "efwfwfwfe", true) assert.Nil(t, u) assert.Error(t, err) @@ -81,7 +82,7 @@ func TestGetUserByClaimsInvalidClaim(t *testing.T) { func TestGetUserByClaimsDisabledAccount(t *testing.T) { accBackend := newAccountsBackend([]*accounts.Account{{AccountEnabled: false}}, expectedRoles) - u, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true) + u, _, err := accBackend.GetUserByClaims(context.Background(), "mail", "foo@example.com", true) assert.Nil(t, u) assert.Error(t, err) @@ -90,7 +91,7 @@ func TestGetUserByClaimsDisabledAccount(t *testing.T) { func TestAuthenticate(t *testing.T) { accBackend := newAccountsBackend(mockAccResp, expectedRoles) - u, err := accBackend.Authenticate(context.Background(), "foo", "secret") + u, _, err := accBackend.Authenticate(context.Background(), "foo", "secret") assert.NoError(t, err) assert.NotNil(t, u) @@ -99,7 +100,7 @@ func TestAuthenticate(t *testing.T) { func TestAuthenticateFailed(t *testing.T) { accBackend := newAccountsBackend([]*accounts.Account{}, expectedRoles) - u, err := accBackend.Authenticate(context.Background(), "foo", "secret") + u, _, err := accBackend.Authenticate(context.Background(), "foo", "secret") assert.Nil(t, u) assert.Error(t, err) @@ -151,7 +152,11 @@ func assertUserMatchesAccount(t *testing.T, exp *accounts.Account, act *userv1be func newAccountsBackend(mockAccounts []*accounts.Account, mockRoles []*settings.UserRoleAssignment) UserBackend { accSvc, roleSvc := getAccountService(mockAccounts, nil), getRoleService(mockRoles, nil) - accBackend := NewAccountsServiceUserBackend(accSvc, roleSvc, "https://idp.example.org", log.NewLogger()) + tokenManager, _ := jwt.New(map[string]interface{}{ + "secret": "change-me", + "expires": int64(24 * 60 * 60), + }) + accBackend := NewAccountsServiceUserBackend(accSvc, roleSvc, "https://idp.example.org", tokenManager, log.NewLogger()) zerolog.SetGlobalLevel(zerolog.Disabled) return accBackend } diff --git a/proxy/pkg/user/backend/backend.go b/proxy/pkg/user/backend/backend.go index 97314fb56..ab3fb3c06 100644 --- a/proxy/pkg/user/backend/backend.go +++ b/proxy/pkg/user/backend/backend.go @@ -23,8 +23,8 @@ var ( // UserBackend allows the proxy to retrieve users from different user-backends (accounts-service, CS3) type UserBackend interface { - GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, error) - Authenticate(ctx context.Context, username string, password string) (*cs3.User, error) + GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) + Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) GetUserGroups(ctx context.Context, userID string) } diff --git a/proxy/pkg/user/backend/cs3.go b/proxy/pkg/user/backend/cs3.go index fda3f138c..dc413919a 100644 --- a/proxy/pkg/user/backend/cs3.go +++ b/proxy/pkg/user/backend/cs3.go @@ -14,42 +14,48 @@ import ( ) type cs3backend struct { - userProvider cs3.UserAPIClient settingsRoleService settings.RoleService authProvider RevaAuthenticator + machineAuthAPIKey string logger log.Logger } // NewCS3UserBackend creates a user-provider which fetches users from a CS3 UserBackend -func NewCS3UserBackend(up cs3.UserAPIClient, rs settings.RoleService, ap RevaAuthenticator, logger log.Logger) UserBackend { +func NewCS3UserBackend(rs settings.RoleService, ap RevaAuthenticator, machineAuthAPIKey string, logger log.Logger) UserBackend { return &cs3backend{ - userProvider: up, settingsRoleService: rs, authProvider: ap, + machineAuthAPIKey: machineAuthAPIKey, logger: logger, } } -func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, error) { - res, err := c.userProvider.GetUserByClaim(ctx, &cs3.GetUserByClaimRequest{ - Claim: claim, - Value: value, +func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, withRoles bool) (*cs3.User, string, error) { + // We only support authentication via username for now + if claim != "username" { + return nil, "", fmt.Errorf("claim: %s not supported", claim) + } + + res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ + Type: "machine", + ClientId: value, + ClientSecret: c.machineAuthAPIKey, }) switch { case err != nil: - return nil, fmt.Errorf("could not get user by claim %v with value %v: %w", claim, value, err) + return nil, "", fmt.Errorf("could not get user by claim %v with value %v: %w", claim, value, err) case res.Status.Code != rpcv1beta1.Code_CODE_OK: if res.Status.Code == rpcv1beta1.Code_CODE_NOT_FOUND { - return nil, ErrAccountNotFound + return nil, "", ErrAccountNotFound } - return nil, fmt.Errorf("could not get user by claim %v with value %v : %w ", claim, value, err) + return nil, "", fmt.Errorf("could not get user by claim %v with value %v : %w ", claim, value, err) } user := res.User if !withRoles { - return user, nil + return user, res.Token, nil } var roleIDs []string @@ -82,10 +88,10 @@ func (c *cs3backend) GetUserByClaims(ctx context.Context, claim, value string, w user.Opaque.Map["roles"] = enc } - return res.User, nil + return user, res.Token, nil } -func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, error) { +func (c *cs3backend) Authenticate(ctx context.Context, username string, password string) (*cs3.User, string, error) { res, err := c.authProvider.Authenticate(ctx, &gateway.AuthenticateRequest{ Type: "basic", ClientId: username, @@ -94,12 +100,12 @@ func (c *cs3backend) Authenticate(ctx context.Context, username string, password switch { case err != nil: - return nil, fmt.Errorf("could not authenticate with username and password user: %s, %w", username, err) + return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, %w", username, err) case res.Status.Code != rpcv1beta1.Code_CODE_OK: - return nil, fmt.Errorf("could not authenticate with username and password user: %s, got code: %d", username, res.Status.Code) + return nil, "", fmt.Errorf("could not authenticate with username and password user: %s, got code: %d", username, res.Status.Code) } - return res.User, nil + return res.User, res.Token, nil } func (c *cs3backend) CreateUserFromClaims(ctx context.Context, claims map[string]interface{}) (*cs3.User, error) { diff --git a/proxy/pkg/user/backend/test/backend_mock.go b/proxy/pkg/user/backend/test/backend_mock.go index 1ebba2b92..be27d84b5 100644 --- a/proxy/pkg/user/backend/test/backend_mock.go +++ b/proxy/pkg/user/backend/test/backend_mock.go @@ -5,9 +5,10 @@ package test import ( "context" - "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" - "github.com/owncloud/ocis/proxy/pkg/user/backend" "sync" + + userv1beta1 "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1" + "github.com/owncloud/ocis/proxy/pkg/user/backend" ) // Ensure, that UserBackendMock does implement UserBackend. @@ -40,13 +41,13 @@ var _ backend.UserBackend = &UserBackendMock{} // } type UserBackendMock struct { // AuthenticateFunc mocks the Authenticate method. - AuthenticateFunc func(ctx context.Context, username string, password string) (*userv1beta1.User, error) + AuthenticateFunc func(ctx context.Context, username string, password string) (*userv1beta1.User, string, error) // CreateUserFromClaimsFunc mocks the CreateUserFromClaims method. CreateUserFromClaimsFunc func(ctx context.Context, claims map[string]interface{}) (*userv1beta1.User, error) // GetUserByClaimsFunc mocks the GetUserByClaims method. - GetUserByClaimsFunc func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) + GetUserByClaimsFunc func(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, string, error) // GetUserGroupsFunc mocks the GetUserGroups method. GetUserGroupsFunc func(ctx context.Context, userID string) @@ -95,7 +96,7 @@ type UserBackendMock struct { } // Authenticate calls AuthenticateFunc. -func (mock *UserBackendMock) Authenticate(ctx context.Context, username string, password string) (*userv1beta1.User, error) { +func (mock *UserBackendMock) Authenticate(ctx context.Context, username string, password string) (*userv1beta1.User, string, error) { if mock.AuthenticateFunc == nil { panic("UserBackendMock.AuthenticateFunc: method is nil but UserBackend.Authenticate was just called") } @@ -169,7 +170,7 @@ func (mock *UserBackendMock) CreateUserFromClaimsCalls() []struct { } // GetUserByClaims calls GetUserByClaimsFunc. -func (mock *UserBackendMock) GetUserByClaims(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, error) { +func (mock *UserBackendMock) GetUserByClaims(ctx context.Context, claim string, value string, withRoles bool) (*userv1beta1.User, string, error) { if mock.GetUserByClaimsFunc == nil { panic("UserBackendMock.GetUserByClaimsFunc: method is nil but UserBackend.GetUserByClaims was just called") }