feat(api): add generic oauth provider template (#5260)

adds functionality to manage templates based OIDC IDPs
This commit is contained in:
Livio Spring 2023-02-24 15:16:06 +01:00 committed by GitHub
parent aa9518ac02
commit 737d14e81b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 3199 additions and 117 deletions

View File

@ -86,7 +86,7 @@
{
"type": "initial",
"maximumWarning": "6mb",
"maximumError": "6mb"
"maximumError": "7mb"
},
{
"type": "anyComponentStyle",

2
docs/docs/apis/assets/assets.md Normal file → Executable file
View File

@ -256,4 +256,4 @@ GET: /users/me/avatar

View File

@ -174,8 +174,29 @@ func (s *Server) ListProviders(ctx context.Context, req *admin_pb.ListProvidersR
}, nil
}
func (s *Server) AddGenericOAuthProvider(ctx context.Context, req *admin_pb.AddGenericOAuthProviderRequest) (*admin_pb.AddGenericOAuthProviderResponse, error) {
id, details, err := s.command.AddInstanceGenericOAuthProvider(ctx, addGenericOAuthProviderToCommand(req))
if err != nil {
return nil, err
}
return &admin_pb.AddGenericOAuthProviderResponse{
Id: id,
Details: object_pb.DomainToAddDetailsPb(details),
}, nil
}
func (s *Server) UpdateGenericOAuthProvider(ctx context.Context, req *admin_pb.UpdateGenericOAuthProviderRequest) (*admin_pb.UpdateGenericOAuthProviderResponse, error) {
details, err := s.command.UpdateInstanceGenericOAuthProvider(ctx, req.Id, updateGenericOAuthProviderToCommand(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdateGenericOAuthProviderResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) AddGoogleProvider(ctx context.Context, req *admin_pb.AddGoogleProviderRequest) (*admin_pb.AddGoogleProviderResponse, error) {
id, details, err := s.command.AddOrgGoogleProvider(ctx, authz.GetCtxData(ctx).OrgID, addGoogleProviderToCommand(req))
id, details, err := s.command.AddInstanceGoogleProvider(ctx, addGoogleProviderToCommand(req))
if err != nil {
return nil, err
}
@ -186,7 +207,7 @@ func (s *Server) AddGoogleProvider(ctx context.Context, req *admin_pb.AddGoogleP
}
func (s *Server) UpdateGoogleProvider(ctx context.Context, req *admin_pb.UpdateGoogleProviderRequest) (*admin_pb.UpdateGoogleProviderResponse, error) {
details, err := s.command.UpdateOrgGoogleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateGoogleProviderToCommand(req))
details, err := s.command.UpdateInstanceGoogleProvider(ctx, req.Id, updateGoogleProviderToCommand(req))
if err != nil {
return nil, err
}

View File

@ -201,6 +201,32 @@ func providerQueryToQuery(idpQuery *admin_pb.ProviderQuery) (query.SearchQuery,
}
}
func addGenericOAuthProviderToCommand(req *admin_pb.AddGenericOAuthProviderRequest) command.GenericOAuthProvider {
return command.GenericOAuthProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
AuthorizationEndpoint: req.AuthorizationEndpoint,
TokenEndpoint: req.TokenEndpoint,
UserEndpoint: req.UserEndpoint,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateGenericOAuthProviderToCommand(req *admin_pb.UpdateGenericOAuthProviderRequest) command.GenericOAuthProvider {
return command.GenericOAuthProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
AuthorizationEndpoint: req.AuthorizationEndpoint,
TokenEndpoint: req.TokenEndpoint,
UserEndpoint: req.UserEndpoint,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func addGoogleProviderToCommand(req *admin_pb.AddGoogleProviderRequest) command.GoogleProvider {
return command.GoogleProvider{
Name: req.Name,

View File

@ -402,31 +402,57 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
IsAutoUpdate: config.IsAutoUpdate,
},
}
if config.OAuthIDPTemplate != nil {
oauthConfigToPb(providerConfig, config.OAuthIDPTemplate)
return providerConfig
}
if config.GoogleIDPTemplate != nil {
providerConfig.Config = &idp_pb.ProviderConfig_Google{
Google: &idp_pb.GoogleConfig{
ClientId: config.GoogleIDPTemplate.ClientID,
Scopes: config.GoogleIDPTemplate.Scopes,
},
}
googleConfigToPb(providerConfig, config.GoogleIDPTemplate)
return providerConfig
}
if config.LDAPIDPTemplate != nil {
providerConfig.Config = &idp_pb.ProviderConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{
Host: config.Host,
Port: config.Port,
Tls: config.TLS,
BaseDn: config.BaseDN,
UserObjectClass: config.UserObjectClass,
UserUniqueAttribute: config.UserUniqueAttribute,
Admin: config.Admin,
Attributes: ldapAttributesToPb(config.LDAPAttributes),
},
}
ldapConfigToPb(providerConfig, config.LDAPIDPTemplate)
return providerConfig
}
return providerConfig
}
func googleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.GoogleIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_Google{
Google: &idp_pb.GoogleConfig{
ClientId: template.ClientID,
Scopes: template.Scopes,
},
}
}
func oauthConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.OAuthIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_Oauth{
Oauth: &idp_pb.OAuthConfig{
ClientId: template.ClientID,
AuthorizationEndpoint: template.AuthorizationEndpoint,
TokenEndpoint: template.TokenEndpoint,
UserEndpoint: template.UserEndpoint,
Scopes: template.Scopes,
},
}
}
func ldapConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.LDAPIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{
Host: template.Host,
Port: template.Port,
Tls: template.TLS,
BaseDn: template.BaseDN,
UserObjectClass: template.UserObjectClass,
UserUniqueAttribute: template.UserUniqueAttribute,
Admin: template.Admin,
Attributes: ldapAttributesToPb(template.LDAPAttributes),
},
}
}
func ldapAttributesToPb(attributes idp.LDAPAttributes) *idp_pb.LDAPAttributes {
return &idp_pb.LDAPAttributes{
IdAttribute: attributes.IDAttribute,

View File

@ -166,6 +166,27 @@ func (s *Server) ListProviders(ctx context.Context, req *mgmt_pb.ListProvidersRe
}, nil
}
func (s *Server) AddGenericOAuthProvider(ctx context.Context, req *mgmt_pb.AddGenericOAuthProviderRequest) (*mgmt_pb.AddGenericOAuthProviderResponse, error) {
id, details, err := s.command.AddOrgGenericOAuthProvider(ctx, authz.GetCtxData(ctx).OrgID, addGenericOAuthProviderToCommand(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddGenericOAuthProviderResponse{
Id: id,
Details: object_pb.DomainToAddDetailsPb(details),
}, nil
}
func (s *Server) UpdateGenericOAuthProvider(ctx context.Context, req *mgmt_pb.UpdateGenericOAuthProviderRequest) (*mgmt_pb.UpdateGenericOAuthProviderResponse, error) {
details, err := s.command.UpdateOrgGenericOAuthProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateGenericOAuthProviderToCommand(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateGenericOAuthProviderResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) AddGoogleProvider(ctx context.Context, req *mgmt_pb.AddGoogleProviderRequest) (*mgmt_pb.AddGoogleProviderResponse, error) {
id, details, err := s.command.AddOrgGoogleProvider(ctx, authz.GetCtxData(ctx).OrgID, addGoogleProviderToCommand(req))
if err != nil {

View File

@ -218,6 +218,32 @@ func providerQueryToQuery(idpQuery *mgmt_pb.ProviderQuery) (query.SearchQuery, e
}
}
func addGenericOAuthProviderToCommand(req *mgmt_pb.AddGenericOAuthProviderRequest) command.GenericOAuthProvider {
return command.GenericOAuthProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
AuthorizationEndpoint: req.AuthorizationEndpoint,
TokenEndpoint: req.TokenEndpoint,
UserEndpoint: req.UserEndpoint,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateGenericOAuthProviderToCommand(req *mgmt_pb.UpdateGenericOAuthProviderRequest) command.GenericOAuthProvider {
return command.GenericOAuthProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
AuthorizationEndpoint: req.AuthorizationEndpoint,
TokenEndpoint: req.TokenEndpoint,
UserEndpoint: req.UserEndpoint,
Scopes: req.Scopes,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func addGoogleProviderToCommand(req *mgmt_pb.AddGoogleProviderRequest) command.GoogleProvider {
return command.GoogleProvider{
Name: req.Name,

View File

@ -2,6 +2,17 @@ package command
import "github.com/zitadel/zitadel/internal/repository/idp"
type GenericOAuthProvider struct {
Name string
ClientID string
ClientSecret string
AuthorizationEndpoint string
TokenEndpoint string
UserEndpoint string
Scopes []string
IDPOptions idp.Options
}
type GoogleProvider struct {
Name string
ClientID string

View File

@ -10,6 +10,118 @@ import (
"github.com/zitadel/zitadel/internal/repository/idpconfig"
)
type OAuthIDPWriteModel struct {
eventstore.WriteModel
Name string
ID string
ClientID string
ClientSecret *crypto.CryptoValue
AuthorizationEndpoint string
TokenEndpoint string
UserEndpoint string
Scopes []string
idp.Options
State domain.IDPState
}
func (wm *OAuthIDPWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *idp.OAuthIDPAddedEvent:
wm.reduceAddedEvent(e)
case *idp.OAuthIDPChangedEvent:
wm.reduceChangedEvent(e)
case *idp.RemovedEvent:
wm.State = domain.IDPStateRemoved
}
}
return wm.WriteModel.Reduce()
}
func (wm *OAuthIDPWriteModel) reduceAddedEvent(e *idp.OAuthIDPAddedEvent) {
wm.Name = e.Name
wm.ClientID = e.ClientID
wm.ClientSecret = e.ClientSecret
wm.AuthorizationEndpoint = e.AuthorizationEndpoint
wm.TokenEndpoint = e.TokenEndpoint
wm.UserEndpoint = e.UserEndpoint
wm.Scopes = e.Scopes
wm.State = domain.IDPStateActive
}
func (wm *OAuthIDPWriteModel) reduceChangedEvent(e *idp.OAuthIDPChangedEvent) {
if e.ClientID != nil {
wm.ClientID = *e.ClientID
}
if e.ClientSecret != nil {
wm.ClientSecret = e.ClientSecret
}
if e.Name != nil {
wm.Name = *e.Name
}
if e.AuthorizationEndpoint != nil {
wm.AuthorizationEndpoint = *e.AuthorizationEndpoint
}
if e.TokenEndpoint != nil {
wm.TokenEndpoint = *e.TokenEndpoint
}
if e.UserEndpoint != nil {
wm.UserEndpoint = *e.UserEndpoint
}
if e.Scopes != nil {
wm.Scopes = e.Scopes
}
wm.Options.ReduceChanges(e.OptionChanges)
}
func (wm *OAuthIDPWriteModel) NewChanges(
name,
clientID,
clientSecretString string,
secretCrypto crypto.Crypto,
authorizationEndpoint,
tokenEndpoint,
userEndpoint string,
scopes []string,
options idp.Options,
) ([]idp.OAuthIDPChanges, error) {
changes := make([]idp.OAuthIDPChanges, 0)
var clientSecret *crypto.CryptoValue
var err error
if clientSecretString != "" {
clientSecret, err = crypto.Crypt([]byte(clientSecretString), secretCrypto)
if err != nil {
return nil, err
}
changes = append(changes, idp.ChangeOAuthClientSecret(clientSecret))
}
if wm.ClientID != clientID {
changes = append(changes, idp.ChangeOAuthClientID(clientID))
}
if wm.Name != name {
changes = append(changes, idp.ChangeOAuthName(name))
}
if wm.AuthorizationEndpoint != authorizationEndpoint {
changes = append(changes, idp.ChangeOAuthAuthorizationEndpoint(authorizationEndpoint))
}
if wm.TokenEndpoint != tokenEndpoint {
changes = append(changes, idp.ChangeOAuthTokenEndpoint(tokenEndpoint))
}
if wm.UserEndpoint != userEndpoint {
changes = append(changes, idp.ChangeOAuthUserEndpoint(userEndpoint))
}
if !reflect.DeepEqual(wm.Scopes, scopes) {
changes = append(changes, idp.ChangeOAuthScopes(scopes))
}
opts := wm.Options.Changes(options)
if !opts.IsZero() {
changes = append(changes, idp.ChangeOAuthOptions(opts))
}
return changes, nil
}
type GoogleIDPWriteModel struct {
eventstore.WriteModel
@ -259,6 +371,10 @@ type IDPRemoveWriteModel struct {
func (wm *IDPRemoveWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *idp.OAuthIDPAddedEvent:
wm.reduceAdded(e.ID, e.Name)
case *idp.OAuthIDPChangedEvent:
wm.reduceChanged(e.ID, e.Name)
case *idp.GoogleIDPAddedEvent:
wm.reduceAdded(e.ID, e.Name)
case *idp.GoogleIDPChangedEvent:

View File

@ -13,13 +13,57 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
)
func (c *Commands) AddInstanceGoogleProvider(ctx context.Context, provider GoogleProvider) (string, *domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
func (c *Commands) AddInstanceGenericOAuthProvider(ctx context.Context, provider GenericOAuthProvider) (string, *domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceGoogleProvider(instanceAgg, id, provider))
writeModel := NewOAuthInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceOAuthProvider(instanceAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return "", nil, err
}
return id, pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) UpdateInstanceGenericOAuthProvider(ctx context.Context, id string, provider GenericOAuthProvider) (*domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
writeModel := NewOAuthInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceOAuthProvider(instanceAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddInstanceGoogleProvider(ctx context.Context, provider GoogleProvider) (string, *domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
writeModel := NewGoogleInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceGoogleProvider(instanceAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
@ -31,14 +75,20 @@ func (c *Commands) AddInstanceGoogleProvider(ctx context.Context, provider Googl
}
func (c *Commands) UpdateInstanceGoogleProvider(ctx context.Context, id string, provider GoogleProvider) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceGoogleProvider(instanceAgg, id, provider))
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
writeModel := NewGoogleInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceGoogleProvider(instanceAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{}, nil
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
@ -48,12 +98,14 @@ func (c *Commands) UpdateInstanceGoogleProvider(ctx context.Context, id string,
}
func (c *Commands) AddInstanceLDAPProvider(ctx context.Context, provider LDAPProvider) (string, *domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceLDAPProvider(instanceAgg, id, provider))
writeModel := NewLDAPInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceLDAPProvider(instanceAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
@ -65,14 +117,20 @@ func (c *Commands) AddInstanceLDAPProvider(ctx context.Context, provider LDAPPro
}
func (c *Commands) UpdateInstanceLDAPProvider(ctx context.Context, id string, provider LDAPProvider) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceLDAPProvider(instanceAgg, id, provider))
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
writeModel := NewLDAPInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceLDAPProvider(instanceAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{}, nil
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
@ -94,7 +152,113 @@ func (c *Commands) DeleteInstanceProvider(ctx context.Context, id string) (*doma
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) prepareAddInstanceGoogleProvider(a *instance.Aggregate, id string, provider GoogleProvider) preparation.Validation {
func (c *Commands) prepareAddInstanceOAuthProvider(a *instance.Aggregate, writeModel *InstanceOAuthIDPWriteModel, provider GenericOAuthProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-D32ef", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Dbgzf", "Errors.Invalid.Argument")
}
if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-DF4ga", "Errors.Invalid.Argument")
}
if provider.AuthorizationEndpoint = strings.TrimSpace(provider.AuthorizationEndpoint); provider.AuthorizationEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-B23bs", "Errors.Invalid.Argument")
}
if provider.TokenEndpoint = strings.TrimSpace(provider.TokenEndpoint); provider.TokenEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-D2gj8", "Errors.Invalid.Argument")
}
if provider.UserEndpoint = strings.TrimSpace(provider.UserEndpoint); provider.UserEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Fb8jk", "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
}
secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption)
if err != nil {
return nil, err
}
return []eventstore.Command{
instance.NewOAuthIDPAddedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
secret,
provider.AuthorizationEndpoint,
provider.TokenEndpoint,
provider.UserEndpoint,
provider.Scopes,
provider.IDPOptions,
),
}, nil
}, nil
}
}
func (c *Commands) prepareUpdateInstanceOAuthProvider(a *instance.Aggregate, writeModel *InstanceOAuthIDPWriteModel, provider GenericOAuthProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-D32ef", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Dbgzf", "Errors.Invalid.Argument")
}
if provider.AuthorizationEndpoint = strings.TrimSpace(provider.AuthorizationEndpoint); provider.AuthorizationEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-B23bs", "Errors.Invalid.Argument")
}
if provider.TokenEndpoint = strings.TrimSpace(provider.TokenEndpoint); provider.TokenEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-D2gj8", "Errors.Invalid.Argument")
}
if provider.UserEndpoint = strings.TrimSpace(provider.UserEndpoint); provider.UserEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Fb8jk", "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-D3r1s", "Errors.Instance.IDPConfig.NotExisting")
}
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
provider.ClientSecret,
c.idpConfigEncryption,
provider.AuthorizationEndpoint,
provider.TokenEndpoint,
provider.UserEndpoint,
provider.Scopes,
provider.IDPOptions,
)
if err != nil {
return nil, err
}
if event == nil {
return nil, nil
}
return []eventstore.Command{event}, nil
}, nil
}
}
func (c *Commands) prepareAddInstanceGoogleProvider(a *instance.Aggregate, writeModel *InstanceGoogleIDPWriteModel, 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")
@ -103,7 +267,6 @@ func (c *Commands) prepareAddInstanceGoogleProvider(a *instance.Aggregate, id st
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-W2vqs", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewGoogleInstanceIDPWriteModel(a.InstanceID, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -120,7 +283,7 @@ func (c *Commands) prepareAddInstanceGoogleProvider(a *instance.Aggregate, id st
instance.NewGoogleIDPAddedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
provider.Name,
provider.ClientID,
secret,
@ -132,16 +295,15 @@ func (c *Commands) prepareAddInstanceGoogleProvider(a *instance.Aggregate, id st
}
}
func (c *Commands) prepareUpdateInstanceGoogleProvider(a *instance.Aggregate, id string, provider GoogleProvider) preparation.Validation {
func (c *Commands) prepareUpdateInstanceGoogleProvider(a *instance.Aggregate, writeModel *InstanceGoogleIDPWriteModel, provider GoogleProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if id = strings.TrimSpace(id); id == "" {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-S32t1", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-ds432", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewGoogleInstanceIDPWriteModel(a.InstanceID, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -156,7 +318,7 @@ func (c *Commands) prepareUpdateInstanceGoogleProvider(a *instance.Aggregate, id
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
provider.Name,
provider.ClientID,
provider.ClientSecret,
@ -175,7 +337,7 @@ func (c *Commands) prepareUpdateInstanceGoogleProvider(a *instance.Aggregate, id
}
}
func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, id string, provider LDAPProvider) preparation.Validation {
func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, writeModel *InstanceLDAPIDPWriteModel, provider LDAPProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAfdd", "Errors.Invalid.Argument")
@ -199,7 +361,6 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, id stri
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sdf5h", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewLDAPInstanceIDPWriteModel(a.InstanceID, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -216,7 +377,7 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, id stri
instance.NewLDAPIDPAddedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
provider.Name,
provider.Host,
provider.Port,
@ -234,9 +395,9 @@ func (c *Commands) prepareAddInstanceLDAPProvider(a *instance.Aggregate, id stri
}
}
func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, id string, provider LDAPProvider) preparation.Validation {
func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, writeModel *InstanceLDAPIDPWriteModel, provider LDAPProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if id = strings.TrimSpace(id); id == "" {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Dgdbs", "Errors.Invalid.Argument")
}
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
@ -258,7 +419,6 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, id s
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-DG45z", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewLDAPInstanceIDPWriteModel(a.InstanceID, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -273,7 +433,7 @@ func (c *Commands) prepareUpdateInstanceLDAPProvider(a *instance.Aggregate, id s
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
writeModel.Name,
provider.Name,
provider.Host,

View File

@ -9,6 +9,89 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
)
type InstanceOAuthIDPWriteModel struct {
OAuthIDPWriteModel
}
func NewOAuthInstanceIDPWriteModel(instanceID, id string) *InstanceOAuthIDPWriteModel {
return &InstanceOAuthIDPWriteModel{
OAuthIDPWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: instanceID,
ResourceOwner: instanceID,
},
ID: id,
},
}
}
func (wm *InstanceOAuthIDPWriteModel) Reduce() error {
return wm.OAuthIDPWriteModel.Reduce()
}
func (wm *InstanceOAuthIDPWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *instance.OAuthIDPAddedEvent:
wm.OAuthIDPWriteModel.AppendEvents(&e.OAuthIDPAddedEvent)
case *instance.OAuthIDPChangedEvent:
wm.OAuthIDPWriteModel.AppendEvents(&e.OAuthIDPChangedEvent)
case *instance.IDPRemovedEvent:
wm.OAuthIDPWriteModel.AppendEvents(&e.RemovedEvent)
}
}
}
func (wm *InstanceOAuthIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(instance.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
instance.OAuthIDPAddedEventType,
instance.OAuthIDPChangedEventType,
instance.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}
func (wm *InstanceOAuthIDPWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID,
clientSecretString string,
secretCrypto crypto.Crypto,
authorizationEndpoint,
tokenEndpoint,
userEndpoint string,
scopes []string,
options idp.Options,
) (*instance.OAuthIDPChangedEvent, error) {
changes, err := wm.OAuthIDPWriteModel.NewChanges(
name,
clientID,
clientSecretString,
secretCrypto,
authorizationEndpoint,
tokenEndpoint,
userEndpoint,
scopes,
options,
)
if err != nil {
return nil, err
}
if len(changes) == 0 {
return nil, nil
}
return instance.NewOAuthIDPChangedEvent(ctx, aggregate, id, changes)
}
type InstanceGoogleIDPWriteModel struct {
GoogleIDPWriteModel
}
@ -76,11 +159,7 @@ func (wm *InstanceGoogleIDPWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, nil
}
changeEvent, err := instance.NewGoogleIDPChangedEvent(ctx, aggregate, id, changes)
if err != nil {
return nil, err
}
return changeEvent, nil
return instance.NewGoogleIDPChangedEvent(ctx, aggregate, id, changes)
}
type InstanceLDAPIDPWriteModel struct {
@ -107,19 +186,10 @@ func (wm *InstanceLDAPIDPWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *instance.LDAPIDPAddedEvent:
if wm.ID != e.ID {
continue
}
wm.LDAPIDPWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
case *instance.LDAPIDPChangedEvent:
if wm.ID != e.ID {
continue
}
wm.LDAPIDPWriteModel.AppendEvents(&e.LDAPIDPChangedEvent)
case *instance.IDPRemovedEvent:
if wm.ID != e.ID {
continue
}
wm.LDAPIDPWriteModel.AppendEvents(&e.RemovedEvent)
default:
wm.LDAPIDPWriteModel.AppendEvents(e)
@ -138,6 +208,7 @@ func (wm *InstanceLDAPIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
instance.LDAPIDPChangedEventType,
instance.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}
@ -180,11 +251,7 @@ func (wm *InstanceLDAPIDPWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, nil
}
changeEvent, err := instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes)
if err != nil {
return nil, err
}
return changeEvent, nil
return instance.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes)
}
type InstanceIDPRemoveWriteModel struct {
@ -210,6 +277,10 @@ func (wm *InstanceIDPRemoveWriteModel) Reduce() error {
func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *instance.OAuthIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.OAuthIDPAddedEvent)
case *instance.OAuthIDPChangedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.OAuthIDPChangedEvent)
case *instance.GoogleIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
case *instance.GoogleIDPChangedEvent:
@ -233,11 +304,14 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
AggregateTypes(instance.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
instance.OAuthIDPAddedEventType,
instance.OAuthIDPChangedEventType,
instance.GoogleIDPAddedEventType,
instance.GoogleIDPChangedEventType,
instance.LDAPIDPAddedEventType,
instance.LDAPIDPChangedEventType,
instance.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}

