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

@@ -24,11 +24,13 @@ func (c *Commands) AddAccessAndRefreshToken(
refreshIdleExpiration,
refreshExpiration time.Duration,
authTime time.Time,
reason domain.TokenReason,
actor *domain.TokenActor,
) (accessToken *domain.Token, newRefreshToken string, err error) {
if refreshToken == "" {
return c.AddNewRefreshTokenAndAccessToken(ctx, userID, orgID, agentID, clientID, audience, scopes, authMethodsReferences, refreshExpiration, accessLifetime, refreshIdleExpiration, authTime)
return c.AddNewRefreshTokenAndAccessToken(ctx, userID, orgID, agentID, clientID, audience, scopes, authMethodsReferences, refreshExpiration, accessLifetime, refreshIdleExpiration, authTime, reason, actor)
}
return c.RenewRefreshTokenAndAccessToken(ctx, userID, orgID, refreshToken, agentID, clientID, audience, scopes, refreshIdleExpiration, accessLifetime)
return c.RenewRefreshTokenAndAccessToken(ctx, userID, orgID, refreshToken, agentID, clientID, audience, scopes, refreshIdleExpiration, accessLifetime, actor)
}
func (c *Commands) AddNewRefreshTokenAndAccessToken(
@@ -44,6 +46,8 @@ func (c *Commands) AddNewRefreshTokenAndAccessToken(
accessLifetime,
refreshIdleExpiration time.Duration,
authTime time.Time,
reason domain.TokenReason,
actor *domain.TokenActor,
) (accessToken *domain.Token, newRefreshToken string, err error) {
if userID == "" || clientID == "" {
return nil, "", zerrors.ThrowInvalidArgument(nil, "COMMAND-adg4r", "Errors.IDMissing")
@@ -53,15 +57,16 @@ func (c *Commands) AddNewRefreshTokenAndAccessToken(
if err != nil {
return nil, "", err
}
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, accessLifetime)
cmds, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, authMethodsReferences, accessLifetime, authTime, reason, actor)
if err != nil {
return nil, "", err
}
refreshTokenEvent, newRefreshToken, err := c.addRefreshToken(ctx, accessToken, authMethodsReferences, authTime, refreshIdleExpiration, refreshExpiration)
refreshTokenEvent, newRefreshToken, err := c.addRefreshToken(ctx, accessToken, authMethodsReferences, authTime, refreshIdleExpiration, refreshExpiration, actor)
if err != nil {
return nil, "", err
}
_, err = c.eventstore.Push(ctx, accessTokenEvent, refreshTokenEvent)
cmds = append(cmds, refreshTokenEvent)
_, err = c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, "", err
}
@@ -79,17 +84,18 @@ func (c *Commands) RenewRefreshTokenAndAccessToken(
scopes []string,
idleExpiration,
accessLifetime time.Duration,
actor *domain.TokenActor,
) (accessToken *domain.Token, newRefreshToken string, err error) {
refreshTokenEvent, refreshTokenID, newRefreshToken, err := c.renewRefreshToken(ctx, userID, orgID, refreshToken, idleExpiration)
renewed, err := c.renewRefreshToken(ctx, userID, orgID, refreshToken, idleExpiration)
if err != nil {
return nil, "", err
}
userWriteModel := NewUserWriteModel(userID, orgID)
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, accessLifetime)
cmds, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, renewed.tokenID, audience, scopes, renewed.authMethodsReferences, accessLifetime, renewed.authTime, domain.TokenReasonRefresh, actor)
if err != nil {
return nil, "", err
}
_, err = c.eventstore.Push(ctx, accessTokenEvent, refreshTokenEvent)
_, err = c.eventstore.Push(ctx, append(cmds, renewed.event)...)
if err != nil {
return nil, "", err
}
@@ -128,7 +134,7 @@ func (c *Commands) RevokeRefreshTokens(ctx context.Context, userID, orgID string
return err
}
func (c *Commands) addRefreshToken(ctx context.Context, accessToken *domain.Token, authMethodsReferences []string, authTime time.Time, idleExpiration, expiration time.Duration) (*user.HumanRefreshTokenAddedEvent, string, error) {
func (c *Commands) addRefreshToken(ctx context.Context, accessToken *domain.Token, authMethodsReferences []string, authTime time.Time, idleExpiration, expiration time.Duration, actor *domain.TokenActor) (*user.HumanRefreshTokenAddedEvent, string, error) {
refreshToken, err := domain.NewRefreshToken(accessToken.AggregateID, accessToken.RefreshTokenID, c.keyAlgorithm)
if err != nil {
return nil, "", err
@@ -136,46 +142,60 @@ func (c *Commands) addRefreshToken(ctx context.Context, accessToken *domain.Toke
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(accessToken.AggregateID, accessToken.ResourceOwner, accessToken.RefreshTokenID)
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
return user.NewHumanRefreshTokenAddedEvent(ctx, userAgg, accessToken.RefreshTokenID, accessToken.ApplicationID, accessToken.UserAgentID,
accessToken.PreferredLanguage, accessToken.Audience, accessToken.Scopes, authMethodsReferences, authTime, idleExpiration, expiration),
accessToken.PreferredLanguage, accessToken.Audience, accessToken.Scopes, authMethodsReferences, authTime, idleExpiration, expiration, actor),
refreshToken, nil
}
func (c *Commands) renewRefreshToken(ctx context.Context, userID, orgID, refreshToken string, idleExpiration time.Duration) (event *user.HumanRefreshTokenRenewedEvent, refreshTokenID, newRefreshToken string, err error) {
type renewedRefreshToken struct {
event *user.HumanRefreshTokenRenewedEvent
authTime time.Time
authMethodsReferences []string
tokenID string
token string
}
func (c *Commands) renewRefreshToken(ctx context.Context, userID, orgID, refreshToken string, idleExpiration time.Duration) (*renewedRefreshToken, error) {
if refreshToken == "" {
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-DHrr3", "Errors.IDMissing")
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-DHrr3", "Errors.IDMissing")
}
tokenUserID, tokenID, token, err := domain.FromRefreshToken(refreshToken, c.keyAlgorithm)
if err != nil {
return nil, "", "", err
return nil, err
}
if tokenUserID != userID {
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-Ht2g2", "Errors.User.RefreshToken.Invalid")
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Ht2g2", "Errors.User.RefreshToken.Invalid")
}
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(userID, orgID, tokenID)
err = c.eventstore.FilterToQueryReducer(ctx, refreshTokenWriteModel)
if err != nil {
return nil, "", "", err
return nil, err
}
if refreshTokenWriteModel.UserState != domain.UserStateActive {
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-BHnhs", "Errors.User.RefreshToken.Invalid")
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-BHnhs", "Errors.User.RefreshToken.Invalid")
}
if refreshTokenWriteModel.RefreshToken != token ||
refreshTokenWriteModel.IdleExpiration.Before(time.Now()) ||
refreshTokenWriteModel.Expiration.Before(time.Now()) {
return nil, "", "", zerrors.ThrowInvalidArgument(nil, "COMMAND-Vr43e", "Errors.User.RefreshToken.Invalid")
return nil, zerrors.ThrowInvalidArgument(nil, "COMMAND-Vr43e", "Errors.User.RefreshToken.Invalid")
}
newToken, err := c.idGenerator.Next()
if err != nil {
return nil, "", "", err
return nil, err
}
newRefreshToken, err = domain.RefreshToken(userID, tokenID, newToken, c.keyAlgorithm)
newRefreshToken, err := domain.RefreshToken(userID, tokenID, newToken, c.keyAlgorithm)
if err != nil {
return nil, "", "", err
return nil, err
}
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
return user.NewHumanRefreshTokenRenewedEvent(ctx, userAgg, tokenID, newToken, idleExpiration), tokenID, newRefreshToken, nil
return &renewedRefreshToken{
event: user.NewHumanRefreshTokenRenewedEvent(ctx, userAgg, tokenID, newToken, idleExpiration),
authTime: refreshTokenWriteModel.AuthTime,
authMethodsReferences: refreshTokenWriteModel.AuthMethodsReferences,
tokenID: tokenID,
token: newRefreshToken,
}, nil
}
func (c *Commands) removeRefreshToken(ctx context.Context, userID, orgID, tokenID string) (*user.HumanRefreshTokenRemovedEvent, *HumanRefreshTokenWriteModel, error) {