Use reva's Authenticate method instead of spawning token managers
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user