feat: token revocation and OP certification (#2594)

* fix: try using only user session if no user is set (id_token_hint) on prompt none

* fix caos errors As implementation

* implement request mode

* return explicit error on invalid refresh token use

* begin token revocation

* token revocation

* tests

* tests

* cleanup

* set op config

* add revocation endpoint to config

* add revocation endpoint to config

* migration version

* error handling in token revocation

* migration version

* update oidc lib to 1.0.0
This commit is contained in:
Livio Amstutz
2021-11-03 08:35:24 +01:00
committed by GitHub
parent 8df5614e4d
commit fc6154cffc
25 changed files with 638 additions and 236 deletions

View File

@@ -10,22 +10,54 @@ import (
"github.com/caos/zitadel/internal/repository/user"
)
func (c *Commands) AddAccessAndRefreshToken(ctx context.Context, orgID, agentID, clientID, userID, refreshToken string, audience, scopes, authMethodsReferences []string, accessLifetime, refreshIdleExpiration, refreshExpiration time.Duration, authTime time.Time) (*domain.Token, string, error) {
func (c *Commands) AddAccessAndRefreshToken(
ctx context.Context,
orgID,
agentID,
clientID,
userID,
refreshToken string,
audience,
scopes,
authMethodsReferences []string,
accessLifetime,
refreshIdleExpiration,
refreshExpiration time.Duration,
authTime time.Time,
) (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.RenewRefreshTokenAndAccessToken(ctx, userID, orgID, refreshToken, agentID, clientID, audience, scopes, refreshIdleExpiration, accessLifetime)
}
func (c *Commands) AddNewRefreshTokenAndAccessToken(
ctx context.Context,
userID,
orgID,
agentID,
clientID string,
audience,
scopes,
authMethodsReferences []string,
refreshExpiration,
accessLifetime,
refreshIdleExpiration time.Duration,
authTime time.Time,
) (accessToken *domain.Token, newRefreshToken string, err error) {
if userID == "" || agentID == "" || clientID == "" {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-adg4r", "Errors.IDMissing")
}
userWriteModel := NewUserWriteModel(userID, orgID)
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, audience, scopes, accessLifetime)
refreshTokenID, err := c.idGenerator.Next()
if err != nil {
return nil, "", err
}
creator := func() (eventstore.EventPusher, string, error) {
return c.addRefreshToken(ctx, accessToken, authMethodsReferences, authTime, refreshIdleExpiration, refreshExpiration)
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, accessLifetime)
if err != nil {
return nil, "", err
}
if refreshToken != "" {
creator = func() (eventstore.EventPusher, string, error) {
return c.renewRefreshToken(ctx, userID, orgID, refreshToken, refreshIdleExpiration)
}
}
refreshTokenEvent, token, err := creator()
refreshTokenEvent, newRefreshToken, err := c.addRefreshToken(ctx, accessToken, authMethodsReferences, authTime, refreshIdleExpiration, refreshExpiration)
if err != nil {
return nil, "", err
}
@@ -33,61 +65,35 @@ func (c *Commands) AddAccessAndRefreshToken(ctx context.Context, orgID, agentID,
if err != nil {
return nil, "", err
}
return accessToken, token, nil
return accessToken, newRefreshToken, nil
}
func (c *Commands) addRefreshToken(ctx context.Context, accessToken *domain.Token, authMethodsReferences []string, authTime time.Time, idleExpiration, expiration time.Duration) (*user.HumanRefreshTokenAddedEvent, string, error) {
tokenID, err := c.idGenerator.Next()
func (c *Commands) RenewRefreshTokenAndAccessToken(
ctx context.Context,
userID,
orgID,
refreshToken,
agentID,
clientID string,
audience,
scopes []string,
idleExpiration,
accessLifetime time.Duration,
) (accessToken *domain.Token, newRefreshToken string, err error) {
refreshTokenEvent, refreshTokenID, newRefreshToken, err := c.renewRefreshToken(ctx, userID, orgID, refreshToken, idleExpiration)
if err != nil {
return nil, "", err
}
refreshToken, err := domain.NewRefreshToken(accessToken.AggregateID, tokenID, c.keyAlgorithm)
userWriteModel := NewUserWriteModel(userID, orgID)
accessTokenEvent, accessToken, err := c.addUserToken(ctx, userWriteModel, agentID, clientID, refreshTokenID, audience, scopes, accessLifetime)
if err != nil {
return nil, "", err
}
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(accessToken.AggregateID, accessToken.ResourceOwner, tokenID)
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
return user.NewHumanRefreshTokenAddedEvent(ctx, userAgg, tokenID, accessToken.ApplicationID, accessToken.UserAgentID,
accessToken.PreferredLanguage, accessToken.Audience, accessToken.Scopes, authMethodsReferences, authTime, idleExpiration, expiration),
refreshToken, nil
}
func (c *Commands) renewRefreshToken(ctx context.Context, userID, orgID, refreshToken string, idleExpiration time.Duration) (event *user.HumanRefreshTokenRenewedEvent, newRefreshToken string, err error) {
if refreshToken == "" {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-DHrr3", "Errors.IDMissing")
}
tokenUserID, tokenID, token, err := domain.FromRefreshToken(refreshToken, c.keyAlgorithm)
if err != nil {
return nil, "", caos_errs.ThrowInvalidArgument(err, "COMMAND-Dbfe4", "Errors.User.RefreshToken.Invalid")
}
if tokenUserID != userID {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-Ht2g2", "Errors.User.RefreshToken.Invalid")
}
refreshTokenWriteModel := NewHumanRefreshTokenWriteModel(userID, orgID, tokenID)
err = c.eventstore.FilterToQueryReducer(ctx, refreshTokenWriteModel)
_, err = c.eventstore.PushEvents(ctx, accessTokenEvent, refreshTokenEvent)
if err != nil {
return nil, "", err
}
if refreshTokenWriteModel.UserState != domain.UserStateActive {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-BHnhs", "Errors.User.RefreshToken.Invalid")
}
if refreshTokenWriteModel.RefreshToken != token ||
refreshTokenWriteModel.IdleExpiration.Before(time.Now()) ||
refreshTokenWriteModel.Expiration.Before(time.Now()) {
return nil, "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-Vr43e", "Errors.User.RefreshToken.Invalid")
}
newToken, err := c.idGenerator.Next()
if err != nil {
return nil, "", err
}
newRefreshToken, err = domain.RefreshToken(userID, tokenID, newToken, c.keyAlgorithm)
if err != nil {
return nil, "", err
}
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
return user.NewHumanRefreshTokenRenewedEvent(ctx, userAgg, tokenID, newToken, idleExpiration), newRefreshToken, nil
return accessToken, newRefreshToken, nil
}
func (c *Commands) RevokeRefreshToken(ctx context.Context, userID, orgID, tokenID string) (*domain.ObjectDetails, error) {
@@ -122,6 +128,56 @@ 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) {
refreshToken, err := domain.NewRefreshToken(accessToken.AggregateID, accessToken.RefreshTokenID, c.keyAlgorithm)
if err != nil {
return nil, "", err
}
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),
refreshToken, nil
}
func (c *Commands) renewRefreshToken(ctx context.Context, userID, orgID, refreshToken string, idleExpiration time.Duration) (event *user.HumanRefreshTokenRenewedEvent, refreshTokenID, newRefreshToken string, err error) {
if refreshToken == "" {
return nil, "", "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-DHrr3", "Errors.IDMissing")
}
tokenUserID, tokenID, token, err := domain.FromRefreshToken(refreshToken, c.keyAlgorithm)
if err != nil {
return nil, "", "", caos_errs.ThrowInvalidArgument(err, "COMMAND-Dbfe4", "Errors.User.RefreshToken.Invalid")
}
if tokenUserID != userID {
return nil, "", "", caos_errs.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
}
if refreshTokenWriteModel.UserState != domain.UserStateActive {
return nil, "", "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-BHnhs", "Errors.User.RefreshToken.Invalid")
}
if refreshTokenWriteModel.RefreshToken != token ||
refreshTokenWriteModel.IdleExpiration.Before(time.Now()) ||
refreshTokenWriteModel.Expiration.Before(time.Now()) {
return nil, "", "", caos_errs.ThrowInvalidArgument(nil, "COMMAND-Vr43e", "Errors.User.RefreshToken.Invalid")
}
newToken, err := c.idGenerator.Next()
if err != nil {
return nil, "", "", err
}
newRefreshToken, err = domain.RefreshToken(userID, tokenID, newToken, c.keyAlgorithm)
if err != nil {
return nil, "", "", err
}
userAgg := UserAggregateFromWriteModel(&refreshTokenWriteModel.WriteModel)
return user.NewHumanRefreshTokenRenewedEvent(ctx, userAgg, tokenID, newToken, idleExpiration), tokenID, newRefreshToken, nil
}
func (c *Commands) removeRefreshToken(ctx context.Context, userID, orgID, tokenID string) (*user.HumanRefreshTokenRemovedEvent, *HumanRefreshTokenWriteModel, error) {
if userID == "" || orgID == "" || tokenID == "" {
return nil, nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-GVDgf", "Errors.IDMissing")