From 5562ee94a6e192566f223754440caf45c0e338af Mon Sep 17 00:00:00 2001 From: Stefan Benz <46600784+stebenz@users.noreply.github.com> Date: Thu, 8 Jun 2023 00:50:53 +0200 Subject: [PATCH] feat: migrate external idp to other types (#5984) * feat: migrate instance oidc to azureAD * feat: migrate instance oidc to azureAD * feat: migrate org oidc to azureAD * feat: migrate oidc to google * fix: correct idp writemodels * fix: review changes --- internal/api/grpc/admin/idp.go | 17 + internal/api/grpc/management/idp.go | 17 + internal/command/idp_model.go | 12 + internal/command/instance_idp.go | 141 +++++- internal/command/instance_idp_model.go | 12 + internal/command/instance_idp_test.go | 468 ++++++++++++++++++ internal/command/org_idp.go | 118 +++++ internal/command/org_idp_model.go | 12 + internal/command/org_idp_test.go | 468 ++++++++++++++++++ internal/domain/idp.go | 3 +- internal/query/projection/idp_template.go | 104 ++++ .../query/projection/idp_template_test.go | 272 ++++++++++ internal/repository/idp/oidc.go | 90 ++++ internal/repository/instance/eventstore.go | 2 + internal/repository/instance/idp.go | 86 ++++ internal/repository/org/eventstore.go | 2 + internal/repository/org/idp.go | 86 ++++ proto/zitadel/admin.proto | 35 ++ proto/zitadel/management.proto | 35 ++ 19 files changed, 1968 insertions(+), 12 deletions(-) diff --git a/internal/api/grpc/admin/idp.go b/internal/api/grpc/admin/idp.go index a66a6e0684..5d178b5b88 100644 --- a/internal/api/grpc/admin/idp.go +++ b/internal/api/grpc/admin/idp.go @@ -6,6 +6,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp" object_pb "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" ) @@ -220,6 +221,22 @@ func (s *Server) UpdateGenericOIDCProvider(ctx context.Context, req *admin_pb.Up }, nil } +func (s *Server) MigrateGenericOIDCProvider(ctx context.Context, req *admin_pb.MigrateGenericOIDCProviderRequest) (*admin_pb.MigrateGenericOIDCProviderResponse, error) { + var details *domain.ObjectDetails + var err error + if req.GetAzure() != nil { + details, err = s.command.MigrateInstanceGenericOIDCToAzureADProvider(ctx, req.GetId(), addAzureADProviderToCommand(req.GetAzure())) + } else if req.GetGoogle() != nil { + details, err = s.command.MigrateInstanceGenericOIDCToGoogleProvider(ctx, req.GetId(), addGoogleProviderToCommand(req.GetGoogle())) + } + if err != nil { + return nil, err + } + return &admin_pb.MigrateGenericOIDCProviderResponse{ + Details: object_pb.DomainToAddDetailsPb(details), + }, nil +} + func (s *Server) AddJWTProvider(ctx context.Context, req *admin_pb.AddJWTProviderRequest) (*admin_pb.AddJWTProviderResponse, error) { id, details, err := s.command.AddInstanceJWTProvider(ctx, addJWTProviderToCommand(req)) if err != nil { diff --git a/internal/api/grpc/management/idp.go b/internal/api/grpc/management/idp.go index 66c7b4c6a7..3803b41ec9 100644 --- a/internal/api/grpc/management/idp.go +++ b/internal/api/grpc/management/idp.go @@ -6,6 +6,7 @@ import ( "github.com/zitadel/zitadel/internal/api/authz" idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp" object_pb "github.com/zitadel/zitadel/internal/api/grpc/object" + "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management" ) @@ -212,6 +213,22 @@ func (s *Server) UpdateGenericOIDCProvider(ctx context.Context, req *mgmt_pb.Upd }, nil } +func (s *Server) MigrateGenericOIDCProvider(ctx context.Context, req *mgmt_pb.MigrateGenericOIDCProviderRequest) (*mgmt_pb.MigrateGenericOIDCProviderResponse, error) { + var details *domain.ObjectDetails + var err error + if req.GetAzure() != nil { + details, err = s.command.MigrateOrgGenericOIDCToAzureADProvider(ctx, authz.GetCtxData(ctx).OrgID, req.GetId(), addAzureADProviderToCommand(req.GetAzure())) + } else if req.GetGoogle() != nil { + details, err = s.command.MigrateOrgGenericOIDCToGoogleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.GetId(), addGoogleProviderToCommand(req.GetGoogle())) + } + if err != nil { + return nil, err + } + return &mgmt_pb.MigrateGenericOIDCProviderResponse{ + Details: object_pb.DomainToAddDetailsPb(details), + }, nil +} + func (s *Server) AddJWTProvider(ctx context.Context, req *mgmt_pb.AddJWTProviderRequest) (*mgmt_pb.AddJWTProviderResponse, error) { id, details, err := s.command.AddOrgJWTProvider(ctx, authz.GetCtxData(ctx).OrgID, addJWTProviderToCommand(req)) if err != nil { diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go index 036a08aa97..0849c5bd01 100644 --- a/internal/command/idp_model.go +++ b/internal/command/idp_model.go @@ -211,6 +211,12 @@ func (wm *OIDCIDPWriteModel) Reduce() error { wm.reduceAddedEvent(e) case *idp.OIDCIDPChangedEvent: wm.reduceChangedEvent(e) + case *idp.OIDCIDPMigratedAzureADEvent: + wm.State = domain.IDPStateMigrated + case *idp.OIDCIDPMigratedGoogleEvent: + wm.State = domain.IDPStateMigrated + case *idp.RemovedEvent: + wm.State = domain.IDPStateRemoved case *idpconfig.IDPConfigAddedEvent: wm.reduceIDPConfigAddedEvent(e) case *idpconfig.IDPConfigChangedEvent: @@ -397,6 +403,8 @@ func (wm *JWTIDPWriteModel) Reduce() error { wm.reduceAddedEvent(e) case *idp.JWTIDPChangedEvent: wm.reduceChangedEvent(e) + case *idp.RemovedEvent: + wm.State = domain.IDPStateRemoved case *idpconfig.IDPConfigAddedEvent: wm.reduceIDPConfigAddedEvent(e) case *idpconfig.IDPConfigChangedEvent: @@ -558,6 +566,8 @@ func (wm *AzureADIDPWriteModel) Reduce() error { switch e := event.(type) { case *idp.AzureADIDPAddedEvent: wm.reduceAddedEvent(e) + case *idp.OIDCIDPMigratedAzureADEvent: + wm.reduceAddedEvent(&e.AzureADIDPAddedEvent) case *idp.AzureADIDPChangedEvent: wm.reduceChangedEvent(e) case *idp.RemovedEvent: @@ -1195,6 +1205,8 @@ func (wm *GoogleIDPWriteModel) Reduce() error { wm.reduceAddedEvent(e) case *idp.GoogleIDPChangedEvent: wm.reduceChangedEvent(e) + case *idp.OIDCIDPMigratedGoogleEvent: + wm.reduceAddedEvent(&e.GoogleIDPAddedEvent) case *idp.RemovedEvent: wm.State = domain.IDPStateRemoved } diff --git a/internal/command/instance_idp.go b/internal/command/instance_idp.go index 9d2533a12f..ea6534970b 100644 --- a/internal/command/instance_idp.go +++ b/internal/command/instance_idp.go @@ -97,6 +97,40 @@ func (c *Commands) UpdateInstanceGenericOIDCProvider(ctx context.Context, id str return pushedEventsToObjectDetails(pushedEvents), nil } +func (c *Commands) MigrateInstanceGenericOIDCToAzureADProvider(ctx context.Context, id string, provider AzureADProvider) (*domain.ObjectDetails, error) { + return c.migrateInstanceGenericOIDC(ctx, id, provider) +} + +func (c *Commands) MigrateInstanceGenericOIDCToGoogleProvider(ctx context.Context, id string, provider GoogleProvider) (*domain.ObjectDetails, error) { + return c.migrateInstanceGenericOIDC(ctx, id, provider) +} + +func (c *Commands) migrateInstanceGenericOIDC(ctx context.Context, id string, provider interface{}) (*domain.ObjectDetails, error) { + instanceID := authz.GetInstance(ctx).InstanceID() + instanceAgg := instance.NewAggregate(instanceID) + writeModel := NewOIDCInstanceIDPWriteModel(instanceID, id) + + var validation preparation.Validation + switch p := provider.(type) { + case AzureADProvider: + validation = c.prepareMigrateInstanceOIDCToAzureADProvider(instanceAgg, writeModel, p) + case GoogleProvider: + validation = c.prepareMigrateInstanceOIDCToGoogleProvider(instanceAgg, writeModel, p) + default: + return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-s9219", "Errors.IDPConfig.NotExisting") + } + + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) + if err != nil { + return nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + func (c *Commands) AddInstanceJWTProvider(ctx context.Context, provider JWTProvider) (string, *domain.ObjectDetails, error) { instanceID := authz.GetInstance(ctx).InstanceID() instanceAgg := instance.NewAggregate(instanceID) @@ -552,7 +586,7 @@ func (c *Commands) prepareUpdateInstanceOAuthProvider(a *instance.Aggregate, wri return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-D3r1s", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-D3r1s", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -646,7 +680,7 @@ func (c *Commands) prepareUpdateInstanceOIDCProvider(a *instance.Aggregate, writ return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-Dg331", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-Dg331", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -669,6 +703,91 @@ func (c *Commands) prepareUpdateInstanceOIDCProvider(a *instance.Aggregate, writ } } +func (c *Commands) prepareMigrateInstanceOIDCToAzureADProvider(a *instance.Aggregate, writeModel *InstanceOIDCIDPWriteModel, provider AzureADProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sdf3g", "Errors.Invalid.Argument") + } + if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Fhbr2", "Errors.Invalid.Argument") + } + if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Dzh3g", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "INST-Dg29201", "Errors.IDPConfig.NotExisting") + } + secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption) + if err != nil { + return nil, err + } + return []eventstore.Command{ + instance.NewOIDCIDPMigratedAzureADEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.ClientID, + secret, + provider.Scopes, + provider.Tenant, + provider.EmailVerified, + provider.IDPOptions, + ), + }, nil + }, nil + } +} + +func (c *Commands) prepareMigrateInstanceOIDCToGoogleProvider(a *instance.Aggregate, writeModel *InstanceOIDCIDPWriteModel, provider GoogleProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-D3fvs", "Errors.Invalid.Argument") + } + if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "INST-W2vqs", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "INST-Dg29202", "Errors.IDPConfig.NotExisting") + } + secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption) + if err != nil { + return nil, err + } + return []eventstore.Command{ + instance.NewOIDCIDPMigratedGoogleEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.ClientID, + secret, + provider.Scopes, + provider.IDPOptions, + ), + }, nil + }, nil + } +} + func (c *Commands) prepareAddInstanceJWTProvider(a *instance.Aggregate, writeModel *InstanceJWTIDPWriteModel, provider JWTProvider) preparation.Validation { return func() (preparation.CreateCommands, error) { if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { @@ -742,7 +861,7 @@ func (c *Commands) prepareUpdateInstanceJWTProvider(a *instance.Aggregate, write return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-Bhju5", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-Bhju5", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -826,7 +945,7 @@ func (c *Commands) prepareUpdateInstanceAzureADProvider(a *instance.Aggregate, w return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-BHz3q", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-BHz3q", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -904,7 +1023,7 @@ func (c *Commands) prepareUpdateInstanceGitHubProvider(a *instance.Aggregate, wr return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-Dr1gs", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-Dr1gs", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -1007,7 +1126,7 @@ func (c *Commands) prepareUpdateInstanceGitHubEnterpriseProvider(a *instance.Agg return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-GBr42", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-GBr42", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -1086,7 +1205,7 @@ func (c *Commands) prepareUpdateInstanceGitLabProvider(a *instance.Aggregate, wr return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-HBReq", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-HBReq", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -1175,7 +1294,7 @@ func (c *Commands) prepareUpdateInstanceGitLabSelfHostedProvider(a *instance.Agg return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-D2tg1", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-D2tg1", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -1252,7 +1371,7 @@ func (c *Commands) prepareUpdateInstanceGoogleProvider(a *instance.Aggregate, wr return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-D3r1s", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-D3r1s", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -1371,7 +1490,7 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writ return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-ASF3F", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-ASF3F", "Errors.IDPConfig.NotExisting") } event, err := writeModel.NewChangedEvent( ctx, @@ -1412,7 +1531,7 @@ func (c *Commands) prepareDeleteInstanceProvider(a *instance.Aggregate, id strin return nil, err } if !writeModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "INST-Se3tg", "Errors.Instance.IDPConfig.NotExisting") + return nil, caos_errs.ThrowNotFound(nil, "INST-Se3tg", "Errors.IDPConfig.NotExisting") } return []eventstore.Command{instance.NewIDPRemovedEvent(ctx, &a.Aggregate, id)}, nil }, nil diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go index 41c971cba3..a0e51648d5 100644 --- a/internal/command/instance_idp_model.go +++ b/internal/command/instance_idp_model.go @@ -113,6 +113,10 @@ func (wm *InstanceOIDCIDPWriteModel) AppendEvents(events ...eventstore.Event) { wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPChangedEvent) case *instance.IDPRemovedEvent: wm.OIDCIDPWriteModel.AppendEvents(&e.RemovedEvent) + case *instance.OIDCIDPMigratedAzureADEvent: + wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedAzureADEvent) + case *instance.OIDCIDPMigratedGoogleEvent: + wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedGoogleEvent) // old events case *instance.IDPConfigAddedEvent: @@ -141,6 +145,8 @@ func (wm *InstanceOIDCIDPWriteModel) Query() *eventstore.SearchQueryBuilder { instance.OIDCIDPAddedEventType, instance.OIDCIDPChangedEventType, instance.IDPRemovedEventType, + instance.OIDCIDPMigratedAzureADEventType, + instance.OIDCIDPMigratedGoogleEventType, ). EventData(map[string]interface{}{"id": wm.ID}). Or(). // old events @@ -305,6 +311,8 @@ func (wm *InstanceAzureADIDPWriteModel) AppendEvents(events ...eventstore.Event) wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPAddedEvent) case *instance.AzureADIDPChangedEvent: wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPChangedEvent) + case *instance.OIDCIDPMigratedAzureADEvent: + wm.AzureADIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedAzureADEvent) case *instance.IDPRemovedEvent: wm.AzureADIDPWriteModel.AppendEvents(&e.RemovedEvent) default: @@ -322,6 +330,7 @@ func (wm *InstanceAzureADIDPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes( instance.AzureADIDPAddedEventType, instance.AzureADIDPChangedEventType, + instance.OIDCIDPMigratedAzureADEventType, instance.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). @@ -655,6 +664,8 @@ func (wm *InstanceGoogleIDPWriteModel) AppendEvents(events ...eventstore.Event) wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPAddedEvent) case *instance.GoogleIDPChangedEvent: wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPChangedEvent) + case *instance.OIDCIDPMigratedGoogleEvent: + wm.GoogleIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedGoogleEvent) case *instance.IDPRemovedEvent: wm.GoogleIDPWriteModel.AppendEvents(&e.RemovedEvent) } @@ -670,6 +681,7 @@ func (wm *InstanceGoogleIDPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes( instance.GoogleIDPAddedEventType, instance.GoogleIDPChangedEventType, + instance.OIDCIDPMigratedGoogleEventType, instance.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). diff --git a/internal/command/instance_idp_test.go b/internal/command/instance_idp_test.go index ed2b5fcaf8..9b9198fd4a 100644 --- a/internal/command/instance_idp_test.go +++ b/internal/command/instance_idp_test.go @@ -1102,6 +1102,474 @@ func TestCommandSide_UpdateInstanceGenericOIDCIDP(t *testing.T) { } } +func TestCommandSide_MigrateInstanceGenericOIDCToAzureADProvider(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + id string + provider AzureADProvider + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + + { + "invalid name", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: AzureADProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-sdf3g", "")) + }, + }, + }, + { + "invalid client id", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: AzureADProvider{ + Name: "name", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Fhbr2", "")) + }, + }, + }, + { + "invalid client secret", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Dzh3g", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + err: caos_errors.IsNotFound, + }, + }, + { + name: "migrate ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewOIDCIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + func() eventstore.Command { + event := instance.NewOIDCIDPMigratedAzureADEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + "", + false, + idp.Options{}, + ) + return event + }(), + ), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + { + name: "migrate ok full", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewOIDCIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + func() eventstore.Command { + event := instance.NewOIDCIDPMigratedAzureADEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + []string{"openid"}, + "tenant", + true, + idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + ) + return event + }(), + ), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + ClientSecret: "clientSecret", + Scopes: []string{"openid"}, + Tenant: "tenant", + EmailVerified: true, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + got, err := c.MigrateInstanceGenericOIDCToAzureADProvider(tt.args.ctx, tt.args.id, tt.args.provider) + 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_MigrateInstanceOIDCToGoogleIDP(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + id string + provider GoogleProvider + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid clientID", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: GoogleProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-D3fvs", "")) + }, + }, + }, + { + "invalid clientSecret", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + provider: GoogleProvider{ + ClientID: "clientID", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-W2vqs", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + err: caos_errors.IsNotFound, + }, + }, + { + name: "migrate ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewOIDCIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + instance.NewOIDCIDPMigratedGoogleEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + idp.Options{}, + )), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + { + name: "migrate ok full", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + instance.NewOIDCIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusherWithInstanceID( + "instance1", + instance.NewOIDCIDPMigratedGoogleEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate, + "id1", + "", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + []string{"openid"}, + idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + )), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "instance1"), + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + ClientSecret: "clientSecret", + Scopes: []string{"openid"}, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "instance1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + got, err := c.MigrateInstanceGenericOIDCToGoogleProvider(tt.args.ctx, tt.args.id, tt.args.provider) + 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_AddInstanceAzureADIDP(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore diff --git a/internal/command/org_idp.go b/internal/command/org_idp.go index e6e22c4473..2fc0bc0483 100644 --- a/internal/command/org_idp.go +++ b/internal/command/org_idp.go @@ -92,6 +92,39 @@ func (c *Commands) UpdateOrgGenericOIDCProvider(ctx context.Context, resourceOwn return pushedEventsToObjectDetails(pushedEvents), nil } +func (c *Commands) MigrateOrgGenericOIDCToAzureADProvider(ctx context.Context, resourceOwner, id string, provider AzureADProvider) (*domain.ObjectDetails, error) { + return c.migrateOrgGenericOIDC(ctx, resourceOwner, id, provider) +} + +func (c *Commands) MigrateOrgGenericOIDCToGoogleProvider(ctx context.Context, resourceOwner, id string, provider GoogleProvider) (*domain.ObjectDetails, error) { + return c.migrateOrgGenericOIDC(ctx, resourceOwner, id, provider) +} + +func (c *Commands) migrateOrgGenericOIDC(ctx context.Context, resourceOwner, id string, provider interface{}) (*domain.ObjectDetails, error) { + orgAgg := org.NewAggregate(resourceOwner) + writeModel := NewOIDCOrgIDPWriteModel(resourceOwner, id) + + var validation preparation.Validation + switch p := provider.(type) { + case AzureADProvider: + validation = c.prepareMigrateOrgOIDCToAzureADProvider(orgAgg, writeModel, p) + case GoogleProvider: + validation = c.prepareMigrateOrgOIDCToGoogleProvider(orgAgg, writeModel, p) + default: + return nil, caos_errs.ThrowInvalidArgument(nil, "COMMAND-s9s2919", "Errors.IDPConfig.NotExisting") + } + + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) + if err != nil { + return nil, err + } + pushedEvents, err := c.eventstore.Push(ctx, cmds...) + if err != nil { + return nil, err + } + return pushedEventsToObjectDetails(pushedEvents), nil +} + func (c *Commands) AddOrgJWTProvider(ctx context.Context, resourceOwner string, provider JWTProvider) (string, *domain.ObjectDetails, error) { orgAgg := org.NewAggregate(resourceOwner) id, err := c.idGenerator.Next() @@ -647,6 +680,91 @@ func (c *Commands) prepareUpdateOrgOIDCProvider(a *org.Aggregate, writeModel *Or } } +func (c *Commands) prepareMigrateOrgOIDCToAzureADProvider(a *org.Aggregate, writeModel *OrgOIDCIDPWriteModel, provider AzureADProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sdf3g", "Errors.Invalid.Argument") + } + if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Fhbr2", "Errors.Invalid.Argument") + } + if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Dzh3g", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "INST-Dg239201", "Errors.Instance.IDPConfig.NotExisting") + } + secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption) + if err != nil { + return nil, err + } + return []eventstore.Command{ + org.NewOIDCIDPMigratedAzureADEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.ClientID, + secret, + provider.Scopes, + provider.Tenant, + provider.EmailVerified, + provider.IDPOptions, + ), + }, nil + }, nil + } +} + +func (c *Commands) prepareMigrateOrgOIDCToGoogleProvider(a *org.Aggregate, writeModel *OrgOIDCIDPWriteModel, provider GoogleProvider) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-D3fvs", "Errors.Invalid.Argument") + } + if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" { + return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-W2vqs", "Errors.Invalid.Argument") + } + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + events, err := filter(ctx, writeModel.Query()) + if err != nil { + return nil, err + } + writeModel.AppendEvents(events...) + if err = writeModel.Reduce(); err != nil { + return nil, err + } + if !writeModel.State.Exists() { + return nil, caos_errs.ThrowNotFound(nil, "INST-x09981", "Errors.Instance.IDPConfig.NotExisting") + } + secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption) + if err != nil { + return nil, err + } + return []eventstore.Command{ + org.NewOIDCIDPMigratedGoogleEvent( + ctx, + &a.Aggregate, + writeModel.ID, + provider.Name, + provider.ClientID, + secret, + provider.Scopes, + provider.IDPOptions, + ), + }, nil + }, nil + } +} + func (c *Commands) prepareAddOrgJWTProvider(a *org.Aggregate, writeModel *OrgJWTIDPWriteModel, provider JWTProvider) preparation.Validation { return func() (preparation.CreateCommands, error) { if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" { diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go index ca8011121c..5f121dbbf4 100644 --- a/internal/command/org_idp_model.go +++ b/internal/command/org_idp_model.go @@ -113,6 +113,10 @@ func (wm *OrgOIDCIDPWriteModel) AppendEvents(events ...eventstore.Event) { wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPAddedEvent) case *org.OIDCIDPChangedEvent: wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPChangedEvent) + case *org.OIDCIDPMigratedAzureADEvent: + wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedAzureADEvent) + case *org.OIDCIDPMigratedGoogleEvent: + wm.OIDCIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedGoogleEvent) case *org.IDPRemovedEvent: wm.OIDCIDPWriteModel.AppendEvents(&e.RemovedEvent) @@ -142,6 +146,8 @@ func (wm *OrgOIDCIDPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes( org.OIDCIDPAddedEventType, org.OIDCIDPChangedEventType, + org.OIDCIDPMigratedAzureADEventType, + org.OIDCIDPMigratedGoogleEventType, org.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). @@ -311,6 +317,8 @@ func (wm *OrgAzureADIDPWriteModel) AppendEvents(events ...eventstore.Event) { wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPAddedEvent) case *org.AzureADIDPChangedEvent: wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPChangedEvent) + case *org.OIDCIDPMigratedAzureADEvent: + wm.AzureADIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedAzureADEvent) case *org.IDPRemovedEvent: wm.AzureADIDPWriteModel.AppendEvents(&e.RemovedEvent) default: @@ -328,6 +336,7 @@ func (wm *OrgAzureADIDPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes( org.AzureADIDPAddedEventType, org.AzureADIDPChangedEventType, + org.OIDCIDPMigratedAzureADEventType, org.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). @@ -663,6 +672,8 @@ func (wm *OrgGoogleIDPWriteModel) AppendEvents(events ...eventstore.Event) { wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPAddedEvent) case *org.GoogleIDPChangedEvent: wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPChangedEvent) + case *org.OIDCIDPMigratedGoogleEvent: + wm.GoogleIDPWriteModel.AppendEvents(&e.OIDCIDPMigratedGoogleEvent) case *org.IDPRemovedEvent: wm.GoogleIDPWriteModel.AppendEvents(&e.RemovedEvent) default: @@ -680,6 +691,7 @@ func (wm *OrgGoogleIDPWriteModel) Query() *eventstore.SearchQueryBuilder { EventTypes( org.GoogleIDPAddedEventType, org.GoogleIDPChangedEventType, + org.OIDCIDPMigratedGoogleEventType, org.IDPRemovedEventType, ). EventData(map[string]interface{}{"id": wm.ID}). diff --git a/internal/command/org_idp_test.go b/internal/command/org_idp_test.go index 7110cdd1dd..063bb70b24 100644 --- a/internal/command/org_idp_test.go +++ b/internal/command/org_idp_test.go @@ -1119,6 +1119,474 @@ func TestCommandSide_UpdateOrgGenericOIDCIDP(t *testing.T) { } } +func TestCommandSide_MigrateOrgGenericOIDCToAzureADProvider(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + resourceOwner string + id string + provider AzureADProvider + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid name", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: AzureADProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-sdf3g", "")) + }, + }, + }, + { + "invalid client id", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: AzureADProvider{ + Name: "name", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Fhbr2", "")) + }, + }, + }, + { + "invalid client secret", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Dzh3g", "")) + }, + }, + }, + { + name: "not found", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "ro", + id: "id1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + err: caos_errors.IsNotFound, + }, + }, + { + name: "migrate ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewOIDCIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + []*repository.Event{ + eventFromEventPusher( + func() eventstore.Command { + event := org.NewOIDCIDPMigratedAzureADEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + "", + false, + idp.Options{}, + ) + return event + }(), + ), + }, + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + { + name: "migrate full ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewOIDCIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + eventPusherToEvents( + org.NewOIDCIDPMigratedAzureADEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + []string{"openid"}, + "tenant", + true, + idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + )), + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: AzureADProvider{ + Name: "name", + ClientID: "clientID", + ClientSecret: "clientSecret", + Scopes: []string{"openid"}, + Tenant: "tenant", + EmailVerified: true, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + got, err := c.MigrateOrgGenericOIDCToAzureADProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.id, tt.args.provider) + 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_MigrateOrgOIDCToGoogleIDP(t *testing.T) { + type fields struct { + eventstore *eventstore.Eventstore + secretCrypto crypto.EncryptionAlgorithm + } + type args struct { + ctx context.Context + resourceOwner string + id string + provider GoogleProvider + } + type res struct { + want *domain.ObjectDetails + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "invalid clientID", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: GoogleProvider{}, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-D3fvs", "")) + }, + }, + }, + { + "invalid clientSecret", + fields{ + eventstore: eventstoreExpect(t), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-W2vqs", "")) + }, + }, + }, + { + "not found", + fields{ + eventstore: eventstoreExpect(t, + expectFilter(), + ), + }, + args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res{ + err: caos_errors.IsNotFound, + }, + }, + { + name: "migrate ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewOIDCIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + eventPusherToEvents( + org.NewOIDCIDPMigratedGoogleEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + idp.Options{}, + )), + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + ClientSecret: "clientSecret", + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + { + name: "migrate full ok", + fields: fields{ + eventstore: eventstoreExpect(t, + expectFilter( + eventFromEventPusher( + org.NewOIDCIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "name", + "issuer", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + nil, + false, + idp.Options{}, + )), + ), + expectPush( + eventPusherToEvents( + org.NewOIDCIDPMigratedGoogleEvent(context.Background(), &org.NewAggregate("org1").Aggregate, + "id1", + "", + "clientID", + &crypto.CryptoValue{ + CryptoType: crypto.TypeEncryption, + Algorithm: "enc", + KeyID: "id", + Crypted: []byte("clientSecret"), + }, + []string{"openid"}, + idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + )), + ), + ), + secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)), + }, + args: args{ + ctx: context.Background(), + resourceOwner: "org1", + id: "id1", + provider: GoogleProvider{ + ClientID: "clientID", + ClientSecret: "clientSecret", + Scopes: []string{"openid"}, + IDPOptions: idp.Options{ + IsCreationAllowed: true, + IsLinkingAllowed: true, + IsAutoCreation: true, + IsAutoUpdate: true, + }, + }, + }, + res: res{ + want: &domain.ObjectDetails{ResourceOwner: "org1"}, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := &Commands{ + eventstore: tt.fields.eventstore, + idpConfigEncryption: tt.fields.secretCrypto, + } + got, err := c.MigrateOrgGenericOIDCToGoogleProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.id, tt.args.provider) + 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_AddOrgAzureADIDP(t *testing.T) { type fields struct { eventstore *eventstore.Eventstore diff --git a/internal/domain/idp.go b/internal/domain/idp.go index f276f8eb4d..d06fcdd1f7 100644 --- a/internal/domain/idp.go +++ b/internal/domain/idp.go @@ -9,6 +9,7 @@ const ( IDPStateActive IDPStateInactive IDPStateRemoved + IDPStateMigrated idpStateCount ) @@ -18,7 +19,7 @@ func (s IDPState) Valid() bool { } func (s IDPState) Exists() bool { - return s != IDPStateUnspecified && s != IDPStateRemoved + return s != IDPStateUnspecified && s != IDPStateRemoved && s != IDPStateMigrated } type IDPType int32 diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go index 4e74e6f529..80fee7e479 100644 --- a/internal/query/projection/idp_template.go +++ b/internal/query/projection/idp_template.go @@ -347,6 +347,10 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer { Event: instance.OIDCIDPChangedEventType, Reduce: p.reduceOIDCIDPChanged, }, + { + Event: instance.OIDCIDPMigratedAzureADEventType, + Reduce: p.reduceOIDCIDPMigratedAzureAD, + }, { Event: instance.JWTIDPAddedEventType, Reduce: p.reduceJWTIDPAdded, @@ -755,6 +759,106 @@ func (p *idpTemplateProjection) reduceOIDCIDPChanged(event eventstore.Event) (*h ), nil } +func (p *idpTemplateProjection) reduceOIDCIDPMigratedAzureAD(event eventstore.Event) (*handler.Statement, error) { + var idpEvent idp.OIDCIDPMigratedAzureADEvent + switch e := event.(type) { + case *org.OIDCIDPMigratedAzureADEvent: + idpEvent = e.OIDCIDPMigratedAzureADEvent + case *instance.OIDCIDPMigratedAzureADEvent: + idpEvent = e.OIDCIDPMigratedAzureADEvent + default: + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.OIDCIDPMigratedAzureADEventType, instance.OIDCIDPMigratedAzureADEventType}) + } + + return crdb.NewMultiStatement( + &idpEvent, + crdb.AddUpdateStatement( + []handler.Column{ + handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()), + handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()), + handler.NewCol(IDPTemplateNameCol, idpEvent.Name), + handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeAzureAD), + handler.NewCol(IDPTemplateIsCreationAllowedCol, idpEvent.IsCreationAllowed), + handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), + handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), + handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + }, + []handler.Condition{ + handler.NewCond(IDPTemplateIDCol, idpEvent.ID), + handler.NewCond(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID), + }, + ), + crdb.AddDeleteStatement( + []handler.Condition{ + handler.NewCond(OIDCIDCol, idpEvent.ID), + handler.NewCond(OIDCInstanceIDCol, idpEvent.Aggregate().InstanceID), + }, + crdb.WithTableSuffix(IDPTemplateOIDCSuffix), + ), + crdb.AddCreateStatement( + []handler.Column{ + handler.NewCol(AzureADIDCol, idpEvent.ID), + handler.NewCol(AzureADInstanceIDCol, idpEvent.Aggregate().InstanceID), + handler.NewCol(AzureADClientIDCol, idpEvent.ClientID), + handler.NewCol(AzureADClientSecretCol, idpEvent.ClientSecret), + handler.NewCol(AzureADScopesCol, database.StringArray(idpEvent.Scopes)), + handler.NewCol(AzureADTenantCol, idpEvent.Tenant), + handler.NewCol(AzureADIsEmailVerified, idpEvent.IsEmailVerified), + }, + crdb.WithTableSuffix(IDPTemplateAzureADSuffix), + ), + ), nil +} + +func (p *idpTemplateProjection) reduceOIDCIDPMigratedGoogle(event eventstore.Event) (*handler.Statement, error) { + var idpEvent idp.OIDCIDPMigratedGoogleEvent + switch e := event.(type) { + case *org.OIDCIDPMigratedGoogleEvent: + idpEvent = e.OIDCIDPMigratedGoogleEvent + case *instance.OIDCIDPMigratedGoogleEvent: + idpEvent = e.OIDCIDPMigratedGoogleEvent + default: + return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.OIDCIDPMigratedGoogleEventType, instance.OIDCIDPMigratedGoogleEventType}) + } + + return crdb.NewMultiStatement( + &idpEvent, + crdb.AddUpdateStatement( + []handler.Column{ + handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()), + handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()), + handler.NewCol(IDPTemplateNameCol, idpEvent.Name), + handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeGoogle), + handler.NewCol(IDPTemplateIsCreationAllowedCol, idpEvent.IsCreationAllowed), + handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed), + handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation), + handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate), + }, + []handler.Condition{ + handler.NewCond(IDPTemplateIDCol, idpEvent.ID), + handler.NewCond(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID), + }, + ), + crdb.AddDeleteStatement( + []handler.Condition{ + handler.NewCond(OIDCIDCol, idpEvent.ID), + handler.NewCond(OIDCInstanceIDCol, idpEvent.Aggregate().InstanceID), + }, + crdb.WithTableSuffix(IDPTemplateOIDCSuffix), + ), + crdb.AddCreateStatement( + []handler.Column{ + handler.NewCol(GoogleIDCol, idpEvent.ID), + handler.NewCol(GoogleInstanceIDCol, idpEvent.Aggregate().InstanceID), + handler.NewCol(GoogleClientIDCol, idpEvent.ClientID), + handler.NewCol(GoogleClientSecretCol, idpEvent.ClientSecret), + handler.NewCol(GoogleScopesCol, database.StringArray(idpEvent.Scopes)), + }, + crdb.WithTableSuffix(IDPTemplateGoogleSuffix), + ), + ), nil +} + func (p *idpTemplateProjection) reduceJWTIDPAdded(event eventstore.Event) (*handler.Statement, error) { var idpEvent idp.JWTIDPAddedEvent var idpOwnerType domain.IdentityProviderType diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go index 64f3fa3a43..355020717e 100644 --- a/internal/query/projection/idp_template_test.go +++ b/internal/query/projection/idp_template_test.go @@ -2686,6 +2686,278 @@ func TestIDPTemplateProjection_reducesOIDC(t *testing.T) { }, }, }, + { + name: "instance reduceOIDCIDPMigratedAzureAD", + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.OIDCIDPMigratedAzureADEventType), + instance.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "name", + "client_id": "client_id", + "client_secret": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "tenant": "tenant", + "isEmailVerified": true, + "scopes": ["profile"], + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), instance.OIDCIDPMigratedAzureADEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceOIDCIDPMigratedAzureAD, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "name", + domain.IDPTypeAzureAD, + true, + true, + true, + true, + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + "client_id", + anyArg{}, + database.StringArray{"profile"}, + "tenant", + true, + }, + }, + }, + }, + }, + }, + { + name: "org reduceOIDCIDPMigratedAzureAD", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OIDCIDPMigratedAzureADEventType), + org.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "name", + "client_id": "client_id", + "client_secret": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "tenant": "tenant", + "isEmailVerified": true, + "scopes": ["profile"], + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), org.OIDCIDPMigratedAzureADEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceOIDCIDPMigratedAzureAD, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "name", + domain.IDPTypeAzureAD, + true, + true, + true, + true, + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "INSERT INTO projections.idp_templates5_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + "client_id", + anyArg{}, + database.StringArray{"profile"}, + "tenant", + true, + }, + }, + }, + }, + }, + }, + { + name: "instance reduceOIDCIDPMigratedGoogle", + args: args{ + event: getEvent(testEvent( + repository.EventType(instance.OIDCIDPMigratedGoogleEventType), + instance.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "name", + "clientId": "client_id", + "clientSecret": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "scopes": ["profile"], + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), instance.OIDCIDPMigratedGoogleEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceOIDCIDPMigratedGoogle, + want: wantReduce{ + aggregateType: eventstore.AggregateType("instance"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "name", + domain.IDPTypeGoogle, + true, + true, + true, + true, + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + "client_id", + anyArg{}, + database.StringArray{"profile"}, + }, + }, + }, + }, + }, + }, + { + name: "org reduceOIDCIDPMigratedGoogle", + args: args{ + event: getEvent(testEvent( + repository.EventType(org.OIDCIDPMigratedGoogleEventType), + org.AggregateType, + []byte(`{ + "id": "idp-id", + "name": "name", + "clientId": "client_id", + "clientSecret": { + "cryptoType": 0, + "algorithm": "RSA-265", + "keyId": "key-id" + }, + "scopes": ["profile"], + "isCreationAllowed": true, + "isLinkingAllowed": true, + "isAutoCreation": true, + "isAutoUpdate": true +}`), + ), org.OIDCIDPMigratedGoogleEventMapper), + }, + reduce: (&idpTemplateProjection{}).reduceOIDCIDPMigratedGoogle, + want: wantReduce{ + aggregateType: eventstore.AggregateType("org"), + sequence: 15, + previousSequence: 10, + executer: &testExecuter{ + executions: []execution{ + { + expectedStmt: "UPDATE projections.idp_templates5 SET (change_date, sequence, name, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) = ($1, $2, $3, $4, $5, $6, $7, $8) WHERE (id = $9) AND (instance_id = $10)", + expectedArgs: []interface{}{ + anyArg{}, + uint64(15), + "name", + domain.IDPTypeGoogle, + true, + true, + true, + true, + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "DELETE FROM projections.idp_templates5_oidc WHERE (idp_id = $1) AND (instance_id = $2)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + }, + }, + { + expectedStmt: "INSERT INTO projections.idp_templates5_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)", + expectedArgs: []interface{}{ + "idp-id", + "instance-id", + "client_id", + anyArg{}, + database.StringArray{"profile"}, + }, + }, + }, + }, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/internal/repository/idp/oidc.go b/internal/repository/idp/oidc.go index ea3b63c674..09437d18e0 100644 --- a/internal/repository/idp/oidc.go +++ b/internal/repository/idp/oidc.go @@ -162,3 +162,93 @@ func OIDCIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error return e, nil } + +type OIDCIDPMigratedAzureADEvent struct { + AzureADIDPAddedEvent +} + +func NewOIDCIDPMigratedAzureADEvent( + base *eventstore.BaseEvent, + id, + name, + clientID string, + clientSecret *crypto.CryptoValue, + scopes []string, + tenant string, + isEmailVerified bool, + options Options, +) *OIDCIDPMigratedAzureADEvent { + return &OIDCIDPMigratedAzureADEvent{ + AzureADIDPAddedEvent: AzureADIDPAddedEvent{ + BaseEvent: *base, + ID: id, + Name: name, + ClientID: clientID, + ClientSecret: clientSecret, + Scopes: scopes, + Tenant: tenant, + IsEmailVerified: isEmailVerified, + Options: options, + }, + } +} + +func (e *OIDCIDPMigratedAzureADEvent) Data() interface{} { + return e +} + +func (e *OIDCIDPMigratedAzureADEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func OIDCIDPMigratedAzureADEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := AzureADIDPAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &OIDCIDPMigratedAzureADEvent{AzureADIDPAddedEvent: *e.(*AzureADIDPAddedEvent)}, nil +} + +type OIDCIDPMigratedGoogleEvent struct { + GoogleIDPAddedEvent +} + +func NewOIDCIDPMigratedGoogleEvent( + base *eventstore.BaseEvent, + id, + name, + clientID string, + clientSecret *crypto.CryptoValue, + scopes []string, + options Options, +) *OIDCIDPMigratedGoogleEvent { + return &OIDCIDPMigratedGoogleEvent{ + GoogleIDPAddedEvent: GoogleIDPAddedEvent{ + BaseEvent: *base, + ID: id, + Name: name, + ClientID: clientID, + ClientSecret: clientSecret, + Scopes: scopes, + Options: options, + }, + } +} + +func (e *OIDCIDPMigratedGoogleEvent) Data() interface{} { + return e +} + +func (e *OIDCIDPMigratedGoogleEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint { + return nil +} + +func OIDCIDPMigratedGoogleEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := GoogleIDPAddedEventMapper(event) + if err != nil { + return nil, err + } + + return &OIDCIDPMigratedGoogleEvent{GoogleIDPAddedEvent: *e.(*GoogleIDPAddedEvent)}, nil +} diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go index b6d8923ecf..4161a3696a 100644 --- a/internal/repository/instance/eventstore.go +++ b/internal/repository/instance/eventstore.go @@ -74,6 +74,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(AggregateType, OAuthIDPChangedEventType, OAuthIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, OIDCIDPAddedEventType, OIDCIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, OIDCIDPChangedEventType, OIDCIDPChangedEventMapper). + RegisterFilterEventMapper(AggregateType, OIDCIDPMigratedAzureADEventType, OIDCIDPMigratedAzureADEventMapper). + RegisterFilterEventMapper(AggregateType, OIDCIDPMigratedGoogleEventType, OIDCIDPMigratedGoogleEventMapper). RegisterFilterEventMapper(AggregateType, JWTIDPAddedEventType, JWTIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, JWTIDPChangedEventType, JWTIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, AzureADIDPAddedEventType, AzureADIDPAddedEventMapper). diff --git a/internal/repository/instance/idp.go b/internal/repository/instance/idp.go index 612933f0ee..b2d42d8eeb 100644 --- a/internal/repository/instance/idp.go +++ b/internal/repository/instance/idp.go @@ -15,6 +15,8 @@ const ( OAuthIDPChangedEventType eventstore.EventType = "instance.idp.oauth.changed" OIDCIDPAddedEventType eventstore.EventType = "instance.idp.oidc.added" OIDCIDPChangedEventType eventstore.EventType = "instance.idp.oidc.changed" + OIDCIDPMigratedAzureADEventType eventstore.EventType = "instance.idp.oidc.migrated.azure" + OIDCIDPMigratedGoogleEventType eventstore.EventType = "instance.idp.oidc.migrated.google" JWTIDPAddedEventType eventstore.EventType = "instance.idp.jwt.added" JWTIDPChangedEventType eventstore.EventType = "instance.idp.jwt.changed" AzureADIDPAddedEventType eventstore.EventType = "instance.idp.azure.added" @@ -198,6 +200,90 @@ func OIDCIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error return &OIDCIDPChangedEvent{OIDCIDPChangedEvent: *e.(*idp.OIDCIDPChangedEvent)}, nil } +type OIDCIDPMigratedAzureADEvent struct { + idp.OIDCIDPMigratedAzureADEvent +} + +func NewOIDCIDPMigratedAzureADEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name, + clientID string, + clientSecret *crypto.CryptoValue, + scopes []string, + tenant string, + isEmailVerified bool, + options idp.Options, +) *OIDCIDPMigratedAzureADEvent { + return &OIDCIDPMigratedAzureADEvent{ + OIDCIDPMigratedAzureADEvent: *idp.NewOIDCIDPMigratedAzureADEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCIDPMigratedAzureADEventType, + ), + id, + name, + clientID, + clientSecret, + scopes, + tenant, + isEmailVerified, + options, + ), + } +} + +func OIDCIDPMigratedAzureADEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.OIDCIDPMigratedAzureADEventMapper(event) + if err != nil { + return nil, err + } + + return &OIDCIDPMigratedAzureADEvent{OIDCIDPMigratedAzureADEvent: *e.(*idp.OIDCIDPMigratedAzureADEvent)}, nil +} + +type OIDCIDPMigratedGoogleEvent struct { + idp.OIDCIDPMigratedGoogleEvent +} + +func NewOIDCIDPMigratedGoogleEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name, + clientID string, + clientSecret *crypto.CryptoValue, + scopes []string, + options idp.Options, +) *OIDCIDPMigratedGoogleEvent { + return &OIDCIDPMigratedGoogleEvent{ + OIDCIDPMigratedGoogleEvent: *idp.NewOIDCIDPMigratedGoogleEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCIDPMigratedAzureADEventType, + ), + id, + name, + clientID, + clientSecret, + scopes, + options, + ), + } +} + +func OIDCIDPMigratedGoogleEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.OIDCIDPMigratedGoogleEventMapper(event) + if err != nil { + return nil, err + } + + return &OIDCIDPMigratedGoogleEvent{OIDCIDPMigratedGoogleEvent: *e.(*idp.OIDCIDPMigratedGoogleEvent)}, nil +} + type JWTIDPAddedEvent struct { idp.JWTIDPAddedEvent } diff --git a/internal/repository/org/eventstore.go b/internal/repository/org/eventstore.go index 662bf77b4b..16332e08fd 100644 --- a/internal/repository/org/eventstore.go +++ b/internal/repository/org/eventstore.go @@ -83,6 +83,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) { RegisterFilterEventMapper(AggregateType, OAuthIDPChangedEventType, OAuthIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, OIDCIDPAddedEventType, OIDCIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, OIDCIDPChangedEventType, OIDCIDPChangedEventMapper). + RegisterFilterEventMapper(AggregateType, OIDCIDPMigratedAzureADEventType, OIDCIDPMigratedAzureADEventMapper). + RegisterFilterEventMapper(AggregateType, OIDCIDPMigratedGoogleEventType, OIDCIDPMigratedGoogleEventMapper). RegisterFilterEventMapper(AggregateType, JWTIDPAddedEventType, JWTIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, JWTIDPChangedEventType, JWTIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, AzureADIDPAddedEventType, AzureADIDPAddedEventMapper). diff --git a/internal/repository/org/idp.go b/internal/repository/org/idp.go index 97b0ebffc1..6c6201db1b 100644 --- a/internal/repository/org/idp.go +++ b/internal/repository/org/idp.go @@ -15,6 +15,8 @@ const ( OAuthIDPChangedEventType eventstore.EventType = "org.idp.oauth.changed" OIDCIDPAddedEventType eventstore.EventType = "org.idp.oidc.added" OIDCIDPChangedEventType eventstore.EventType = "org.idp.oidc.changed" + OIDCIDPMigratedAzureADEventType eventstore.EventType = "org.idp.oidc.migrated.azure" + OIDCIDPMigratedGoogleEventType eventstore.EventType = "org.idp.oidc.migrated.google" JWTIDPAddedEventType eventstore.EventType = "org.idp.jwt.added" JWTIDPChangedEventType eventstore.EventType = "org.idp.jwt.changed" AzureADIDPAddedEventType eventstore.EventType = "org.idp.azure.added" @@ -198,6 +200,90 @@ func OIDCIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error return &OIDCIDPChangedEvent{OIDCIDPChangedEvent: *e.(*idp.OIDCIDPChangedEvent)}, nil } +type OIDCIDPMigratedAzureADEvent struct { + idp.OIDCIDPMigratedAzureADEvent +} + +func NewOIDCIDPMigratedAzureADEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name, + clientID string, + clientSecret *crypto.CryptoValue, + scopes []string, + tenant string, + isEmailVerified bool, + options idp.Options, +) *OIDCIDPMigratedAzureADEvent { + return &OIDCIDPMigratedAzureADEvent{ + OIDCIDPMigratedAzureADEvent: *idp.NewOIDCIDPMigratedAzureADEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCIDPMigratedAzureADEventType, + ), + id, + name, + clientID, + clientSecret, + scopes, + tenant, + isEmailVerified, + options, + ), + } +} + +func OIDCIDPMigratedAzureADEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.OIDCIDPMigratedAzureADEventMapper(event) + if err != nil { + return nil, err + } + + return &OIDCIDPMigratedAzureADEvent{OIDCIDPMigratedAzureADEvent: *e.(*idp.OIDCIDPMigratedAzureADEvent)}, nil +} + +type OIDCIDPMigratedGoogleEvent struct { + idp.OIDCIDPMigratedGoogleEvent +} + +func NewOIDCIDPMigratedGoogleEvent( + ctx context.Context, + aggregate *eventstore.Aggregate, + id, + name, + clientID string, + clientSecret *crypto.CryptoValue, + scopes []string, + options idp.Options, +) *OIDCIDPMigratedGoogleEvent { + return &OIDCIDPMigratedGoogleEvent{ + OIDCIDPMigratedGoogleEvent: *idp.NewOIDCIDPMigratedGoogleEvent( + eventstore.NewBaseEventForPush( + ctx, + aggregate, + OIDCIDPMigratedGoogleEventType, + ), + id, + name, + clientID, + clientSecret, + scopes, + options, + ), + } +} + +func OIDCIDPMigratedGoogleEventMapper(event *repository.Event) (eventstore.Event, error) { + e, err := idp.OIDCIDPMigratedGoogleEventMapper(event) + if err != nil { + return nil, err + } + + return &OIDCIDPMigratedGoogleEvent{OIDCIDPMigratedGoogleEvent: *e.(*idp.OIDCIDPMigratedGoogleEvent)}, nil +} + type JWTIDPAddedEvent struct { idp.JWTIDPAddedEvent } diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 1424d7cd29..e50d3afb33 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -1342,6 +1342,24 @@ service AdminService { }; } + // Migrate an existing OIDC identity provider on the instance + rpc MigrateGenericOIDCProvider(MigrateGenericOIDCProviderRequest) returns (MigrateGenericOIDCProviderResponse) { + option (google.api.http) = { + post: "/idps/generic_oidc/{id}/_migrate" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Migrate Generic OIDC Identity Provider"; + description: ""; + }; + } + // Add a new JWT identity provider on the instance rpc AddJWTProvider(AddJWTProviderRequest) returns (AddJWTProviderResponse) { option (google.api.http) = { @@ -4828,6 +4846,23 @@ message UpdateGenericOIDCProviderResponse { zitadel.v1.ObjectDetails details = 1; } +message MigrateGenericOIDCProviderRequest{ + string id = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"69629023906488334\""; + } + ]; + oneof template { + AddAzureADProviderRequest azure = 2; + AddGoogleProviderRequest google = 3; + } +} + +message MigrateGenericOIDCProviderResponse{ + zitadel.v1.ObjectDetails details = 1; +} + message AddJWTProviderRequest { string name = 1 [ (validate.rules).string = {min_len: 1, max_len: 200}, diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index cf055e4a55..b0d5bc97fe 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -6558,6 +6558,24 @@ service ManagementService { }; } + // Migrate an existing OIDC identity provider in the organization + rpc MigrateGenericOIDCProvider(MigrateGenericOIDCProviderRequest) returns (MigrateGenericOIDCProviderResponse) { + option (google.api.http) = { + post: "/idps/generic_oidc/{id}/_migrate" + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "org.idp.write" + }; + + option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = { + tags: "Identity Providers"; + summary: "Migrate Generic OIDC Identity Provider"; + description: ""; + }; + } + // Add a new JWT identity provider in the organization rpc AddJWTProvider(AddJWTProviderRequest) returns (AddJWTProviderResponse) { option (google.api.http) = { @@ -11526,6 +11544,23 @@ message UpdateGenericOIDCProviderResponse { zitadel.v1.ObjectDetails details = 1; } +message MigrateGenericOIDCProviderRequest{ + string id = 1 [ + (validate.rules).string = {min_len: 1, max_len: 200}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"69629023906488334\""; + } + ]; + oneof template { + AddAzureADProviderRequest azure = 2; + AddGoogleProviderRequest google = 3; + } +} + +message MigrateGenericOIDCProviderResponse{ + zitadel.v1.ObjectDetails details = 1; +} + message AddJWTProviderRequest { string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}]; string issuer = 2 [