Skip to content
Open
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
3 changes: 3 additions & 0 deletions changelog/unreleased/idp-config-identifier.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Enhancement: allow using different field as identifier, configurable per idp

https://github.com/cs3org/reva/pull/5556
4 changes: 4 additions & 0 deletions internal/grpc/services/authprovider/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ func (s *service) Authenticate(ctx context.Context, req *provider.AuthenticateRe
return &provider.AuthenticateResponse{
Status: status.NewNotFound(ctx, "unknown client id: "+err.Error()),
}, nil
case errtypes.Conflict:
return &provider.AuthenticateResponse{
Status: status.NewConflict(ctx, v, err.Error()),
}, nil
default:
err = errors.Wrap(err, "authsvc: error in Authenticate")
return &provider.AuthenticateResponse{
Expand Down
2 changes: 2 additions & 0 deletions internal/grpc/services/gateway/authprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ func (s *svc) Authenticate(ctx context.Context, req *gateway.AuthenticateRequest
case res.Status.Code == rpc.Code_CODE_UNAUTHENTICATED:
fallthrough
case res.Status.Code == rpc.Code_CODE_NOT_FOUND:
fallthrough
case res.Status.Code == rpc.Code_CODE_ABORTED:
// normal failures, no need to log
return &gateway.AuthenticateResponse{
Status: res.Status,
Expand Down
7 changes: 5 additions & 2 deletions internal/grpc/services/userprovider/userprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,9 +123,12 @@ func (s *service) GetUserByClaim(ctx context.Context, req *userpb.GetUserByClaim
user, err := s.usermgr.GetUserByClaim(ctx, req.Claim, req.Value, req.SkipFetchingUserGroups)
if err != nil {
res := &userpb.GetUserByClaimResponse{}
if _, ok := err.(errtypes.NotFound); ok {
switch err.(type) {
case errtypes.NotFound:
res.Status = status.NewNotFound(ctx, fmt.Sprintf("user not found %s %s", req.Claim, req.Value))
} else {
case errtypes.Conflict:
res.Status = status.NewConflict(ctx, err, fmt.Sprintf("conflict getting user %s by claim %s", req.Value, req.Claim))
default:
err = errors.Wrap(err, "userprovidersvc: error getting user by claim")
res.Status = status.NewInternal(ctx, err, fmt.Sprintf("error getting user %s by claim %s", req.Value, req.Claim))
}
Expand Down
8 changes: 7 additions & 1 deletion internal/http/interceptors/auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -301,7 +301,13 @@ func authenticateUser(w http.ResponseWriter, r *http.Request, conf *config, sign

if res.Status.Code != rpc.Code_CODE_OK {
err := status.NewErrorFromCode(res.Status.Code, "auth")
logError(isUnprotectedEndpoint, log, err, "error generating access token from credentials", http.StatusUnauthorized, w)
if res.Status.Code == rpc.Code_CODE_ABORTED {
// A conflict occurs when an identity cannot be resolved for a known reason,
// e.g. a lightweight account that has been linked to a primary account.
logError(isUnprotectedEndpoint, log, err, "conflict when generating access token from credentials", http.StatusConflict, w)
} else {
logError(isUnprotectedEndpoint, log, err, "error generating access token from credentials", http.StatusUnauthorized, w)
}
return nil, err
}

Expand Down
38 changes: 26 additions & 12 deletions pkg/auth/manager/oidc/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,15 @@ type mgr struct {
}

type config struct {
Insecure bool `docs:"false;Whether to skip certificate checks when sending requests." mapstructure:"insecure"`
Issuer string `docs:";The issuer of the OIDC token." mapstructure:"issuer"`
IDClaim string `docs:"sub;The claim containing the ID of the user." mapstructure:"id_claim"`
UIDClaim string `docs:";The claim containing the UID of the user." mapstructure:"uid_claim"`
GIDClaim string `docs:";The claim containing the GID of the user." mapstructure:"gid_claim"`
GatewaySvc string `docs:";The endpoint at which the GRPC gateway is exposed." mapstructure:"gatewaysvc"`
UsersMapping string `docs:"; The optional OIDC users mapping file path" mapstructure:"users_mapping"`
GroupClaim string `docs:"; The group claim to be looked up to map the user (default to 'groups')." mapstructure:"group_claim"`
Insecure bool `docs:"false;Whether to skip certificate checks when sending requests." mapstructure:"insecure"`
Issuer string `docs:";The issuer of the OIDC token." mapstructure:"issuer"`
DefaultIDClaim string `docs:"sub;The default claim used as the user ID when no per-IDP override is configured." mapstructure:"default_id_claim"`
IDPToIDClaim map[string]string `docs:";Per-IDP claim to use as the user ID, keyed by the value of the identity_provider token claim. Overrides default_id_claim for matching IDPs." mapstructure:"idp_to_id_claim"`
UIDClaim string `docs:";The claim containing the UID of the user." mapstructure:"uid_claim"`
GIDClaim string `docs:";The claim containing the GID of the user." mapstructure:"gid_claim"`
GatewaySvc string `docs:";The endpoint at which the GRPC gateway is exposed." mapstructure:"gatewaysvc"`
UsersMapping string `docs:"; The optional OIDC users mapping file path" mapstructure:"users_mapping"`
GroupClaim string `docs:"; The group claim to be looked up to map the user (default to 'groups')." mapstructure:"group_claim"`
}

type oidcUserMapping struct {
Expand All @@ -79,9 +80,9 @@ type oidcUserMapping struct {
}

func (c *config) ApplyDefaults() {
if c.IDClaim == "" {
if c.DefaultIDClaim == "" {
// sub is stable and defined as unique. the user manager needs to take care of the sub to user metadata lookup
c.IDClaim = "sub"
c.DefaultIDClaim = "sub"
}
if c.GroupClaim == "" {
c.GroupClaim = "groups"
Expand Down Expand Up @@ -184,10 +185,20 @@ func (am *mgr) isIssuerAllowed(issuer string) bool {
return false
}

func (am *mgr) idClaimForToken(claims jwt.MapClaims) string {
if idp, ok := claims["identity_provider"].(string); ok && idp != "" {
if claim, ok := am.c.IDPToIDClaim[idp]; ok {
return claim
}
}
return am.c.DefaultIDClaim
}

func (am *mgr) doUserMapping(tkn *oidc.IDToken, claims jwt.MapClaims) (string, error) {
idClaim := am.idClaimForToken(claims)
var sub = tkn.Subject
if am.c.IDClaim != "sub" && claims[am.c.IDClaim] != nil {
sub, _ = claims[am.c.IDClaim].(string)
if idClaim != "sub" && claims[idClaim] != nil {
sub, _ = claims[idClaim].(string)
}
if len(am.oidcUsersMapping) == 0 {
return sub, nil
Expand Down Expand Up @@ -273,6 +284,9 @@ func (am *mgr) Authenticate(ctx context.Context, _, clientSecret string) (*user.
return nil, nil, errors.Wrapf(err, "error getting user by username '%v'", sub)
}
if userRes.Status.Code != rpc.Code_CODE_OK {
if userRes.Status.Code == rpc.Code_CODE_ABORTED {
return nil, nil, errtypes.Conflict(userRes.Status.Message)
}
return nil, nil, status.NewErrorFromCode(userRes.Status.Code, "oidc")
}

Expand Down
Loading