From 7d6c933485130117f2d7cce311cf50b86079272f Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Fri, 25 Feb 2022 16:05:06 +0100 Subject: [PATCH] feat: OIDC setting (#3245) * feat: add oidc config struct * feat: oidc config command side * feat: oidc configuration query side * feat: add translations * feat: add tests * feat: add translations * feat: rename oidc config to oidc settings * feat: rename oidc config to oidc settings --- docs/docs/apis/proto/admin.md | 66 +++++ internal/api/grpc/admin/oidc_settings.go | 29 ++ .../api/grpc/admin/oidc_settings_converter.go | 29 ++ internal/command/iam_oidc_settings.go | 79 ++++++ internal/command/iam_oidc_settings_model.go | 101 +++++++ internal/command/iam_oidc_settings_test.go | 264 ++++++++++++++++++ internal/domain/oidc_settings.go | 37 +++ internal/query/oidc_settings.go | 115 ++++++++ internal/query/oidc_settings_test.go | 136 +++++++++ internal/query/projection/action.go | 2 +- internal/query/projection/app.go | 2 +- internal/query/projection/authn_key.go | 2 +- internal/query/projection/custom_text.go | 2 +- internal/query/projection/feature.go | 2 +- internal/query/projection/flow.go | 2 +- internal/query/projection/iam.go | 2 +- internal/query/projection/iam_member.go | 2 +- internal/query/projection/idp.go | 2 +- .../query/projection/idp_login_policy_link.go | 2 +- internal/query/projection/idp_user_link.go | 2 +- internal/query/projection/key.go | 2 +- internal/query/projection/label_policy.go | 2 +- internal/query/projection/lockout_policy.go | 2 +- internal/query/projection/login_name.go | 2 +- internal/query/projection/login_policy.go | 2 +- internal/query/projection/mail_template.go | 2 +- internal/query/projection/message_texts.go | 2 +- internal/query/projection/oidc_settings.go | 114 ++++++++ .../query/projection/oidc_settings_test.go | 106 +++++++ internal/query/projection/org.go | 2 +- internal/query/projection/org_domain.go | 2 +- internal/query/projection/org_iam_policy.go | 2 +- internal/query/projection/org_member.go | 2 +- .../query/projection/password_age_policy.go | 2 +- .../projection/password_complexity_policy.go | 2 +- internal/query/projection/privacy_policy.go | 2 +- internal/query/projection/project.go | 2 +- internal/query/projection/project_grant.go | 2 +- .../query/projection/project_grant_member.go | 2 +- internal/query/projection/project_member.go | 2 +- internal/query/projection/project_role.go | 2 +- internal/query/projection/projection.go | 1 + internal/query/projection/secret_generator.go | 2 +- internal/query/projection/sms.go | 2 +- internal/query/projection/smtp.go | 2 +- internal/query/projection/user.go | 2 +- internal/query/projection/user_auth_method.go | 2 +- internal/query/projection/user_grant.go | 2 +- internal/query/projection/user_metadata.go | 2 +- .../projection/user_personal_access_token.go | 2 +- internal/repository/iam/eventstore.go | 2 + internal/repository/iam/oidc_settings.go | 145 ++++++++++ internal/static/i18n/de.yaml | 43 +++ internal/static/i18n/en.yaml | 44 +++ internal/static/i18n/it.yaml | 43 +++ proto/zitadel/admin.proto | 40 +++ proto/zitadel/settings.proto | 10 +- 57 files changed, 1440 insertions(+), 40 deletions(-) create mode 100644 internal/api/grpc/admin/oidc_settings.go create mode 100644 internal/api/grpc/admin/oidc_settings_converter.go create mode 100644 internal/command/iam_oidc_settings.go create mode 100644 internal/command/iam_oidc_settings_model.go create mode 100644 internal/command/iam_oidc_settings_test.go create mode 100644 internal/domain/oidc_settings.go create mode 100644 internal/query/oidc_settings.go create mode 100644 internal/query/oidc_settings_test.go create mode 100644 internal/query/projection/oidc_settings.go create mode 100644 internal/query/projection/oidc_settings_test.go create mode 100644 internal/repository/iam/oidc_settings.go diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 36539a2558..1864bebc60 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -188,6 +188,30 @@ Update twilio sms provider token PUT: /sms/twilio/{id}/token +### GetOIDCSettings + +> **rpc** GetOIDCSettings([GetOIDCSettingsRequest](#getoidcsettingsrequest)) +[GetOIDCSettingsResponse](#getoidcsettingsresponse) + +Get OIDC settings (e.g token lifetimes, etc.) + + + + GET: /settings/oidc + + +### UpdateOIDCSettings + +> **rpc** UpdateOIDCSettings([UpdateOIDCSettingsRequest](#updateoidcsettingsrequest)) +[UpdateOIDCSettingsResponse](#updateoidcsettingsresponse) + +Update oidc settings (e.g token lifetimes, etc) + + + + PUT: /settings/oidc + + ### GetOrgByID > **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest)) @@ -2046,6 +2070,23 @@ This is an empty request +### GetOIDCSettingsRequest +This is an empty request + + + + +### GetOIDCSettingsResponse + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| settings | zitadel.settings.v1.OIDCSettings | - | | + + + + ### GetOrgByIDRequest @@ -3613,6 +3654,31 @@ This is an empty request +### UpdateOIDCSettingsRequest + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| access_token_lifetime | google.protobuf.Duration | - | | +| id_token_lifetime | google.protobuf.Duration | - | | +| refresh_token_idle_expiration | google.protobuf.Duration | - | | +| refresh_token_expiration | google.protobuf.Duration | - | | + + + + +### UpdateOIDCSettingsResponse + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| details | zitadel.v1.ObjectDetails | - | | + + + + ### UpdateOrgIAMPolicyRequest diff --git a/internal/api/grpc/admin/oidc_settings.go b/internal/api/grpc/admin/oidc_settings.go new file mode 100644 index 0000000000..a6a0abce18 --- /dev/null +++ b/internal/api/grpc/admin/oidc_settings.go @@ -0,0 +1,29 @@ +package admin + +import ( + "context" + + "github.com/caos/zitadel/internal/api/grpc/object" + "github.com/caos/zitadel/internal/domain" + admin_pb "github.com/caos/zitadel/pkg/grpc/admin" +) + +func (s *Server) GetOIDCSettings(ctx context.Context, _ *admin_pb.GetOIDCSettingsRequest) (*admin_pb.GetOIDCSettingsResponse, error) { + result, err := s.query.OIDCSettingsByAggID(ctx, domain.IAMID) + if err != nil { + return nil, err + } + return &admin_pb.GetOIDCSettingsResponse{ + Settings: OIDCSettingsToPb(result), + }, nil +} + +func (s *Server) UpdateOIDCSettings(ctx context.Context, req *admin_pb.UpdateOIDCSettingsRequest) (*admin_pb.UpdateOIDCSettingsResponse, error) { + result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req)) + if err != nil { + return nil, err + } + return &admin_pb.UpdateOIDCSettingsResponse{ + Details: object.DomainToChangeDetailsPb(result), + }, nil +} diff --git a/internal/api/grpc/admin/oidc_settings_converter.go b/internal/api/grpc/admin/oidc_settings_converter.go new file mode 100644 index 0000000000..b82d7458d8 --- /dev/null +++ b/internal/api/grpc/admin/oidc_settings_converter.go @@ -0,0 +1,29 @@ +package admin + +import ( + obj_grpc "github.com/caos/zitadel/internal/api/grpc/object" + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/query" + admin_pb "github.com/caos/zitadel/pkg/grpc/admin" + settings_pb "github.com/caos/zitadel/pkg/grpc/settings" + "google.golang.org/protobuf/types/known/durationpb" +) + +func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings { + return &settings_pb.OIDCSettings{ + Details: obj_grpc.ToViewDetailsPb(config.Sequence, config.CreationDate, config.ChangeDate, config.AggregateID), + AccessTokenLifetime: durationpb.New(config.AccessTokenLifetime), + IdTokenLifetime: durationpb.New(config.IdTokenLifetime), + RefreshTokenIdleExpiration: durationpb.New(config.RefreshTokenIdleExpiration), + RefreshTokenExpiration: durationpb.New(config.RefreshTokenExpiration), + } +} + +func UpdateOIDCConfigToConfig(req *admin_pb.UpdateOIDCSettingsRequest) *domain.OIDCSettings { + return &domain.OIDCSettings{ + AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(), + IdTokenLifetime: req.IdTokenLifetime.AsDuration(), + RefreshTokenIdleExpiration: req.RefreshTokenIdleExpiration.AsDuration(), + RefreshTokenExpiration: req.RefreshTokenExpiration.AsDuration(), + } +} diff --git a/internal/command/iam_oidc_settings.go b/internal/command/iam_oidc_settings.go new file mode 100644 index 0000000000..cd3267f42d --- /dev/null +++ b/internal/command/iam_oidc_settings.go @@ -0,0 +1,79 @@ +package command + +import ( + "context" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/repository/iam" +) + +func (c *Commands) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) { + oidcSettingWriteModel, err := c.getOIDCSettings(ctx) + if err != nil { + return nil, err + } + if oidcSettingWriteModel.State.Exists() { + return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-d9nlw", "Errors.OIDCSettings.AlreadyExists") + } + iamAgg := IAMAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel) + pushedEvents, err := c.eventstore.Push(ctx, iam.NewOIDCSettingsAddedEvent( + ctx, + iamAgg, + settings.AccessTokenLifetime, + settings.IdTokenLifetime, + settings.RefreshTokenIdleExpiration, + settings.RefreshTokenExpiration)) + if err != nil { + return nil, err + } + err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil +} + +func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) { + oidcSettingWriteModel, err := c.getOIDCSettings(ctx) + if err != nil { + return nil, err + } + if !oidcSettingWriteModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "COMMAND-8snEr", "Errors.OIDCSettings.NotFound") + } + iamAgg := IAMAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel) + + changedEvent, hasChanged, err := oidcSettingWriteModel.NewChangedEvent( + ctx, + iamAgg, + settings.AccessTokenLifetime, + settings.IdTokenLifetime, + settings.RefreshTokenIdleExpiration, + settings.RefreshTokenExpiration) + if err != nil { + return nil, err + } + if !hasChanged { + return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-398uF", "Errors.NoChangesFound") + } + pushedEvents, err := c.eventstore.Push(ctx, changedEvent) + if err != nil { + return nil, err + } + err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...) + if err != nil { + return nil, err + } + return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil +} + +func (c *Commands) getOIDCSettings(ctx context.Context) (_ *IAMOIDCSettingsWriteModel, err error) { + writeModel := NewIAMOIDCSettingsWriteModel() + err = c.eventstore.FilterToQueryReducer(ctx, writeModel) + if err != nil { + return nil, err + } + + return writeModel, nil +} diff --git a/internal/command/iam_oidc_settings_model.go b/internal/command/iam_oidc_settings_model.go new file mode 100644 index 0000000000..20ca6141a1 --- /dev/null +++ b/internal/command/iam_oidc_settings_model.go @@ -0,0 +1,101 @@ +package command + +import ( + "context" + "time" + + "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/repository/iam" +) + +type IAMOIDCSettingsWriteModel struct { + eventstore.WriteModel + + AccessTokenLifetime time.Duration + IdTokenLifetime time.Duration + RefreshTokenIdleExpiration time.Duration + RefreshTokenExpiration time.Duration + State domain.OIDCSettingsState +} + +func NewIAMOIDCSettingsWriteModel() *IAMOIDCSettingsWriteModel { + return &IAMOIDCSettingsWriteModel{ + WriteModel: eventstore.WriteModel{ + AggregateID: domain.IAMID, + ResourceOwner: domain.IAMID, + }, + } +} + +func (wm *IAMOIDCSettingsWriteModel) Reduce() error { + for _, event := range wm.Events { + switch e := event.(type) { + case *iam.OIDCSettingsAddedEvent: + wm.AccessTokenLifetime = e.AccessTokenLifetime + wm.IdTokenLifetime = e.IdTokenLifetime + wm.RefreshTokenIdleExpiration = e.RefreshTokenIdleExpiration + wm.RefreshTokenExpiration = e.RefreshTokenExpiration + wm.State = domain.OIDCSettingsStateActive + case *iam.OIDCSettingsChangedEvent: + if e.AccessTokenLifetime != nil { + wm.AccessTokenLifetime = *e.AccessTokenLifetime + } + if e.IdTokenLifetime != nil { + wm.IdTokenLifetime = *e.IdTokenLifetime + } + if e.RefreshTokenIdleExpiration != nil { + wm.RefreshTokenIdleExpiration = *e.RefreshTokenIdleExpiration + } + if e.RefreshTokenExpiration != nil { + wm.RefreshTokenExpiration = *e.RefreshTokenExpiration + } + } + } + return wm.WriteModel.Reduce() +} + +func (wm *IAMOIDCSettingsWriteModel) Query() *eventstore.SearchQueryBuilder { + return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). + ResourceOwner(wm.ResourceOwner). + AddQuery(). + AggregateTypes(iam.AggregateType). + AggregateIDs(wm.AggregateID). + EventTypes( + iam.OIDCSettingsAddedEventType, + iam.OIDCSettingsChangedEventType). + Builder() +} + +func (wm *IAMOIDCSettingsWriteModel) NewChangedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + accessTokenLifetime, + idTokenLifetime, + refreshTokenIdleExpiration, + refreshTokenExpiration time.Duration, +) (*iam.OIDCSettingsChangedEvent, bool, error) { + changes := make([]iam.OIDCSettingsChanges, 0, 4) + var err error + + if wm.AccessTokenLifetime != accessTokenLifetime { + changes = append(changes, iam.ChangeOIDCSettingsAccessTokenLifetime(accessTokenLifetime)) + } + if wm.IdTokenLifetime != idTokenLifetime { + changes = append(changes, iam.ChangeOIDCSettingsIdTokenLifetime(idTokenLifetime)) + } + if wm.RefreshTokenIdleExpiration != refreshTokenIdleExpiration { + changes = append(changes, iam.ChangeOIDCSettingsRefreshTokenIdleExpiration(refreshTokenIdleExpiration)) + } + if wm.RefreshTokenExpiration != refreshTokenExpiration { + changes = append(changes, iam.ChangeOIDCSettingsRefreshTokenExpiration(refreshTokenExpiration)) + } + if len(changes) == 0 { + return nil, false, nil + } + changeEvent, err := iam.NewOIDCSettingsChangeEvent(ctx, aggregate, changes) + if err != nil { + return nil, false, err + } + return changeEvent, true, nil +} diff --git a/internal/command/iam_oidc_settings_test.go b/internal/command/iam_oidc_settings_test.go new file mode 100644 index 0000000000..20b006646b --- /dev/null +++ b/internal/command/iam_oidc_settings_test.go @@ -0,0 +1,264 @@ +package command + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/caos/zitadel/internal/domain" + caos_errs "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" +) + +func TestCommandSide_AddOIDCConfig(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + oidcConfig *domain.OIDCSettings + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "oidc config, error already exists", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewOIDCSettingsAddedEvent(context.Background(), + &iam.NewAggregate().Aggregate, + time.Hour*1, + time.Hour*1, + time.Hour*1, + time.Hour*1, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorAlreadyExists, + }, + }, + { + name: "add secret generator, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + expectPush( + []*repository.Event{ + eventFromEventPusher(iam.NewOIDCSettingsAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + time.Hour*1, + time.Hour*1, + time.Hour*1, + time.Hour*1, + ), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.AddOIDCSettings(tt.args.ctx, tt.args.oidcConfig) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func TestCommandSide_ChangeOIDCConfig(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + } + type args struct { + ctx context.Context + oidcConfig *domain.OIDCSettings + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + name: "oidc config not existing, not found error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + }, + res: res{ + err: caos_errs.IsNotFound, + }, + }, + { + name: "no changes, precondition error", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewOIDCSettingsAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + time.Hour*1, + time.Hour*1, + time.Hour*1, + time.Hour*1, + ), + ), + ), + ), + }, + args: args{ + ctx: context.Background(), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsPreconditionFailed, + }, + }, + { + name: "secret generator change, ok", + fields: fields{ + eventstore: eventstoreExpect( + t, + expectFilter( + eventFromEventPusher( + iam.NewOIDCSettingsAddedEvent( + context.Background(), + &iam.NewAggregate().Aggregate, + time.Hour*1, + time.Hour*1, + time.Hour*1, + time.Hour*1, + ), + ), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + newOIDCConfigChangedEvent(context.Background(), + time.Hour*2, + time.Hour*2, + time.Hour*2, + time.Hour*2), + ), + }, + ), + ), + }, + args: args{ + ctx: context.Background(), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 2 * time.Hour, + IdTokenLifetime: 2 * time.Hour, + RefreshTokenIdleExpiration: 2 * time.Hour, + RefreshTokenExpiration: 2 * time.Hour, + }, + }, + res: res{ + want: &domain.ObjectDetails{ + ResourceOwner: "IAM", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + r := &Commands{ + eventstore: tt.fields.eventstore, + } + got, err := r.ChangeOIDCSettings(tt.args.ctx, tt.args.oidcConfig) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.want, got) + } + }) + } +} + +func newOIDCConfigChangedEvent(ctx context.Context, accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration) *iam.OIDCSettingsChangedEvent { + changes := []iam.OIDCSettingsChanges{ + iam.ChangeOIDCSettingsAccessTokenLifetime(accessTokenLifetime), + iam.ChangeOIDCSettingsIdTokenLifetime(idTokenLifetime), + iam.ChangeOIDCSettingsRefreshTokenIdleExpiration(refreshTokenIdleExpiration), + iam.ChangeOIDCSettingsRefreshTokenExpiration(refreshTokenExpiration), + } + event, _ := iam.NewOIDCSettingsChangeEvent(ctx, + &iam.NewAggregate().Aggregate, + changes, + ) + return event +} diff --git a/internal/domain/oidc_settings.go b/internal/domain/oidc_settings.go new file mode 100644 index 0000000000..acf6aab147 --- /dev/null +++ b/internal/domain/oidc_settings.go @@ -0,0 +1,37 @@ +package domain + +import ( + "time" + + "github.com/caos/zitadel/internal/eventstore/v1/models" +) + +type OIDCSettings struct { + models.ObjectRoot + + State OIDCSettingsState + Default bool + + AccessTokenLifetime time.Duration + IdTokenLifetime time.Duration + RefreshTokenIdleExpiration time.Duration + RefreshTokenExpiration time.Duration +} + +type OIDCSettingsState int32 + +const ( + OIDCSettingsStateUnspecified OIDCSettingsState = iota + OIDCSettingsStateActive + OIDCSettingsStateRemoved + + oidcSettingsStateCount +) + +func (c OIDCSettingsState) Valid() bool { + return c >= 0 && c < oidcSettingsStateCount +} + +func (s OIDCSettingsState) Exists() bool { + return s != OIDCSettingsStateUnspecified && s != OIDCSettingsStateRemoved +} diff --git a/internal/query/oidc_settings.go b/internal/query/oidc_settings.go new file mode 100644 index 0000000000..61323cc32f --- /dev/null +++ b/internal/query/oidc_settings.go @@ -0,0 +1,115 @@ +package query + +import ( + "context" + "database/sql" + errs "errors" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/query/projection" +) + +var ( + oidcSettingsTable = table{ + name: projection.OIDCSettingsProjectionTable, + } + OIDCSettingsColumnAggregateID = Column{ + name: projection.OIDCSettingsColumnAggregateID, + table: oidcSettingsTable, + } + OIDCSettingsColumnCreationDate = Column{ + name: projection.OIDCSettingsColumnCreationDate, + table: oidcSettingsTable, + } + OIDCSettingsColumnChangeDate = Column{ + name: projection.OIDCSettingsColumnChangeDate, + table: oidcSettingsTable, + } + OIDCSettingsColumnResourceOwner = Column{ + name: projection.OIDCSettingsColumnResourceOwner, + table: oidcSettingsTable, + } + OIDCSettingsColumnSequence = Column{ + name: projection.OIDCSettingsColumnSequence, + table: oidcSettingsTable, + } + OIDCSettingsColumnAccessTokenLifetime = Column{ + name: projection.OIDCSettingsColumnAccessTokenLifetime, + table: oidcSettingsTable, + } + OIDCSettingsColumnIdTokenLifetime = Column{ + name: projection.OIDCSettingsColumnIdTokenLifetime, + table: oidcSettingsTable, + } + OIDCSettingsColumnRefreshTokenIdleExpiration = Column{ + name: projection.OIDCSettingsColumnRefreshTokenIdleExpiration, + table: oidcSettingsTable, + } + OIDCSettingsColumnRefreshTokenExpiration = Column{ + name: projection.OIDCSettingsColumnRefreshTokenExpiration, + table: oidcSettingsTable, + } +) + +type OIDCSettings struct { + AggregateID string + CreationDate time.Time + ChangeDate time.Time + ResourceOwner string + Sequence uint64 + + AccessTokenLifetime time.Duration + IdTokenLifetime time.Duration + RefreshTokenIdleExpiration time.Duration + RefreshTokenExpiration time.Duration +} + +func (q *Queries) OIDCSettingsByAggID(ctx context.Context, aggregateID string) (*OIDCSettings, error) { + stmt, scan := prepareOIDCSettingsQuery() + query, args, err := stmt.Where(sq.Eq{ + OIDCSettingsColumnAggregateID.identifier(): aggregateID, + }).ToSql() + if err != nil { + return nil, errors.ThrowInternal(err, "QUERY-s9nle", "Errors.Query.SQLStatment") + } + + row := q.client.QueryRowContext(ctx, query, args...) + return scan(row) +} + +func prepareOIDCSettingsQuery() (sq.SelectBuilder, func(*sql.Row) (*OIDCSettings, error)) { + return sq.Select( + OIDCSettingsColumnAggregateID.identifier(), + OIDCSettingsColumnCreationDate.identifier(), + OIDCSettingsColumnChangeDate.identifier(), + OIDCSettingsColumnResourceOwner.identifier(), + OIDCSettingsColumnSequence.identifier(), + OIDCSettingsColumnAccessTokenLifetime.identifier(), + OIDCSettingsColumnIdTokenLifetime.identifier(), + OIDCSettingsColumnRefreshTokenIdleExpiration.identifier(), + OIDCSettingsColumnRefreshTokenExpiration.identifier()). + From(oidcSettingsTable.identifier()).PlaceholderFormat(sq.Dollar), + func(row *sql.Row) (*OIDCSettings, error) { + oidcSettings := new(OIDCSettings) + err := row.Scan( + &oidcSettings.AggregateID, + &oidcSettings.CreationDate, + &oidcSettings.ChangeDate, + &oidcSettings.ResourceOwner, + &oidcSettings.Sequence, + &oidcSettings.AccessTokenLifetime, + &oidcSettings.IdTokenLifetime, + &oidcSettings.RefreshTokenIdleExpiration, + &oidcSettings.RefreshTokenExpiration, + ) + if err != nil { + if errs.Is(err, sql.ErrNoRows) { + return nil, errors.ThrowNotFound(err, "QUERY-s9nlw", "Errors.OIDCSettings.NotFound") + } + return nil, errors.ThrowInternal(err, "QUERY-9bf8s", "Errors.Internal") + } + return oidcSettings, nil + } +} diff --git a/internal/query/oidc_settings_test.go b/internal/query/oidc_settings_test.go new file mode 100644 index 0000000000..601766023f --- /dev/null +++ b/internal/query/oidc_settings_test.go @@ -0,0 +1,136 @@ +package query + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + "time" + + errs "github.com/caos/zitadel/internal/errors" +) + +func Test_OIDCConfigsPrepares(t *testing.T) { + type want struct { + sqlExpectations sqlExpectation + err checkErr + } + tests := []struct { + name string + prepare interface{} + want want + object interface{} + }{ + { + name: "prepareOIDCSettingsQuery no result", + prepare: prepareOIDCSettingsQuery, + want: want{ + sqlExpectations: mockQueries( + `SELECT zitadel.projections.oidc_settings.aggregate_id,`+ + ` zitadel.projections.oidc_settings.creation_date,`+ + ` zitadel.projections.oidc_settings.change_date,`+ + ` zitadel.projections.oidc_settings.resource_owner,`+ + ` zitadel.projections.oidc_settings.sequence,`+ + ` zitadel.projections.oidc_settings.access_token_lifetime,`+ + ` zitadel.projections.oidc_settings.id_token_lifetime,`+ + ` zitadel.projections.oidc_settings.refresh_token_idle_expiration,`+ + ` zitadel.projections.oidc_settings.refresh_token_expiration`+ + ` FROM zitadel.projections.oidc_settings`, + nil, + nil, + ), + err: func(err error) (error, bool) { + if !errs.IsNotFound(err) { + return fmt.Errorf("err should be zitadel.NotFoundError got: %w", err), false + } + return nil, true + }, + }, + object: (*OIDCSettings)(nil), + }, + { + name: "prepareOIDCSettingsQuery found", + prepare: prepareOIDCSettingsQuery, + want: want{ + sqlExpectations: mockQuery( + regexp.QuoteMeta(`SELECT zitadel.projections.oidc_settings.aggregate_id,`+ + ` zitadel.projections.oidc_settings.creation_date,`+ + ` zitadel.projections.oidc_settings.change_date,`+ + ` zitadel.projections.oidc_settings.resource_owner,`+ + ` zitadel.projections.oidc_settings.sequence,`+ + ` zitadel.projections.oidc_settings.access_token_lifetime,`+ + ` zitadel.projections.oidc_settings.id_token_lifetime,`+ + ` zitadel.projections.oidc_settings.refresh_token_idle_expiration,`+ + ` zitadel.projections.oidc_settings.refresh_token_expiration`+ + ` FROM zitadel.projections.oidc_settings`), + []string{ + "aggregate_id", + "creation_date", + "change_date", + "resource_owner", + "sequence", + "access_token_lifetime", + "id_token_lifetime", + "refresh_token_idle_expiration", + "refresh_token_expiration", + }, + []driver.Value{ + "agg-id", + testNow, + testNow, + "ro", + uint64(20211108), + time.Minute * 1, + time.Minute * 2, + time.Minute * 3, + time.Minute * 4, + }, + ), + }, + object: &OIDCSettings{ + AggregateID: "agg-id", + CreationDate: testNow, + ChangeDate: testNow, + ResourceOwner: "ro", + Sequence: 20211108, + AccessTokenLifetime: time.Minute * 1, + IdTokenLifetime: time.Minute * 2, + RefreshTokenIdleExpiration: time.Minute * 3, + RefreshTokenExpiration: time.Minute * 4, + }, + }, + { + name: "prepareOIDCSettingsQuery sql err", + prepare: prepareOIDCSettingsQuery, + want: want{ + sqlExpectations: mockQueryErr( + regexp.QuoteMeta(`SELECT zitadel.projections.oidc_settings.aggregate_id,`+ + ` zitadel.projections.oidc_settings.creation_date,`+ + ` zitadel.projections.oidc_settings.change_date,`+ + ` zitadel.projections.oidc_settings.resource_owner,`+ + ` zitadel.projections.oidc_settings.sequence,`+ + ` zitadel.projections.oidc_settings.access_token_lifetime,`+ + ` zitadel.projections.oidc_settings.id_token_lifetime,`+ + ` zitadel.projections.oidc_settings.refresh_token_idle_expiration,`+ + ` zitadel.projections.oidc_settings.refresh_token_expiration`+ + ` FROM zitadel.projections.oidc_settings`), + sql.ErrConnDone, + ), + err: func(err error) (error, bool) { + if !errors.Is(err, sql.ErrConnDone) { + return fmt.Errorf("err should be sql.ErrConnDone got: %w", err), false + } + return nil, true + }, + }, + object: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + assertPrepare(t, tt.prepare, tt.object, tt.want.sqlExpectations, tt.want.err) + }) + } +} diff --git a/internal/query/projection/action.go b/internal/query/projection/action.go index 8f10d59453..923e0cf105 100644 --- a/internal/query/projection/action.go +++ b/internal/query/projection/action.go @@ -32,7 +32,7 @@ type ActionProjection struct { } func NewActionProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ActionProjection { - p := &ActionProjection{} + p := new(ActionProjection) config.ProjectionName = ActionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/app.go b/internal/query/projection/app.go index 240632cc83..7e82b2100a 100644 --- a/internal/query/projection/app.go +++ b/internal/query/projection/app.go @@ -25,7 +25,7 @@ const ( ) func NewAppProjection(ctx context.Context, config crdb.StatementHandlerConfig) *AppProjection { - p := &AppProjection{} + p := new(AppProjection) config.ProjectionName = AppProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/authn_key.go b/internal/query/projection/authn_key.go index 40de700852..27c090f452 100644 --- a/internal/query/projection/authn_key.go +++ b/internal/query/projection/authn_key.go @@ -35,7 +35,7 @@ type AuthNKeyProjection struct { } func NewAuthNKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *AuthNKeyProjection { - p := &AuthNKeyProjection{} + p := new(AuthNKeyProjection) config.ProjectionName = AuthNKeyTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/custom_text.go b/internal/query/projection/custom_text.go index 424989840e..e49a8b202c 100644 --- a/internal/query/projection/custom_text.go +++ b/internal/query/projection/custom_text.go @@ -33,7 +33,7 @@ const ( ) func NewCustomTextProjection(ctx context.Context, config crdb.StatementHandlerConfig) *CustomTextProjection { - p := &CustomTextProjection{} + p := new(CustomTextProjection) config.ProjectionName = CustomTextTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/feature.go b/internal/query/projection/feature.go index b77b17534b..7e411aafe8 100644 --- a/internal/query/projection/feature.go +++ b/internal/query/projection/feature.go @@ -25,7 +25,7 @@ const ( ) func NewFeatureProjection(ctx context.Context, config crdb.StatementHandlerConfig) *FeatureProjection { - p := &FeatureProjection{} + p := new(FeatureProjection) config.ProjectionName = FeatureTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/flow.go b/internal/query/projection/flow.go index 844756cd69..37c3008cbc 100644 --- a/internal/query/projection/flow.go +++ b/internal/query/projection/flow.go @@ -26,7 +26,7 @@ type FlowProjection struct { } func NewFlowProjection(ctx context.Context, config crdb.StatementHandlerConfig) *FlowProjection { - p := &FlowProjection{} + p := new(FlowProjection) config.ProjectionName = FlowTriggerTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/iam.go b/internal/query/projection/iam.go index cf565e9df3..ad1ce047d0 100644 --- a/internal/query/projection/iam.go +++ b/internal/query/projection/iam.go @@ -20,7 +20,7 @@ const ( ) func NewIAMProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IAMProjection { - p := &IAMProjection{} + p := new(IAMProjection) config.ProjectionName = IAMProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/iam_member.go b/internal/query/projection/iam_member.go index dad4cefe36..3d16267caf 100644 --- a/internal/query/projection/iam_member.go +++ b/internal/query/projection/iam_member.go @@ -22,7 +22,7 @@ const ( ) func NewIAMMemberProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IAMMemberProjection { - p := &IAMMemberProjection{} + p := new(IAMMemberProjection) config.ProjectionName = IAMMemberProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/idp.go b/internal/query/projection/idp.go index a33c745813..31125b6bc9 100644 --- a/internal/query/projection/idp.go +++ b/internal/query/projection/idp.go @@ -26,7 +26,7 @@ const ( ) func NewIDPProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IDPProjection { - p := &IDPProjection{} + p := new(IDPProjection) config.ProjectionName = IDPTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/idp_login_policy_link.go b/internal/query/projection/idp_login_policy_link.go index 81cf1b9c66..8dd2d1641f 100644 --- a/internal/query/projection/idp_login_policy_link.go +++ b/internal/query/projection/idp_login_policy_link.go @@ -23,7 +23,7 @@ const ( ) func NewIDPLoginPolicyLinkProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IDPLoginPolicyLinkProjection { - p := &IDPLoginPolicyLinkProjection{} + p := new(IDPLoginPolicyLinkProjection) config.ProjectionName = IDPLoginPolicyLinkTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/idp_user_link.go b/internal/query/projection/idp_user_link.go index 48c4036b69..121c35fe68 100644 --- a/internal/query/projection/idp_user_link.go +++ b/internal/query/projection/idp_user_link.go @@ -18,7 +18,7 @@ type IDPUserLinkProjection struct { } func NewIDPUserLinkProjection(ctx context.Context, config crdb.StatementHandlerConfig) *IDPUserLinkProjection { - p := &IDPUserLinkProjection{} + p := new(IDPUserLinkProjection) config.ProjectionName = IDPUserLinkTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/key.go b/internal/query/projection/key.go index 871ab84f89..67673400d5 100644 --- a/internal/query/projection/key.go +++ b/internal/query/projection/key.go @@ -27,7 +27,7 @@ const ( ) func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyConfig *crypto.KeyConfig, keyChan chan<- interface{}) (_ *KeyProjection, err error) { - p := &KeyProjection{} + p := new(KeyProjection) config.ProjectionName = KeyProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/label_policy.go b/internal/query/projection/label_policy.go index ffe0fe000a..9bad47e24a 100644 --- a/internal/query/projection/label_policy.go +++ b/internal/query/projection/label_policy.go @@ -23,7 +23,7 @@ const ( ) func NewLabelPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *LabelPolicyProjection { - p := &LabelPolicyProjection{} + p := new(LabelPolicyProjection) config.ProjectionName = LabelPolicyTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/lockout_policy.go b/internal/query/projection/lockout_policy.go index 2fe9d5e392..9805542f64 100644 --- a/internal/query/projection/lockout_policy.go +++ b/internal/query/projection/lockout_policy.go @@ -33,7 +33,7 @@ const ( ) func NewLockoutPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *LockoutPolicyProjection { - p := &LockoutPolicyProjection{} + p := new(LockoutPolicyProjection) config.ProjectionName = LockoutPolicyTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/login_name.go b/internal/query/projection/login_name.go index 20a4ef2d24..c7b1420e75 100644 --- a/internal/query/projection/login_name.go +++ b/internal/query/projection/login_name.go @@ -26,7 +26,7 @@ const ( ) func NewLoginNameProjection(ctx context.Context, config crdb.StatementHandlerConfig) *LoginNameProjection { - p := &LoginNameProjection{} + p := new(LoginNameProjection) config.ProjectionName = LoginNameProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/login_policy.go b/internal/query/projection/login_policy.go index 5f2d8fa5a7..43ba9b4eb3 100644 --- a/internal/query/projection/login_policy.go +++ b/internal/query/projection/login_policy.go @@ -23,7 +23,7 @@ const ( ) func NewLoginPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *LoginPolicyProjection { - p := &LoginPolicyProjection{} + p := new(LoginPolicyProjection) config.ProjectionName = LoginPolicyTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/mail_template.go b/internal/query/projection/mail_template.go index 027769c49b..1251dcb4a5 100644 --- a/internal/query/projection/mail_template.go +++ b/internal/query/projection/mail_template.go @@ -31,7 +31,7 @@ const ( ) func NewMailTemplateProjection(ctx context.Context, config crdb.StatementHandlerConfig) *MailTemplateProjection { - p := &MailTemplateProjection{} + p := new(MailTemplateProjection) config.ProjectionName = MailTemplateTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/message_texts.go b/internal/query/projection/message_texts.go index 512f07bf7f..1d7e437bc2 100644 --- a/internal/query/projection/message_texts.go +++ b/internal/query/projection/message_texts.go @@ -39,7 +39,7 @@ const ( ) func NewMessageTextProjection(ctx context.Context, config crdb.StatementHandlerConfig) *MessageTextProjection { - p := &MessageTextProjection{} + p := new(MessageTextProjection) config.ProjectionName = MessageTextTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/oidc_settings.go b/internal/query/projection/oidc_settings.go new file mode 100644 index 0000000000..14417db29d --- /dev/null +++ b/internal/query/projection/oidc_settings.go @@ -0,0 +1,114 @@ +package projection + +import ( + "context" + + "github.com/caos/logging" + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/handler/crdb" + "github.com/caos/zitadel/internal/repository/iam" + "github.com/caos/zitadel/internal/repository/project" +) + +type OIDCSettingsProjection struct { + crdb.StatementHandler +} + +const ( + OIDCSettingsProjectionTable = "zitadel.projections.oidc_settings" +) + +func NewOIDCSettingsProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OIDCSettingsProjection { + p := new(OIDCSettingsProjection) + config.ProjectionName = OIDCSettingsProjectionTable + config.Reducers = p.reducers() + p.StatementHandler = crdb.NewStatementHandler(ctx, config) + return p +} + +func (p *OIDCSettingsProjection) reducers() []handler.AggregateReducer { + return []handler.AggregateReducer{ + { + Aggregate: project.AggregateType, + EventRedusers: []handler.EventReducer{ + { + Event: iam.OIDCSettingsAddedEventType, + Reduce: p.reduceOIDCSettingsAdded, + }, + { + Event: iam.OIDCSettingsChangedEventType, + Reduce: p.reduceOIDCSettingsChanged, + }, + }, + }, + } +} + +const ( + OIDCSettingsColumnAggregateID = "aggregate_id" + OIDCSettingsColumnCreationDate = "creation_date" + OIDCSettingsColumnChangeDate = "change_date" + OIDCSettingsColumnResourceOwner = "resource_owner" + OIDCSettingsColumnSequence = "sequence" + OIDCSettingsColumnAccessTokenLifetime = "access_token_lifetime" + OIDCSettingsColumnIdTokenLifetime = "id_token_lifetime" + OIDCSettingsColumnRefreshTokenIdleExpiration = "refresh_token_idle_expiration" + OIDCSettingsColumnRefreshTokenExpiration = "refresh_token_expiration" +) + +func (p *OIDCSettingsProjection) reduceOIDCSettingsAdded(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*iam.OIDCSettingsAddedEvent) + if !ok { + logging.WithFields("seq", event.Sequence(), "expectedType", iam.OIDCSettingsAddedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-f9nwf", "reduce.wrong.event.type") + } + return crdb.NewCreateStatement( + e, + []handler.Column{ + handler.NewCol(OIDCSettingsColumnAggregateID, e.Aggregate().ID), + handler.NewCol(OIDCSettingsColumnCreationDate, e.CreationDate()), + handler.NewCol(OIDCSettingsColumnChangeDate, e.CreationDate()), + handler.NewCol(OIDCSettingsColumnResourceOwner, e.Aggregate().ResourceOwner), + handler.NewCol(OIDCSettingsColumnSequence, e.Sequence()), + handler.NewCol(OIDCSettingsColumnAccessTokenLifetime, e.AccessTokenLifetime), + handler.NewCol(OIDCSettingsColumnIdTokenLifetime, e.IdTokenLifetime), + handler.NewCol(OIDCSettingsColumnRefreshTokenIdleExpiration, e.RefreshTokenIdleExpiration), + handler.NewCol(OIDCSettingsColumnRefreshTokenExpiration, e.RefreshTokenExpiration), + }, + ), nil +} + +func (p *OIDCSettingsProjection) reduceOIDCSettingsChanged(event eventstore.Event) (*handler.Statement, error) { + e, ok := event.(*iam.OIDCSettingsChangedEvent) + if !ok { + logging.WithFields("seq", event.Sequence(), "expected", iam.OIDCSettingsChangedEventType).Error("wrong event type") + return nil, errors.ThrowInvalidArgument(nil, "HANDL-8JJ2d", "reduce.wrong.event.type") + } + + columns := make([]handler.Column, 0, 6) + columns = append(columns, + handler.NewCol(OIDCSettingsColumnChangeDate, e.CreationDate()), + handler.NewCol(OIDCSettingsColumnSequence, e.Sequence()), + ) + if e.AccessTokenLifetime != nil { + columns = append(columns, handler.NewCol(OIDCSettingsColumnAccessTokenLifetime, *e.AccessTokenLifetime)) + } + if e.IdTokenLifetime != nil { + columns = append(columns, handler.NewCol(OIDCSettingsColumnIdTokenLifetime, *e.IdTokenLifetime)) + } + if e.RefreshTokenIdleExpiration != nil { + columns = append(columns, handler.NewCol(OIDCSettingsColumnRefreshTokenIdleExpiration, *e.RefreshTokenIdleExpiration)) + } + if e.RefreshTokenExpiration != nil { + columns = append(columns, handler.NewCol(OIDCSettingsColumnRefreshTokenExpiration, *e.RefreshTokenExpiration)) + } + return crdb.NewUpdateStatement( + e, + columns, + []handler.Condition{ + handler.NewCond(OIDCSettingsColumnAggregateID, e.Aggregate().ID), + }, + ), nil +} diff --git a/internal/query/projection/oidc_settings_test.go b/internal/query/projection/oidc_settings_test.go new file mode 100644 index 0000000000..729859fa59 --- /dev/null +++ b/internal/query/projection/oidc_settings_test.go @@ -0,0 +1,106 @@ +package projection + +import ( + "testing" + "time" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/handler" + "github.com/caos/zitadel/internal/eventstore/repository" + "github.com/caos/zitadel/internal/repository/iam" +) + +func TestOIDCSettingsProjection_reduces(t *testing.T) { + type args struct { + event func(t *testing.T) eventstore.Event + } + tests := []struct { + name string + args args + reduce func(event eventstore.Event) (*handler.Statement, error) + want wantReduce + }{ + { + name: "reduceOIDCSettingsChanged", + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.OIDCSettingsChangedEventType), + iam.AggregateType, + []byte(`{"accessTokenLifetime": 10000000, "idTokenLifetime": 10000000, "refreshTokenIdleExpiration": 10000000, "refreshTokenExpiration": 10000000}`), + ), iam.OIDCSettingsChangedEventMapper), + }, + reduce: (&OIDCSettingsProjection{}).reduceOIDCSettingsChanged, + want: wantReduce{ + projection: OIDCSettingsProjectionTable, + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE zitadel.projections.oidc_settings SET (change_date, sequence, access_token_lifetime, id_token_lifetime, refresh_token_idle_expiration, refresh_token_expiration) = ($1, $2, $3, $4, $5, $6) WHERE (aggregate_id = $7)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + "agg-id", + }, + }, + }, + }, + }, + }, + { + name: "reduceOIDCSettingsAdded", + args: args{ + event: getEvent(testEvent( + repository.EventType(iam.OIDCSettingsAddedEventType), + iam.AggregateType, + []byte(`{"accessTokenLifetime": 10000000, "idTokenLifetime": 10000000, "refreshTokenIdleExpiration": 10000000, "refreshTokenExpiration": 10000000}`), + ), iam.OIDCSettingsAddedEventMapper), + }, + reduce: (&OIDCSettingsProjection{}).reduceOIDCSettingsAdded, + want: wantReduce{ + projection: OIDCSettingsProjectionTable, + aggregateType: eventstore.AggregateType("iam"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "INSERT INTO zitadel.projections.oidc_settings (aggregate_id, creation_date, change_date, resource_owner, sequence, access_token_lifetime, id_token_lifetime, refresh_token_idle_expiration, refresh_token_expiration) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)", + expectedArgs: []interface{}{ + "agg-id", + anyArg{}, + anyArg{}, + "ro-id", + uint64(15), + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + time.Millisecond * 10, + }, + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + event := baseEvent(t) + got, err := tt.reduce(event) + if _, ok := err.(errors.InvalidArgument); !ok { + t.Errorf("no wrong event mapping: %v, got: %v", err, got) + } + + event = tt.args.event(t) + got, err = tt.reduce(event) + assertReduce(t, got, err, tt.want) + }) + } +} diff --git a/internal/query/projection/org.go b/internal/query/projection/org.go index e13e76a653..27956ccd7c 100644 --- a/internal/query/projection/org.go +++ b/internal/query/projection/org.go @@ -22,7 +22,7 @@ const ( ) func NewOrgProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OrgProjection { - p := &OrgProjection{} + p := new(OrgProjection) config.ProjectionName = OrgProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/org_domain.go b/internal/query/projection/org_domain.go index 5193a1b976..eb60c654da 100644 --- a/internal/query/projection/org_domain.go +++ b/internal/query/projection/org_domain.go @@ -22,7 +22,7 @@ const ( ) func NewOrgDomainProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OrgDomainProjection { - p := &OrgDomainProjection{} + p := new(OrgDomainProjection) config.ProjectionName = OrgDomainTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/org_iam_policy.go b/internal/query/projection/org_iam_policy.go index 444c8296fc..50e8cd5c4e 100644 --- a/internal/query/projection/org_iam_policy.go +++ b/internal/query/projection/org_iam_policy.go @@ -32,7 +32,7 @@ const ( ) func NewOrgIAMPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OrgIAMPolicyProjection { - p := &OrgIAMPolicyProjection{} + p := new(OrgIAMPolicyProjection) config.ProjectionName = OrgIAMPolicyTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/org_member.go b/internal/query/projection/org_member.go index a3d0871cf0..6628dca7ff 100644 --- a/internal/query/projection/org_member.go +++ b/internal/query/projection/org_member.go @@ -22,7 +22,7 @@ const ( ) func NewOrgMemberProjection(ctx context.Context, config crdb.StatementHandlerConfig) *OrgMemberProjection { - p := &OrgMemberProjection{} + p := new(OrgMemberProjection) config.ProjectionName = OrgMemberProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/password_age_policy.go b/internal/query/projection/password_age_policy.go index 045aea9a99..348b8ec03f 100644 --- a/internal/query/projection/password_age_policy.go +++ b/internal/query/projection/password_age_policy.go @@ -23,7 +23,7 @@ const ( ) func NewPasswordAgeProjection(ctx context.Context, config crdb.StatementHandlerConfig) *PasswordAgeProjection { - p := &PasswordAgeProjection{} + p := new(PasswordAgeProjection) config.ProjectionName = PasswordAgeTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/password_complexity_policy.go b/internal/query/projection/password_complexity_policy.go index 2f26c6e027..0a5660b571 100644 --- a/internal/query/projection/password_complexity_policy.go +++ b/internal/query/projection/password_complexity_policy.go @@ -23,7 +23,7 @@ const ( ) func NewPasswordComplexityProjection(ctx context.Context, config crdb.StatementHandlerConfig) *PasswordComplexityProjection { - p := &PasswordComplexityProjection{} + p := new(PasswordComplexityProjection) config.ProjectionName = PasswordComplexityTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/privacy_policy.go b/internal/query/projection/privacy_policy.go index 3ca35ecbcf..552c8d031f 100644 --- a/internal/query/projection/privacy_policy.go +++ b/internal/query/projection/privacy_policy.go @@ -33,7 +33,7 @@ const ( ) func NewPrivacyPolicyProjection(ctx context.Context, config crdb.StatementHandlerConfig) *PrivacyPolicyProjection { - p := &PrivacyPolicyProjection{} + p := new(PrivacyPolicyProjection) config.ProjectionName = PrivacyPolicyTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project.go b/internal/query/projection/project.go index 6ff50ce32f..fe552f4f62 100644 --- a/internal/query/projection/project.go +++ b/internal/query/projection/project.go @@ -21,7 +21,7 @@ const ( ) func NewProjectProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ProjectProjection { - p := &ProjectProjection{} + p := new(ProjectProjection) config.ProjectionName = ProjectProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project_grant.go b/internal/query/projection/project_grant.go index 0d6aaec5af..adaddcb27e 100644 --- a/internal/query/projection/project_grant.go +++ b/internal/query/projection/project_grant.go @@ -20,7 +20,7 @@ type ProjectGrantProjection struct { const ProjectGrantProjectionTable = "zitadel.projections.project_grants" func NewProjectGrantProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ProjectGrantProjection { - p := &ProjectGrantProjection{} + p := new(ProjectGrantProjection) config.ProjectionName = ProjectGrantProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project_grant_member.go b/internal/query/projection/project_grant_member.go index 7860e775bd..07e7989d1b 100644 --- a/internal/query/projection/project_grant_member.go +++ b/internal/query/projection/project_grant_member.go @@ -24,7 +24,7 @@ const ( ) func NewProjectGrantMemberProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ProjectGrantMemberProjection { - p := &ProjectGrantMemberProjection{} + p := new(ProjectGrantMemberProjection) config.ProjectionName = ProjectGrantMemberProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project_member.go b/internal/query/projection/project_member.go index d338e6195f..dd3dafb145 100644 --- a/internal/query/projection/project_member.go +++ b/internal/query/projection/project_member.go @@ -24,7 +24,7 @@ const ( ) func NewProjectMemberProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ProjectMemberProjection { - p := &ProjectMemberProjection{} + p := new(ProjectMemberProjection) config.ProjectionName = ProjectMemberProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/project_role.go b/internal/query/projection/project_role.go index 10fc21f116..3138625617 100644 --- a/internal/query/projection/project_role.go +++ b/internal/query/projection/project_role.go @@ -18,7 +18,7 @@ type ProjectRoleProjection struct { const ProjectRoleProjectionTable = "zitadel.projections.project_roles" func NewProjectRoleProjection(ctx context.Context, config crdb.StatementHandlerConfig) *ProjectRoleProjection { - p := &ProjectRoleProjection{} + p := new(ProjectRoleProjection) config.ProjectionName = ProjectRoleProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index ce41288725..af22ce0910 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -72,6 +72,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co NewSecretGeneratorProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["secret_generators"])) NewSMTPConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["smtp_configs"])) NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"])) + NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"])) _, err := NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyConfig, keyChan) return err diff --git a/internal/query/projection/secret_generator.go b/internal/query/projection/secret_generator.go index 4ce6cc841e..934eecad2a 100644 --- a/internal/query/projection/secret_generator.go +++ b/internal/query/projection/secret_generator.go @@ -21,7 +21,7 @@ const ( ) func NewSecretGeneratorProjection(ctx context.Context, config crdb.StatementHandlerConfig) *SecretGeneratorProjection { - p := &SecretGeneratorProjection{} + p := new(SecretGeneratorProjection) config.ProjectionName = SecretGeneratorProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/sms.go b/internal/query/projection/sms.go index 26ec120479..ad484dd194 100644 --- a/internal/query/projection/sms.go +++ b/internal/query/projection/sms.go @@ -22,7 +22,7 @@ const ( ) func NewSMSConfigProjection(ctx context.Context, config crdb.StatementHandlerConfig) *SMSConfigProjection { - p := &SMSConfigProjection{} + p := new(SMSConfigProjection) config.ProjectionName = SMSConfigProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/smtp.go b/internal/query/projection/smtp.go index a31399ff2a..69b94bf4dd 100644 --- a/internal/query/projection/smtp.go +++ b/internal/query/projection/smtp.go @@ -21,7 +21,7 @@ const ( ) func NewSMTPConfigProjection(ctx context.Context, config crdb.StatementHandlerConfig) *SMTPConfigProjection { - p := &SMTPConfigProjection{} + p := new(SMTPConfigProjection) config.ProjectionName = SMTPConfigProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/user.go b/internal/query/projection/user.go index f5873954f5..ab31ba4a1d 100644 --- a/internal/query/projection/user.go +++ b/internal/query/projection/user.go @@ -25,7 +25,7 @@ const ( ) func NewUserProjection(ctx context.Context, config crdb.StatementHandlerConfig) *UserProjection { - p := &UserProjection{} + p := new(UserProjection) config.ProjectionName = UserTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/user_auth_method.go b/internal/query/projection/user_auth_method.go index a11ebc6c95..c99cb60b2b 100644 --- a/internal/query/projection/user_auth_method.go +++ b/internal/query/projection/user_auth_method.go @@ -21,7 +21,7 @@ const ( ) func NewUserAuthMethodProjection(ctx context.Context, config crdb.StatementHandlerConfig) *UserAuthMethodProjection { - p := &UserAuthMethodProjection{} + p := new(UserAuthMethodProjection) config.ProjectionName = UserAuthMethodTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/user_grant.go b/internal/query/projection/user_grant.go index fff3e3fe40..28ad9a0184 100644 --- a/internal/query/projection/user_grant.go +++ b/internal/query/projection/user_grant.go @@ -25,7 +25,7 @@ const ( ) func NewUserGrantProjection(ctx context.Context, config crdb.StatementHandlerConfig) *UserGrantProjection { - p := &UserGrantProjection{} + p := new(UserGrantProjection) config.ProjectionName = UserGrantProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/user_metadata.go b/internal/query/projection/user_metadata.go index 36a5250f33..c94e50f195 100644 --- a/internal/query/projection/user_metadata.go +++ b/internal/query/projection/user_metadata.go @@ -19,7 +19,7 @@ type UserMetadataProjection struct { const UserMetadataProjectionTable = "zitadel.projections.user_metadata" func NewUserMetadataProjection(ctx context.Context, config crdb.StatementHandlerConfig) *UserMetadataProjection { - p := &UserMetadataProjection{} + p := new(UserMetadataProjection) config.ProjectionName = UserMetadataProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/query/projection/user_personal_access_token.go b/internal/query/projection/user_personal_access_token.go index 74ce54f1ef..a11a3b45c5 100644 --- a/internal/query/projection/user_personal_access_token.go +++ b/internal/query/projection/user_personal_access_token.go @@ -22,7 +22,7 @@ const ( ) func NewPersonalAccessTokenProjection(ctx context.Context, config crdb.StatementHandlerConfig) *PersonalAccessTokenProjection { - p := &PersonalAccessTokenProjection{} + p := new(PersonalAccessTokenProjection) config.ProjectionName = PersonalAccessTokenProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) diff --git a/internal/repository/iam/eventstore.go b/internal/repository/iam/eventstore.go index f0d1cabb6d..402dd418ad 100644 --- a/internal/repository/iam/eventstore.go +++ b/internal/repository/iam/eventstore.go @@ -23,6 +23,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(SMSConfigActivatedEventType, SMSConfigActivatedEventMapper). RegisterFilterEventMapper(SMSConfigDeactivatedEventType, SMSConfigDeactivatedEventMapper). RegisterFilterEventMapper(SMSConfigRemovedEventType, SMSConfigRemovedEventMapper). + RegisterFilterEventMapper(OIDCSettingsAddedEventType, OIDCSettingsAddedEventMapper). + RegisterFilterEventMapper(OIDCSettingsChangedEventType, OIDCSettingsChangedEventMapper). RegisterFilterEventMapper(LabelPolicyAddedEventType, LabelPolicyAddedEventMapper). RegisterFilterEventMapper(LabelPolicyChangedEventType, LabelPolicyChangedEventMapper). RegisterFilterEventMapper(LabelPolicyActivatedEventType, LabelPolicyActivatedEventMapper). diff --git a/internal/repository/iam/oidc_settings.go b/internal/repository/iam/oidc_settings.go new file mode 100644 index 0000000000..063217116e --- /dev/null +++ b/internal/repository/iam/oidc_settings.go @@ -0,0 +1,145 @@ +package iam + +import ( + "context" + "encoding/json" + "time" + + "github.com/caos/zitadel/internal/errors" + "github.com/caos/zitadel/internal/eventstore" + "github.com/caos/zitadel/internal/eventstore/repository" +) + +const ( + oidcSettingsPrefix = "oidc.settings." + OIDCSettingsAddedEventType = iamEventTypePrefix + oidcSettingsPrefix + "added" + OIDCSettingsChangedEventType = iamEventTypePrefix + oidcSettingsPrefix + "changed" + OIDCSettingsRemovedEventType = iamEventTypePrefix + oidcSettingsPrefix + "removed" +) + +type OIDCSettingsAddedEvent struct { + eventstore.BaseEvent `json:"-"` + + AccessTokenLifetime time.Duration `json:"accessTokenLifetime,omitempty"` + IdTokenLifetime time.Duration `json:"idTokenLifetime,omitempty"` + RefreshTokenIdleExpiration time.Duration `json:"refreshTokenIdleExpiration,omitempty"` + RefreshTokenExpiration time.Duration `json:"refreshTokenExpiration,omitempty"` +} + +func NewOIDCSettingsAddedEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + accessTokenLifetime, + idTokenLifetime, + refreshTokenIdleExpiration, + refreshTokenExpiration time.Duration, +) *OIDCSettingsAddedEvent { + return &OIDCSettingsAddedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCSettingsAddedEventType, + ), + AccessTokenLifetime: accessTokenLifetime, + IdTokenLifetime: idTokenLifetime, + RefreshTokenIdleExpiration: refreshTokenIdleExpiration, + RefreshTokenExpiration: refreshTokenExpiration, + } +} + +func (e *OIDCSettingsAddedEvent) Data() interface{} { + return e +} + +func (e *OIDCSettingsAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func OIDCSettingsAddedEventMapper(event *repository.Event) (eventstore.Event, error) { + oidcSettingsAdded := &OIDCSettingsAddedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + err := json.Unmarshal(event.Data, oidcSettingsAdded) + if err != nil { + return nil, errors.ThrowInternal(err, "IAM-soiwj", "unable to unmarshal oidc config added") + } + + return oidcSettingsAdded, nil +} + +type OIDCSettingsChangedEvent struct { + eventstore.BaseEvent `json:"-"` + + AccessTokenLifetime *time.Duration `json:"accessTokenLifetime,omitempty"` + IdTokenLifetime *time.Duration `json:"idTokenLifetime,omitempty"` + RefreshTokenIdleExpiration *time.Duration `json:"refreshTokenIdleExpiration,omitempty"` + RefreshTokenExpiration *time.Duration `json:"refreshTokenExpiration,omitempty"` +} + +func (e *OIDCSettingsChangedEvent) Data() interface{} { + return e +} + +func (e *OIDCSettingsChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func NewOIDCSettingsChangeEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + changes []OIDCSettingsChanges, +) (*OIDCSettingsChangedEvent, error) { + if len(changes) == 0 { + return nil, errors.ThrowPreconditionFailed(nil, "IAM-dnlwe", "Errors.NoChangesFound") + } + changeEvent := &OIDCSettingsChangedEvent{ + BaseEvent: *eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCSettingsChangedEventType, + ), + } + for _, change := range changes { + change(changeEvent) + } + return changeEvent, nil +} + +type OIDCSettingsChanges func(event *OIDCSettingsChangedEvent) + +func ChangeOIDCSettingsAccessTokenLifetime(accessTokenLifetime time.Duration) func(event *OIDCSettingsChangedEvent) { + return func(e *OIDCSettingsChangedEvent) { + e.AccessTokenLifetime = &accessTokenLifetime + } +} + +func ChangeOIDCSettingsIdTokenLifetime(idTokenLifetime time.Duration) func(event *OIDCSettingsChangedEvent) { + return func(e *OIDCSettingsChangedEvent) { + e.IdTokenLifetime = &idTokenLifetime + } +} + +func ChangeOIDCSettingsRefreshTokenIdleExpiration(refreshTokenIdleExpiration time.Duration) func(event *OIDCSettingsChangedEvent) { + return func(e *OIDCSettingsChangedEvent) { + e.RefreshTokenIdleExpiration = &refreshTokenIdleExpiration + } +} + +func ChangeOIDCSettingsRefreshTokenExpiration(refreshTokenExpiration time.Duration) func(event *OIDCSettingsChangedEvent) { + return func(e *OIDCSettingsChangedEvent) { + e.RefreshTokenExpiration = &refreshTokenExpiration + } +} + +func OIDCSettingsChangedEventMapper(event *repository.Event) (eventstore.Event, error) { + e := &OIDCSettingsChangedEvent{ + BaseEvent: *eventstore.BaseEventFromRepo(event), + } + + err := json.Unmarshal(event.Data, e) + if err != nil { + return nil, errors.ThrowInternal(err, "IAM-f98uf", "unable to unmarshal oidc settings changed") + } + + return e, nil +} diff --git a/internal/static/i18n/de.yaml b/internal/static/i18n/de.yaml index 01eecb3bb9..5eda4ff579 100644 --- a/internal/static/i18n/de.yaml +++ b/internal/static/i18n/de.yaml @@ -29,6 +29,20 @@ Errors: ExceedsDefault: Limit überschreitet default Limit Language: NotParsed: Sprache konnte nicht gemapped werde + OIDCSettings: + NotFound: OIDC Konfiguration konnte nicht gefunden werden + AlreadyExists: OIDC Konfiguration existiert bereits + SecretGenerator: + AlreadyExists: Passwort Generator exisiter bereits + TypeMissing: Passwort Generator Typ fehlt + NotFound: Passwort Generator nicht gefunden + SMSConfig: + NotFound: SMS Konfiguration nicht gefunden + AlreadyActive: SMS Konfiguration ist bereits aktiviert + AlreadyDeactivated: SMS Konfiguration ist bereits deaktiviert + SMTPConfig: + NotFound: SMTP Konfiguration nicht gefunden + AlreadyExists: SMTP Konfiguration existiert bereits User: NotFound: Benutzer konnte nicht gefunden werden AlreadyExists: Benutzer existierts bereits @@ -816,6 +830,35 @@ EventTypes: removed: Schrift von Label Richtlinie entfernt assets: removed: Bilder und Schrift von Label Richtlinie entfernt + default: + language: + set: Standard Sprache gesetzt + oidc: + settings: + added: OIDC Konfiguration hinzugefügt + changed: OIDC Konfiguration geändert + removed: OIDC Konfiguration gelöscht + secret: + generator: + added: Passwort Generator hinzugefügt + changed: Passwort Generator geändert + removed: Passwort Generator gelöscht + smtp: + config: + added: SMTP Konfiguration hinzugefügt + changed: SMTP Konfiguration geändert + password: + changed: SMTP Konfigurations Passwort geändert + sms: + config: + twilio: + added: Twilio SMS Provider hinzugefügt + changed: Twilio SMS Provider geändert + token: + changed: Twilio SMS Provider Token geändert + removed: Twilio SMS Provider entfernt + activated: Twilio SMS Provider aktiviert + deactivated: Twilio SMS Provider deaktiviert key_pair: added: Schlüsselpaar hinzugefügt action: diff --git a/internal/static/i18n/en.yaml b/internal/static/i18n/en.yaml index 70bb408df2..0c4961f656 100644 --- a/internal/static/i18n/en.yaml +++ b/internal/static/i18n/en.yaml @@ -29,6 +29,20 @@ Errors: ExceedsDefault: Limit exceeds default limit Language: NotParsed: Could not parse languge + OIDCSettings: + NotFound: OIDC Configuration not found + AlreadyExists: OIDC configuration already exists + SecretGenerator: + AlreadyExists: Secret generator already exists + TypeMissing: Secret generator type missing + NotFound: Secret generator not found + SMSConfig: + NotFound: SMS configuration not found + AlreadyActive: SMS configuration already active + AlreadyDeactivated: SMS configuration already deactived + SMTPConfig: + NotFound: SMTP configuration not found + AlreadyExists: SMTP configuration already exists User: NotFound: User could not be found AlreadyExists: User already exists @@ -813,6 +827,35 @@ EventTypes: removed: Font removed from Label Policy assets: removed: Assets removed from Label Policy + default: + language: + set: Default language set + oidc: + settings: + added: OIDC configuration added + changed: OIDC configuration changed + removed: OIDC configuration removed + secret: + generator: + added: Secret generator added + changed: Secret generator changed + removed: Secret generator removed + smtp: + config: + added: SMTP configuration added + changed: SMTP configuration changed + password: + changed: SMTP configuration secret changed + sms: + config: + twilio: + added: Twilio SMS provider added + changed: Twilio SMS provider changed + token: + changed: Twilio SMS provider token changed + removed: Twilio SMS provider removed + activated: Twilio SMS provider activated + deactivated: Twilio SMS provider deactivated key_pair: added: Key pair added action: @@ -821,6 +864,7 @@ EventTypes: deactivated: Action deactivated reactivated: Action reactivated removed: Action removed + Application: OIDC: UnsupportedVersion: Your OIDC version is not supported diff --git a/internal/static/i18n/it.yaml b/internal/static/i18n/it.yaml index 92ea347056..9963483a96 100644 --- a/internal/static/i18n/it.yaml +++ b/internal/static/i18n/it.yaml @@ -29,6 +29,20 @@ Errors: ExceedsDefault: Il limite supera quello predefinito Language: NotParsed: Impossibile analizzare la lingua + OIDCSettings: + NotFound: Impossibile trovare la configurazione OIDC + AlreadyExists: La configurazione OIDC esiste già + SecretGenerator: + AlreadyExists: Il generatore di segreti esiste già + TypeMissing: Manca il tipo di generatore segreto + NotFound: Generatore segreto non trovato + SMSConfig: + NotFound: Configurazione SMS non trovata + AlreadyActive: Configurazione SMS già attiva + AlreadyDeactivated: Configurazione SMS già disattivata + SMTPConfig: + NotFound: Configurazione SMTP non trovata + AlreadyExists: La configurazione SMTP esiste già User: NotFound: L'utente non è stato trovato AlreadyExists: L'utente già esistente @@ -811,6 +825,35 @@ EventTypes: removed: Font rimosso assets: removed: Asset rimosse + default: + language: + set: lingua predefinita impostata + oidc: + settings: + added: Configurazione OIDC aggiunta + changed: Configurazione OIDC cambiata + removed: Configurazione OIDC rimossa + secret: + generator: + added: Generatore di segreti aggiunto + changed: Generatore di segreti cambiato + removed: Generatore di segreti cambiato + smtp: + config: + added: SMTP configuration added + changed: SMTP configuration changed + password: + changed: SMTP configuration secret changed + sms: + config: + twilio: + added: Aggiunto il fornitore di SMS Twilio + changed: Twilio SMS Provider cambiato + token: + changed: Twilio SMS Provider token cambiato + removed: Provider SMS Twilio rimosso + activated: Provider SMS Twilio attivato + deactivated: Provider SMS Twilio disattivato key_pair: added: Keypair aggiunto action: diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 1a5a3d9024..bf955bbbd5 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -313,6 +313,28 @@ service AdminService { }; } + // Get OIDC settings (e.g token lifetimes, etc.) + rpc GetOIDCSettings(GetOIDCSettingsRequest) returns (GetOIDCSettingsResponse) { + option (google.api.http) = { + get: "/settings/oidc"; + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.read"; + }; + } + + // Update oidc settings (e.g token lifetimes, etc) + rpc UpdateOIDCSettings(UpdateOIDCSettingsRequest) returns (UpdateOIDCSettingsResponse) { + option (google.api.http) = { + put: "/settings/oidc"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + } // Returns an organisation by id rpc GetOrgByID(GetOrgByIDRequest) returns (GetOrgByIDResponse) { @@ -2539,6 +2561,24 @@ message UpdateSMSProviderTwilioTokenResponse { zitadel.v1.ObjectDetails details = 1; } +// This is an empty request +message GetOIDCSettingsRequest {} + +message GetOIDCSettingsResponse { + zitadel.settings.v1.OIDCSettings settings = 1; +} + +message UpdateOIDCSettingsRequest { + google.protobuf.Duration access_token_lifetime = 1; + google.protobuf.Duration id_token_lifetime = 2; + google.protobuf.Duration refresh_token_idle_expiration = 3; + google.protobuf.Duration refresh_token_expiration = 4; +} + +message UpdateOIDCSettingsResponse { + zitadel.v1.ObjectDetails details = 1; +} + // if name or domain is already in use, org is not unique message IsOrgUniqueRequest { option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = { diff --git a/proto/zitadel/settings.proto b/proto/zitadel/settings.proto index 56cf4778b1..8af7610166 100644 --- a/proto/zitadel/settings.proto +++ b/proto/zitadel/settings.proto @@ -20,7 +20,6 @@ message SecretGenerator { bool include_symbols = 8; } - message SecretGeneratorQuery { oneof query { option (validate.required) = true; @@ -67,9 +66,16 @@ message TwilioConfig { string sender_number = 2; } - enum SMSProviderConfigState { SMS_PROVIDER_CONFIG_STATE_UNSPECIFIED = 0; SMS_PROVIDER_CONFIG_ACTIVE = 1; SMS_PROVIDER_CONFIG_INACTIVE = 2; } + +message OIDCSettings { + zitadel.v1.ObjectDetails details = 1; + google.protobuf.Duration access_token_lifetime = 2; + google.protobuf.Duration id_token_lifetime = 3; + google.protobuf.Duration refresh_token_idle_expiration = 4; + google.protobuf.Duration refresh_token_expiration = 5; +}