View File

@ -20,6 +20,542 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
)
func TestCommandSide_AddInstanceGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
provider GenericOAuthProvider
}
type res struct {
id string
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientID",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientSecret",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid auth endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid token endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid user endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
name: "ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
},
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
id: "id1",
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
name: "ok all set",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
[]string{"user"},
idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
)),
},
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
Scopes: []string{"user"},
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
id: "id1",
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
idpConfigEncryption: tt.fields.secretCrypto,
}
id, got, err := c.AddInstanceGenericOAuthProvider(tt.args.ctx, 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.id, id)
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_UpdateInstanceGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
id string
provider GenericOAuthProvider
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientID",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid auth endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid token endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid user endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
name: "not found",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
err: caos_errors.IsNotFound,
},
},
{
name: "no changes",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
name: "change ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
instance.NewOAuthIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
func() eventstore.Command {
t := true
event, _ := instance.NewOAuthIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
[]idp.OAuthIDPChanges{
idp.ChangeOAuthName("new name"),
idp.ChangeOAuthClientID("clientID2"),
idp.ChangeOAuthClientSecret(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("newSecret"),
}),
idp.ChangeOAuthAuthorizationEndpoint("new auth"),
idp.ChangeOAuthTokenEndpoint("new token"),
idp.ChangeOAuthUserEndpoint("new user"),
idp.ChangeOAuthScopes([]string{"openid", "profile"}),
idp.ChangeOAuthOptions(idp.OptionChanges{
IsCreationAllowed: &t,
IsLinkingAllowed: &t,
IsAutoCreation: &t,
IsAutoUpdate: &t,
}),
},
)
return event
}(),
),
},
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: GenericOAuthProvider{
Name: "new name",
ClientID: "clientID2",
ClientSecret: "newSecret",
AuthorizationEndpoint: "new auth",
TokenEndpoint: "new token",
UserEndpoint: "new user",
Scopes: []string{"openid", "profile"},
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.UpdateInstanceGenericOAuthProvider(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_AddInstanceGoogleIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -281,7 +817,7 @@ func TestCommandSide_UpdateInstanceGoogleIDP(t *testing.T) {
},
},
res: res{
want: &domain.ObjectDetails{},
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
@ -893,7 +1429,7 @@ func TestCommandSide_UpdateInstanceLDAPIDP(t *testing.T) {
},
},
res: res{
want: &domain.ObjectDetails{},
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{

View File

@ -12,13 +12,54 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
func (c *Commands) AddOrgGenericOAuthProvider(ctx context.Context, resourceOwner string, provider GenericOAuthProvider) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
writeModel := NewOAuthOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgOAuthProvider(orgAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return "", nil, err
}
return id, pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) UpdateOrgGenericOAuthProvider(ctx context.Context, resourceOwner, id string, provider GenericOAuthProvider) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
writeModel := NewOAuthOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgOAuthProvider(orgAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddOrgGoogleProvider(ctx context.Context, resourceOwner string, provider GoogleProvider) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgGoogleProvider(orgAgg, resourceOwner, id, provider))
writeModel := NewGoogleOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgGoogleProvider(orgAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
@ -31,13 +72,18 @@ func (c *Commands) AddOrgGoogleProvider(ctx context.Context, resourceOwner strin
func (c *Commands) UpdateOrgGoogleProvider(ctx context.Context, resourceOwner, id string, provider GoogleProvider) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgGoogleProvider(orgAgg, resourceOwner, id, provider))
writeModel := NewGoogleOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgGoogleProvider(orgAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{}, nil
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
@ -52,7 +98,8 @@ func (c *Commands) AddOrgLDAPProvider(ctx context.Context, resourceOwner string,
if err != nil {
return "", nil, err
}
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgLDAPProvider(orgAgg, resourceOwner, id, provider))
writeModel := NewLDAPOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgLDAPProvider(orgAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
@ -65,13 +112,18 @@ func (c *Commands) AddOrgLDAPProvider(ctx context.Context, resourceOwner string,
func (c *Commands) UpdateOrgLDAPProvider(ctx context.Context, resourceOwner, id string, provider LDAPProvider) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgLDAPProvider(orgAgg, resourceOwner, id, provider))
writeModel := NewLDAPOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgLDAPProvider(orgAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{}, nil
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
@ -93,16 +145,27 @@ func (c *Commands) DeleteOrgProvider(ctx context.Context, resourceOwner, id stri
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) prepareAddOrgGoogleProvider(a *org.Aggregate, resourceOwner, id string, provider GoogleProvider) preparation.Validation {
func (c *Commands) prepareAddOrgOAuthProvider(a *org.Aggregate, writeModel *OrgOAuthIDPWriteModel, provider GenericOAuthProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-D32ef", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-D3fvs", "Errors.Invalid.Argument")
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Dbgzf", "Errors.Invalid.Argument")
}
if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-W2vqs", "Errors.Invalid.Argument")
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-DF4ga", "Errors.Invalid.Argument")
}
if provider.AuthorizationEndpoint = strings.TrimSpace(provider.AuthorizationEndpoint); provider.AuthorizationEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-B23bs", "Errors.Invalid.Argument")
}
if provider.TokenEndpoint = strings.TrimSpace(provider.TokenEndpoint); provider.TokenEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-D2gj8", "Errors.Invalid.Argument")
}
if provider.UserEndpoint = strings.TrimSpace(provider.UserEndpoint); provider.UserEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Fb8jk", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewGoogleOrgIDPWriteModel(resourceOwner, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -116,22 +179,127 @@ func (c *Commands) prepareAddOrgGoogleProvider(a *org.Aggregate, resourceOwner,
return nil, err
}
return []eventstore.Command{
org.NewGoogleIDPAddedEvent(ctx, &a.Aggregate, id, provider.Name, provider.ClientID, secret, provider.Scopes, provider.IDPOptions),
org.NewOAuthIDPAddedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
secret,
provider.AuthorizationEndpoint,
provider.TokenEndpoint,
provider.UserEndpoint,
provider.Scopes,
provider.IDPOptions,
),
}, nil
}, nil
}
}
func (c *Commands) prepareUpdateOrgGoogleProvider(a *org.Aggregate, resourceOwner, id string, provider GoogleProvider) preparation.Validation {
func (c *Commands) prepareUpdateOrgOAuthProvider(a *org.Aggregate, writeModel *OrgOAuthIDPWriteModel, provider GenericOAuthProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if id = strings.TrimSpace(id); id == "" {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-asfsa", "Errors.Invalid.Argument")
}
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-D32ef", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Dbgzf", "Errors.Invalid.Argument")
}
if provider.AuthorizationEndpoint = strings.TrimSpace(provider.AuthorizationEndpoint); provider.AuthorizationEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-B23bs", "Errors.Invalid.Argument")
}
if provider.TokenEndpoint = strings.TrimSpace(provider.TokenEndpoint); provider.TokenEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-D2gj8", "Errors.Invalid.Argument")
}
if provider.UserEndpoint = strings.TrimSpace(provider.UserEndpoint); provider.UserEndpoint == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Fb8jk", "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, "ORG-JNsd3", "Errors.Org.IDPConfig.NotExisting")
}
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
provider.ClientSecret,
c.idpConfigEncryption,
provider.AuthorizationEndpoint,
provider.TokenEndpoint,
provider.UserEndpoint,
provider.Scopes,
provider.IDPOptions,
)
if err != nil {
return nil, err
}
if event == nil {
return nil, nil
}
return []eventstore.Command{event}, nil
}, nil
}
}
func (c *Commands) prepareAddOrgGoogleProvider(a *org.Aggregate, writeModel *OrgGoogleIDPWriteModel, 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
}
secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption)
if err != nil {
return nil, err
}
return []eventstore.Command{
org.NewGoogleIDPAddedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
secret,
provider.Scopes,
provider.IDPOptions,
),
}, nil
}, nil
}
}
func (c *Commands) prepareUpdateOrgGoogleProvider(a *org.Aggregate, writeModel *OrgGoogleIDPWriteModel, provider GoogleProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-S32t1", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-ds432", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewGoogleOrgIDPWriteModel(resourceOwner, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -146,7 +314,7 @@ func (c *Commands) prepareUpdateOrgGoogleProvider(a *org.Aggregate, resourceOwne
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
provider.Name,
provider.ClientID,
provider.ClientSecret,
@ -165,7 +333,7 @@ func (c *Commands) prepareUpdateOrgGoogleProvider(a *org.Aggregate, resourceOwne
}
}
func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, resourceOwner, id string, provider LDAPProvider) preparation.Validation {
func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLDAPIDPWriteModel, provider LDAPProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAfdd", "Errors.Invalid.Argument")
@ -189,7 +357,6 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, resourceOwner, id
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sdf5h", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewLDAPOrgIDPWriteModel(resourceOwner, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -206,7 +373,7 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, resourceOwner, id
org.NewLDAPIDPAddedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
provider.Name,
provider.Host,
provider.Port,
@ -224,9 +391,9 @@ func (c *Commands) prepareAddOrgLDAPProvider(a *org.Aggregate, resourceOwner, id
}
}
func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, resourceOwner, id string, provider LDAPProvider) preparation.Validation {
func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, writeModel *OrgLDAPIDPWriteModel, provider LDAPProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if id = strings.TrimSpace(id); id == "" {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Dgdbs", "Errors.Invalid.Argument")
}
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
@ -248,7 +415,6 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, resourceOwner,
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-DG45z", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
writeModel := NewLDAPOrgIDPWriteModel(resourceOwner, id)
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
@ -263,7 +429,7 @@ func (c *Commands) prepareUpdateOrgLDAPProvider(a *org.Aggregate, resourceOwner,
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
id,
writeModel.ID,
writeModel.Name,
provider.Name,
provider.Host,

View File

@ -9,6 +9,91 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
type OrgOAuthIDPWriteModel struct {
OAuthIDPWriteModel
}
func NewOAuthOrgIDPWriteModel(orgID, id string) *OrgOAuthIDPWriteModel {
return &OrgOAuthIDPWriteModel{
OAuthIDPWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
ID: id,
},
}
}
func (wm *OrgOAuthIDPWriteModel) Reduce() error {
return wm.OAuthIDPWriteModel.Reduce()
}
func (wm *OrgOAuthIDPWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *org.OAuthIDPAddedEvent:
wm.OAuthIDPWriteModel.AppendEvents(&e.OAuthIDPAddedEvent)
case *org.OAuthIDPChangedEvent:
wm.OAuthIDPWriteModel.AppendEvents(&e.OAuthIDPChangedEvent)
case *org.IDPRemovedEvent:
wm.OAuthIDPWriteModel.AppendEvents(&e.RemovedEvent)
default:
wm.OAuthIDPWriteModel.AppendEvents(e)
}
}
}
func (wm *OrgOAuthIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(org.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
org.OAuthIDPAddedEventType,
org.OAuthIDPChangedEventType,
org.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}
func (wm *OrgOAuthIDPWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID,
clientSecretString string,
secretCrypto crypto.Crypto,
authorizationEndpoint,
tokenEndpoint,
userEndpoint string,
scopes []string,
options idp.Options,
) (*org.OAuthIDPChangedEvent, error) {
changes, err := wm.OAuthIDPWriteModel.NewChanges(
name,
clientID,
clientSecretString,
secretCrypto,
authorizationEndpoint,
tokenEndpoint,
userEndpoint,
scopes,
options,
)
if err != nil {
return nil, err
}
if len(changes) == 0 {
return nil, nil
}
return org.NewOAuthIDPChangedEvent(ctx, aggregate, id, changes)
}
type OrgGoogleIDPWriteModel struct {
GoogleIDPWriteModel
}
@ -78,11 +163,7 @@ func (wm *OrgGoogleIDPWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, nil
}
changeEvent, err := org.NewGoogleIDPChangedEvent(ctx, aggregate, id, changes)
if err != nil {
return nil, err
}
return changeEvent, nil
return org.NewGoogleIDPChangedEvent(ctx, aggregate, id, changes)
}
type OrgLDAPIDPWriteModel struct {
@ -109,19 +190,10 @@ func (wm *OrgLDAPIDPWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *org.LDAPIDPAddedEvent:
if wm.ID != e.ID {
continue
}
wm.LDAPIDPWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
case *org.LDAPIDPChangedEvent:
if wm.ID != e.ID {
continue
}
wm.LDAPIDPWriteModel.AppendEvents(&e.LDAPIDPChangedEvent)
case *org.IDPRemovedEvent:
if wm.ID != e.ID {
continue
}
wm.LDAPIDPWriteModel.AppendEvents(&e.RemovedEvent)
default:
wm.LDAPIDPWriteModel.AppendEvents(e)
@ -140,6 +212,7 @@ func (wm *OrgLDAPIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
org.LDAPIDPChangedEventType,
org.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}
@ -182,11 +255,7 @@ func (wm *OrgLDAPIDPWriteModel) NewChangedEvent(
if len(changes) == 0 {
return nil, nil
}
changeEvent, err := org.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes)
if err != nil {
return nil, err
}
return changeEvent, nil
return org.NewLDAPIDPChangedEvent(ctx, aggregate, id, oldName, changes)
}
type OrgIDPRemoveWriteModel struct {
@ -212,6 +281,10 @@ func (wm *OrgIDPRemoveWriteModel) Reduce() error {
func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *org.OAuthIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.OAuthIDPAddedEvent)
case *org.OAuthIDPChangedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.OAuthIDPChangedEvent)
case *org.GoogleIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
case *org.GoogleIDPChangedEvent:
@ -235,11 +308,14 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
AggregateTypes(org.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
org.OAuthIDPAddedEventType,
org.OAuthIDPChangedEventType,
org.GoogleIDPAddedEventType,
org.GoogleIDPChangedEventType,
org.LDAPIDPAddedEventType,
org.LDAPIDPChangedEventType,
org.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}

View File

@ -18,6 +18,552 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
func TestCommandSide_AddOrgGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
resourceOwner string
provider GenericOAuthProvider
}
type res struct {
id string
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientID",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientSecret",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid auth endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid token endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid user endpoint",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
name: "ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
eventPusherToEvents(
org.NewOAuthIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
id: "id1",
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
{
name: "ok all set",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
eventPusherToEvents(
org.NewOAuthIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
[]string{"user"},
idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
Scopes: []string{"user"},
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
id: "id1",
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idGenerator: tt.fields.idGenerator,
idpConfigEncryption: tt.fields.secretCrypto,
}
id, got, err := c.AddOrgGenericOAuthProvider(tt.args.ctx, tt.args.resourceOwner, 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.id, id)
assert.Equal(t, tt.res.want, got)
}
})
}
}
func TestCommandSide_UpdateOrgGenericOAuthIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
resourceOwner string
id string
provider GenericOAuthProvider
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid clientID",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid auth endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid token endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
"invalid user endpoint",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
},
},
res{
err: caos_errors.IsErrorInvalidArgument,
},
},
{
name: "not found",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
err: caos_errors.IsNotFound,
},
},
{
name: "no changes",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
org.NewOAuthIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "name",
ClientID: "clientID",
AuthorizationEndpoint: "auth",
TokenEndpoint: "token",
UserEndpoint: "user",
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
{
name: "change ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
org.NewOAuthIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
"auth",
"token",
"user",
nil,
idp.Options{},
)),
),
expectPush(
eventPusherToEvents(
func() eventstore.Command {
t := true
event, _ := org.NewOAuthIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
[]idp.OAuthIDPChanges{
idp.ChangeOAuthName("new name"),
idp.ChangeOAuthClientID("clientID2"),
idp.ChangeOAuthClientSecret(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("newSecret"),
}),
idp.ChangeOAuthAuthorizationEndpoint("new auth"),
idp.ChangeOAuthTokenEndpoint("new token"),
idp.ChangeOAuthUserEndpoint("new user"),
idp.ChangeOAuthScopes([]string{"openid", "profile"}),
idp.ChangeOAuthOptions(idp.OptionChanges{
IsCreationAllowed: &t,
IsLinkingAllowed: &t,
IsAutoCreation: &t,
IsAutoUpdate: &t,
}),
},
)
return event
}(),
),
),
),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: GenericOAuthProvider{
Name: "new name",
ClientID: "clientID2",
ClientSecret: "newSecret",
AuthorizationEndpoint: "new auth",
TokenEndpoint: "new token",
UserEndpoint: "new user",
Scopes: []string{"openid", "profile"},
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.UpdateOrgGenericOAuthProvider(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_AddOrgGoogleIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
@ -283,7 +829,7 @@ func TestCommandSide_UpdateOrgGoogleIDP(t *testing.T) {
},
},
res: res{
want: &domain.ObjectDetails{},
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
{
@ -907,7 +1453,7 @@ func TestCommandSide_UpdateOrgLDAPIDP(t *testing.T) {
},
},
res: res{
want: &domain.ObjectDetails{},
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
{

View File

@ -33,6 +33,7 @@ type IDPTemplate struct {
IsLinkingAllowed bool
IsAutoCreation bool
IsAutoUpdate bool
*OAuthIDPTemplate
*GoogleIDPTemplate
*LDAPIDPTemplate
}
@ -42,6 +43,16 @@ type IDPTemplates struct {
Templates []*IDPTemplate
}
type OAuthIDPTemplate struct {
IDPID string
ClientID string
ClientSecret *crypto.CryptoValue
AuthorizationEndpoint string
TokenEndpoint string
UserEndpoint string
Scopes database.StringArray
}
type GoogleIDPTemplate struct {
IDPID string
ClientID string
@ -129,6 +140,45 @@ var (
}
)
var (
oauthIdpTemplateTable = table{
name: projection.IDPTemplateOAuthTable,
instanceIDCol: projection.OAuthInstanceIDCol,
}
OAuthIDCol = Column{
name: projection.OAuthIDCol,
table: oauthIdpTemplateTable,
}
OAuthInstanceIDCol = Column{
name: projection.OAuthInstanceIDCol,
table: oauthIdpTemplateTable,
}
OAuthClientIDCol = Column{
name: projection.OAuthClientIDCol,
table: oauthIdpTemplateTable,
}
OAuthClientSecretCol = Column{
name: projection.OAuthClientSecretCol,
table: oauthIdpTemplateTable,
}
OAuthAuthorizationEndpointCol = Column{
name: projection.OAuthAuthorizationEndpointCol,
table: oauthIdpTemplateTable,
}
OAuthTokenEndpointCol = Column{
name: projection.OAuthTokenEndpointCol,
table: oauthIdpTemplateTable,
}
OAuthUserEndpointCol = Column{
name: projection.OAuthUserEndpointCol,
table: oauthIdpTemplateTable,
}
OAuthScopesCol = Column{
name: projection.OAuthScopesCol,
table: oauthIdpTemplateTable,
}
)
var (
googleIdpTemplateTable = table{
name: projection.IDPTemplateGoogleTable,
@ -370,10 +420,20 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
IDPTemplateIsLinkingAllowedCol.identifier(),
IDPTemplateIsAutoCreationCol.identifier(),
IDPTemplateIsAutoUpdateCol.identifier(),
// oauth
OAuthIDCol.identifier(),
OAuthClientIDCol.identifier(),
OAuthClientSecretCol.identifier(),
OAuthAuthorizationEndpointCol.identifier(),
OAuthTokenEndpointCol.identifier(),
OAuthUserEndpointCol.identifier(),
OAuthScopesCol.identifier(),
// google
GoogleIDCol.identifier(),
GoogleClientIDCol.identifier(),
GoogleClientSecretCol.identifier(),
GoogleScopesCol.identifier(),
// ldap
LDAPIDCol.identifier(),
LDAPHostCol.identifier(),
LDAPPortCol.identifier(),
@ -397,6 +457,7 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
LDAPAvatarURLAttributeCol.identifier(),
LDAPProfileAttributeCol.identifier(),
).From(idpTemplateTable.identifier()).
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
PlaceholderFormat(sq.Dollar),
@ -405,6 +466,14 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
name := sql.NullString{}
oauthID := sql.NullString{}
oauthClientID := sql.NullString{}
oauthClientSecret := new(crypto.CryptoValue)
oauthAuthorizationEndpoint := sql.NullString{}
oauthTokenEndpoint := sql.NullString{}
oauthUserEndpoint := sql.NullString{}
oauthScopes := database.StringArray{}
googleID := sql.NullString{}
googleClientID := sql.NullString{}
googleClientSecret := new(crypto.CryptoValue)
@ -447,10 +516,20 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
&idpTemplate.IsLinkingAllowed,
&idpTemplate.IsAutoCreation,
&idpTemplate.IsAutoUpdate,
// oauth
&oauthID,
&oauthClientID,
&oauthClientSecret,
&oauthAuthorizationEndpoint,
&oauthTokenEndpoint,
&oauthUserEndpoint,
&oauthScopes,
// google
&googleID,
&googleClientID,
&googleClientSecret,
&googleScopes,
// ldap
&ldapID,
&ldapHost,
&ldapPort,
@ -483,6 +562,17 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
idpTemplate.Name = name.String
if oauthID.Valid {
idpTemplate.OAuthIDPTemplate = &OAuthIDPTemplate{
IDPID: oauthID.String,
ClientID: oauthClientID.String,
ClientSecret: oauthClientSecret,
AuthorizationEndpoint: oauthAuthorizationEndpoint.String,
TokenEndpoint: oauthTokenEndpoint.String,
UserEndpoint: oauthUserEndpoint.String,
Scopes: oauthScopes,
}
}
if googleID.Valid {
idpTemplate.GoogleIDPTemplate = &GoogleIDPTemplate{
IDPID: googleID.String,
@ -490,7 +580,8 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
ClientSecret: googleClientSecret,
Scopes: googleScopes,
}
} else if ldapID.Valid {
}
if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
IDPID: ldapID.String,
Host: ldapHost.String,
@ -538,10 +629,20 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
IDPTemplateIsLinkingAllowedCol.identifier(),
IDPTemplateIsAutoCreationCol.identifier(),
IDPTemplateIsAutoUpdateCol.identifier(),
// oauth
OAuthIDCol.identifier(),
OAuthClientIDCol.identifier(),
OAuthClientSecretCol.identifier(),
OAuthAuthorizationEndpointCol.identifier(),
OAuthTokenEndpointCol.identifier(),
OAuthUserEndpointCol.identifier(),
OAuthScopesCol.identifier(),
// google
GoogleIDCol.identifier(),
GoogleClientIDCol.identifier(),
GoogleClientSecretCol.identifier(),
GoogleScopesCol.identifier(),
// ldap
LDAPIDCol.identifier(),
LDAPHostCol.identifier(),
LDAPPortCol.identifier(),
@ -566,6 +667,7 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
LDAPProfileAttributeCol.identifier(),
countColumn.identifier(),
).From(idpTemplateTable.identifier()).
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
PlaceholderFormat(sq.Dollar),
@ -577,6 +679,14 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
name := sql.NullString{}
oauthID := sql.NullString{}
oauthClientID := sql.NullString{}
oauthClientSecret := new(crypto.CryptoValue)
oauthAuthorizationEndpoint := sql.NullString{}
oauthTokenEndpoint := sql.NullString{}
oauthUserEndpoint := sql.NullString{}
oauthScopes := database.StringArray{}
googleID := sql.NullString{}
googleClientID := sql.NullString{}
googleClientSecret := new(crypto.CryptoValue)
@ -619,10 +729,20 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
&idpTemplate.IsLinkingAllowed,
&idpTemplate.IsAutoCreation,
&idpTemplate.IsAutoUpdate,
// oauth
&oauthID,
&oauthClientID,
&oauthClientSecret,
&oauthAuthorizationEndpoint,
&oauthTokenEndpoint,
&oauthUserEndpoint,
&oauthScopes,
// google
&googleID,
&googleClientID,
&googleClientSecret,
&googleScopes,
// ldap
&ldapID,
&ldapHost,
&ldapPort,
@ -654,6 +774,17 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
idpTemplate.Name = name.String
if oauthID.Valid {
idpTemplate.OAuthIDPTemplate = &OAuthIDPTemplate{
IDPID: oauthID.String,
ClientID: oauthClientID.String,
ClientSecret: oauthClientSecret,
AuthorizationEndpoint: oauthAuthorizationEndpoint.String,
TokenEndpoint: oauthTokenEndpoint.String,
UserEndpoint: oauthUserEndpoint.String,
Scopes: oauthScopes,
}
}
if googleID.Valid {
idpTemplate.GoogleIDPTemplate = &GoogleIDPTemplate{
IDPID: googleID.String,
@ -661,7 +792,8 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
ClientSecret: googleClientSecret,
Scopes: googleScopes,
}
} else if ldapID.Valid {
}
if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
IDPID: ldapID.String,
Host: ldapHost.String,

View File

@ -28,10 +28,20 @@ var (
` projections.idp_templates.is_linking_allowed,` +
` projections.idp_templates.is_auto_creation,` +
` projections.idp_templates.is_auto_update,` +
// oauth
` projections.idp_templates_oauth.idp_id,` +
` projections.idp_templates_oauth.client_id,` +
` projections.idp_templates_oauth.client_secret,` +
` projections.idp_templates_oauth.authorization_endpoint,` +
` projections.idp_templates_oauth.token_endpoint,` +
` projections.idp_templates_oauth.user_endpoint,` +
` projections.idp_templates_oauth.scopes,` +
// google
` projections.idp_templates_google.idp_id,` +
` projections.idp_templates_google.client_id,` +
` projections.idp_templates_google.client_secret,` +
` projections.idp_templates_google.scopes,` +
// ldap
` projections.idp_templates_ldap.idp_id,` +
` projections.idp_templates_ldap.host,` +
` projections.idp_templates_ldap.port,` +
@ -55,6 +65,7 @@ var (
` projections.idp_templates_ldap.avatar_url_attribute,` +
` projections.idp_templates_ldap.profile_attribute` +
` FROM projections.idp_templates` +
` LEFT JOIN projections.idp_templates_oauth ON projections.idp_templates.id = projections.idp_templates_oauth.idp_id AND projections.idp_templates.instance_id = projections.idp_templates_oauth.instance_id` +
` LEFT JOIN projections.idp_templates_google ON projections.idp_templates.id = projections.idp_templates_google.idp_id AND projections.idp_templates.instance_id = projections.idp_templates_google.instance_id` +
` LEFT JOIN projections.idp_templates_ldap ON projections.idp_templates.id = projections.idp_templates_ldap.idp_id AND projections.idp_templates.instance_id = projections.idp_templates_ldap.instance_id`
idpTemplateCols = []string{
@ -71,6 +82,14 @@ var (
"is_linking_allowed",
"is_auto_creation",
"is_auto_update",
// oauth config
"idp_id",
"client_id",
"client_secret",
"authorization_endpoint",
"token_endpoint",
"user_endpoint",
"scopes",
// google config
"idp_id",
"client_id",
@ -113,10 +132,20 @@ var (
` projections.idp_templates.is_linking_allowed,` +
` projections.idp_templates.is_auto_creation,` +
` projections.idp_templates.is_auto_update,` +
// oauth
` projections.idp_templates_oauth.idp_id,` +
` projections.idp_templates_oauth.client_id,` +
` projections.idp_templates_oauth.client_secret,` +
` projections.idp_templates_oauth.authorization_endpoint,` +
` projections.idp_templates_oauth.token_endpoint,` +
` projections.idp_templates_oauth.user_endpoint,` +
` projections.idp_templates_oauth.scopes,` +
// google
` projections.idp_templates_google.idp_id,` +
` projections.idp_templates_google.client_id,` +
` projections.idp_templates_google.client_secret,` +
` projections.idp_templates_google.scopes,` +
// ldap
` projections.idp_templates_ldap.idp_id,` +
` projections.idp_templates_ldap.host,` +
` projections.idp_templates_ldap.port,` +
@ -141,6 +170,7 @@ var (
` projections.idp_templates_ldap.profile_attribute,` +
` COUNT(*) OVER ()` +
` FROM projections.idp_templates` +
` LEFT JOIN projections.idp_templates_oauth ON projections.idp_templates.id = projections.idp_templates_oauth.idp_id AND projections.idp_templates.instance_id = projections.idp_templates_oauth.instance_id` +
` LEFT JOIN projections.idp_templates_google ON projections.idp_templates.id = projections.idp_templates_google.idp_id AND projections.idp_templates.instance_id = projections.idp_templates_google.instance_id` +
` LEFT JOIN projections.idp_templates_ldap ON projections.idp_templates.id = projections.idp_templates_ldap.idp_id AND projections.idp_templates.instance_id = projections.idp_templates_ldap.instance_id`
idpTemplatesCols = []string{
@ -157,6 +187,14 @@ var (
"is_linking_allowed",
"is_auto_creation",
"is_auto_update",
// oauth config
"idp_id",
"client_id",
"client_secret",
"authorization_endpoint",
"token_endpoint",
"user_endpoint",
"scopes",
// google config
"idp_id",
"client_id",
@ -218,7 +256,91 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
},
object: (*IDPTemplate)(nil),
},
{
name: "prepareIDPTemplateByIDQuery oauth idp",
prepare: prepareIDPTemplateByIDQuery,
want: want{
sqlExpectations: mockQuery(
regexp.QuoteMeta(idpTemplateQuery),
idpTemplateCols,
[]driver.Value{
"idp-id",
"ro",
testNow,
testNow,
uint64(20211109),
domain.IDPConfigStateActive,
"idp-name",
domain.IDPTypeOAuth,
domain.IdentityProviderTypeOrg,
true,
true,
true,
true,
// oauth
"idp-id",
"client_id",
nil,
"authorization",
"token",
"user",
database.StringArray{"profile"},
// google
nil,
nil,
nil,
nil,
// ldap config
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
),
},
object: &IDPTemplate{
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211109,
ResourceOwner: "ro",
ID: "idp-id",
State: domain.IDPStateActive,
Name: "idp-name",
Type: domain.IDPTypeOAuth,
OwnerType: domain.IdentityProviderTypeOrg,
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
OAuthIDPTemplate: &OAuthIDPTemplate{
IDPID: "idp-id",
ClientID: "client_id",
ClientSecret: nil,
AuthorizationEndpoint: "authorization",
TokenEndpoint: "token",
UserEndpoint: "user",
Scopes: []string{"profile"},
},
},
},
{
name: "prepareIDPTemplateByIDQuery google idp",
prepare: prepareIDPTemplateByIDQuery,
@ -240,6 +362,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google
"idp-id",
"client_id",
@ -314,6 +444,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google
nil,
nil,
@ -407,6 +545,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google config
nil,
nil,
@ -511,6 +657,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google config
nil,
nil,
@ -613,6 +767,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google config
nil,
nil,
@ -690,6 +852,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google config
nil,
nil,
@ -733,6 +903,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
// oauth
nil,
nil,
nil,
nil,
nil,
nil,
nil,
// google
"idp-id-google",
"client_id",
@ -762,12 +940,63 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
},
{
"idp-id-oauth",
"ro",
testNow,
testNow,
uint64(20211109),
domain.IDPConfigStateActive,
"idp-name",
domain.IDPTypeOAuth,
domain.IdentityProviderTypeOrg,
true,
true,
true,
true,
// oauth
"idp-id-oauth",
"client_id",
nil,
"authorization",
"token",
"user",
database.StringArray{"profile"},
// google
nil,
nil,
nil,
nil,
// ldap config
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
nil,
},
},
),
},
object: &IDPTemplates{
SearchResponse: SearchResponse{
Count: 2,
Count: 3,
},
Templates: []*IDPTemplate{
{
@ -831,6 +1060,31 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
Scopes: []string{"profile"},
},
},
{
CreationDate: testNow,
ChangeDate: testNow,
Sequence: 20211109,
ResourceOwner: "ro",
ID: "idp-id-oauth",
State: domain.IDPStateActive,
Name: "idp-name",
Type: domain.IDPTypeOAuth,
OwnerType: domain.IdentityProviderTypeOrg,
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
OAuthIDPTemplate: &OAuthIDPTemplate{
IDPID: "idp-id-oauth",
ClientID: "client_id",
ClientSecret: nil,
AuthorizationEndpoint: "authorization",
TokenEndpoint: "token",
UserEndpoint: "user",
Scopes: []string{"profile"},
},
},
},
},
},

View File

@ -17,9 +17,11 @@ import (
const (
IDPTemplateTable = "projections.idp_templates"
IDPTemplateOAuthTable = IDPTemplateTable + "_" + IDPTemplateOAuthSuffix
IDPTemplateGoogleTable = IDPTemplateTable + "_" + IDPTemplateGoogleSuffix
IDPTemplateLDAPTable = IDPTemplateTable + "_" + IDPTemplateLDAPSuffix
IDPTemplateOAuthSuffix = "oauth"
IDPTemplateGoogleSuffix = "google"
IDPTemplateLDAPSuffix = "ldap"
@ -39,6 +41,15 @@ const (
IDPTemplateIsAutoCreationCol = "is_auto_creation"
IDPTemplateIsAutoUpdateCol = "is_auto_update"
OAuthIDCol = "idp_id"
OAuthInstanceIDCol = "instance_id"
OAuthClientIDCol = "client_id"
OAuthClientSecretCol = "client_secret"
OAuthAuthorizationEndpointCol = "authorization_endpoint"
OAuthTokenEndpointCol = "token_endpoint"
OAuthUserEndpointCol = "user_endpoint"
OAuthScopesCol = "scopes"
GoogleIDCol = "idp_id"
GoogleInstanceIDCol = "instance_id"
GoogleClientIDCol = "client_id"
@ -100,6 +111,20 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
crdb.WithIndex(crdb.NewIndex("resource_owner", []string{IDPTemplateResourceOwnerCol})),
crdb.WithIndex(crdb.NewIndex("owner_removed", []string{IDPTemplateOwnerRemovedCol})),
),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(OAuthIDCol, crdb.ColumnTypeText),
crdb.NewColumn(OAuthInstanceIDCol, crdb.ColumnTypeText),
crdb.NewColumn(OAuthClientIDCol, crdb.ColumnTypeText),
crdb.NewColumn(OAuthClientSecretCol, crdb.ColumnTypeJSONB),
crdb.NewColumn(OAuthAuthorizationEndpointCol, crdb.ColumnTypeText),
crdb.NewColumn(OAuthTokenEndpointCol, crdb.ColumnTypeText),
crdb.NewColumn(OAuthUserEndpointCol, crdb.ColumnTypeText),
crdb.NewColumn(OAuthScopesCol, crdb.ColumnTypeTextArray, crdb.Nullable()),
},
crdb.NewPrimaryKey(OAuthInstanceIDCol, OAuthIDCol),
IDPTemplateOAuthSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(GoogleIDCol, crdb.ColumnTypeText),
crdb.NewColumn(GoogleInstanceIDCol, crdb.ColumnTypeText),
@ -150,6 +175,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
{
Aggregate: instance.AggregateType,
EventRedusers: []handler.EventReducer{
{
Event: instance.OAuthIDPAddedEventType,
Reduce: p.reduceOAuthIDPAdded,
},
{
Event: instance.OAuthIDPChangedEventType,
Reduce: p.reduceOAuthIDPChanged,
},
{
Event: instance.GoogleIDPAddedEventType,
Reduce: p.reduceGoogleIDPAdded,
@ -179,6 +212,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
{
Aggregate: org.AggregateType,
EventRedusers: []handler.EventReducer{
{
Event: org.OAuthIDPAddedEventType,
Reduce: p.reduceOAuthIDPAdded,
},
{
Event: org.OAuthIDPChangedEventType,
Reduce: p.reduceOAuthIDPChanged,
},
{
Event: org.GoogleIDPAddedEventType,
Reduce: p.reduceGoogleIDPAdded,
@ -208,6 +249,97 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
}
}
func (p *idpTemplateProjection) reduceOAuthIDPAdded(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.OAuthIDPAddedEvent
var idpOwnerType domain.IdentityProviderType
switch e := event.(type) {
case *org.OAuthIDPAddedEvent:
idpEvent = e.OAuthIDPAddedEvent
idpOwnerType = domain.IdentityProviderTypeOrg
case *instance.OAuthIDPAddedEvent:
idpEvent = e.OAuthIDPAddedEvent
idpOwnerType = domain.IdentityProviderTypeSystem
default:
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ap9ihb", "reduce.wrong.event.type %v", []eventstore.EventType{org.OAuthIDPAddedEventType, instance.OAuthIDPAddedEventType})
}
return crdb.NewMultiStatement(
&idpEvent,
crdb.AddCreateStatement(
[]handler.Column{
handler.NewCol(IDPTemplateIDCol, idpEvent.ID),
handler.NewCol(IDPTemplateCreationDateCol, idpEvent.CreationDate()),
handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()),
handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()),
handler.NewCol(IDPTemplateResourceOwnerCol, idpEvent.Aggregate().ResourceOwner),
handler.NewCol(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID),
handler.NewCol(IDPTemplateStateCol, domain.IDPStateActive),
handler.NewCol(IDPTemplateNameCol, idpEvent.Name),
handler.NewCol(IDPTemplateOwnerTypeCol, idpOwnerType),
handler.NewCol(IDPTemplateTypeCol, domain.IDPTypeOAuth),
handler.NewCol(IDPTemplateIsCreationAllowedCol, idpEvent.IsCreationAllowed),
handler.NewCol(IDPTemplateIsLinkingAllowedCol, idpEvent.IsLinkingAllowed),
handler.NewCol(IDPTemplateIsAutoCreationCol, idpEvent.IsAutoCreation),
handler.NewCol(IDPTemplateIsAutoUpdateCol, idpEvent.IsAutoUpdate),
},
),
crdb.AddCreateStatement(
[]handler.Column{
handler.NewCol(OAuthIDCol, idpEvent.ID),
handler.NewCol(OAuthInstanceIDCol, idpEvent.Aggregate().InstanceID),
handler.NewCol(OAuthClientIDCol, idpEvent.ClientID),
handler.NewCol(OAuthClientSecretCol, idpEvent.ClientSecret),
handler.NewCol(OAuthAuthorizationEndpointCol, idpEvent.AuthorizationEndpoint),
handler.NewCol(OAuthTokenEndpointCol, idpEvent.TokenEndpoint),
handler.NewCol(OAuthUserEndpointCol, idpEvent.UserEndpoint),
handler.NewCol(OAuthScopesCol, database.StringArray(idpEvent.Scopes)),
},
crdb.WithTableSuffix(IDPTemplateOAuthSuffix),
),
), nil
}
func (p *idpTemplateProjection) reduceOAuthIDPChanged(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.OAuthIDPChangedEvent
switch e := event.(type) {
case *org.OAuthIDPChangedEvent:
idpEvent = e.OAuthIDPChangedEvent
case *instance.OAuthIDPChangedEvent:
idpEvent = e.OAuthIDPChangedEvent
default:
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.OAuthIDPChangedEventType, instance.OAuthIDPChangedEventType})
}
ops := make([]func(eventstore.Event) crdb.Exec, 0, 2)
ops = append(ops,
crdb.AddUpdateStatement(
reduceIDPChangedTemplateColumns(idpEvent.Name, idpEvent.CreationDate(), idpEvent.Sequence(), idpEvent.OptionChanges),
[]handler.Condition{
handler.NewCond(IDPTemplateIDCol, idpEvent.ID),
handler.NewCond(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID),
},
),
)
oauthCols := reduceOAuthIDPChangedColumns(idpEvent)
if len(oauthCols) > 0 {
ops = append(ops,
crdb.AddUpdateStatement(
oauthCols,
[]handler.Condition{
handler.NewCond(OAuthIDCol, idpEvent.ID),
handler.NewCond(OAuthInstanceIDCol, idpEvent.Aggregate().InstanceID),
},
crdb.WithTableSuffix(IDPTemplateOAuthSuffix),
),
)
}
return crdb.NewMultiStatement(
&idpEvent,
ops...,
), nil
}
func (p *idpTemplateProjection) reduceGoogleIDPAdded(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.GoogleIDPAddedEvent
var idpOwnerType domain.IdentityProviderType
@ -466,6 +598,29 @@ func reduceIDPChangedTemplateColumns(name *string, creationDate time.Time, seque
)
}
func reduceOAuthIDPChangedColumns(idpEvent idp.OAuthIDPChangedEvent) []handler.Column {
oauthCols := make([]handler.Column, 0, 6)
if idpEvent.ClientID != nil {
oauthCols = append(oauthCols, handler.NewCol(OAuthClientIDCol, *idpEvent.ClientID))
}
if idpEvent.ClientSecret != nil {
oauthCols = append(oauthCols, handler.NewCol(OAuthClientSecretCol, *idpEvent.ClientSecret))
}
if idpEvent.AuthorizationEndpoint != nil {
oauthCols = append(oauthCols, handler.NewCol(OAuthAuthorizationEndpointCol, *idpEvent.AuthorizationEndpoint))
}
if idpEvent.TokenEndpoint != nil {
oauthCols = append(oauthCols, handler.NewCol(OAuthTokenEndpointCol, *idpEvent.TokenEndpoint))
}
if idpEvent.UserEndpoint != nil {
oauthCols = append(oauthCols, handler.NewCol(OAuthUserEndpointCol, *idpEvent.UserEndpoint))
}
if idpEvent.Scopes != nil {
oauthCols = append(oauthCols, handler.NewCol(OAuthScopesCol, database.StringArray(idpEvent.Scopes)))
}
return oauthCols
}
func reduceGoogleIDPChangedColumns(idpEvent idp.GoogleIDPChangedEvent) []handler.Column {
googleCols := make([]handler.Column, 0, 3)
if idpEvent.ClientID != nil {

View File

@ -125,6 +125,276 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) {
}
}
func TestIDPTemplateProjection_reducesOAuth(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: "instance reduceOAuthIDPAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.OAuthIDPAddedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"authorizationEndpoint": "auth",
"tokenEndpoint": "token",
"userEndpoint": "user",
"scopes": ["profile"],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true
}`),
), instance.OAuthIDPAddedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceOAuthIDPAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.idp_templates (id, creation_date, change_date, sequence, resource_owner, instance_id, state, name, owner_type, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
expectedArgs: []interface{}{
"idp-id",
anyArg{},
anyArg{},
uint64(15),
"ro-id",
"instance-id",
domain.IDPStateActive,
"custom-zitadel-instance",
domain.IdentityProviderTypeSystem,
domain.IDPTypeOAuth,
true,
true,
true,
true,
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates_oauth (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"client_id",
anyArg{},
"auth",
"token",
"user",
database.StringArray{"profile"},
},
},
},
},
},
},
{
name: "org reduceOAuthIDPAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(org.OAuthIDPAddedEventType),
org.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"authorizationEndpoint": "auth",
"tokenEndpoint": "token",
"userEndpoint": "user",
"scopes": ["profile"],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true
}`),
), org.OAuthIDPAddedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceOAuthIDPAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("org"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "INSERT INTO projections.idp_templates (id, creation_date, change_date, sequence, resource_owner, instance_id, state, name, owner_type, type, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)",
expectedArgs: []interface{}{
"idp-id",
anyArg{},
anyArg{},
uint64(15),
"ro-id",
"instance-id",
domain.IDPStateActive,
"custom-zitadel-instance",
domain.IdentityProviderTypeOrg,
domain.IDPTypeOAuth,
true,
true,
true,
true,
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates_oauth (idp_id, instance_id, client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"client_id",
anyArg{},
"auth",
"token",
"user",
database.StringArray{"profile"},
},
},
},
},
},
},
{
name: "instance reduceOAuthIDPChanged minimal",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.OAuthIDPChangedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"isCreationAllowed": true,
"client_id": "id"
}`),
), instance.OAuthIDPChangedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceOAuthIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.idp_templates SET (is_creation_allowed, change_date, sequence) = ($1, $2, $3) WHERE (id = $4) AND (instance_id = $5)",
expectedArgs: []interface{}{
true,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates_oauth SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"id",
"idp-id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceOAuthIDPChanged",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.OAuthIDPChangedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "custom-zitadel-instance",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"authorizationEndpoint": "auth",
"tokenEndpoint": "token",
"userEndpoint": "user",
"scopes": ["profile"],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true
}`),
), instance.OAuthIDPChangedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceOAuthIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: "UPDATE projections.idp_templates SET (name, is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, change_date, sequence) = ($1, $2, $3, $4, $5, $6, $7) WHERE (id = $8) AND (instance_id = $9)",
expectedArgs: []interface{}{
"custom-zitadel-instance",
true,
true,
true,
true,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates_oauth SET (client_id, client_secret, authorization_endpoint, token_endpoint, user_endpoint, scopes) = ($1, $2, $3, $4, $5, $6) WHERE (idp_id = $7) AND (instance_id = $8)",
expectedArgs: []interface{}{
"client_id",
anyArg{},
"auth",
"token",
"user",
database.StringArray{"profile"},
"idp-id",
"instance-id",
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
event := baseEvent(t)
got, err := tt.reduce(event)
if !errors.IsErrorInvalidArgument(err) {
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, IDPTemplateTable, tt.want)
})
}
}
func TestIDPTemplateProjection_reducesGoogle(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.Event

View File

@ -47,8 +47,8 @@ func (o *Options) ReduceChanges(changes OptionChanges) {
if changes.IsLinkingAllowed != nil {
o.IsLinkingAllowed = *changes.IsLinkingAllowed
}
if changes.IsAutoUpdate != nil {
o.IsAutoUpdate = *changes.IsAutoUpdate
if changes.IsAutoCreation != nil {
o.IsAutoCreation = *changes.IsAutoCreation
}
if changes.IsAutoUpdate != nil {
o.IsAutoUpdate = *changes.IsAutoUpdate

View File

@ -0,0 +1,173 @@
package idp
import (
"encoding/json"
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
"github.com/zitadel/zitadel/internal/eventstore/repository"
)
type OAuthIDPAddedEvent struct {
eventstore.BaseEvent `json:"-"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"`
AuthorizationEndpoint string `json:"authorizationEndpoint,omitempty"`
TokenEndpoint string `json:"tokenEndpoint,omitempty"`
UserEndpoint string `json:"userEndpoint,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Options
}
func NewOAuthIDPAddedEvent(
base *eventstore.BaseEvent,
id,
name,
clientID string,
clientSecret *crypto.CryptoValue,
authorizationEndpoint,
tokenEndpoint,
userEndpoint string,
scopes []string,
options Options,
) *OAuthIDPAddedEvent {
return &OAuthIDPAddedEvent{
BaseEvent: *base,
ID: id,
Name: name,
ClientID: clientID,
ClientSecret: clientSecret,
AuthorizationEndpoint: authorizationEndpoint,
TokenEndpoint: tokenEndpoint,
UserEndpoint: userEndpoint,
Scopes: scopes,
Options: options,
}
}
func (e *OAuthIDPAddedEvent) Data() interface{} {
return e
}
func (e *OAuthIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func OAuthIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &OAuthIDPAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "IDP-Et1dq", "unable to unmarshal event")
}
return e, nil
}
type OAuthIDPChangedEvent struct {
eventstore.BaseEvent `json:"-"`
ID string `json:"id"`
Name *string `json:"name,omitempty"`
ClientID *string `json:"client_id,omitempty"`
ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"`
AuthorizationEndpoint *string `json:"authorizationEndpoint,omitempty"`
TokenEndpoint *string `json:"tokenEndpoint,omitempty"`
UserEndpoint *string `json:"userEndpoint,omitempty"`
Scopes []string `json:"scopes,omitempty"`
OptionChanges
}
func NewOAuthIDPChangedEvent(
base *eventstore.BaseEvent,
id string,
changes []OAuthIDPChanges,
) (*OAuthIDPChangedEvent, error) {
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "IDP-BH3dl", "Errors.NoChangesFound")
}
changedEvent := &OAuthIDPChangedEvent{
BaseEvent: *base,
ID: id,
}
for _, change := range changes {
change(changedEvent)
}
return changedEvent, nil
}
type OAuthIDPChanges func(*OAuthIDPChangedEvent)
func ChangeOAuthName(name string) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.Name = &name
}
}
func ChangeOAuthClientID(clientID string) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.ClientID = &clientID
}
}
func ChangeOAuthClientSecret(clientSecret *crypto.CryptoValue) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.ClientSecret = clientSecret
}
}
func ChangeOAuthOptions(options OptionChanges) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.OptionChanges = options
}
}
func ChangeOAuthAuthorizationEndpoint(authorizationEndpoint string) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.AuthorizationEndpoint = &authorizationEndpoint
}
}
func ChangeOAuthTokenEndpoint(tokenEndpoint string) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.TokenEndpoint = &tokenEndpoint
}
}
func ChangeOAuthUserEndpoint(userEndpoint string) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.UserEndpoint = &userEndpoint
}
}
func ChangeOAuthScopes(scopes []string) func(*OAuthIDPChangedEvent) {
return func(e *OAuthIDPChangedEvent) {
e.Scopes = scopes
}
}
func (e *OAuthIDPChangedEvent) Data() interface{} {
return e
}
func (e *OAuthIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func OAuthIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &OAuthIDPChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "IDP-SAf3gw", "unable to unmarshal event")
}
return e, nil
}

