diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md
index e48ccd622..797bf1036 100644
--- a/docs/docs/apis/proto/admin.md
+++ b/docs/docs/apis/proto/admin.md
@@ -593,12 +593,36 @@ Returns an identity provider of the instance
GET: /idps/templates/{id}
+### AddGoogleProvider
+
+> **rpc** AddGoogleProvider([AddGoogleProviderRequest](#addgoogleproviderrequest))
+[AddGoogleProviderResponse](#addgoogleproviderresponse)
+
+Add a new Google identity provider on the instance
+
+
+
+ POST: /idps/google
+
+
+### UpdateGoogleProvider
+
+> **rpc** UpdateGoogleProvider([UpdateGoogleProviderRequest](#updategoogleproviderrequest))
+[UpdateGoogleProviderResponse](#updategoogleproviderresponse)
+
+Change an existing Google identity provider on the instance
+
+
+
+ PUT: /idps/google/{id}
+
+
### AddLDAPProvider
> **rpc** AddLDAPProvider([AddLDAPProviderRequest](#addldapproviderrequest))
[AddLDAPProviderResponse](#addldapproviderresponse)
-Add a new ldap identity provider on the instance
+Add a new LDAP identity provider on the instance
@@ -610,7 +634,7 @@ Add a new ldap identity provider on the instance
> **rpc** UpdateLDAPProvider([UpdateLDAPProviderRequest](#updateldapproviderrequest))
[UpdateLDAPProviderResponse](#updateldapproviderresponse)
-Change an existing ldap identity provider on the instance
+Change an existing LDAP identity provider on the instance
@@ -1849,6 +1873,33 @@ This is an empty request
+### AddGoogleProviderRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| name | string | Google will be used as default, if no name is provided | string.max_len: 200
|
+| client_id | string | - | string.min_len: 1
string.max_len: 200
|
+| client_secret | string | - | string.min_len: 1
string.max_len: 200
|
+| scopes | repeated string | - | repeated.max_items: 20
repeated.items.string.min_len: 1
repeated.items.string.max_len: 100
|
+| provider_options | zitadel.idp.v1.Options | - | |
+
+
+
+
+### AddGoogleProviderResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+| id | string | - | |
+
+
+
+
### AddIAMMemberRequest
@@ -4788,6 +4839,33 @@ this is en empty request
+### UpdateGoogleProviderRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| id | string | - | string.min_len: 1
string.max_len: 200
|
+| name | string | - | string.max_len: 200
|
+| client_id | string | - | string.min_len: 1
string.max_len: 200
|
+| client_secret | string | client_secret will only be updated if provided | string.max_len: 200
|
+| scopes | repeated string | - | repeated.max_items: 20
repeated.items.string.min_len: 1
repeated.items.string.max_len: 100
|
+| provider_options | zitadel.idp.v1.Options | - | |
+
+
+
+
+### UpdateGoogleProviderResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### UpdateIAMMemberRequest
diff --git a/docs/docs/apis/proto/idp.md b/docs/docs/apis/proto/idp.md
index cf923573b..227950218 100644
--- a/docs/docs/apis/proto/idp.md
+++ b/docs/docs/apis/proto/idp.md
@@ -9,6 +9,18 @@ title: zitadel/idp.proto
## Messages
+### GoogleConfig
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| client_id | string | - | |
+| scopes | repeated string | - | |
+
+
+
+
### IDP
@@ -201,6 +213,7 @@ title: zitadel/idp.proto
| ----- | ---- | ----------- | ----------- |
| options | Options | - | |
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.ldap | LDAPConfig | - | |
+| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.google | GoogleConfig | - | |
diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md
index cfcac546f..2c93a965d 100644
--- a/docs/docs/apis/proto/management.md
+++ b/docs/docs/apis/proto/management.md
@@ -3050,12 +3050,36 @@ Returns an identity provider of the organisation
GET: /idps/templates/{id}
+### AddGoogleProvider
+
+> **rpc** AddGoogleProvider([AddGoogleProviderRequest](#addgoogleproviderrequest))
+[AddGoogleProviderResponse](#addgoogleproviderresponse)
+
+Add a new Google identity provider in the organisation
+
+
+
+ POST: /idps/google
+
+
+### UpdateGoogleProvider
+
+> **rpc** UpdateGoogleProvider([UpdateGoogleProviderRequest](#updategoogleproviderrequest))
+[UpdateGoogleProviderResponse](#updategoogleproviderresponse)
+
+Change an existing Google identity provider in the organisation
+
+
+
+ PUT: /idps/google/{id}
+
+
### AddLDAPProvider
> **rpc** AddLDAPProvider([AddLDAPProviderRequest](#addldapproviderrequest))
[AddLDAPProviderResponse](#addldapproviderresponse)
-Add a new ldap identity provider in the organisation
+Add a new LDAP identity provider in the organisation
@@ -3067,7 +3091,7 @@ Add a new ldap identity provider in the organisation
> **rpc** UpdateLDAPProvider([UpdateLDAPProviderRequest](#updateldapproviderrequest))
[UpdateLDAPProviderResponse](#updateldapproviderresponse)
-Change an existing ldap identity provider in the organisation
+Change an existing LDAP identity provider in the organisation
@@ -3523,6 +3547,33 @@ This is an empty request
+### AddGoogleProviderRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| name | string | Google will be used as default, if no name is provided | string.max_len: 200
|
+| client_id | string | - | string.min_len: 1
string.max_len: 200
|
+| client_secret | string | - | string.min_len: 1
string.max_len: 200
|
+| scopes | repeated string | - | repeated.max_items: 20
repeated.items.string.min_len: 1
repeated.items.string.max_len: 100
|
+| provider_options | zitadel.idp.v1.Options | - | |
+
+
+
+
+### AddGoogleProviderResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+| id | string | - | |
+
+
+
+
### AddHumanUserRequest
@@ -8860,6 +8911,33 @@ This is an empty request
+### UpdateGoogleProviderRequest
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| id | string | - | string.min_len: 1
string.max_len: 200
|
+| name | string | - | string.max_len: 200
|
+| client_id | string | - | string.min_len: 1
string.max_len: 200
|
+| client_secret | string | client_secret will only be updated if provided | string.max_len: 200
|
+| scopes | repeated string | - | repeated.max_items: 20
repeated.items.string.min_len: 1
repeated.items.string.max_len: 100
|
+| provider_options | zitadel.idp.v1.Options | - | |
+
+
+
+
+### UpdateGoogleProviderResponse
+
+
+
+| Field | Type | Description | Validation |
+| ----- | ---- | ----------- | ----------- |
+| details | zitadel.v1.ObjectDetails | - | |
+
+
+
+
### UpdateHumanEmailRequest
diff --git a/internal/api/grpc/admin/idp.go b/internal/api/grpc/admin/idp.go
index f0e73037b..5d3ca5700 100644
--- a/internal/api/grpc/admin/idp.go
+++ b/internal/api/grpc/admin/idp.go
@@ -174,6 +174,27 @@ func (s *Server) ListProviders(ctx context.Context, req *admin_pb.ListProvidersR
}, 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))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.AddGoogleProviderResponse{
+ Id: id,
+ Details: object_pb.DomainToAddDetailsPb(details),
+ }, nil
+}
+
+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))
+ if err != nil {
+ return nil, err
+ }
+ return &admin_pb.UpdateGoogleProviderResponse{
+ Details: object_pb.DomainToChangeDetailsPb(details),
+ }, nil
+}
+
func (s *Server) AddLDAPProvider(ctx context.Context, req *admin_pb.AddLDAPProviderRequest) (*admin_pb.AddLDAPProviderResponse, error) {
id, details, err := s.command.AddInstanceLDAPProvider(ctx, addLDAPProviderToCommand(req))
if err != nil {
diff --git a/internal/api/grpc/admin/idp_converter.go b/internal/api/grpc/admin/idp_converter.go
index 43dac5e0d..1bf605088 100644
--- a/internal/api/grpc/admin/idp_converter.go
+++ b/internal/api/grpc/admin/idp_converter.go
@@ -201,6 +201,26 @@ func providerQueryToQuery(idpQuery *admin_pb.ProviderQuery) (query.SearchQuery,
}
}
+func addGoogleProviderToCommand(req *admin_pb.AddGoogleProviderRequest) command.GoogleProvider {
+ return command.GoogleProvider{
+ Name: req.Name,
+ ClientID: req.ClientId,
+ ClientSecret: req.ClientSecret,
+ Scopes: req.Scopes,
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ }
+}
+
+func updateGoogleProviderToCommand(req *admin_pb.UpdateGoogleProviderRequest) command.GoogleProvider {
+ return command.GoogleProvider{
+ Name: req.Name,
+ ClientID: req.ClientId,
+ ClientSecret: req.ClientSecret,
+ Scopes: req.Scopes,
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ }
+}
+
func addLDAPProviderToCommand(req *admin_pb.AddLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{
Name: req.Name,
diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go
index 895a2ea97..9724703bf 100644
--- a/internal/api/grpc/idp/converter.go
+++ b/internal/api/grpc/idp/converter.go
@@ -402,6 +402,14 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
IsAutoUpdate: config.IsAutoUpdate,
},
}
+ if config.GoogleIDPTemplate != nil {
+ providerConfig.Config = &idp_pb.ProviderConfig_Google{
+ Google: &idp_pb.GoogleConfig{
+ ClientId: config.GoogleIDPTemplate.ClientID,
+ Scopes: config.GoogleIDPTemplate.Scopes,
+ },
+ }
+ }
if config.LDAPIDPTemplate != nil {
providerConfig.Config = &idp_pb.ProviderConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{
diff --git a/internal/api/grpc/management/idp.go b/internal/api/grpc/management/idp.go
index 8fb798a0b..0af131d54 100644
--- a/internal/api/grpc/management/idp.go
+++ b/internal/api/grpc/management/idp.go
@@ -166,6 +166,27 @@ func (s *Server) ListProviders(ctx context.Context, req *mgmt_pb.ListProvidersRe
}, 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 {
+ return nil, err
+ }
+ return &mgmt_pb.AddGoogleProviderResponse{
+ Id: id,
+ Details: object_pb.DomainToAddDetailsPb(details),
+ }, nil
+}
+
+func (s *Server) UpdateGoogleProvider(ctx context.Context, req *mgmt_pb.UpdateGoogleProviderRequest) (*mgmt_pb.UpdateGoogleProviderResponse, error) {
+ details, err := s.command.UpdateOrgGoogleProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateGoogleProviderToCommand(req))
+ if err != nil {
+ return nil, err
+ }
+ return &mgmt_pb.UpdateGoogleProviderResponse{
+ Details: object_pb.DomainToChangeDetailsPb(details),
+ }, nil
+}
+
func (s *Server) AddLDAPProvider(ctx context.Context, req *mgmt_pb.AddLDAPProviderRequest) (*mgmt_pb.AddLDAPProviderResponse, error) {
id, details, err := s.command.AddOrgLDAPProvider(ctx, authz.GetCtxData(ctx).OrgID, addLDAPProviderToCommand(req))
if err != nil {
diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go
index ba65ef33d..792f87684 100644
--- a/internal/api/grpc/management/idp_converter.go
+++ b/internal/api/grpc/management/idp_converter.go
@@ -218,6 +218,26 @@ func providerQueryToQuery(idpQuery *mgmt_pb.ProviderQuery) (query.SearchQuery, e
}
}
+func addGoogleProviderToCommand(req *mgmt_pb.AddGoogleProviderRequest) command.GoogleProvider {
+ return command.GoogleProvider{
+ Name: req.Name,
+ ClientID: req.ClientId,
+ ClientSecret: req.ClientSecret,
+ Scopes: req.Scopes,
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ }
+}
+
+func updateGoogleProviderToCommand(req *mgmt_pb.UpdateGoogleProviderRequest) command.GoogleProvider {
+ return command.GoogleProvider{
+ Name: req.Name,
+ ClientID: req.ClientId,
+ ClientSecret: req.ClientSecret,
+ Scopes: req.Scopes,
+ IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
+ }
+}
+
func addLDAPProviderToCommand(req *mgmt_pb.AddLDAPProviderRequest) command.LDAPProvider {
return command.LDAPProvider{
Name: req.Name,
diff --git a/internal/command/idp.go b/internal/command/idp.go
index 113bd3725..60f33ff5e 100644
--- a/internal/command/idp.go
+++ b/internal/command/idp.go
@@ -2,6 +2,14 @@ package command
import "github.com/zitadel/zitadel/internal/repository/idp"
+type GoogleProvider struct {
+ Name string
+ ClientID string
+ ClientSecret string
+ Scopes []string
+ IDPOptions idp.Options
+}
+
type LDAPProvider struct {
Name string
Host string
diff --git a/internal/command/idp_model.go b/internal/command/idp_model.go
index f21e45e6c..1431f1412 100644
--- a/internal/command/idp_model.go
+++ b/internal/command/idp_model.go
@@ -1,6 +1,8 @@
package command
import (
+ "reflect"
+
"github.com/zitadel/zitadel/internal/crypto"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -8,6 +10,93 @@ import (
"github.com/zitadel/zitadel/internal/repository/idpconfig"
)
+type GoogleIDPWriteModel struct {
+ eventstore.WriteModel
+
+ ID string
+ Name string
+ ClientID string
+ ClientSecret *crypto.CryptoValue
+ Scopes []string
+ idp.Options
+
+ State domain.IDPState
+}
+
+func (wm *GoogleIDPWriteModel) Reduce() error {
+ for _, event := range wm.Events {
+ switch e := event.(type) {
+ case *idp.GoogleIDPAddedEvent:
+ wm.reduceAddedEvent(e)
+ case *idp.GoogleIDPChangedEvent:
+ wm.reduceChangedEvent(e)
+ case *idp.RemovedEvent:
+ wm.State = domain.IDPStateRemoved
+ }
+ }
+ return wm.WriteModel.Reduce()
+}
+
+func (wm *GoogleIDPWriteModel) reduceAddedEvent(e *idp.GoogleIDPAddedEvent) {
+ wm.Name = e.Name
+ wm.ClientID = e.ClientID
+ wm.ClientSecret = e.ClientSecret
+ wm.Scopes = e.Scopes
+ wm.Options = e.Options
+ wm.State = domain.IDPStateActive
+}
+
+func (wm *GoogleIDPWriteModel) reduceChangedEvent(e *idp.GoogleIDPChangedEvent) {
+ if e.Name != nil {
+ wm.Name = *e.Name
+ }
+ if e.ClientID != nil {
+ wm.ClientID = *e.ClientID
+ }
+ if e.ClientSecret != nil {
+ wm.ClientSecret = e.ClientSecret
+ }
+ if e.Scopes != nil {
+ wm.Scopes = e.Scopes
+ }
+ wm.Options.ReduceChanges(e.OptionChanges)
+}
+
+func (wm *GoogleIDPWriteModel) NewChanges(
+ name string,
+ clientID string,
+ clientSecretString string,
+ secretCrypto crypto.Crypto,
+ scopes []string,
+ options idp.Options,
+) ([]idp.GoogleIDPChanges, error) {
+ changes := make([]idp.GoogleIDPChanges, 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.ChangeGoogleClientSecret(clientSecret))
+ }
+ if wm.Name != name {
+ changes = append(changes, idp.ChangeGoogleName(name))
+ }
+ if wm.ClientID != clientID {
+ changes = append(changes, idp.ChangeGoogleClientID(clientID))
+ }
+ if !reflect.DeepEqual(wm.Scopes, scopes) {
+ changes = append(changes, idp.ChangeGoogleScopes(scopes))
+ }
+
+ opts := wm.Options.Changes(options)
+ if !opts.IsZero() {
+ changes = append(changes, idp.ChangeGoogleOptions(opts))
+ }
+ return changes, nil
+}
+
type LDAPIDPWriteModel struct {
eventstore.WriteModel
@@ -34,7 +123,7 @@ func (wm *LDAPIDPWriteModel) Reduce() error {
if wm.ID != e.ID {
continue
}
- wm.reduceAddeddEvent(e)
+ wm.reduceAddedEvent(e)
case *idp.LDAPIDPChangedEvent:
if wm.ID != e.ID {
continue
@@ -50,7 +139,7 @@ func (wm *LDAPIDPWriteModel) Reduce() error {
return wm.WriteModel.Reduce()
}
-func (wm *LDAPIDPWriteModel) reduceAddeddEvent(e *idp.LDAPIDPAddedEvent) {
+func (wm *LDAPIDPWriteModel) reduceAddedEvent(e *idp.LDAPIDPAddedEvent) {
wm.Name = e.Name
wm.Host = e.Host
wm.Port = e.Port
@@ -170,6 +259,10 @@ type IDPRemoveWriteModel struct {
func (wm *IDPRemoveWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
+ case *idp.GoogleIDPAddedEvent:
+ wm.reduceAdded(e.ID, e.Name)
+ case *idp.GoogleIDPChangedEvent:
+ wm.reduceChanged(e.ID, e.Name)
case *idp.LDAPIDPAddedEvent:
wm.reduceAdded(e.ID, e.Name)
case *idp.LDAPIDPChangedEvent:
diff --git a/internal/command/instance_idp.go b/internal/command/instance_idp.go
index 60f696ce5..e970c5033 100644
--- a/internal/command/instance_idp.go
+++ b/internal/command/instance_idp.go
@@ -13,6 +13,40 @@ 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())
+ id, err := c.idGenerator.Next()
+ if err != nil {
+ return "", nil, err
+ }
+ cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceGoogleProvider(instanceAgg, id, 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) 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))
+ if err != nil {
+ return nil, err
+ }
+ if len(cmds) == 0 {
+ // no change, so return directly
+ return &domain.ObjectDetails{}, nil
+ }
+ pushedEvents, err := c.eventstore.Push(ctx, cmds...)
+ if err != nil {
+ return nil, err
+ }
+ return pushedEventsToObjectDetails(pushedEvents), nil
+}
+
func (c *Commands) AddInstanceLDAPProvider(ctx context.Context, provider LDAPProvider) (string, *domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
id, err := c.idGenerator.Next()
@@ -60,6 +94,87 @@ 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 {
+ return func() (preparation.CreateCommands, error) {
+ if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-D3fvs", "Errors.Invalid.Argument")
+ }
+ if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" {
+ return nil, caos_errs.ThrowInvalidArgument(nil, "INST-W2vqs", "Errors.Invalid.Argument")
+ }
+ return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
+ writeModel := NewGoogleInstanceIDPWriteModel(a.InstanceID, id)
+ 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.NewGoogleIDPAddedEvent(
+ ctx,
+ &a.Aggregate,
+ id,
+ provider.Name,
+ provider.ClientID,
+ secret,
+ provider.Scopes,
+ provider.IDPOptions,
+ ),
+ }, nil
+ }, nil
+ }
+}
+
+func (c *Commands) prepareUpdateInstanceGoogleProvider(a *instance.Aggregate, id string, provider GoogleProvider) preparation.Validation {
+ return func() (preparation.CreateCommands, error) {
+ if id = strings.TrimSpace(id); 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
+ }
+ 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,
+ id,
+ provider.Name,
+ provider.ClientID,
+ provider.ClientSecret,
+ c.idpConfigEncryption,
+ 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) prepareAddInstanceLDAPProvider(a *instance.Aggregate, id string, provider LDAPProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
diff --git a/internal/command/instance_idp_model.go b/internal/command/instance_idp_model.go
index 6108ef256..d9757c323 100644
--- a/internal/command/instance_idp_model.go
+++ b/internal/command/instance_idp_model.go
@@ -9,6 +9,80 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
)
+type InstanceGoogleIDPWriteModel struct {
+ GoogleIDPWriteModel
+}
+
+func NewGoogleInstanceIDPWriteModel(instanceID, id string) *InstanceGoogleIDPWriteModel {
+ return &InstanceGoogleIDPWriteModel{
+ GoogleIDPWriteModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: instanceID,
+ ResourceOwner: instanceID,
+ },
+ ID: id,
+ },
+ }
+}
+
+func (wm *InstanceGoogleIDPWriteModel) Reduce() error {
+ return wm.GoogleIDPWriteModel.Reduce()
+}
+
+func (wm *InstanceGoogleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
+ for _, event := range events {
+ switch e := event.(type) {
+ case *instance.GoogleIDPAddedEvent:
+ wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
+ case *instance.GoogleIDPChangedEvent:
+ wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPChangedEvent)
+ case *instance.IDPRemovedEvent:
+ wm.GoogleIDPWriteModel.AppendEvents(&e.RemovedEvent)
+ }
+ }
+}
+
+func (wm *InstanceGoogleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ ResourceOwner(wm.ResourceOwner).
+ AddQuery().
+ AggregateTypes(instance.AggregateType).
+ AggregateIDs(wm.AggregateID).
+ EventTypes(
+ instance.GoogleIDPAddedEventType,
+ instance.GoogleIDPChangedEventType,
+ instance.IDPRemovedEventType,
+ ).
+ EventData(map[string]interface{}{"id": wm.ID}).
+ Builder()
+}
+
+func (wm *InstanceGoogleIDPWriteModel) NewChangedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ id,
+ name,
+ clientID,
+ clientSecretString string,
+ secretCrypto crypto.Crypto,
+ scopes []string,
+ options idp.Options,
+) (*instance.GoogleIDPChangedEvent, error) {
+
+ changes, err := wm.GoogleIDPWriteModel.NewChanges(name, clientID, clientSecretString, secretCrypto, scopes, options)
+ if err != nil {
+ return nil, err
+ }
+ if len(changes) == 0 {
+ return nil, nil
+ }
+ changeEvent, err := instance.NewGoogleIDPChangedEvent(ctx, aggregate, id, changes)
+ if err != nil {
+ return nil, err
+ }
+ return changeEvent, nil
+}
+
type InstanceLDAPIDPWriteModel struct {
LDAPIDPWriteModel
}
@@ -136,6 +210,10 @@ func (wm *InstanceIDPRemoveWriteModel) Reduce() error {
func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
+ case *instance.GoogleIDPAddedEvent:
+ wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
+ case *instance.GoogleIDPChangedEvent:
+ wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPChangedEvent)
case *instance.LDAPIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
case *instance.LDAPIDPChangedEvent:
@@ -155,6 +233,8 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
AggregateTypes(instance.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
+ instance.GoogleIDPAddedEventType,
+ instance.GoogleIDPChangedEventType,
instance.LDAPIDPAddedEventType,
instance.LDAPIDPChangedEventType,
instance.IDPRemovedEventType,
diff --git a/internal/command/instance_idp_test.go b/internal/command/instance_idp_test.go
index 2eb526950..d1279e113 100644
--- a/internal/command/instance_idp_test.go
+++ b/internal/command/instance_idp_test.go
@@ -20,6 +20,363 @@ import (
"github.com/zitadel/zitadel/internal/repository/instance"
)
+func TestCommandSide_AddInstanceGoogleIDP(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ idGenerator id.Generator
+ secretCrypto crypto.EncryptionAlgorithm
+ }
+ type args struct {
+ ctx context.Context
+ provider GoogleProvider
+ }
+ type res struct {
+ id string
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ "invalid clientID",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ provider: GoogleProvider{},
+ },
+ 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: GoogleProvider{
+ ClientID: "clientID",
+ },
+ },
+ res{
+ err: caos_errors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "ok",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusherWithInstanceID(
+ "instance1",
+ instance.NewGoogleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ nil,
+ idp.Options{},
+ )),
+ },
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ },
+ args: args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ ClientSecret: "clientSecret",
+ },
+ },
+ 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.NewGoogleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ []string{"openid"},
+ idp.Options{
+ IsCreationAllowed: true,
+ IsLinkingAllowed: true,
+ IsAutoCreation: true,
+ IsAutoUpdate: true,
+ },
+ )),
+ },
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ },
+ args: args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ ClientSecret: "clientSecret",
+ Scopes: []string{"openid"},
+ 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.AddInstanceGoogleProvider(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_UpdateInstanceGoogleIDP(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ secretCrypto crypto.EncryptionAlgorithm
+ }
+ type args struct {
+ ctx context.Context
+ id string
+ provider GoogleProvider
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ "invalid id",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ provider: GoogleProvider{},
+ },
+ res{
+ err: caos_errors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ "invalid clientID",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ id: "id1",
+ provider: GoogleProvider{},
+ },
+ 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: GoogleProvider{
+ ClientID: "clientID",
+ },
+ },
+ res: res{
+ err: caos_errors.IsNotFound,
+ },
+ },
+ {
+ name: "no changes",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewGoogleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ nil,
+ idp.Options{},
+ )),
+ ),
+ ),
+ },
+ args: args{
+ ctx: authz.WithInstanceID(context.Background(), "instance1"),
+ id: "id1",
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{},
+ },
+ },
+ {
+ name: "change ok",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(
+ eventFromEventPusher(
+ instance.NewGoogleIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ nil,
+ idp.Options{},
+ )),
+ ),
+ expectPush(
+ []*repository.Event{
+ eventFromEventPusherWithInstanceID(
+ "instance1",
+ func() eventstore.Command {
+ t := true
+ event, _ := instance.NewGoogleIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
+ "id1",
+ []idp.GoogleIDPChanges{
+ idp.ChangeGoogleClientID("clientID2"),
+ idp.ChangeGoogleClientSecret(&crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("newSecret"),
+ }),
+ idp.ChangeGoogleScopes([]string{"openid", "profile"}),
+ idp.ChangeGoogleOptions(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: GoogleProvider{
+ ClientID: "clientID2",
+ ClientSecret: "newSecret",
+ 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.UpdateInstanceGoogleProvider(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_AddInstanceLDAPIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
diff --git a/internal/command/org_idp.go b/internal/command/org_idp.go
index 2935b5731..97b95b33c 100644
--- a/internal/command/org_idp.go
+++ b/internal/command/org_idp.go
@@ -12,6 +12,40 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
+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))
+ 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) 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))
+ if err != nil {
+ return nil, err
+ }
+ if len(cmds) == 0 {
+ // no change, so return directly
+ return &domain.ObjectDetails{}, nil
+ }
+ pushedEvents, err := c.eventstore.Push(ctx, cmds...)
+ if err != nil {
+ return nil, err
+ }
+ return pushedEventsToObjectDetails(pushedEvents), nil
+}
+
func (c *Commands) AddOrgLDAPProvider(ctx context.Context, resourceOwner string, provider LDAPProvider) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
id, err := c.idGenerator.Next()
@@ -59,6 +93,78 @@ 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 {
+ 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) {
+ writeModel := NewGoogleOrgIDPWriteModel(resourceOwner, id)
+ 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, id, provider.Name, provider.ClientID, secret, provider.Scopes, provider.IDPOptions),
+ }, nil
+ }, nil
+ }
+}
+
+func (c *Commands) prepareUpdateOrgGoogleProvider(a *org.Aggregate, resourceOwner, id string, provider GoogleProvider) preparation.Validation {
+ return func() (preparation.CreateCommands, error) {
+ if id = strings.TrimSpace(id); 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
+ }
+ writeModel.AppendEvents(events...)
+ if err = writeModel.Reduce(); err != nil {
+ return nil, err
+ }
+ if !writeModel.State.Exists() {
+ return nil, caos_errs.ThrowNotFound(nil, "ORG-Dqrg1", "Errors.Org.IDPConfig.NotExisting")
+ }
+ event, err := writeModel.NewChangedEvent(
+ ctx,
+ &a.Aggregate,
+ id,
+ provider.Name,
+ provider.ClientID,
+ provider.ClientSecret,
+ c.idpConfigEncryption,
+ 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) prepareAddOrgLDAPProvider(a *org.Aggregate, resourceOwner, id string, provider LDAPProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
diff --git a/internal/command/org_idp_model.go b/internal/command/org_idp_model.go
index 4ae982de1..e9b0d5077 100644
--- a/internal/command/org_idp_model.go
+++ b/internal/command/org_idp_model.go
@@ -9,6 +9,82 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
+type OrgGoogleIDPWriteModel struct {
+ GoogleIDPWriteModel
+}
+
+func NewGoogleOrgIDPWriteModel(orgID, id string) *OrgGoogleIDPWriteModel {
+ return &OrgGoogleIDPWriteModel{
+ GoogleIDPWriteModel{
+ WriteModel: eventstore.WriteModel{
+ AggregateID: orgID,
+ ResourceOwner: orgID,
+ },
+ ID: id,
+ },
+ }
+}
+
+func (wm *OrgGoogleIDPWriteModel) Reduce() error {
+ return wm.GoogleIDPWriteModel.Reduce()
+}
+
+func (wm *OrgGoogleIDPWriteModel) AppendEvents(events ...eventstore.Event) {
+ for _, event := range events {
+ switch e := event.(type) {
+ case *org.GoogleIDPAddedEvent:
+ wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
+ case *org.GoogleIDPChangedEvent:
+ wm.GoogleIDPWriteModel.AppendEvents(&e.GoogleIDPChangedEvent)
+ case *org.IDPRemovedEvent:
+ wm.GoogleIDPWriteModel.AppendEvents(&e.RemovedEvent)
+ default:
+ wm.GoogleIDPWriteModel.AppendEvents(e)
+ }
+ }
+}
+
+func (wm *OrgGoogleIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
+ return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
+ ResourceOwner(wm.ResourceOwner).
+ AddQuery().
+ AggregateTypes(org.AggregateType).
+ AggregateIDs(wm.AggregateID).
+ EventTypes(
+ org.GoogleIDPAddedEventType,
+ org.GoogleIDPChangedEventType,
+ org.IDPRemovedEventType,
+ ).
+ EventData(map[string]interface{}{"id": wm.ID}).
+ Builder()
+}
+
+func (wm *OrgGoogleIDPWriteModel) NewChangedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ id,
+ name,
+ clientID,
+ clientSecretString string,
+ secretCrypto crypto.Crypto,
+ scopes []string,
+ options idp.Options,
+) (*org.GoogleIDPChangedEvent, error) {
+
+ changes, err := wm.GoogleIDPWriteModel.NewChanges(name, clientID, clientSecretString, secretCrypto, scopes, options)
+ if err != nil {
+ return nil, err
+ }
+ if len(changes) == 0 {
+ return nil, nil
+ }
+ changeEvent, err := org.NewGoogleIDPChangedEvent(ctx, aggregate, id, changes)
+ if err != nil {
+ return nil, err
+ }
+ return changeEvent, nil
+}
+
type OrgLDAPIDPWriteModel struct {
LDAPIDPWriteModel
}
@@ -136,6 +212,10 @@ func (wm *OrgIDPRemoveWriteModel) Reduce() error {
func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
+ case *org.GoogleIDPAddedEvent:
+ wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPAddedEvent)
+ case *org.GoogleIDPChangedEvent:
+ wm.IDPRemoveWriteModel.AppendEvents(&e.GoogleIDPChangedEvent)
case *org.LDAPIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.LDAPIDPAddedEvent)
case *org.LDAPIDPChangedEvent:
@@ -155,6 +235,8 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
AggregateTypes(org.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
+ org.GoogleIDPAddedEventType,
+ org.GoogleIDPChangedEventType,
org.LDAPIDPAddedEventType,
org.LDAPIDPChangedEventType,
org.IDPRemovedEventType,
diff --git a/internal/command/org_idp_test.go b/internal/command/org_idp_test.go
index e8e1bfc88..b3bc1c4a7 100644
--- a/internal/command/org_idp_test.go
+++ b/internal/command/org_idp_test.go
@@ -18,6 +18,365 @@ import (
"github.com/zitadel/zitadel/internal/repository/org"
)
+func TestCommandSide_AddOrgGoogleIDP(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ idGenerator id.Generator
+ secretCrypto crypto.EncryptionAlgorithm
+ }
+ type args struct {
+ ctx context.Context
+ resourceOwner string
+ provider GoogleProvider
+ }
+ type res struct {
+ id string
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ "invalid clientID",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: GoogleProvider{},
+ },
+ res{
+ err: caos_errors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ "invalid clientSecret",
+ fields{
+ eventstore: eventstoreExpect(t),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ },
+ },
+ res{
+ err: caos_errors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "ok",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(),
+ expectPush(
+ eventPusherToEvents(
+ org.NewGoogleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ nil,
+ idp.Options{},
+ )),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ ClientSecret: "clientSecret",
+ },
+ },
+ res: res{
+ id: "id1",
+ want: &domain.ObjectDetails{ResourceOwner: "org1"},
+ },
+ },
+ {
+ name: "ok all set",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(),
+ expectPush(
+ eventPusherToEvents(
+ org.NewGoogleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ []string{"openid"},
+ idp.Options{
+ IsCreationAllowed: true,
+ IsLinkingAllowed: true,
+ IsAutoCreation: true,
+ IsAutoUpdate: true,
+ },
+ )),
+ ),
+ ),
+ idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
+ secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ ClientSecret: "clientSecret",
+ Scopes: []string{"openid"},
+ 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.AddOrgGoogleProvider(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_UpdateOrgGoogleIDP(t *testing.T) {
+ type fields struct {
+ eventstore *eventstore.Eventstore
+ secretCrypto crypto.EncryptionAlgorithm
+ }
+ type args struct {
+ ctx context.Context
+ resourceOwner string
+ id string
+ provider GoogleProvider
+ }
+ type res struct {
+ want *domain.ObjectDetails
+ err func(error) bool
+ }
+ tests := []struct {
+ name string
+ fields fields
+ args args
+ res res
+ }{
+ {
+ "invalid id",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ provider: GoogleProvider{},
+ },
+ res{
+ err: caos_errors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ "invalid clientID",
+ fields{
+ eventstore: eventstoreExpect(t),
+ },
+ args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ id: "id1",
+ provider: GoogleProvider{},
+ },
+ res{
+ err: caos_errors.IsErrorInvalidArgument,
+ },
+ },
+ {
+ name: "not found",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ id: "id1",
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ },
+ },
+ res: res{
+ err: caos_errors.IsNotFound,
+ },
+ },
+ {
+ name: "no changes",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(
+ eventFromEventPusher(
+ org.NewGoogleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ nil,
+ idp.Options{},
+ )),
+ ),
+ ),
+ },
+ args: args{
+ ctx: context.Background(),
+ resourceOwner: "org1",
+ id: "id1",
+ provider: GoogleProvider{
+ ClientID: "clientID",
+ },
+ },
+ res: res{
+ want: &domain.ObjectDetails{},
+ },
+ },
+ {
+ name: "change ok",
+ fields: fields{
+ eventstore: eventstoreExpect(t,
+ expectFilter(
+ eventFromEventPusher(
+ org.NewGoogleIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
+ "id1",
+ "",
+ "clientID",
+ &crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("clientSecret"),
+ },
+ nil,
+ idp.Options{},
+ )),
+ ),
+ expectPush(
+ eventPusherToEvents(
+ func() eventstore.Command {
+ t := true
+ event, _ := org.NewGoogleIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
+ "id1",
+ []idp.GoogleIDPChanges{
+ idp.ChangeGoogleClientID("clientID2"),
+ idp.ChangeGoogleClientSecret(&crypto.CryptoValue{
+ CryptoType: crypto.TypeEncryption,
+ Algorithm: "enc",
+ KeyID: "id",
+ Crypted: []byte("newSecret"),
+ }),
+ idp.ChangeGoogleScopes([]string{"openid", "profile"}),
+ idp.ChangeGoogleOptions(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: GoogleProvider{
+ ClientID: "clientID2",
+ ClientSecret: "newSecret",
+ 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.UpdateOrgGoogleProvider(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_AddOrgLDAPIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
diff --git a/internal/query/idp_template.go b/internal/query/idp_template.go
index 5340ae7ab..d99b8a8bd 100644
--- a/internal/query/idp_template.go
+++ b/internal/query/idp_template.go
@@ -11,6 +11,7 @@ import (
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/crypto"
+ "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/query/projection"
@@ -32,6 +33,7 @@ type IDPTemplate struct {
IsLinkingAllowed bool
IsAutoCreation bool
IsAutoUpdate bool
+ *GoogleIDPTemplate
*LDAPIDPTemplate
}
@@ -40,6 +42,13 @@ type IDPTemplates struct {
Templates []*IDPTemplate
}
+type GoogleIDPTemplate struct {
+ IDPID string
+ ClientID string
+ ClientSecret *crypto.CryptoValue
+ Scopes database.StringArray
+}
+
type LDAPIDPTemplate struct {
IDPID string
Host string
@@ -51,7 +60,6 @@ type LDAPIDPTemplate struct {
Admin string
Password *crypto.CryptoValue
idp.LDAPAttributes
- idp.Options
}
var (
@@ -121,6 +129,33 @@ var (
}
)
+var (
+ googleIdpTemplateTable = table{
+ name: projection.IDPTemplateGoogleTable,
+ instanceIDCol: projection.GoogleInstanceIDCol,
+ }
+ GoogleIDCol = Column{
+ name: projection.GoogleIDCol,
+ table: googleIdpTemplateTable,
+ }
+ GoogleInstanceIDCol = Column{
+ name: projection.GoogleInstanceIDCol,
+ table: googleIdpTemplateTable,
+ }
+ GoogleClientIDCol = Column{
+ name: projection.GoogleClientIDCol,
+ table: googleIdpTemplateTable,
+ }
+ GoogleClientSecretCol = Column{
+ name: projection.GoogleClientSecretCol,
+ table: googleIdpTemplateTable,
+ }
+ GoogleScopesCol = Column{
+ name: projection.GoogleScopesCol,
+ table: googleIdpTemplateTable,
+ }
+)
+
var (
ldapIdpTemplateTable = table{
name: projection.IDPTemplateLDAPTable,
@@ -335,6 +370,10 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
IDPTemplateIsLinkingAllowedCol.identifier(),
IDPTemplateIsAutoCreationCol.identifier(),
IDPTemplateIsAutoUpdateCol.identifier(),
+ GoogleIDCol.identifier(),
+ GoogleClientIDCol.identifier(),
+ GoogleClientSecretCol.identifier(),
+ GoogleScopesCol.identifier(),
LDAPIDCol.identifier(),
LDAPHostCol.identifier(),
LDAPPortCol.identifier(),
@@ -358,11 +397,19 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
LDAPAvatarURLAttributeCol.identifier(),
LDAPProfileAttributeCol.identifier(),
).From(idpTemplateTable.identifier()).
+ LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*IDPTemplate, error) {
idpTemplate := new(IDPTemplate)
+ name := sql.NullString{}
+
+ googleID := sql.NullString{}
+ googleClientID := sql.NullString{}
+ googleClientSecret := new(crypto.CryptoValue)
+ googleScopes := database.StringArray{}
+
ldapID := sql.NullString{}
ldapHost := sql.NullString{}
ldapPort := sql.NullString{}
@@ -393,13 +440,17 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
&idpTemplate.ChangeDate,
&idpTemplate.Sequence,
&idpTemplate.State,
- &idpTemplate.Name,
+ &name,
&idpTemplate.Type,
&idpTemplate.OwnerType,
&idpTemplate.IsCreationAllowed,
&idpTemplate.IsLinkingAllowed,
&idpTemplate.IsAutoCreation,
&idpTemplate.IsAutoUpdate,
+ &googleID,
+ &googleClientID,
+ &googleClientSecret,
+ &googleScopes,
&ldapID,
&ldapHost,
&ldapPort,
@@ -430,7 +481,16 @@ func prepareIDPTemplateByIDQuery() (sq.SelectBuilder, func(*sql.Row) (*IDPTempla
return nil, errors.ThrowInternal(err, "QUERY-ADG42", "Errors.Internal")
}
- if ldapID.Valid {
+ idpTemplate.Name = name.String
+
+ if googleID.Valid {
+ idpTemplate.GoogleIDPTemplate = &GoogleIDPTemplate{
+ IDPID: googleID.String,
+ ClientID: googleClientID.String,
+ ClientSecret: googleClientSecret,
+ Scopes: googleScopes,
+ }
+ } else if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
IDPID: ldapID.String,
Host: ldapHost.String,
@@ -478,6 +538,10 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
IDPTemplateIsLinkingAllowedCol.identifier(),
IDPTemplateIsAutoCreationCol.identifier(),
IDPTemplateIsAutoUpdateCol.identifier(),
+ GoogleIDCol.identifier(),
+ GoogleClientIDCol.identifier(),
+ GoogleClientSecretCol.identifier(),
+ GoogleScopesCol.identifier(),
LDAPIDCol.identifier(),
LDAPHostCol.identifier(),
LDAPPortCol.identifier(),
@@ -502,6 +566,7 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
LDAPProfileAttributeCol.identifier(),
countColumn.identifier(),
).From(idpTemplateTable.identifier()).
+ LeftJoin(join(GoogleIDCol, IDPTemplateIDCol)).
LeftJoin(join(LDAPIDCol, IDPTemplateIDCol)).
PlaceholderFormat(sq.Dollar),
func(rows *sql.Rows) (*IDPTemplates, error) {
@@ -510,6 +575,13 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
for rows.Next() {
idpTemplate := new(IDPTemplate)
+ name := sql.NullString{}
+
+ googleID := sql.NullString{}
+ googleClientID := sql.NullString{}
+ googleClientSecret := new(crypto.CryptoValue)
+ googleScopes := database.StringArray{}
+
ldapID := sql.NullString{}
ldapHost := sql.NullString{}
ldapPort := sql.NullString{}
@@ -540,13 +612,17 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
&idpTemplate.ChangeDate,
&idpTemplate.Sequence,
&idpTemplate.State,
- &idpTemplate.Name,
+ &name,
&idpTemplate.Type,
&idpTemplate.OwnerType,
&idpTemplate.IsCreationAllowed,
&idpTemplate.IsLinkingAllowed,
&idpTemplate.IsAutoCreation,
&idpTemplate.IsAutoUpdate,
+ &googleID,
+ &googleClientID,
+ &googleClientSecret,
+ &googleScopes,
&ldapID,
&ldapHost,
&ldapPort,
@@ -576,7 +652,16 @@ func prepareIDPTemplatesQuery() (sq.SelectBuilder, func(*sql.Rows) (*IDPTemplate
return nil, err
}
- if ldapID.Valid {
+ idpTemplate.Name = name.String
+
+ if googleID.Valid {
+ idpTemplate.GoogleIDPTemplate = &GoogleIDPTemplate{
+ IDPID: googleID.String,
+ ClientID: googleClientID.String,
+ ClientSecret: googleClientSecret,
+ Scopes: googleScopes,
+ }
+ } else if ldapID.Valid {
idpTemplate.LDAPIDPTemplate = &LDAPIDPTemplate{
IDPID: ldapID.String,
Host: ldapHost.String,
diff --git a/internal/query/idp_template_test.go b/internal/query/idp_template_test.go
index d164fbcbe..e1a4167c1 100644
--- a/internal/query/idp_template_test.go
+++ b/internal/query/idp_template_test.go
@@ -8,6 +8,7 @@ import (
"regexp"
"testing"
+ "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
errs "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/repository/idp"
@@ -27,6 +28,10 @@ var (
` projections.idp_templates.is_linking_allowed,` +
` projections.idp_templates.is_auto_creation,` +
` projections.idp_templates.is_auto_update,` +
+ ` projections.idp_templates_google.idp_id,` +
+ ` projections.idp_templates_google.client_id,` +
+ ` projections.idp_templates_google.client_secret,` +
+ ` projections.idp_templates_google.scopes,` +
` projections.idp_templates_ldap.idp_id,` +
` projections.idp_templates_ldap.host,` +
` projections.idp_templates_ldap.port,` +
@@ -50,6 +55,7 @@ var (
` projections.idp_templates_ldap.avatar_url_attribute,` +
` projections.idp_templates_ldap.profile_attribute` +
` FROM projections.idp_templates` +
+ ` 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{
"id",
@@ -65,6 +71,11 @@ var (
"is_linking_allowed",
"is_auto_creation",
"is_auto_update",
+ // google config
+ "idp_id",
+ "client_id",
+ "client_secret",
+ "scopes",
// ldap config
"idp_id",
"host",
@@ -102,6 +113,10 @@ var (
` projections.idp_templates.is_linking_allowed,` +
` projections.idp_templates.is_auto_creation,` +
` projections.idp_templates.is_auto_update,` +
+ ` projections.idp_templates_google.idp_id,` +
+ ` projections.idp_templates_google.client_id,` +
+ ` projections.idp_templates_google.client_secret,` +
+ ` projections.idp_templates_google.scopes,` +
` projections.idp_templates_ldap.idp_id,` +
` projections.idp_templates_ldap.host,` +
` projections.idp_templates_ldap.port,` +
@@ -126,6 +141,7 @@ var (
` projections.idp_templates_ldap.profile_attribute,` +
` COUNT(*) OVER ()` +
` FROM projections.idp_templates` +
+ ` 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{
"id",
@@ -141,6 +157,12 @@ var (
"is_linking_allowed",
"is_auto_creation",
"is_auto_update",
+ // google config
+ "idp_id",
+ "client_id",
+ "client_secret",
+ "scopes",
+ // ldap config
"idp_id",
"host",
"port",
@@ -196,6 +218,81 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
},
object: (*IDPTemplate)(nil),
},
+
+ {
+ name: "prepareIDPTemplateByIDQuery google 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.IDPTypeGoogle,
+ domain.IdentityProviderTypeOrg,
+ true,
+ true,
+ true,
+ true,
+ // google
+ "idp-id",
+ "client_id",
+ nil,
+ database.StringArray{"profile"},
+ // 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.IDPTypeGoogle,
+ OwnerType: domain.IdentityProviderTypeOrg,
+ IsCreationAllowed: true,
+ IsLinkingAllowed: true,
+ IsAutoCreation: true,
+ IsAutoUpdate: true,
+ GoogleIDPTemplate: &GoogleIDPTemplate{
+ IDPID: "idp-id",
+ ClientID: "client_id",
+ ClientSecret: nil,
+ Scopes: []string{"profile"},
+ },
+ },
+ },
{
name: "prepareIDPTemplateByIDQuery ldap idp",
prepare: prepareIDPTemplateByIDQuery,
@@ -217,6 +314,11 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
+ // google
+ nil,
+ nil,
+ nil,
+ nil,
// ldap config
"idp-id",
"host",
@@ -305,6 +407,11 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
+ // google config
+ nil,
+ nil,
+ nil,
+ nil,
// ldap config
nil,
nil,
@@ -404,6 +511,11 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
+ // google config
+ nil,
+ nil,
+ nil,
+ nil,
// ldap config
"idp-id",
"host",
@@ -501,6 +613,11 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
+ // google config
+ nil,
+ nil,
+ nil,
+ nil,
// ldap config
nil,
nil,
@@ -560,7 +677,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
idpTemplatesCols,
[][]driver.Value{
{
- "idp-id-1",
+ "idp-id-ldap",
"ro",
testNow,
testNow,
@@ -573,8 +690,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
true,
true,
true,
+ // google config
+ nil,
+ nil,
+ nil,
+ nil,
// ldap config
- "idp-id",
+ "idp-id-ldap",
"host",
"port",
true,
@@ -598,19 +720,24 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"profile",
},
{
- "idp-id-2",
+ "idp-id-google",
"ro",
testNow,
testNow,
uint64(20211109),
domain.IDPConfigStateActive,
"idp-name",
- domain.IDPTypeLDAP,
+ domain.IDPTypeGoogle,
domain.IdentityProviderTypeOrg,
true,
true,
true,
true,
+ // google
+ "idp-id-google",
+ "client_id",
+ nil,
+ database.StringArray{"profile"},
// ldap config
nil,
nil,
@@ -648,7 +775,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
ChangeDate: testNow,
Sequence: 20211109,
ResourceOwner: "ro",
- ID: "idp-id-1",
+ ID: "idp-id-ldap",
State: domain.IDPStateActive,
Name: "idp-name",
Type: domain.IDPTypeLDAP,
@@ -658,7 +785,7 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
IsAutoCreation: true,
IsAutoUpdate: true,
LDAPIDPTemplate: &LDAPIDPTemplate{
- IDPID: "idp-id",
+ IDPID: "idp-id-ldap",
Host: "host",
Port: "port",
TLS: true,
@@ -688,15 +815,21 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
ChangeDate: testNow,
Sequence: 20211109,
ResourceOwner: "ro",
- ID: "idp-id-2",
+ ID: "idp-id-google",
State: domain.IDPStateActive,
Name: "idp-name",
- Type: domain.IDPTypeLDAP,
+ Type: domain.IDPTypeGoogle,
OwnerType: domain.IdentityProviderTypeOrg,
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
+ GoogleIDPTemplate: &GoogleIDPTemplate{
+ IDPID: "idp-id-google",
+ ClientID: "client_id",
+ ClientSecret: nil,
+ Scopes: []string{"profile"},
+ },
},
},
},
diff --git a/internal/query/projection/idp_template.go b/internal/query/projection/idp_template.go
index fa25113a2..1c7c3750a 100644
--- a/internal/query/projection/idp_template.go
+++ b/internal/query/projection/idp_template.go
@@ -2,7 +2,9 @@ package projection
import (
"context"
+ "time"
+ "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -14,10 +16,12 @@ import (
)
const (
- IDPTemplateTable = "projections.idp_templates"
- IDPTemplateLDAPTable = IDPTemplateTable + "_" + IDPTemplateLDAPSuffix
+ IDPTemplateTable = "projections.idp_templates"
+ IDPTemplateGoogleTable = IDPTemplateTable + "_" + IDPTemplateGoogleSuffix
+ IDPTemplateLDAPTable = IDPTemplateTable + "_" + IDPTemplateLDAPSuffix
- IDPTemplateLDAPSuffix = "ldap"
+ IDPTemplateGoogleSuffix = "google"
+ IDPTemplateLDAPSuffix = "ldap"
IDPTemplateIDCol = "id"
IDPTemplateCreationDateCol = "creation_date"
@@ -35,6 +39,12 @@ const (
IDPTemplateIsAutoCreationCol = "is_auto_creation"
IDPTemplateIsAutoUpdateCol = "is_auto_update"
+ GoogleIDCol = "idp_id"
+ GoogleInstanceIDCol = "instance_id"
+ GoogleClientIDCol = "client_id"
+ GoogleClientSecretCol = "client_secret"
+ GoogleScopesCol = "scopes"
+
LDAPIDCol = "idp_id"
LDAPInstanceIDCol = "instance_id"
LDAPHostCol = "host"
@@ -90,6 +100,17 @@ 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(GoogleIDCol, crdb.ColumnTypeText),
+ crdb.NewColumn(GoogleInstanceIDCol, crdb.ColumnTypeText),
+ crdb.NewColumn(GoogleClientIDCol, crdb.ColumnTypeText),
+ crdb.NewColumn(GoogleClientSecretCol, crdb.ColumnTypeJSONB),
+ crdb.NewColumn(GoogleScopesCol, crdb.ColumnTypeTextArray, crdb.Nullable()),
+ },
+ crdb.NewPrimaryKey(GoogleInstanceIDCol, GoogleIDCol),
+ IDPTemplateGoogleSuffix,
+ crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
+ ),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(LDAPIDCol, crdb.ColumnTypeText),
crdb.NewColumn(LDAPInstanceIDCol, crdb.ColumnTypeText),
@@ -129,6 +150,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
{
Aggregate: instance.AggregateType,
EventRedusers: []handler.EventReducer{
+ {
+ Event: instance.GoogleIDPAddedEventType,
+ Reduce: p.reduceGoogleIDPAdded,
+ },
+ {
+ Event: instance.GoogleIDPChangedEventType,
+ Reduce: p.reduceGoogleIDPChanged,
+ },
{
Event: instance.LDAPIDPAddedEventType,
Reduce: p.reduceLDAPIDPAdded,
@@ -150,6 +179,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
{
Aggregate: org.AggregateType,
EventRedusers: []handler.EventReducer{
+ {
+ Event: org.GoogleIDPAddedEventType,
+ Reduce: p.reduceGoogleIDPAdded,
+ },
+ {
+ Event: org.GoogleIDPChangedEventType,
+ Reduce: p.reduceGoogleIDPChanged,
+ },
{
Event: org.LDAPIDPAddedEventType,
Reduce: p.reduceLDAPIDPAdded,
@@ -171,6 +208,94 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
}
}
+func (p *idpTemplateProjection) reduceGoogleIDPAdded(event eventstore.Event) (*handler.Statement, error) {
+ var idpEvent idp.GoogleIDPAddedEvent
+ var idpOwnerType domain.IdentityProviderType
+ switch e := event.(type) {
+ case *org.GoogleIDPAddedEvent:
+ idpEvent = e.GoogleIDPAddedEvent
+ idpOwnerType = domain.IdentityProviderTypeOrg
+ case *instance.GoogleIDPAddedEvent:
+ idpEvent = e.GoogleIDPAddedEvent
+ idpOwnerType = domain.IdentityProviderTypeSystem
+ default:
+ return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-ap9ihb", "reduce.wrong.event.type %v", []eventstore.EventType{org.GoogleIDPAddedEventType, instance.GoogleIDPAddedEventType})
+ }
+
+ 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.IDPTypeGoogle),
+ 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(GoogleIDCol, idpEvent.ID),
+ handler.NewCol(GoogleInstanceIDCol, idpEvent.Aggregate().InstanceID),
+ handler.NewCol(GoogleClientIDCol, idpEvent.ClientID),
+ handler.NewCol(GoogleClientSecretCol, idpEvent.ClientSecret),
+ handler.NewCol(GoogleScopesCol, database.StringArray(idpEvent.Scopes)),
+ },
+ crdb.WithTableSuffix(IDPTemplateGoogleSuffix),
+ ),
+ ), nil
+}
+
+func (p *idpTemplateProjection) reduceGoogleIDPChanged(event eventstore.Event) (*handler.Statement, error) {
+ var idpEvent idp.GoogleIDPChangedEvent
+ switch e := event.(type) {
+ case *org.GoogleIDPChangedEvent:
+ idpEvent = e.GoogleIDPChangedEvent
+ case *instance.GoogleIDPChangedEvent:
+ idpEvent = e.GoogleIDPChangedEvent
+ default:
+ return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.GoogleIDPChangedEventType, instance.GoogleIDPChangedEventType})
+ }
+
+ 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),
+ },
+ ),
+ )
+ googleCols := reduceGoogleIDPChangedColumns(idpEvent)
+ if len(googleCols) > 0 {
+ ops = append(ops,
+ crdb.AddUpdateStatement(
+ googleCols,
+ []handler.Condition{
+ handler.NewCond(GoogleIDCol, idpEvent.ID),
+ handler.NewCond(GoogleInstanceIDCol, idpEvent.Aggregate().InstanceID),
+ },
+ crdb.WithTableSuffix(IDPTemplateGoogleSuffix),
+ ),
+ )
+ }
+
+ return crdb.NewMultiStatement(
+ &idpEvent,
+ ops...,
+ ), nil
+}
+
func (p *idpTemplateProjection) reduceLDAPIDPAdded(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.LDAPIDPAddedEvent
var idpOwnerType domain.IdentityProviderType
@@ -247,13 +372,10 @@ func (p *idpTemplateProjection) reduceLDAPIDPChanged(event eventstore.Event) (*h
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.LDAPIDPChangedEventType, instance.LDAPIDPChangedEventType})
}
- cols := reduceLDAPIDPChangedTemplateColumns(idpEvent)
- ldapCols := reduceLDAPIDPChangedLDAPColumns(idpEvent)
-
ops := make([]func(eventstore.Event) crdb.Exec, 0, 2)
ops = append(ops,
crdb.AddUpdateStatement(
- cols,
+ reduceIDPChangedTemplateColumns(idpEvent.Name, idpEvent.CreationDate(), idpEvent.Sequence(), idpEvent.OptionChanges),
[]handler.Condition{
handler.NewCond(IDPTemplateIDCol, idpEvent.ID),
handler.NewCond(IDPTemplateInstanceIDCol, idpEvent.Aggregate().InstanceID),
@@ -261,6 +383,7 @@ func (p *idpTemplateProjection) reduceLDAPIDPChanged(event eventstore.Event) (*h
),
)
+ ldapCols := reduceLDAPIDPChangedColumns(idpEvent)
if len(ldapCols) > 0 {
ops = append(ops,
crdb.AddUpdateStatement(
@@ -320,30 +443,44 @@ func (p *idpTemplateProjection) reduceOwnerRemoved(event eventstore.Event) (*han
), nil
}
-func reduceLDAPIDPChangedTemplateColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column {
+func reduceIDPChangedTemplateColumns(name *string, creationDate time.Time, sequence uint64, optionChanges idp.OptionChanges) []handler.Column {
cols := make([]handler.Column, 0, 7)
- if idpEvent.Name != nil {
- cols = append(cols, handler.NewCol(IDPTemplateNameCol, *idpEvent.Name))
+ if name != nil {
+ cols = append(cols, handler.NewCol(IDPTemplateNameCol, *name))
}
- if idpEvent.IsCreationAllowed != nil {
- cols = append(cols, handler.NewCol(IDPTemplateIsCreationAllowedCol, *idpEvent.IsCreationAllowed))
+ if optionChanges.IsCreationAllowed != nil {
+ cols = append(cols, handler.NewCol(IDPTemplateIsCreationAllowedCol, *optionChanges.IsCreationAllowed))
}
- if idpEvent.IsLinkingAllowed != nil {
- cols = append(cols, handler.NewCol(IDPTemplateIsLinkingAllowedCol, *idpEvent.IsLinkingAllowed))
+ if optionChanges.IsLinkingAllowed != nil {
+ cols = append(cols, handler.NewCol(IDPTemplateIsLinkingAllowedCol, *optionChanges.IsLinkingAllowed))
}
- if idpEvent.IsAutoCreation != nil {
- cols = append(cols, handler.NewCol(IDPTemplateIsAutoCreationCol, *idpEvent.IsAutoCreation))
+ if optionChanges.IsAutoCreation != nil {
+ cols = append(cols, handler.NewCol(IDPTemplateIsAutoCreationCol, *optionChanges.IsAutoCreation))
}
- if idpEvent.IsAutoUpdate != nil {
- cols = append(cols, handler.NewCol(IDPTemplateIsAutoUpdateCol, *idpEvent.IsAutoUpdate))
+ if optionChanges.IsAutoUpdate != nil {
+ cols = append(cols, handler.NewCol(IDPTemplateIsAutoUpdateCol, *optionChanges.IsAutoUpdate))
}
return append(cols,
- handler.NewCol(IDPTemplateChangeDateCol, idpEvent.CreationDate()),
- handler.NewCol(IDPTemplateSequenceCol, idpEvent.Sequence()),
+ handler.NewCol(IDPTemplateChangeDateCol, creationDate),
+ handler.NewCol(IDPTemplateSequenceCol, sequence),
)
}
-func reduceLDAPIDPChangedLDAPColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column {
+func reduceGoogleIDPChangedColumns(idpEvent idp.GoogleIDPChangedEvent) []handler.Column {
+ googleCols := make([]handler.Column, 0, 3)
+ if idpEvent.ClientID != nil {
+ googleCols = append(googleCols, handler.NewCol(GoogleClientIDCol, *idpEvent.ClientID))
+ }
+ if idpEvent.ClientSecret != nil {
+ googleCols = append(googleCols, handler.NewCol(GoogleClientSecretCol, *idpEvent.ClientSecret))
+ }
+ if idpEvent.Scopes != nil {
+ googleCols = append(googleCols, handler.NewCol(GoogleScopesCol, database.StringArray(idpEvent.Scopes)))
+ }
+ return googleCols
+}
+
+func reduceLDAPIDPChangedColumns(idpEvent idp.LDAPIDPChangedEvent) []handler.Column {
ldapCols := make([]handler.Column, 0, 4)
if idpEvent.Host != nil {
ldapCols = append(ldapCols, handler.NewCol(LDAPHostCol, *idpEvent.Host))
diff --git a/internal/query/projection/idp_template_test.go b/internal/query/projection/idp_template_test.go
index ed67f0ec2..7de4bf9a2 100644
--- a/internal/query/projection/idp_template_test.go
+++ b/internal/query/projection/idp_template_test.go
@@ -3,6 +3,7 @@ package projection
import (
"testing"
+ "github.com/zitadel/zitadel/internal/database"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore"
@@ -124,6 +125,254 @@ func TestIDPTemplateProjection_reducesRemove(t *testing.T) {
}
}
+func TestIDPTemplateProjection_reducesGoogle(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 reduceGoogleIDPAdded",
+ args: args{
+ event: getEvent(testEvent(
+ repository.EventType(instance.GoogleIDPAddedEventType),
+ instance.AggregateType,
+ []byte(`{
+ "id": "idp-id",
+ "clientID": "client_id",
+ "clientSecret": {
+ "cryptoType": 0,
+ "algorithm": "RSA-265",
+ "keyId": "key-id"
+ },
+ "scopes": ["profile"],
+ "isCreationAllowed": true,
+ "isLinkingAllowed": true,
+ "isAutoCreation": true,
+ "isAutoUpdate": true
+}`),
+ ), instance.GoogleIDPAddedEventMapper),
+ },
+ reduce: (&idpTemplateProjection{}).reduceGoogleIDPAdded,
+ 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,
+ "",
+ domain.IdentityProviderTypeSystem,
+ domain.IDPTypeGoogle,
+ true,
+ true,
+ true,
+ true,
+ },
+ },
+ {
+ expectedStmt: "INSERT INTO projections.idp_templates_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)",
+ expectedArgs: []interface{}{
+ "idp-id",
+ "instance-id",
+ "client_id",
+ anyArg{},
+ database.StringArray{"profile"},
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "org reduceGoogleIDPAdded",
+ args: args{
+ event: getEvent(testEvent(
+ repository.EventType(org.GoogleIDPAddedEventType),
+ org.AggregateType,
+ []byte(`{
+ "id": "idp-id",
+ "clientID": "client_id",
+ "clientSecret": {
+ "cryptoType": 0,
+ "algorithm": "RSA-265",
+ "keyId": "key-id"
+ },
+ "scopes": ["profile"],
+ "isCreationAllowed": true,
+ "isLinkingAllowed": true,
+ "isAutoCreation": true,
+ "isAutoUpdate": true
+}`),
+ ), org.GoogleIDPAddedEventMapper),
+ },
+ reduce: (&idpTemplateProjection{}).reduceGoogleIDPAdded,
+ 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,
+ "",
+ domain.IdentityProviderTypeOrg,
+ domain.IDPTypeGoogle,
+ true,
+ true,
+ true,
+ true,
+ },
+ },
+ {
+ expectedStmt: "INSERT INTO projections.idp_templates_google (idp_id, instance_id, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5)",
+ expectedArgs: []interface{}{
+ "idp-id",
+ "instance-id",
+ "client_id",
+ anyArg{},
+ database.StringArray{"profile"},
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "instance reduceGoogleIDPChanged minimal",
+ args: args{
+ event: getEvent(testEvent(
+ repository.EventType(instance.GoogleIDPChangedEventType),
+ instance.AggregateType,
+ []byte(`{
+ "id": "idp-id",
+ "isCreationAllowed": true,
+ "clientID": "id"
+}`),
+ ), instance.GoogleIDPChangedEventMapper),
+ },
+ reduce: (&idpTemplateProjection{}).reduceGoogleIDPChanged,
+ 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_google SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
+ expectedArgs: []interface{}{
+ "id",
+ "idp-id",
+ "instance-id",
+ },
+ },
+ },
+ },
+ },
+ },
+ {
+ name: "instance reduceGoogleIDPChanged",
+ args: args{
+ event: getEvent(testEvent(
+ repository.EventType(instance.GoogleIDPChangedEventType),
+ instance.AggregateType,
+ []byte(`{
+ "id": "idp-id",
+ "clientID": "client_id",
+ "clientSecret": {
+ "cryptoType": 0,
+ "algorithm": "RSA-265",
+ "keyId": "key-id"
+ },
+ "scopes": ["profile"],
+ "isCreationAllowed": true,
+ "isLinkingAllowed": true,
+ "isAutoCreation": true,
+ "isAutoUpdate": true
+}`),
+ ), instance.GoogleIDPChangedEventMapper),
+ },
+ reduce: (&idpTemplateProjection{}).reduceGoogleIDPChanged,
+ want: wantReduce{
+ aggregateType: eventstore.AggregateType("instance"),
+ sequence: 15,
+ previousSequence: 10,
+ executer: &testExecuter{
+ executions: []execution{
+ {
+ expectedStmt: "UPDATE projections.idp_templates SET (is_creation_allowed, is_linking_allowed, is_auto_creation, is_auto_update, change_date, sequence) = ($1, $2, $3, $4, $5, $6) WHERE (id = $7) AND (instance_id = $8)",
+ expectedArgs: []interface{}{
+ true,
+ true,
+ true,
+ true,
+ anyArg{},
+ uint64(15),
+ "idp-id",
+ "instance-id",
+ },
+ },
+ {
+ expectedStmt: "UPDATE projections.idp_templates_google SET (client_id, client_secret, scopes) = ($1, $2, $3) WHERE (idp_id = $4) AND (instance_id = $5)",
+ expectedArgs: []interface{}{
+ "client_id",
+ anyArg{},
+ 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_reducesLDAP(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.Event
diff --git a/internal/repository/idp/google.go b/internal/repository/idp/google.go
new file mode 100644
index 000000000..5cff7eb33
--- /dev/null
+++ b/internal/repository/idp/google.go
@@ -0,0 +1,143 @@
+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 GoogleIDPAddedEvent struct {
+ eventstore.BaseEvent `json:"-"`
+
+ ID string `json:"id"`
+ Name string `json:"name,omitempty"`
+ ClientID string `json:"clientID"`
+ ClientSecret *crypto.CryptoValue `json:"clientSecret"`
+ Scopes []string `json:"scopes,omitempty"`
+ Options
+}
+
+func NewGoogleIDPAddedEvent(
+ base *eventstore.BaseEvent,
+ id,
+ name,
+ clientID string,
+ clientSecret *crypto.CryptoValue,
+ scopes []string,
+ options Options,
+) *GoogleIDPAddedEvent {
+ return &GoogleIDPAddedEvent{
+ BaseEvent: *base,
+ ID: id,
+ Name: name,
+ ClientID: clientID,
+ ClientSecret: clientSecret,
+ Scopes: scopes,
+ Options: options,
+ }
+}
+
+func (e *GoogleIDPAddedEvent) Data() interface{} {
+ return e
+}
+
+func (e *GoogleIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
+ return nil
+}
+
+func GoogleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
+ e := &GoogleIDPAddedEvent{
+ BaseEvent: *eventstore.BaseEventFromRepo(event),
+ }
+
+ err := json.Unmarshal(event.Data, e)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "IDP-SAff1", "unable to unmarshal event")
+ }
+
+ return e, nil
+}
+
+type GoogleIDPChangedEvent struct {
+ eventstore.BaseEvent `json:"-"`
+
+ ID string `json:"id"`
+ Name *string `json:"name,omitempty"`
+ ClientID *string `json:"clientID,omitempty"`
+ ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"`
+ Scopes []string `json:"scopes,omitempty"`
+ OptionChanges
+}
+
+func NewGoogleIDPChangedEvent(
+ base *eventstore.BaseEvent,
+ id string,
+ changes []GoogleIDPChanges,
+) (*GoogleIDPChangedEvent, error) {
+ if len(changes) == 0 {
+ return nil, errors.ThrowPreconditionFailed(nil, "IDP-Dg3qs", "Errors.NoChangesFound")
+ }
+ changedEvent := &GoogleIDPChangedEvent{
+ BaseEvent: *base,
+ ID: id,
+ }
+ for _, change := range changes {
+ change(changedEvent)
+ }
+ return changedEvent, nil
+}
+
+type GoogleIDPChanges func(*GoogleIDPChangedEvent)
+
+func ChangeGoogleName(name string) func(*GoogleIDPChangedEvent) {
+ return func(e *GoogleIDPChangedEvent) {
+ e.Name = &name
+ }
+}
+func ChangeGoogleClientID(clientID string) func(*GoogleIDPChangedEvent) {
+ return func(e *GoogleIDPChangedEvent) {
+ e.ClientID = &clientID
+ }
+}
+
+func ChangeGoogleClientSecret(clientSecret *crypto.CryptoValue) func(*GoogleIDPChangedEvent) {
+ return func(e *GoogleIDPChangedEvent) {
+ e.ClientSecret = clientSecret
+ }
+}
+
+func ChangeGoogleScopes(scopes []string) func(*GoogleIDPChangedEvent) {
+ return func(e *GoogleIDPChangedEvent) {
+ e.Scopes = scopes
+ }
+}
+
+func ChangeGoogleOptions(options OptionChanges) func(*GoogleIDPChangedEvent) {
+ return func(e *GoogleIDPChangedEvent) {
+ e.OptionChanges = options
+ }
+}
+
+func (e *GoogleIDPChangedEvent) Data() interface{} {
+ return e
+}
+
+func (e *GoogleIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
+ return nil
+}
+
+func GoogleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
+ e := &GoogleIDPChangedEvent{
+ BaseEvent: *eventstore.BaseEventFromRepo(event),
+ }
+
+ err := json.Unmarshal(event.Data, e)
+ if err != nil {
+ return nil, errors.ThrowInternal(err, "IDP-SF3t2", "unable to unmarshal event")
+ }
+
+ return e, nil
+}
diff --git a/internal/repository/instance/eventstore.go b/internal/repository/instance/eventstore.go
index 642044619..18fd6cff1 100644
--- a/internal/repository/instance/eventstore.go
+++ b/internal/repository/instance/eventstore.go
@@ -70,6 +70,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
+ RegisterFilterEventMapper(AggregateType, GoogleIDPAddedEventType, GoogleIDPAddedEventMapper).
+ RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper).
diff --git a/internal/repository/instance/idp.go b/internal/repository/instance/idp.go
index a0ae9580a..cb580cbe6 100644
--- a/internal/repository/instance/idp.go
+++ b/internal/repository/instance/idp.go
@@ -10,11 +10,89 @@ import (
)
const (
- LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.added"
- LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.changed"
- IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
+ GoogleIDPAddedEventType eventstore.EventType = "instance.idp.google.added"
+ GoogleIDPChangedEventType eventstore.EventType = "instance.idp.google.changed"
+ LDAPIDPAddedEventType eventstore.EventType = "instance.idp.ldap.added"
+ LDAPIDPChangedEventType eventstore.EventType = "instance.idp.ldap.changed"
+ IDPRemovedEventType eventstore.EventType = "instance.idp.removed"
)
+type GoogleIDPAddedEvent struct {
+ idp.GoogleIDPAddedEvent
+}
+
+func NewGoogleIDPAddedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ id,
+ name,
+ clientID string,
+ clientSecret *crypto.CryptoValue,
+ scopes []string,
+ options idp.Options,
+) *GoogleIDPAddedEvent {
+
+ return &GoogleIDPAddedEvent{
+ GoogleIDPAddedEvent: *idp.NewGoogleIDPAddedEvent(
+ eventstore.NewBaseEventForPush(
+ ctx,
+ aggregate,
+ GoogleIDPAddedEventType,
+ ),
+ id,
+ name,
+ clientID,
+ clientSecret,
+ scopes,
+ options,
+ ),
+ }
+}
+
+func GoogleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
+ e, err := idp.GoogleIDPAddedEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &GoogleIDPAddedEvent{GoogleIDPAddedEvent: *e.(*idp.GoogleIDPAddedEvent)}, nil
+}
+
+type GoogleIDPChangedEvent struct {
+ idp.GoogleIDPChangedEvent
+}
+
+func NewGoogleIDPChangedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ id string,
+ changes []idp.GoogleIDPChanges,
+) (*GoogleIDPChangedEvent, error) {
+
+ changedEvent, err := idp.NewGoogleIDPChangedEvent(
+ eventstore.NewBaseEventForPush(
+ ctx,
+ aggregate,
+ GoogleIDPChangedEventType,
+ ),
+ id,
+ changes,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &GoogleIDPChangedEvent{GoogleIDPChangedEvent: *changedEvent}, nil
+}
+
+func GoogleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
+ e, err := idp.GoogleIDPChangedEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &GoogleIDPChangedEvent{GoogleIDPChangedEvent: *e.(*idp.GoogleIDPChangedEvent)}, nil
+}
+
type LDAPIDPAddedEvent struct {
idp.LDAPIDPAddedEvent
}
diff --git a/internal/repository/org/eventstore.go b/internal/repository/org/eventstore.go
index 4e8d556cc..f3d7f86d5 100644
--- a/internal/repository/org/eventstore.go
+++ b/internal/repository/org/eventstore.go
@@ -78,6 +78,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
+ RegisterFilterEventMapper(AggregateType, GoogleIDPAddedEventType, GoogleIDPAddedEventMapper).
+ RegisterFilterEventMapper(AggregateType, GoogleIDPChangedEventType, GoogleIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, LDAPIDPAddedEventType, LDAPIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, LDAPIDPChangedEventType, LDAPIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper).
diff --git a/internal/repository/org/idp.go b/internal/repository/org/idp.go
index fe3b971d7..c41e8537d 100644
--- a/internal/repository/org/idp.go
+++ b/internal/repository/org/idp.go
@@ -10,11 +10,89 @@ import (
)
const (
- LDAPIDPAddedEventType eventstore.EventType = "org.idp.ldap.added"
- LDAPIDPChangedEventType eventstore.EventType = "org.idp.ldap.changed"
- IDPRemovedEventType eventstore.EventType = "org.idp.removed"
+ GoogleIDPAddedEventType eventstore.EventType = "org.idp.google.added"
+ GoogleIDPChangedEventType eventstore.EventType = "org.idp.google.changed"
+ LDAPIDPAddedEventType eventstore.EventType = "org.idp.ldap.added"
+ LDAPIDPChangedEventType eventstore.EventType = "org.idp.ldap.changed"
+ IDPRemovedEventType eventstore.EventType = "org.idp.removed"
)
+type GoogleIDPAddedEvent struct {
+ idp.GoogleIDPAddedEvent
+}
+
+func NewGoogleIDPAddedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ id,
+ name,
+ clientID string,
+ clientSecret *crypto.CryptoValue,
+ scopes []string,
+ options idp.Options,
+) *GoogleIDPAddedEvent {
+
+ return &GoogleIDPAddedEvent{
+ GoogleIDPAddedEvent: *idp.NewGoogleIDPAddedEvent(
+ eventstore.NewBaseEventForPush(
+ ctx,
+ aggregate,
+ GoogleIDPAddedEventType,
+ ),
+ id,
+ name,
+ clientID,
+ clientSecret,
+ scopes,
+ options,
+ ),
+ }
+}
+
+func GoogleIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
+ e, err := idp.GoogleIDPAddedEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &GoogleIDPAddedEvent{GoogleIDPAddedEvent: *e.(*idp.GoogleIDPAddedEvent)}, nil
+}
+
+type GoogleIDPChangedEvent struct {
+ idp.GoogleIDPChangedEvent
+}
+
+func NewGoogleIDPChangedEvent(
+ ctx context.Context,
+ aggregate *eventstore.Aggregate,
+ id string,
+ changes []idp.GoogleIDPChanges,
+) (*GoogleIDPChangedEvent, error) {
+
+ changedEvent, err := idp.NewGoogleIDPChangedEvent(
+ eventstore.NewBaseEventForPush(
+ ctx,
+ aggregate,
+ GoogleIDPChangedEventType,
+ ),
+ id,
+ changes,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &GoogleIDPChangedEvent{GoogleIDPChangedEvent: *changedEvent}, nil
+}
+
+func GoogleIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
+ e, err := idp.GoogleIDPChangedEventMapper(event)
+ if err != nil {
+ return nil, err
+ }
+
+ return &GoogleIDPChangedEvent{GoogleIDPChangedEvent: *e.(*idp.GoogleIDPChangedEvent)}, nil
+}
+
type LDAPIDPAddedEvent struct {
idp.LDAPIDPAddedEvent
}
diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto
index f66bfab81..70754ee4f 100644
--- a/proto/zitadel/admin.proto
+++ b/proto/zitadel/admin.proto
@@ -1033,7 +1033,31 @@ service AdminService {
};
}
- // Add a new ldap identity provider on the instance
+ // Add a new Google identity provider on the instance
+ rpc AddGoogleProvider(AddGoogleProviderRequest) returns (AddGoogleProviderResponse) {
+ option (google.api.http) = {
+ post: "/idps/google"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.idp.write"
+ };
+ }
+
+ // Change an existing Google identity provider on the instance
+ rpc UpdateGoogleProvider(UpdateGoogleProviderRequest) returns (UpdateGoogleProviderResponse) {
+ option (google.api.http) = {
+ put: "/idps/google/{id}"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "iam.idp.write"
+ };
+ }
+
+ // Add a new LDAP identity provider on the instance
rpc AddLDAPProvider(AddLDAPProviderRequest) returns (AddLDAPProviderResponse) {
option (google.api.http) = {
post: "/idps/ldap"
@@ -1045,7 +1069,7 @@ service AdminService {
};
}
- // Change an existing ldap identity provider on the instance
+ // Change an existing LDAP identity provider on the instance
rpc UpdateLDAPProvider(UpdateLDAPProviderRequest) returns (UpdateLDAPProviderResponse) {
option (google.api.http) = {
put: "/idps/ldap/{id}"
@@ -3710,6 +3734,34 @@ message GetProviderByIDResponse {
zitadel.idp.v1.Provider idp = 1;
}
+message AddGoogleProviderRequest {
+ // Google will be used as default, if no name is provided
+ string name = 1 [(validate.rules).string = {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}];
+ repeated string scopes = 4 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
+ zitadel.idp.v1.Options provider_options = 5;
+}
+
+message AddGoogleProviderResponse {
+ zitadel.v1.ObjectDetails details = 1;
+ string id = 2;
+}
+
+message UpdateGoogleProviderRequest {
+ string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string name = 2 [(validate.rules).string = {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}];
+ repeated string scopes = 5 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
+ zitadel.idp.v1.Options provider_options = 6;
+}
+
+message UpdateGoogleProviderResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
message AddLDAPProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string host = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto
index 94a3e622d..eae33cf02 100644
--- a/proto/zitadel/idp.proto
+++ b/proto/zitadel/idp.proto
@@ -262,9 +262,15 @@ message ProviderConfig {
Options options = 1;
oneof config {
LDAPConfig ldap = 2;
+ GoogleConfig google = 3;
}
}
+message GoogleConfig {
+ string client_id = 1;
+ repeated string scopes = 2;
+}
+
message LDAPConfig {
string host = 1;
string port = 2;
diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto
index b9d0e0a61..534945ec3 100644
--- a/proto/zitadel/management.proto
+++ b/proto/zitadel/management.proto
@@ -3031,7 +3031,32 @@ service ManagementService {
};
}
- // Add a new ldap identity provider in the organisation
+
+ // Add a new Google identity provider in the organisation
+ rpc AddGoogleProvider(AddGoogleProviderRequest) returns (AddGoogleProviderResponse) {
+ option (google.api.http) = {
+ post: "/idps/google"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "org.idp.write"
+ };
+ }
+
+ // Change an existing Google identity provider in the organisation
+ rpc UpdateGoogleProvider(UpdateGoogleProviderRequest) returns (UpdateGoogleProviderResponse) {
+ option (google.api.http) = {
+ put: "/idps/google/{id}"
+ body: "*"
+ };
+
+ option (zitadel.v1.auth_option) = {
+ permission: "org.idp.write"
+ };
+ }
+
+ // Add a new LDAP identity provider in the organisation
rpc AddLDAPProvider(AddLDAPProviderRequest) returns (AddLDAPProviderResponse) {
option (google.api.http) = {
post: "/idps/ldap"
@@ -3043,7 +3068,7 @@ service ManagementService {
};
}
- // Change an existing ldap identity provider in the organisation
+ // Change an existing LDAP identity provider in the organisation
rpc UpdateLDAPProvider(UpdateLDAPProviderRequest) returns (UpdateLDAPProviderResponse) {
option (google.api.http) = {
put: "/idps/ldap/{id}"
@@ -6008,6 +6033,34 @@ message GetProviderByIDResponse {
zitadel.idp.v1.Provider idp = 1;
}
+message AddGoogleProviderRequest {
+ // Google will be used as default, if no name is provided
+ string name = 1 [(validate.rules).string = {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}];
+ repeated string scopes = 4 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
+ zitadel.idp.v1.Options provider_options = 5;
+}
+
+message AddGoogleProviderResponse {
+ zitadel.v1.ObjectDetails details = 1;
+ string id = 2;
+}
+
+message UpdateGoogleProviderRequest {
+ string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
+ string name = 2 [(validate.rules).string = {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}];
+ repeated string scopes = 5 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
+ zitadel.idp.v1.Options provider_options = 6;
+}
+
+message UpdateGoogleProviderResponse {
+ zitadel.v1.ObjectDetails details = 1;
+}
+
message AddLDAPProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string host = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];