feat(oidc): token exchange impersonation (#7516)

* add token exchange feature flag

* allow setting reason and actor to access tokens

* impersonation

* set token types and scopes in response

* upgrade oidc to working draft state

* fix tests

* audience and scope validation

* id toke and jwt as input

* return id tokens

* add grant type  token exchange to app config

* add integration tests

* check and deny actors in api calls

* fix instance setting tests by triggering projection on write and cleanup

* insert sleep statements again

* solve linting issues

* add translations

* pin oidc v3.15.0

* resolve comments, add event translation

* fix refreshtoken test

* use ValidateAuthReqScopes from oidc

* apparently the linter can't make up its mind

* persist actor thru refresh tokens and check in tests

* remove unneeded triggers
This commit is contained in:
Tim Möhlmann
2024-03-20 12:18:46 +02:00
committed by GitHub
parent b338171585
commit 6398349c24
104 changed files with 2149 additions and 248 deletions

View File

@@ -232,16 +232,29 @@ func (c *Commands) RemoveUser(ctx context.Context, userID, resourceOwner string,
return writeModelToObjectDetails(&existingUser.WriteModel), nil
}
func (c *Commands) AddUserToken(ctx context.Context, orgID, agentID, clientID, userID string, audience, scopes []string, lifetime time.Duration) (*domain.Token, error) {
func (c *Commands) AddUserToken(
ctx context.Context,
orgID,
agentID,
clientID,
userID string,
audience,
scopes,
authMethodsReferences []string,
lifetime time.Duration,
authTime time.Time,
reason domain.TokenReason,
actor *domain.TokenActor,
) (*domain.Token, error) {
if userID == "" { //do not check for empty orgID (JWT Profile requests won't provide it, so service user requests fail)
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Dbge4", "Errors.IDMissing")
}
userWriteModel := NewUserWriteModel(userID, orgID)
event, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, "", audience, scopes, lifetime)
cmds, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, "", audience, scopes, authMethodsReferences, lifetime, authTime, reason, actor)
if err != nil {
return nil, err
}
_, err = c.eventstore.Push(ctx, event)
_, err = c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
@@ -264,7 +277,7 @@ func (c *Commands) RevokeAccessToken(ctx context.Context, userID, orgID, tokenID
return writeModelToObjectDetails(&accessTokenWriteModel.WriteModel), nil
}
func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteModel, agentID, clientID, refreshTokenID string, audience, scopes []string, lifetime time.Duration) (*user.UserTokenAddedEvent, *domain.Token, error) {
func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteModel, agentID, clientID, refreshTokenID string, audience, scopes, authMethodsReferences []string, lifetime time.Duration, authTime time.Time, reason domain.TokenReason, actor *domain.TokenActor) ([]eventstore.Command, *domain.Token, error) {
err := c.eventstore.FilterToQueryReducer(ctx, userWriteModel)
if err != nil {
return nil, nil, err
@@ -273,10 +286,24 @@ func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteMo
return nil, nil, zerrors.ThrowNotFound(nil, "COMMAND-1d6Gg", "Errors.User.NotFound")
}
//nolint:contextcheck
userAgg := UserAggregateFromWriteModel(&userWriteModel.WriteModel)
var cmds []eventstore.Command
if reason == domain.TokenReasonImpersonation {
if err := c.checkPermission(ctx, "impersonation", userWriteModel.ResourceOwner, userWriteModel.AggregateID); err != nil {
return nil, nil, err
}
cmds = append(cmds, user.NewUserImpersonatedEvent(ctx, userAgg, clientID, actor))
}
audience = domain.AddAudScopeToAudience(ctx, audience, scopes)
preferredLanguage := ""
existingHuman, err := c.getHumanWriteModelByID(ctx, userWriteModel.AggregateID, userWriteModel.ResourceOwner)
if err != nil {
return nil, nil, err
}
if existingHuman != nil {
preferredLanguage = existingHuman.PreferredLanguage.String()
}
@@ -286,21 +313,25 @@ func (c *Commands) addUserToken(ctx context.Context, userWriteModel *UserWriteMo
return nil, nil, err
}
userAgg := UserAggregateFromWriteModel(&userWriteModel.WriteModel)
return user.NewUserTokenAddedEvent(ctx, userAgg, tokenID, clientID, agentID, preferredLanguage, refreshTokenID, audience, scopes, expiration),
&domain.Token{
ObjectRoot: models.ObjectRoot{
AggregateID: userWriteModel.AggregateID,
},
TokenID: tokenID,
UserAgentID: agentID,
ApplicationID: clientID,
RefreshTokenID: refreshTokenID,
Audience: audience,
Scopes: scopes,
Expiration: expiration,
PreferredLanguage: preferredLanguage,
}, nil
cmds = append(cmds,
user.NewUserTokenAddedEvent(ctx, userAgg, tokenID, clientID, agentID, preferredLanguage, refreshTokenID, audience, scopes, authMethodsReferences, authTime, expiration, reason, actor),
)
return cmds, &domain.Token{
ObjectRoot: models.ObjectRoot{
AggregateID: userWriteModel.AggregateID,
},
TokenID: tokenID,
UserAgentID: agentID,
ApplicationID: clientID,
RefreshTokenID: refreshTokenID,
Audience: audience,
Scopes: scopes,
Expiration: expiration,
PreferredLanguage: preferredLanguage,
Reason: reason,
Actor: actor,
}, nil
}
func (c *Commands) removeAccessToken(ctx context.Context, userID, orgID, tokenID string) (*user.UserTokenRemovedEvent, *UserAccessTokenWriteModel, error) {