View File

@ -70,6 +70,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
RegisterFilterEventMapper(AggregateType, OAuthIDPAddedEventType, OAuthIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, OAuthIDPChangedEventType, OAuthIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GoogleIDPAddedEventType, GoogleIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).

View File

@ -10,6 +10,8 @@ import (
)
const (
OAuthIDPAddedEventType eventstore.EventType = "instance.idp.oauth.added"
OAuthIDPChangedEventType eventstore.EventType = "instance.idp.oauth.changed"
GoogleIDPAddedEventType eventstore.EventType = "instance.idp.google.added"
GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed"
LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.added"
@ -17,6 +19,88 @@ const (
IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
)
type OAuthIDPAddedEvent struct {
idp.OAuthIDPAddedEvent
}
func NewOAuthIDPAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID string,
clientSecret *crypto.CryptoValue,
authorizationEndpoint,
tokenEndpoint,
userEndpoint string,
scopes []string,
options idp.Options,
) *OAuthIDPAddedEvent {
return &OAuthIDPAddedEvent{
OAuthIDPAddedEvent: *idp.NewOAuthIDPAddedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
OAuthIDPAddedEventType,
),
id,
name,
clientID,
clientSecret,
authorizationEndpoint,
tokenEndpoint,
userEndpoint,
scopes,
options,
),
}
}
func OAuthIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.OAuthIDPAddedEventMapper(event)
if err != nil {
return nil, err
}
return &OAuthIDPAddedEvent{OAuthIDPAddedEvent: *e.(*idp.OAuthIDPAddedEvent)}, nil
}
type OAuthIDPChangedEvent struct {
idp.OAuthIDPChangedEvent
}
func NewOAuthIDPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
changes []idp.OAuthIDPChanges,
) (*OAuthIDPChangedEvent, error) {
changedEvent, err := idp.NewOAuthIDPChangedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
OAuthIDPChangedEventType,
),
id,
changes,
)
if err != nil {
return nil, err
}
return &OAuthIDPChangedEvent{OAuthIDPChangedEvent: *changedEvent}, nil
}
func OAuthIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.OAuthIDPChangedEventMapper(event)
if err != nil {
return nil, err
}
return &OAuthIDPChangedEvent{OAuthIDPChangedEvent: *e.(*idp.OAuthIDPChangedEvent)}, nil
}
type GoogleIDPAddedEvent struct {
idp.GoogleIDPAddedEvent
}

View File

@ -78,6 +78,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
RegisterFilterEventMapper(AggregateType, OAuthIDPAddedEventType, OAuthIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, OAuthIDPChangedEventType, OAuthIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GoogleIDPAddedEventType, GoogleIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).

View File

@ -10,6 +10,8 @@ import (
)
const (
OAuthIDPAddedEventType eventstore.EventType = "org.idp.oauth.added"
OAuthIDPChangedEventType eventstore.EventType = "org.idp.oauth.changed"
GoogleIDPAddedEventType eventstore.EventType = "org.idp.google.added"
GoogleIDPChangedEventType eventstore.EventType = "org.idp.google.changed"
LDAPIDPAddedEventType eventstore.EventType = "org.idp.ldap.added"
@ -17,6 +19,88 @@ const (
IDPRemovedEventType eventstore.EventType = "org.idp.removed"
)
type OAuthIDPAddedEvent struct {
idp.OAuthIDPAddedEvent
}
func NewOAuthIDPAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID string,
clientSecret *crypto.CryptoValue,
authorizationEndpoint,
tokenEndpoint,
userEndpoint string,
scopes []string,
options idp.Options,
) *OAuthIDPAddedEvent {
return &OAuthIDPAddedEvent{
OAuthIDPAddedEvent: *idp.NewOAuthIDPAddedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
OAuthIDPAddedEventType,
),
id,
name,
clientID,
clientSecret,
authorizationEndpoint,
tokenEndpoint,
userEndpoint,
scopes,
options,
),
}
}
func OAuthIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.OAuthIDPAddedEventMapper(event)
if err != nil {
return nil, err
}
return &OAuthIDPAddedEvent{OAuthIDPAddedEvent: *e.(*idp.OAuthIDPAddedEvent)}, nil
}
type OAuthIDPChangedEvent struct {
idp.OAuthIDPChangedEvent
}
func NewOAuthIDPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
changes []idp.OAuthIDPChanges,
) (*OAuthIDPChangedEvent, error) {
changedEvent, err := idp.NewOAuthIDPChangedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
OAuthIDPChangedEventType,
),
id,
changes,
)
if err != nil {
return nil, err
}
return &OAuthIDPChangedEvent{OAuthIDPChangedEvent: *changedEvent}, nil
}
func OAuthIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.OAuthIDPChangedEventMapper(event)
if err != nil {
return nil, err
}
return &OAuthIDPChangedEvent{OAuthIDPChangedEvent: *e.(*idp.OAuthIDPChangedEvent)}, nil
}
type GoogleIDPAddedEvent struct {
idp.GoogleIDPAddedEvent
}

View File

@ -1248,6 +1248,30 @@ service AdminService {
};
}
// Add a new OAuth2 identity provider on the instance
rpc AddGenericOAuthProvider(AddGenericOAuthProviderRequest) returns (AddGenericOAuthProviderResponse) {
option (google.api.http) = {
post: "/idps/oauth"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "iam.idp.write"
};
}
// Change an existing OAuth2 identity provider on the instance
rpc UpdateGenericOAuthProvider(UpdateGenericOAuthProviderRequest) returns (UpdateGenericOAuthProviderResponse) {
option (google.api.http) = {
put: "/idps/oauth/{id}"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "iam.idp.write"
};
}
// Add a new Google identity provider on the instance
rpc AddGoogleProvider(AddGoogleProviderRequest) returns (AddGoogleProviderResponse) {
option (google.api.http) = {
@ -4259,6 +4283,39 @@ message GetProviderByIDResponse {
zitadel.idp.v1.Provider idp = 1;
}
message AddGenericOAuthProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_secret = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
string authorization_endpoint = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string token_endpoint = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
string user_endpoint = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
repeated string scopes = 7 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 8;
}
message AddGenericOAuthProviderResponse {
zitadel.v1.ObjectDetails details = 1;
string id = 2;
}
message UpdateGenericOAuthProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
// client_secret will only be updated if provided
string client_secret = 4 [(validate.rules).string = {max_len: 200}];
string authorization_endpoint = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
string token_endpoint = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
string user_endpoint = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
repeated string scopes = 8 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 9;
}
message UpdateGenericOAuthProviderResponse {
zitadel.v1.ObjectDetails details = 1;
}
message AddGoogleProviderRequest {
// Google will be used as default, if no name is provided
string name = 1 [(validate.rules).string = {max_len: 200}];

View File

@ -263,8 +263,16 @@ message ProviderConfig {
oneof config {
LDAPConfig ldap = 2;
GoogleConfig google = 3;
OAuthConfig oauth = 4;
}
}
message OAuthConfig {
string client_id = 1;
string authorization_endpoint = 2;
string token_endpoint = 3;
string user_endpoint = 4;
repeated string scopes = 5;
}
message GoogleConfig {
string client_id = 1;

View File

@ -4369,6 +4369,29 @@ service ManagementService {
};
}
// Add a new OAuth2 identity provider in the organisation
rpc AddGenericOAuthProvider(AddGenericOAuthProviderRequest) returns (AddGenericOAuthProviderResponse) {
option (google.api.http) = {
post: "/idps/oauth"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "org.idp.write"
};
}
// Change an existing OAuth2 identity provider in the organisation
rpc UpdateGenericOAuthProvider(UpdateGenericOAuthProviderRequest) returns (UpdateGenericOAuthProviderResponse) {
option (google.api.http) = {
put: "/idps/oauth/{id}"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "org.idp.write"
};
}
// Add a new Google identity provider in the organisation
rpc AddGoogleProvider(AddGoogleProviderRequest) returns (AddGoogleProviderResponse) {
@ -7874,6 +7897,39 @@ message GetProviderByIDResponse {
zitadel.idp.v1.Provider idp = 1;
}
message AddGenericOAuthProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_secret = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
string authorization_endpoint = 4 [(validate.rules).string = {min_len: 1, max_len: 200}];
string token_endpoint = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
string user_endpoint = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
repeated string scopes = 7 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 8;
}
message AddGenericOAuthProviderResponse {
zitadel.v1.ObjectDetails details = 1;
string id = 2;
}
message UpdateGenericOAuthProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
// client_secret will only be updated if provided
string client_secret = 4 [(validate.rules).string = {max_len: 200}];
string authorization_endpoint = 5 [(validate.rules).string = {min_len: 1, max_len: 200}];
string token_endpoint = 6 [(validate.rules).string = {min_len: 1, max_len: 200}];
string user_endpoint = 7 [(validate.rules).string = {min_len: 1, max_len: 200}];
repeated string scopes = 8 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 9;
}
message UpdateGenericOAuthProviderResponse {
zitadel.v1.ObjectDetails details = 1;
}
message AddGoogleProviderRequest {
// Google will be used as default, if no name is provided
string name = 1 [(validate.rules).string = {max_len: 200}];