feat: add azure provider templates (#5441)

Adds possibility to manage and use Microsoft Azure template based providers
This commit is contained in:
Livio Spring 2023-03-15 07:48:37 +01:00 committed by GitHub
parent 93e1fe0056
commit 5a307afe62
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 2843 additions and 60 deletions

View File

@ -241,6 +241,27 @@ func (s *Server) UpdateJWTProvider(ctx context.Context, req *admin_pb.UpdateJWTP
}, nil
}
func (s *Server) AddAzureADProvider(ctx context.Context, req *admin_pb.AddAzureADProviderRequest) (*admin_pb.AddAzureADProviderResponse, error) {
id, details, err := s.command.AddInstanceAzureADProvider(ctx, addAzureADProviderToCommand(req))
if err != nil {
return nil, err
}
return &admin_pb.AddAzureADProviderResponse{
Id: id,
Details: object_pb.DomainToAddDetailsPb(details),
}, nil
}
func (s *Server) UpdateAzureADProvider(ctx context.Context, req *admin_pb.UpdateAzureADProviderRequest) (*admin_pb.UpdateAzureADProviderResponse, error) {
details, err := s.command.UpdateInstanceAzureADProvider(ctx, req.Id, updateAzureADProviderToCommand(req))
if err != nil {
return nil, err
}
return &admin_pb.UpdateAzureADProviderResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) AddGitHubProvider(ctx context.Context, req *admin_pb.AddGitHubProviderRequest) (*admin_pb.AddGitHubProviderResponse, error) {
id, details, err := s.command.AddInstanceGitHubProvider(ctx, addGitHubProviderToCommand(req))
if err != nil {

View File

@ -273,6 +273,30 @@ func updateJWTProviderToCommand(req *admin_pb.UpdateJWTProviderRequest) command.
}
}
func addAzureADProviderToCommand(req *admin_pb.AddAzureADProviderRequest) command.AzureADProvider {
return command.AzureADProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
Scopes: req.Scopes,
Tenant: idp_grpc.AzureADTenantToCommand(req.Tenant),
EmailVerified: req.EmailVerified,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateAzureADProviderToCommand(req *admin_pb.UpdateAzureADProviderRequest) command.AzureADProvider {
return command.AzureADProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
Scopes: req.Scopes,
Tenant: idp_grpc.AzureADTenantToCommand(req.Tenant),
EmailVerified: req.EmailVerified,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func addGitHubProviderToCommand(req *admin_pb.AddGitHubProviderRequest) command.GitHubProvider {
return command.GitHubProvider{
Name: req.Name,

View File

@ -4,6 +4,7 @@ import (
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model"
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/internal/repository/idp"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
@ -329,6 +330,33 @@ func LDAPAttributesToCommand(attributes *idp_pb.LDAPAttributes) idp.LDAPAttribut
}
}
func AzureADTenantToCommand(tenant *idp_pb.AzureADTenant) string {
if tenant == nil {
return string(azuread.CommonTenant)
}
switch t := tenant.Type.(type) {
case *idp_pb.AzureADTenant_TenantType:
return string(azureADTenantTypeToCommand(t.TenantType))
case *idp_pb.AzureADTenant_TenantId:
return t.TenantId
default:
return string(azuread.CommonTenant)
}
}
func azureADTenantTypeToCommand(tenantType idp_pb.AzureADTenantType) azuread.TenantType {
switch tenantType {
case idp_pb.AzureADTenantType_AZURE_AD_TENANT_TYPE_COMMON:
return azuread.CommonTenant
case idp_pb.AzureADTenantType_AZURE_AD_TENANT_TYPE_ORGANISATIONS:
return azuread.OrganizationsTenant
case idp_pb.AzureADTenantType_AZURE_AD_TENANT_TYPE_CONSUMERS:
return azuread.ConsumersTenant
default:
return azuread.CommonTenant
}
}
func ProvidersToPb(providers []*query.IDPTemplate) []*idp_pb.Provider {
list := make([]*idp_pb.Provider, len(providers))
for i, provider := range providers {
@ -412,6 +440,10 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
jwtConfigToPb(providerConfig, config.JWTIDPTemplate)
return providerConfig
}
if config.AzureADIDPTemplate != nil {
azureConfigToPb(providerConfig, config.AzureADIDPTemplate)
return providerConfig
}
if config.GitHubIDPTemplate != nil {
githubConfigToPb(providerConfig, config.GitHubIDPTemplate)
return providerConfig
@ -473,6 +505,32 @@ func jwtConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.JWTIDP
}
}
func azureConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.AzureADIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_AzureAd{
AzureAd: &idp_pb.AzureADConfig{
ClientId: template.ClientID,
Tenant: azureTenantToPb(template.Tenant),
EmailVerified: template.IsEmailVerified,
Scopes: template.Scopes,
},
}
}
func azureTenantToPb(tenant string) *idp_pb.AzureADTenant {
var tenantType idp_pb.IsAzureADTenantType
switch azuread.TenantType(tenant) {
case azuread.CommonTenant:
tenantType = &idp_pb.AzureADTenant_TenantType{TenantType: idp_pb.AzureADTenantType_AZURE_AD_TENANT_TYPE_COMMON}
case azuread.OrganizationsTenant:
tenantType = &idp_pb.AzureADTenant_TenantType{TenantType: idp_pb.AzureADTenantType_AZURE_AD_TENANT_TYPE_ORGANISATIONS}
case azuread.ConsumersTenant:
tenantType = &idp_pb.AzureADTenant_TenantType{TenantType: idp_pb.AzureADTenantType_AZURE_AD_TENANT_TYPE_CONSUMERS}
default:
tenantType = &idp_pb.AzureADTenant_TenantId{TenantId: tenant}
}
return &idp_pb.AzureADTenant{Type: tenantType}
}
func githubConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.GitHubIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_Github{
Github: &idp_pb.GitHubConfig{

View File

@ -233,6 +233,27 @@ func (s *Server) UpdateJWTProvider(ctx context.Context, req *mgmt_pb.UpdateJWTPr
}, nil
}
func (s *Server) AddAzureADProvider(ctx context.Context, req *mgmt_pb.AddAzureADProviderRequest) (*mgmt_pb.AddAzureADProviderResponse, error) {
id, details, err := s.command.AddOrgAzureADProvider(ctx, authz.GetCtxData(ctx).OrgID, addAzureADProviderToCommand(req))
if err != nil {
return nil, err
}
return &mgmt_pb.AddAzureADProviderResponse{
Id: id,
Details: object_pb.DomainToAddDetailsPb(details),
}, nil
}
func (s *Server) UpdateAzureADProvider(ctx context.Context, req *mgmt_pb.UpdateAzureADProviderRequest) (*mgmt_pb.UpdateAzureADProviderResponse, error) {
details, err := s.command.UpdateOrgAzureADProvider(ctx, authz.GetCtxData(ctx).OrgID, req.Id, updateAzureADProviderToCommand(req))
if err != nil {
return nil, err
}
return &mgmt_pb.UpdateAzureADProviderResponse{
Details: object_pb.DomainToChangeDetailsPb(details),
}, nil
}
func (s *Server) AddGitHubProvider(ctx context.Context, req *mgmt_pb.AddGitHubProviderRequest) (*mgmt_pb.AddGitHubProviderResponse, error) {
id, details, err := s.command.AddOrgGitHubProvider(ctx, authz.GetCtxData(ctx).OrgID, addGitHubProviderToCommand(req))
if err != nil {

View File

@ -290,6 +290,28 @@ func updateJWTProviderToCommand(req *mgmt_pb.UpdateJWTProviderRequest) command.J
}
}
func addAzureADProviderToCommand(req *mgmt_pb.AddAzureADProviderRequest) command.AzureADProvider {
return command.AzureADProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
Tenant: idp_grpc.AzureADTenantToCommand(req.Tenant),
EmailVerified: req.EmailVerified,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func updateAzureADProviderToCommand(req *mgmt_pb.UpdateAzureADProviderRequest) command.AzureADProvider {
return command.AzureADProvider{
Name: req.Name,
ClientID: req.ClientId,
ClientSecret: req.ClientSecret,
Tenant: idp_grpc.AzureADTenantToCommand(req.Tenant),
EmailVerified: req.EmailVerified,
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
}
}
func addGitHubProviderToCommand(req *mgmt_pb.AddGitHubProviderRequest) command.GitHubProvider {
return command.GitHubProvider{
Name: req.Name,

View File

@ -18,6 +18,7 @@ import (
"github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
"github.com/zitadel/zitadel/internal/idp/providers/github"
"github.com/zitadel/zitadel/internal/idp/providers/gitlab"
"github.com/zitadel/zitadel/internal/idp/providers/google"
@ -144,6 +145,8 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
provider, err = l.oidcProvider(r.Context(), identityProvider)
case domain.IDPTypeJWT:
provider, err = l.jwtProvider(identityProvider)
case domain.IDPTypeAzureAD:
provider, err = l.azureProvider(r.Context(), identityProvider)
case domain.IDPTypeGitHub:
provider, err = l.githubProvider(r.Context(), identityProvider)
case domain.IDPTypeGitHubEnterprise:
@ -155,7 +158,6 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider)
case domain.IDPTypeLDAP,
domain.IDPTypeAzureAD,
domain.IDPTypeUnspecified:
fallthrough
default:
@ -212,6 +214,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
return
}
session = &openid.Session{Provider: provider.(*openid.Provider), Code: data.Code}
case domain.IDPTypeAzureAD:
provider, err = l.azureProvider(r.Context(), identityProvider)
if err != nil {
l.externalAuthFailed(w, r, authReq, nil, nil, err)
return
}
session = &oauth.Session{Provider: provider.(*azuread.Provider).Provider, Code: data.Code}
case domain.IDPTypeGitHub:
provider, err = l.githubProvider(r.Context(), identityProvider)
if err != nil {
@ -249,7 +258,6 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
case domain.IDPTypeJWT,
domain.IDPTypeLDAP,
domain.IDPTypeAzureAD,
domain.IDPTypeUnspecified:
fallthrough
default:
@ -666,6 +674,28 @@ func (l *Login) oauthProvider(ctx context.Context, identityProvider *query.IDPTe
)
}
func (l *Login) azureProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*azuread.Provider, error) {
secret, err := crypto.DecryptString(identityProvider.AzureADIDPTemplate.ClientSecret, l.idpConfigAlg)
if err != nil {
return nil, err
}
opts := make([]azuread.ProviderOptions, 0, 2)
if identityProvider.AzureADIDPTemplate.IsEmailVerified {
opts = append(opts, azuread.WithEmailVerified())
}
if identityProvider.AzureADIDPTemplate.Tenant != "" {
opts = append(opts, azuread.WithTenant(azuread.TenantType(identityProvider.AzureADIDPTemplate.Tenant)))
}
return azuread.New(
identityProvider.Name,
identityProvider.AzureADIDPTemplate.ClientID,
secret,
l.baseURL(ctx)+EndpointExternalLoginCallback,
identityProvider.AzureADIDPTemplate.Scopes,
opts...,
)
}
func (l *Login) githubProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*github.Provider, error) {
secret, err := crypto.DecryptString(identityProvider.GitHubIDPTemplate.ClientSecret, l.idpConfigAlg)
if err != nil {

View File

@ -38,6 +38,16 @@ type JWTProvider struct {
IDPOptions idp.Options
}
type AzureADProvider struct {
Name string
ClientID string
ClientSecret string
Scopes []string
Tenant string
EmailVerified bool
IDPOptions idp.Options
}
type GitHubProvider struct {
Name string
ClientID string

View File

@ -413,6 +413,111 @@ func (wm *JWTIDPWriteModel) reduceJWTConfigChangedEvent(e *idpconfig.JWTConfigCh
}
}
type AzureADIDPWriteModel struct {
eventstore.WriteModel
ID string
Name string
ClientID string
ClientSecret *crypto.CryptoValue
Scopes []string
Tenant string
IsEmailVerified bool
idp.Options
State domain.IDPState
}
func (wm *AzureADIDPWriteModel) Reduce() error {
for _, event := range wm.Events {
switch e := event.(type) {
case *idp.AzureADIDPAddedEvent:
wm.reduceAddedEvent(e)
case *idp.AzureADIDPChangedEvent:
wm.reduceChangedEvent(e)
case *idp.RemovedEvent:
wm.State = domain.IDPStateRemoved
}
}
return wm.WriteModel.Reduce()
}
func (wm *AzureADIDPWriteModel) reduceAddedEvent(e *idp.AzureADIDPAddedEvent) {
wm.Name = e.Name
wm.ClientID = e.ClientID
wm.ClientSecret = e.ClientSecret
wm.Scopes = e.Scopes
wm.Tenant = e.Tenant
wm.IsEmailVerified = e.IsEmailVerified
wm.Options = e.Options
wm.State = domain.IDPStateActive
}
func (wm *AzureADIDPWriteModel) reduceChangedEvent(e *idp.AzureADIDPChangedEvent) {
if e.ClientID != nil {
wm.ClientID = *e.ClientID
}
if e.ClientSecret != nil {
wm.ClientSecret = e.ClientSecret
}
if e.Name != nil {
wm.Name = *e.Name
}
if e.Scopes != nil {
wm.Scopes = e.Scopes
}
if e.Tenant != nil {
wm.Tenant = *e.Tenant
}
if e.IsEmailVerified != nil {
wm.IsEmailVerified = *e.IsEmailVerified
}
wm.Options.ReduceChanges(e.OptionChanges)
}
func (wm *AzureADIDPWriteModel) NewChanges(
name string,
clientID string,
clientSecretString string,
secretCrypto crypto.Crypto,
scopes []string,
tenant string,
isEmailVerified bool,
options idp.Options,
) ([]idp.AzureADIDPChanges, error) {
changes := make([]idp.AzureADIDPChanges, 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.ChangeAzureADClientSecret(clientSecret))
}
if wm.Name != name {
changes = append(changes, idp.ChangeAzureADName(name))
}
if wm.ClientID != clientID {
changes = append(changes, idp.ChangeAzureADClientID(clientID))
}
if wm.Tenant != tenant {
changes = append(changes, idp.ChangeAzureADTenant(tenant))
}
if wm.IsEmailVerified != isEmailVerified {
changes = append(changes, idp.ChangeAzureADIsEmailVerified(isEmailVerified))
}
if !reflect.DeepEqual(wm.Scopes, scopes) {
changes = append(changes, idp.ChangeAzureADScopes(scopes))
}
opts := wm.Options.Changes(options)
if !opts.IsZero() {
changes = append(changes, idp.ChangeAzureADOptions(opts))
}
return changes, nil
}
type GitHubIDPWriteModel struct {
eventstore.WriteModel
@ -1049,6 +1154,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
wm.reduceAdded(e.ID)
case *idp.JWTIDPAddedEvent:
wm.reduceAdded(e.ID)
case *idp.AzureADIDPAddedEvent:
wm.reduceAdded(e.ID)
case *idp.GitHubIDPAddedEvent:
wm.reduceAdded(e.ID)
case *idp.GitHubEnterpriseIDPAddedEvent:

View File

@ -139,6 +139,48 @@ func (c *Commands) UpdateInstanceJWTProvider(ctx context.Context, id string, pro
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddInstanceAzureADProvider(ctx context.Context, provider AzureADProvider) (string, *domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
writeModel := NewAzureADInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddInstanceAzureADProvider(instanceAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return "", nil, err
}
return id, pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) UpdateInstanceAzureADProvider(ctx context.Context, id string, provider AzureADProvider) (*domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
writeModel := NewAzureADInstanceIDPWriteModel(instanceID, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateInstanceAzureADProvider(instanceAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddInstanceGitHubProvider(ctx context.Context, provider GitHubProvider) (string, *domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(instanceID)
@ -719,6 +761,92 @@ func (c *Commands) prepareUpdateInstanceJWTProvider(a *instance.Aggregate, write
}
}
func (c *Commands) prepareAddInstanceAzureADProvider(a *instance.Aggregate, writeModel *InstanceAzureADIDPWriteModel, provider AzureADProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-sdf3g", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Fhbr2", "Errors.Invalid.Argument")
}
if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-Dzh3g", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
}
writeModel.AppendEvents(events...)
if err = writeModel.Reduce(); err != nil {
return nil, err
}
secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption)
if err != nil {
return nil, err
}
return []eventstore.Command{
instance.NewAzureADIDPAddedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
secret,
provider.Scopes,
provider.Tenant,
provider.EmailVerified,
provider.IDPOptions,
),
}, nil
}, nil
}
}
func (c *Commands) prepareUpdateInstanceAzureADProvider(a *instance.Aggregate, writeModel *InstanceAzureADIDPWriteModel, provider AzureADProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-SAgh2", "Errors.Invalid.Argument")
}
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-fh3h1", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "INST-dmitg", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
}
writeModel.AppendEvents(events...)
if err = writeModel.Reduce(); err != nil {
return nil, err
}
if !writeModel.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "INST-BHz3q", "Errors.Instance.IDPConfig.NotExisting")
}
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
provider.ClientSecret,
c.idpConfigEncryption,
provider.Scopes,
provider.Tenant,
provider.EmailVerified,
provider.IDPOptions,
)
if err != nil || event == nil {
return nil, err
}
return []eventstore.Command{event}, nil
}, nil
}
}
func (c *Commands) prepareAddInstanceGitHubProvider(a *instance.Aggregate, writeModel *InstanceGitHubIDPWriteModel, provider GitHubProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {

View File

@ -279,6 +279,82 @@ func (wm *InstanceJWTIDPWriteModel) NewChangedEvent(
return instance.NewJWTIDPChangedEvent(ctx, aggregate, id, changes)
}
type InstanceAzureADIDPWriteModel struct {
AzureADIDPWriteModel
}
func NewAzureADInstanceIDPWriteModel(instanceID, id string) *InstanceAzureADIDPWriteModel {
return &InstanceAzureADIDPWriteModel{
AzureADIDPWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: instanceID,
ResourceOwner: instanceID,
},
ID: id,
},
}
}
func (wm *InstanceAzureADIDPWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *instance.AzureADIDPAddedEvent:
wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPAddedEvent)
case *instance.AzureADIDPChangedEvent:
wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPChangedEvent)
case *instance.IDPRemovedEvent:
wm.AzureADIDPWriteModel.AppendEvents(&e.RemovedEvent)
default:
wm.AzureADIDPWriteModel.AppendEvents(e)
}
}
}
func (wm *InstanceAzureADIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(instance.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
instance.AzureADIDPAddedEventType,
instance.AzureADIDPChangedEventType,
instance.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}
func (wm *InstanceAzureADIDPWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID,
clientSecretString string,
secretCrypto crypto.Crypto,
scopes []string,
tenant string,
isEmailVerified bool,
options idp.Options,
) (*instance.AzureADIDPChangedEvent, error) {
changes, err := wm.AzureADIDPWriteModel.NewChanges(
name,
clientID,
clientSecretString,
secretCrypto,
scopes,
tenant,
isEmailVerified,
options,
)
if err != nil || len(changes) == 0 {
return nil, err
}
return instance.NewAzureADIDPChangedEvent(ctx, aggregate, id, changes)
}
type InstanceGitHubIDPWriteModel struct {
GitHubIDPWriteModel
}
@ -726,6 +802,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event)
wm.IDPRemoveWriteModel.AppendEvents(&e.OIDCIDPAddedEvent)
case *instance.JWTIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.JWTIDPAddedEvent)
case *instance.AzureADIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.AzureADIDPAddedEvent)
case *instance.GitHubIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.GitHubIDPAddedEvent)
case *instance.GitHubEnterpriseIDPAddedEvent:
@ -760,6 +838,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
instance.OAuthIDPAddedEventType,
instance.OIDCIDPAddedEventType,
instance.JWTIDPAddedEventType,
instance.AzureADIDPAddedEventType,
instance.GitHubIDPAddedEventType,
instance.GitHubEnterpriseIDPAddedEventType,
instance.GitLabIDPAddedEventType,

View File

@ -637,6 +637,428 @@ func TestCommandSide_UpdateInstanceGenericOAuthIDP(t *testing.T) {
}
}
func TestCommandSide_AddInstanceAzureADIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
provider AzureADProvider
}
type res struct {
id string
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: AzureADProvider{},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-sdf3g", ""))
},
},
},
{
"invalid client id",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: AzureADProvider{
Name: "name",
},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Fhbr2", ""))
},
},
},
{
"invalid client secret",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-Dzh3g", ""))
},
},
},
{
name: "ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
instance.NewAzureADIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
nil,
"",
false,
idp.Options{},
)),
},
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: AzureADProvider{
Name: "name",
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.NewAzureADIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
[]string{"openid"},
"tenant",
true,
idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
)),
},
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
Scopes: []string{"openid"},
Tenant: "tenant",
EmailVerified: true,
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
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.AddInstanceAzureADProvider(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_UpdateInstanceAzureADIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
id string
provider AzureADProvider
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
provider: AzureADProvider{},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-SAgh2", ""))
},
},
},
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: AzureADProvider{},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-fh3h1", ""))
},
},
},
{
"invalid client id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: AzureADProvider{
Name: "name",
},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "INST-dmitg", ""))
},
},
},
{
name: "not found",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
},
},
res: res{
err: caos_errors.IsNotFound,
},
},
{
name: "no changes",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
instance.NewAzureADIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
nil,
"",
false,
idp.Options{},
)),
),
),
},
args: args{
ctx: authz.WithInstanceID(context.Background(), "instance1"),
id: "id1",
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
{
name: "change ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
instance.NewAzureADIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
nil,
"",
false,
idp.Options{},
)),
),
expectPush(
[]*repository.Event{
eventFromEventPusherWithInstanceID(
"instance1",
func() eventstore.Command {
t := true
event, _ := instance.NewAzureADIDPChangedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
"id1",
[]idp.AzureADIDPChanges{
idp.ChangeAzureADName("new name"),
idp.ChangeAzureADClientID("new clientID"),
idp.ChangeAzureADClientSecret(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("new clientSecret"),
}),
idp.ChangeAzureADScopes([]string{"openid", "profile"}),
idp.ChangeAzureADTenant("new tenant"),
idp.ChangeAzureADIsEmailVerified(true),
idp.ChangeAzureADOptions(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: AzureADProvider{
Name: "new name",
ClientID: "new clientID",
ClientSecret: "new clientSecret",
Scopes: []string{"openid", "profile"},
Tenant: "new tenant",
EmailVerified: true,
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idpConfigEncryption: tt.fields.secretCrypto,
}
got, err := c.UpdateInstanceAzureADProvider(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_AddInstanceGitHubIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore

View File

@ -131,6 +131,45 @@ func (c *Commands) UpdateOrgJWTProvider(ctx context.Context, resourceOwner, id s
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddOrgAzureADProvider(ctx context.Context, resourceOwner string, provider AzureADProvider) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
id, err := c.idGenerator.Next()
if err != nil {
return "", nil, err
}
writeModel := NewAzureADOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgAzureADProvider(orgAgg, writeModel, provider))
if err != nil {
return "", nil, err
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return "", nil, err
}
return id, pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) UpdateOrgAzureADProvider(ctx context.Context, resourceOwner, id string, provider AzureADProvider) (*domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
writeModel := NewAzureADOrgIDPWriteModel(resourceOwner, id)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareUpdateOrgAzureADProvider(orgAgg, writeModel, provider))
if err != nil {
return nil, err
}
if len(cmds) == 0 {
// no change, so return directly
return &domain.ObjectDetails{
Sequence: writeModel.ProcessedSequence,
EventDate: writeModel.ChangeDate,
ResourceOwner: writeModel.ResourceOwner,
}, nil
}
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
if err != nil {
return nil, err
}
return pushedEventsToObjectDetails(pushedEvents), nil
}
func (c *Commands) AddOrgGitHubProvider(ctx context.Context, resourceOwner string, provider GitHubProvider) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner)
@ -700,6 +739,92 @@ func (c *Commands) prepareUpdateOrgJWTProvider(a *org.Aggregate, writeModel *Org
}
}
func (c *Commands) prepareAddOrgAzureADProvider(a *org.Aggregate, writeModel *OrgAzureADIDPWriteModel, provider AzureADProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-sdf3g", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Fhbr2", "Errors.Invalid.Argument")
}
if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-Dzh3g", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
}
writeModel.AppendEvents(events...)
if err = writeModel.Reduce(); err != nil {
return nil, err
}
secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption)
if err != nil {
return nil, err
}
return []eventstore.Command{
org.NewAzureADIDPAddedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
secret,
provider.Scopes,
provider.Tenant,
provider.EmailVerified,
provider.IDPOptions,
),
}, nil
}, nil
}
}
func (c *Commands) prepareUpdateOrgAzureADProvider(a *org.Aggregate, writeModel *OrgAzureADIDPWriteModel, provider AzureADProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if writeModel.ID = strings.TrimSpace(writeModel.ID); writeModel.ID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-SAgh2", "Errors.Invalid.Argument")
}
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-fh3h1", "Errors.Invalid.Argument")
}
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
return nil, caos_errs.ThrowInvalidArgument(nil, "ORG-dmitg", "Errors.Invalid.Argument")
}
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
events, err := filter(ctx, writeModel.Query())
if err != nil {
return nil, err
}
writeModel.AppendEvents(events...)
if err = writeModel.Reduce(); err != nil {
return nil, err
}
if !writeModel.State.Exists() {
return nil, caos_errs.ThrowNotFound(nil, "ORG-BHz3q", "Errors.Org.IDPConfig.NotExisting")
}
event, err := writeModel.NewChangedEvent(
ctx,
&a.Aggregate,
writeModel.ID,
provider.Name,
provider.ClientID,
provider.ClientSecret,
c.idpConfigEncryption,
provider.Scopes,
provider.Tenant,
provider.EmailVerified,
provider.IDPOptions,
)
if err != nil || event == nil {
return nil, err
}
return []eventstore.Command{event}, nil
}, nil
}
}
func (c *Commands) prepareAddOrgGitHubProvider(a *org.Aggregate, writeModel *OrgGitHubIDPWriteModel, provider GitHubProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) {
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {

View File

@ -281,6 +281,86 @@ func (wm *OrgJWTIDPWriteModel) NewChangedEvent(
return org.NewJWTIDPChangedEvent(ctx, aggregate, id, changes)
}
type OrgAzureADIDPWriteModel struct {
AzureADIDPWriteModel
}
func NewAzureADOrgIDPWriteModel(orgID, id string) *OrgAzureADIDPWriteModel {
return &OrgAzureADIDPWriteModel{
AzureADIDPWriteModel{
WriteModel: eventstore.WriteModel{
AggregateID: orgID,
ResourceOwner: orgID,
},
ID: id,
},
}
}
func (wm *OrgAzureADIDPWriteModel) Reduce() error {
return wm.AzureADIDPWriteModel.Reduce()
}
func (wm *OrgAzureADIDPWriteModel) AppendEvents(events ...eventstore.Event) {
for _, event := range events {
switch e := event.(type) {
case *org.AzureADIDPAddedEvent:
wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPAddedEvent)
case *org.AzureADIDPChangedEvent:
wm.AzureADIDPWriteModel.AppendEvents(&e.AzureADIDPChangedEvent)
case *org.IDPRemovedEvent:
wm.AzureADIDPWriteModel.AppendEvents(&e.RemovedEvent)
default:
wm.AzureADIDPWriteModel.AppendEvents(e)
}
}
}
func (wm *OrgAzureADIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(org.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(
org.AzureADIDPAddedEventType,
org.AzureADIDPChangedEventType,
org.IDPRemovedEventType,
).
EventData(map[string]interface{}{"id": wm.ID}).
Builder()
}
func (wm *OrgAzureADIDPWriteModel) NewChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID,
clientSecretString string,
secretCrypto crypto.Crypto,
scopes []string,
tenant string,
isEmailVerified bool,
options idp.Options,
) (*org.AzureADIDPChangedEvent, error) {
changes, err := wm.AzureADIDPWriteModel.NewChanges(
name,
clientID,
clientSecretString,
secretCrypto,
scopes,
tenant,
isEmailVerified,
options,
)
if err != nil || len(changes) == 0 {
return nil, err
}
return org.NewAzureADIDPChangedEvent(ctx, aggregate, id, changes)
}
type OrgGitHubIDPWriteModel struct {
GitHubIDPWriteModel
}
@ -732,6 +812,8 @@ func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
wm.IDPRemoveWriteModel.AppendEvents(&e.OIDCIDPAddedEvent)
case *org.JWTIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.JWTIDPAddedEvent)
case *org.AzureADIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.AzureADIDPAddedEvent)
case *org.GitHubIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.GitHubIDPAddedEvent)
case *org.GitHubEnterpriseIDPAddedEvent:
@ -766,6 +848,7 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
org.OAuthIDPAddedEventType,
org.OIDCIDPAddedEventType,
org.JWTIDPAddedEventType,
org.AzureADIDPAddedEventType,
org.GitHubIDPAddedEventType,
org.GitHubEnterpriseIDPAddedEventType,
org.GitLabIDPAddedEventType,

View File

@ -648,6 +648,432 @@ func TestCommandSide_UpdateOrgGenericOAuthIDP(t *testing.T) {
}
}
func TestCommandSide_AddOrgAzureADIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
idGenerator id.Generator
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
resourceOwner string
provider AzureADProvider
}
type res struct {
id string
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: AzureADProvider{},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-sdf3g", ""))
},
},
},
{
"invalid client id",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: AzureADProvider{
Name: "name",
},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Fhbr2", ""))
},
},
},
{
"invalid client secret",
fields{
eventstore: eventstoreExpect(t),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-Dzh3g", ""))
},
},
},
{
name: "ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
expectPush(
eventPusherToEvents(
org.NewAzureADIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
nil,
"",
false,
idp.Options{},
)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
provider: AzureADProvider{
Name: "name",
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.NewAzureADIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
[]string{"openid"},
"tenant",
true,
idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
)),
),
),
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
ClientSecret: "clientSecret",
Scopes: []string{"openid"},
Tenant: "tenant",
EmailVerified: true,
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
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.AddOrgAzureADProvider(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_UpdateOrgAzureADIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore
secretCrypto crypto.EncryptionAlgorithm
}
type args struct {
ctx context.Context
resourceOwner string
id string
provider AzureADProvider
}
type res struct {
want *domain.ObjectDetails
err func(error) bool
}
tests := []struct {
name string
fields fields
args args
res res
}{
{
"invalid id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
provider: AzureADProvider{},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-SAgh2", ""))
},
},
},
{
"invalid name",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: AzureADProvider{},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-fh3h1", ""))
},
},
},
{
"invalid client id",
fields{
eventstore: eventstoreExpect(t),
},
args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: AzureADProvider{
Name: "name",
},
},
res{
err: func(err error) bool {
return errors.Is(err, caos_errors.ThrowInvalidArgument(nil, "ORG-dmitg", ""))
},
},
},
{
name: "not found",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
},
},
res: res{
err: caos_errors.IsNotFound,
},
},
{
name: "no changes",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
org.NewAzureADIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
nil,
"",
false,
idp.Options{},
)),
),
),
},
args: args{
ctx: context.Background(),
resourceOwner: "org1",
id: "id1",
provider: AzureADProvider{
Name: "name",
ClientID: "clientID",
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
{
name: "change ok",
fields: fields{
eventstore: eventstoreExpect(t,
expectFilter(
eventFromEventPusher(
org.NewAzureADIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
"name",
"clientID",
&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("clientSecret"),
},
nil,
"",
false,
idp.Options{},
)),
),
expectPush(
eventPusherToEvents(
func() eventstore.Command {
t := true
event, _ := org.NewAzureADIDPChangedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
"id1",
[]idp.AzureADIDPChanges{
idp.ChangeAzureADName("new name"),
idp.ChangeAzureADClientID("new clientID"),
idp.ChangeAzureADClientSecret(&crypto.CryptoValue{
CryptoType: crypto.TypeEncryption,
Algorithm: "enc",
KeyID: "id",
Crypted: []byte("new clientSecret"),
}),
idp.ChangeAzureADScopes([]string{"openid", "profile"}),
idp.ChangeAzureADTenant("new tenant"),
idp.ChangeAzureADIsEmailVerified(true),
idp.ChangeAzureADOptions(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: AzureADProvider{
Name: "new name",
ClientID: "new clientID",
ClientSecret: "new clientSecret",
Scopes: []string{"openid", "profile"},
Tenant: "new tenant",
EmailVerified: true,
IDPOptions: idp.Options{
IsCreationAllowed: true,
IsLinkingAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
},
},
},
res: res{
want: &domain.ObjectDetails{ResourceOwner: "org1"},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Commands{
eventstore: tt.fields.eventstore,
idpConfigEncryption: tt.fields.secretCrypto,
}
got, err := c.UpdateOrgAzureADProvider(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_AddOrgGitHubIDP(t *testing.T) {
type fields struct {
eventstore *eventstore.Eventstore

View File

@ -15,7 +15,7 @@ import (
const (
authURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize"
tokenURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/token"
userinfoURL string = "https://graph.microsoft.com/oidc/userinfo"
userinfoURL string = "https://graph.microsoft.com/v1.0/me"
)
// TenantType are the well known tenant types to scope the users that can authenticate. TenantType is not an
@ -73,7 +73,7 @@ func WithOAuthOptions(opts ...oauth.ProviderOpts) ProviderOptions {
// New creates an AzureAD provider using the [oauth.Provider] (OAuth 2.0 generic provider).
// By default, it uses the [CommonTenant] and unverified emails.
func New(name, clientID, clientSecret, redirectURI string, opts ...ProviderOptions) (*Provider, error) {
func New(name, clientID, clientSecret, redirectURI string, scopes []string, opts ...ProviderOptions) (*Provider, error) {
provider := &Provider{
tenant: CommonTenant,
options: make([]oauth.ProviderOpts, 0),
@ -81,7 +81,7 @@ func New(name, clientID, clientSecret, redirectURI string, opts ...ProviderOptio
for _, opt := range opts {
opt(provider)
}
config := newConfig(provider.tenant, clientID, clientSecret, redirectURI, []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail})
config := newConfig(provider.tenant, clientID, clientSecret, redirectURI, scopes)
rp, err := oauth.New(
config,
name,
@ -121,34 +121,38 @@ func newConfig(tenant TenantType, clientID, secret, callbackURL string, scopes [
// AzureAD does not return an `email_verified` claim.
// The verification can be automatically activated on the provider ([WithEmailVerified])
type User struct {
Sub string `json:"sub"`
FamilyName string `json:"family_name"`
GivenName string `json:"given_name"`
Name string `json:"name"`
PreferredUsername string `json:"preferred_username"`
Email domain.EmailAddress `json:"email"`
Picture string `json:"picture"`
ID string `json:"id"`
BusinessPhones []domain.PhoneNumber `json:"businessPhones"`
DisplayName string `json:"displayName"`
FirstName string `json:"givenName"`
JobTitle string `json:"jobTitle"`
Email domain.EmailAddress `json:"mail"`
MobilePhone domain.PhoneNumber `json:"mobilePhone"`
OfficeLocation string `json:"officeLocation"`
PreferredLanguage string `json:"preferredLanguage"`
LastName string `json:"surname"`
UserPrincipalName string `json:"userPrincipalName"`
isEmailVerified bool
}
// GetID is an implementation of the [idp.User] interface.
func (u *User) GetID() string {
return u.Sub
return u.ID
}
// GetFirstName is an implementation of the [idp.User] interface.
func (u *User) GetFirstName() string {
return u.GivenName
return u.FirstName
}
// GetLastName is an implementation of the [idp.User] interface.
func (u *User) GetLastName() string {
return u.FamilyName
return u.LastName
}
// GetDisplayName is an implementation of the [idp.User] interface.
func (u *User) GetDisplayName() string {
return u.Name
return u.DisplayName
}
// GetNickname is an implementation of the [idp.User] interface.
@ -159,11 +163,16 @@ func (u *User) GetNickname() string {
// GetPreferredUsername is an implementation of the [idp.User] interface.
func (u *User) GetPreferredUsername() string {
return u.PreferredUsername
return u.UserPrincipalName
}
// GetEmail is an implementation of the [idp.User] interface.
func (u *User) GetEmail() domain.EmailAddress {
if u.Email == "" {
// if the user used a social login on Azure as well, the email will be empty
// but is used as username
return domain.EmailAddress(u.UserPrincipalName)
}
return u.Email
}
@ -188,10 +197,8 @@ func (u *User) IsPhoneVerified() bool {
}
// GetPreferredLanguage is an implementation of the [idp.User] interface.
// It returns [language.Und] because AzureAD does not provide the user's language
func (u *User) GetPreferredLanguage() language.Tag {
// AzureAD does not provide the user's language
return language.Und
return language.Make(u.PreferredLanguage)
}
// GetProfile is an implementation of the [idp.User] interface.
@ -202,5 +209,5 @@ func (u *User) GetProfile() string {
// GetAvatarURL is an implementation of the [idp.User] interface.
func (u *User) GetAvatarURL() string {
return u.Picture
return ""
}

View File

@ -7,6 +7,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v2/pkg/client/rp"
openid "github.com/zitadel/oidc/v2/pkg/oidc"
"github.com/zitadel/zitadel/internal/idp"
"github.com/zitadel/zitadel/internal/idp/providers/oauth"
@ -19,6 +20,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID string
clientSecret string
redirectURI string
scopes []string
options []ProviderOptions
}
tests := []struct {
@ -34,7 +36,7 @@ func TestProvider_BeginAuth(t *testing.T) {
redirectURI: "redirectURI",
},
want: &oidc.Session{
AuthURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
AuthURL: "https://login.microsoftonline.com/common/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
},
},
{
@ -48,7 +50,22 @@ func TestProvider_BeginAuth(t *testing.T) {
},
},
want: &oidc.Session{
AuthURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
AuthURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=redirectURI&response_type=code&scope=openid&state=testState",
},
},
{
name: "custom scopes",
fields: fields{
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
scopes: []string{openid.ScopeOpenID, openid.ScopeProfile, "user"},
options: []ProviderOptions{
WithTenant(ConsumersTenant),
},
},
want: &oidc.Session{
AuthURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&prompt=select_account&redirect_uri=redirectURI&response_type=code&scope=openid+profile+user&state=testState",
},
},
}
@ -57,7 +74,7 @@ func TestProvider_BeginAuth(t *testing.T) {
a := assert.New(t)
r := require.New(t)
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.options...)
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...)
r.NoError(err)
session, err := provider.BeginAuth(context.Background(), "testState")
@ -74,6 +91,7 @@ func TestProvider_Options(t *testing.T) {
clientID string
clientSecret string
redirectURI string
scopes []string
options []ProviderOptions
}
type want struct {
@ -98,6 +116,7 @@ func TestProvider_Options(t *testing.T) {
clientID: "clientID",
clientSecret: "clientSecret",
redirectURI: "redirectURI",
scopes: nil,
options: nil,
},
want: want{
@ -146,7 +165,7 @@ func TestProvider_Options(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
a := assert.New(t)
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.options...)
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...)
require.NoError(t, err)
a.Equal(tt.want.name, provider.Name())

View File

@ -25,6 +25,7 @@ func TestSession_FetchUser(t *testing.T) {
clientID string
clientSecret string
redirectURI string
scopes []string
httpMock func()
options []ProviderOptions
authURL string
@ -61,7 +62,7 @@ func TestSession_FetchUser(t *testing.T) {
redirectURI: "redirectURI",
httpMock: func() {
gock.New("https://graph.microsoft.com").
Get("/oidc/userinfo").
Get("/v1.0/me").
Reply(200).
JSON(userinfo())
},
@ -82,7 +83,7 @@ func TestSession_FetchUser(t *testing.T) {
redirectURI: "redirectURI",
httpMock: func() {
gock.New("https://graph.microsoft.com").
Get("/oidc/userinfo").
Get("/v1.0/me").
Reply(http.StatusInternalServerError)
},
authURL: "https://login.microsoftonline.com/consumers/oauth2/v2.0/authorize?client_id=clientID&redirect_uri=redirectURI&response_type=code&scope=openid+profile+email&state=testState",
@ -119,7 +120,7 @@ func TestSession_FetchUser(t *testing.T) {
redirectURI: "redirectURI",
httpMock: func() {
gock.New("https://graph.microsoft.com").
Get("/oidc/userinfo").
Get("/v1.0/me").
Reply(200).
JSON(userinfo())
},
@ -145,16 +146,20 @@ func TestSession_FetchUser(t *testing.T) {
},
want: want{
user: &User{
Sub: "sub",
FamilyName: "lastname",
GivenName: "firstname",
Name: "firstname lastname",
PreferredUsername: "username",
ID: "id",
BusinessPhones: []domain.PhoneNumber{"phone1", "phone2"},
DisplayName: "firstname lastname",
FirstName: "firstname",
JobTitle: "title",
Email: "email",
Picture: "picture",
MobilePhone: "mobile",
OfficeLocation: "office",
PreferredLanguage: "en",
LastName: "lastname",
UserPrincipalName: "username",
isEmailVerified: false,
},
id: "sub",
id: "id",
firstName: "firstname",
lastName: "lastname",
displayName: "firstname lastname",
@ -164,8 +169,7 @@ func TestSession_FetchUser(t *testing.T) {
isEmailVerified: false,
phone: "",
isPhoneVerified: false,
preferredLanguage: language.Und,
avatarURL: "picture",
preferredLanguage: language.English,
profile: "",
},
},
@ -180,7 +184,7 @@ func TestSession_FetchUser(t *testing.T) {
},
httpMock: func() {
gock.New("https://graph.microsoft.com").
Get("/oidc/userinfo").
Get("/v1.0/me").
Reply(200).
JSON(userinfo())
},
@ -206,16 +210,20 @@ func TestSession_FetchUser(t *testing.T) {
},
want: want{
user: &User{
Sub: "sub",
FamilyName: "lastname",
GivenName: "firstname",
Name: "firstname lastname",
PreferredUsername: "username",
ID: "id",
BusinessPhones: []domain.PhoneNumber{"phone1", "phone2"},
DisplayName: "firstname lastname",
FirstName: "firstname",
JobTitle: "title",
Email: "email",
Picture: "picture",
MobilePhone: "mobile",
OfficeLocation: "office",
PreferredLanguage: "en",
LastName: "lastname",
UserPrincipalName: "username",
isEmailVerified: true,
},
id: "sub",
id: "id",
firstName: "firstname",
lastName: "lastname",
displayName: "firstname lastname",
@ -225,8 +233,7 @@ func TestSession_FetchUser(t *testing.T) {
isEmailVerified: true,
phone: "",
isPhoneVerified: false,
preferredLanguage: language.Und,
avatarURL: "picture",
preferredLanguage: language.English,
profile: "",
},
},
@ -237,7 +244,7 @@ func TestSession_FetchUser(t *testing.T) {
tt.fields.httpMock()
a := assert.New(t)
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.options...)
provider, err := New(tt.fields.name, tt.fields.clientID, tt.fields.clientSecret, tt.fields.redirectURI, tt.fields.scopes, tt.fields.options...)
require.NoError(t, err)
session := &oauth.Session{
@ -272,15 +279,18 @@ func TestSession_FetchUser(t *testing.T) {
}
}
func userinfo() oidc.UserInfoSetter {
userinfo := oidc.NewUserInfo()
userinfo.SetSubject("sub")
userinfo.SetName("firstname lastname")
userinfo.SetPreferredUsername("username")
userinfo.SetNickname("nickname")
userinfo.SetEmail("email", false) // azure add does not send the email_verified claim
userinfo.SetPicture("picture")
userinfo.SetGivenName("firstname")
userinfo.SetFamilyName("lastname")
return userinfo
func userinfo() *User {
return &User{
ID: "id",
BusinessPhones: []domain.PhoneNumber{"phone1", "phone2"},
DisplayName: "firstname lastname",
FirstName: "firstname",
JobTitle: "title",
Email: "email",
MobilePhone: "mobile",
OfficeLocation: "office",
PreferredLanguage: "en",
LastName: "lastname",
UserPrincipalName: "username",
}
}

View File

@ -37,6 +37,7 @@ type IDPTemplate struct {
*OAuthIDPTemplate
*OIDCIDPTemplate
*JWTIDPTemplate
*AzureADIDPTemplate
*GitHubIDPTemplate
*GitHubEnterpriseIDPTemplate
*GitLabIDPTemplate
@ -77,6 +78,15 @@ type JWTIDPTemplate struct {
Endpoint string
}
type AzureADIDPTemplate struct {
IDPID string
ClientID string
ClientSecret *crypto.CryptoValue
Scopes database.StringArray
Tenant string
IsEmailVerified bool
}
type GitHubIDPTemplate struct {
IDPID string
ClientID string
@ -301,6 +311,41 @@ var (
}
)
var (
azureadIdpTemplateTable = table{
name: projection.IDPTemplateAzureADTable,
instanceIDCol: projection.AzureADInstanceIDCol,
}
AzureADIDCol = Column{
name: projection.AzureADIDCol,
table: azureadIdpTemplateTable,
}
AzureADInstanceIDCol = Column{
name: projection.AzureADInstanceIDCol,
table: azureadIdpTemplateTable,
}
AzureADClientIDCol = Column{
name: projection.AzureADClientIDCol,
table: azureadIdpTemplateTable,
}
AzureADClientSecretCol = Column{
name: projection.AzureADClientSecretCol,
table: azureadIdpTemplateTable,
}
AzureADScopesCol = Column{
name: projection.AzureADScopesCol,
table: azureadIdpTemplateTable,
}
AzureADTenantCol = Column{
name: projection.AzureADTenantCol,
table: azureadIdpTemplateTable,
}
AzureADIsEmailVerified = Column{
name: projection.AzureADIsEmailVerified,
table: azureadIdpTemplateTable,
}
)
var (
githubIdpTemplateTable = table{
name: projection.IDPTemplateGitHubTable,
@ -683,6 +728,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
JWTEndpointCol.identifier(),
JWTKeysEndpointCol.identifier(),
JWTHeaderNameCol.identifier(),
// azure
AzureADIDCol.identifier(),
AzureADClientIDCol.identifier(),
AzureADClientSecretCol.identifier(),
AzureADScopesCol.identifier(),
AzureADTenantCol.identifier(),
AzureADIsEmailVerified.identifier(),
// github
GitHubIDCol.identifier(),
GitHubClientIDCol.identifier(),
@ -739,6 +791,7 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
LeftJoin(join(OIDCIDCol, IDPTemplateIDCol)).
LeftJoin(join(JWTIDCol, IDPTemplateIDCol)).
LeftJoin(join(AzureADIDCol, IDPTemplateIDCol)).
LeftJoin(join(GitHubIDCol, IDPTemplateIDCol)).
LeftJoin(join(GitHubEnterpriseIDCol, IDPTemplateIDCol)).
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
@ -772,6 +825,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
jwtKeysEndpoint := sql.NullString{}
jwtHeaderName := sql.NullString{}
azureadID := sql.NullString{}
azureadClientID := sql.NullString{}
azureadClientSecret := new(crypto.CryptoValue)
azureadScopes := database.StringArray{}
azureadTenant := sql.NullString{}
azureadIsEmailVerified := sql.NullBool{}
githubID := sql.NullString{}
githubClientID := sql.NullString{}
githubClientSecret := new(crypto.CryptoValue)
@ -859,6 +919,13 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
&jwtEndpoint,
&jwtKeysEndpoint,
&jwtHeaderName,
// azure
&azureadID,
&azureadClientID,
&azureadClientSecret,
&azureadScopes,
&azureadTenant,
&azureadIsEmailVerified,
// github
&githubID,
&githubClientID,
@ -951,6 +1018,16 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
Endpoint: jwtEndpoint.String,
}
}
if azureadID.Valid {
idpTemplate.AzureADIDPTemplate = &AzureADIDPTemplate{
IDPID: azureadID.String,
ClientID: azureadClientID.String,
ClientSecret: azureadClientSecret,
Scopes: azureadScopes,
Tenant: azureadTenant.String,
IsEmailVerified: azureadIsEmailVerified.Bool,
}
}
if githubID.Valid {
idpTemplate.GitHubIDPTemplate = &GitHubIDPTemplate{
IDPID: githubID.String,
@ -1064,6 +1141,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
JWTEndpointCol.identifier(),
JWTKeysEndpointCol.identifier(),
JWTHeaderNameCol.identifier(),
// azure
AzureADIDCol.identifier(),
AzureADClientIDCol.identifier(),
AzureADClientSecretCol.identifier(),
AzureADScopesCol.identifier(),
AzureADTenantCol.identifier(),
AzureADIsEmailVerified.identifier(),
// github
GitHubIDCol.identifier(),
GitHubClientIDCol.identifier(),
@ -1121,6 +1205,7 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
LeftJoin(join(OAuthIDCol, IDPTemplateIDCol)).
LeftJoin(join(OIDCIDCol, IDPTemplateIDCol)).
LeftJoin(join(JWTIDCol, IDPTemplateIDCol)).
LeftJoin(join(AzureADIDCol, IDPTemplateIDCol)).
LeftJoin(join(GitHubIDCol, IDPTemplateIDCol)).
LeftJoin(join(GitHubEnterpriseIDCol, IDPTemplateIDCol)).
LeftJoin(join(GitLabIDCol, IDPTemplateIDCol)).
@ -1157,6 +1242,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
jwtKeysEndpoint := sql.NullString{}
jwtHeaderName := sql.NullString{}
azureadID := sql.NullString{}
azureadClientID := sql.NullString{}
azureadClientSecret := new(crypto.CryptoValue)
azureadScopes := database.StringArray{}
azureadTenant := sql.NullString{}
azureadIsEmailVerified := sql.NullBool{}
githubID := sql.NullString{}
githubClientID := sql.NullString{}
githubClientSecret := new(crypto.CryptoValue)
@ -1244,6 +1336,13 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
&jwtEndpoint,
&jwtKeysEndpoint,
&jwtHeaderName,
// azure
&azureadID,
&azureadClientID,
&azureadClientSecret,
&azureadScopes,
&azureadTenant,
&azureadIsEmailVerified,
// github
&githubID,
&githubClientID,
@ -1335,6 +1434,16 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
Endpoint: jwtEndpoint.String,
}
}
if azureadID.Valid {
idpTemplate.AzureADIDPTemplate = &AzureADIDPTemplate{
IDPID: azureadID.String,
ClientID: azureadClientID.String,
ClientSecret: azureadClientSecret,
Scopes: azureadScopes,
Tenant: azureadTenant.String,
IsEmailVerified: azureadIsEmailVerified.Bool,
}
}
if githubID.Valid {
idpTemplate.GitHubIDPTemplate = &GitHubIDPTemplate{
IDPID: githubID.String,

View File

@ -49,6 +49,13 @@ var (
` projections.idp_templates3_jwt.jwt_endpoint,` +
` projections.idp_templates3_jwt.keys_endpoint,` +
` projections.idp_templates3_jwt.header_name,` +
// azure
` projections.idp_templates3_azure.idp_id,` +
` projections.idp_templates3_azure.client_id,` +
` projections.idp_templates3_azure.client_secret,` +
` projections.idp_templates3_azure.scopes,` +
` projections.idp_templates3_azure.tenant,` +
` projections.idp_templates3_azure.is_email_verified,` +
// github
` projections.idp_templates3_github.idp_id,` +
` projections.idp_templates3_github.client_id,` +
@ -105,6 +112,7 @@ var (
` LEFT JOIN projections.idp_templates3_oauth2 ON projections.idp_templates3.id = projections.idp_templates3_oauth2.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_oauth2.instance_id` +
` LEFT JOIN projections.idp_templates3_oidc ON projections.idp_templates3.id = projections.idp_templates3_oidc.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_oidc.instance_id` +
` LEFT JOIN projections.idp_templates3_jwt ON projections.idp_templates3.id = projections.idp_templates3_jwt.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_jwt.instance_id` +
` LEFT JOIN projections.idp_templates3_azure ON projections.idp_templates3.id = projections.idp_templates3_azure.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_azure.instance_id` +
` LEFT JOIN projections.idp_templates3_github ON projections.idp_templates3.id = projections.idp_templates3_github.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_github.instance_id` +
` LEFT JOIN projections.idp_templates3_github_enterprise ON projections.idp_templates3.id = projections.idp_templates3_github_enterprise.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_github_enterprise.instance_id` +
` LEFT JOIN projections.idp_templates3_gitlab ON projections.idp_templates3.id = projections.idp_templates3_gitlab.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_gitlab.instance_id` +
@ -147,6 +155,13 @@ var (
"jwt_endpoint",
"keys_endpoint",
"header_name",
// azure
"idp_id",
"client_id",
"client_secret",
"scopes",
"tenant",
"is_email_verified",
// github config
"idp_id",
"client_id",
@ -234,6 +249,13 @@ var (
` projections.idp_templates3_jwt.jwt_endpoint,` +
` projections.idp_templates3_jwt.keys_endpoint,` +
` projections.idp_templates3_jwt.header_name,` +
// azure
` projections.idp_templates3_azure.idp_id,` +
` projections.idp_templates3_azure.client_id,` +
` projections.idp_templates3_azure.client_secret,` +
` projections.idp_templates3_azure.scopes,` +
` projections.idp_templates3_azure.tenant,` +
` projections.idp_templates3_azure.is_email_verified,` +
// github
` projections.idp_templates3_github.idp_id,` +
` projections.idp_templates3_github.client_id,` +
@ -291,6 +313,7 @@ var (
` LEFT JOIN projections.idp_templates3_oauth2 ON projections.idp_templates3.id = projections.idp_templates3_oauth2.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_oauth2.instance_id` +
` LEFT JOIN projections.idp_templates3_oidc ON projections.idp_templates3.id = projections.idp_templates3_oidc.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_oidc.instance_id` +
` LEFT JOIN projections.idp_templates3_jwt ON projections.idp_templates3.id = projections.idp_templates3_jwt.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_jwt.instance_id` +
` LEFT JOIN projections.idp_templates3_azure ON projections.idp_templates3.id = projections.idp_templates3_azure.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_azure.instance_id` +
` LEFT JOIN projections.idp_templates3_github ON projections.idp_templates3.id = projections.idp_templates3_github.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_github.instance_id` +
` LEFT JOIN projections.idp_templates3_github_enterprise ON projections.idp_templates3.id = projections.idp_templates3_github_enterprise.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_github_enterprise.instance_id` +
` LEFT JOIN projections.idp_templates3_gitlab ON projections.idp_templates3.id = projections.idp_templates3_gitlab.idp_id AND projections.idp_templates3.instance_id = projections.idp_templates3_gitlab.instance_id` +
@ -333,6 +356,13 @@ var (
"jwt_endpoint",
"keys_endpoint",
"header_name",
// azure
"idp_id",
"client_id",
"client_secret",
"scopes",
"tenant",
"is_email_verified",
// github config
"idp_id",
"client_id",
@ -460,6 +490,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -583,6 +620,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -703,6 +747,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"jwt",
"keys",
"header",
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -823,6 +874,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
"idp-id",
"client_id",
@ -942,6 +1000,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1061,6 +1126,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1181,6 +1253,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1300,6 +1379,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1438,6 +1524,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1587,6 +1680,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1734,6 +1834,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1856,6 +1963,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -1944,6 +2058,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -2032,6 +2153,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -2120,6 +2248,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil,
nil,
nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,
@ -2208,6 +2343,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"jwt",
"keys",
"header",
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github
nil,
nil,

View File

@ -21,6 +21,7 @@ const (
IDPTemplateOAuthTable = IDPTemplateTable + "_" + IDPTemplateOAuthSuffix
IDPTemplateOIDCTable = IDPTemplateTable + "_" + IDPTemplateOIDCSuffix
IDPTemplateJWTTable = IDPTemplateTable + "_" + IDPTemplateJWTSuffix
IDPTemplateAzureADTable = IDPTemplateTable + "_" + IDPTemplateAzureADSuffix
IDPTemplateGitHubTable = IDPTemplateTable + "_" + IDPTemplateGitHubSuffix
IDPTemplateGitHubEnterpriseTable = IDPTemplateTable + "_" + IDPTemplateGitHubEnterpriseSuffix
IDPTemplateGitLabTable = IDPTemplateTable + "_" + IDPTemplateGitLabSuffix
@ -31,6 +32,7 @@ const (
IDPTemplateOAuthSuffix = "oauth2"
IDPTemplateOIDCSuffix = "oidc"
IDPTemplateJWTSuffix = "jwt"
IDPTemplateAzureADSuffix = "azure"
IDPTemplateGitHubSuffix = "github"
IDPTemplateGitHubEnterpriseSuffix = "github_enterprise"
IDPTemplateGitLabSuffix = "gitlab"
@ -78,6 +80,14 @@ const (
JWTKeysEndpointCol = "keys_endpoint"
JWTHeaderNameCol = "header_name"
AzureADIDCol = "idp_id"
AzureADInstanceIDCol = "instance_id"
AzureADClientIDCol = "client_id"
AzureADClientSecretCol = "client_secret"
AzureADScopesCol = "scopes"
AzureADTenantCol = "tenant"
AzureADIsEmailVerified = "is_email_verified"
GitHubIDCol = "idp_id"
GitHubInstanceIDCol = "instance_id"
GitHubClientIDCol = "client_id"
@ -206,6 +216,19 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
IDPTemplateJWTSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(AzureADIDCol, crdb.ColumnTypeText),
crdb.NewColumn(AzureADInstanceIDCol, crdb.ColumnTypeText),
crdb.NewColumn(AzureADClientIDCol, crdb.ColumnTypeText),
crdb.NewColumn(AzureADClientSecretCol, crdb.ColumnTypeJSONB),
crdb.NewColumn(AzureADScopesCol, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(AzureADTenantCol, crdb.ColumnTypeText, crdb.Nullable()),
crdb.NewColumn(AzureADIsEmailVerified, crdb.ColumnTypeBool, crdb.Default(false)),
},
crdb.NewPrimaryKey(AzureADInstanceIDCol, AzureADIDCol),
IDPTemplateAzureADSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()),
),
crdb.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(GitHubIDCol, crdb.ColumnTypeText),
crdb.NewColumn(GitHubInstanceIDCol, crdb.ColumnTypeText),
@ -352,6 +375,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
Event: instance.IDPJWTConfigChangedEventType,
Reduce: p.reduceOldJWTConfigChanged,
},
{
Event: instance.AzureADIDPAddedEventType,
Reduce: p.reduceAzureADIDPAdded,
},
{
Event: instance.AzureADIDPChangedEventType,
Reduce: p.reduceAzureADIDPChanged,
},
{
Event: instance.GitHubIDPAddedEventType,
Reduce: p.reduceGitHubIDPAdded,
@ -429,7 +460,6 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
Event: org.OIDCIDPChangedEventType,
Reduce: p.reduceOIDCIDPChanged,
},
{
Event: org.JWTIDPAddedEventType,
Reduce: p.reduceJWTIDPAdded,
@ -462,6 +492,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
Event: org.IDPJWTConfigChangedEventType,
Reduce: p.reduceOldJWTConfigChanged,
},
{
Event: org.AzureADIDPAddedEventType,
Reduce: p.reduceAzureADIDPAdded,
},
{
Event: org.AzureADIDPChangedEventType,
Reduce: p.reduceAzureADIDPChanged,
},
{
Event: org.GitHubIDPAddedEventType,
Reduce: p.reduceGitHubIDPAdded,
@ -1049,6 +1087,96 @@ func (p *idpTemplateProjection) reduceOldJWTConfigChanged(event eventstore.Event
), nil
}
func (p *idpTemplateProjection) reduceAzureADIDPAdded(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.AzureADIDPAddedEvent
var idpOwnerType domain.IdentityProviderType
switch e := event.(type) {
case *org.AzureADIDPAddedEvent:
idpEvent = e.AzureADIDPAddedEvent
idpOwnerType = domain.IdentityProviderTypeOrg
case *instance.AzureADIDPAddedEvent:
idpEvent = e.AzureADIDPAddedEvent
idpOwnerType = domain.IdentityProviderTypeSystem
default:
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-x9a022b", "reduce.wrong.event.type %v", []eventstore.EventType{org.AzureADIDPAddedEventType, instance.AzureADIDPAddedEventType})
}
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.IDPTypeAzureAD),
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(AzureADIDCol, idpEvent.ID),
handler.NewCol(AzureADInstanceIDCol, idpEvent.Aggregate().InstanceID),
handler.NewCol(AzureADClientIDCol, idpEvent.ClientID),
handler.NewCol(AzureADClientSecretCol, idpEvent.ClientSecret),
handler.NewCol(AzureADScopesCol, database.StringArray(idpEvent.Scopes)),
handler.NewCol(AzureADTenantCol, idpEvent.Tenant),
handler.NewCol(AzureADIsEmailVerified, idpEvent.IsEmailVerified),
},
crdb.WithTableSuffix(IDPTemplateAzureADSuffix),
),
), nil
}
func (p *idpTemplateProjection) reduceAzureADIDPChanged(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.AzureADIDPChangedEvent
switch e := event.(type) {
case *org.AzureADIDPChangedEvent:
idpEvent = e.AzureADIDPChangedEvent
case *instance.AzureADIDPChangedEvent:
idpEvent = e.AzureADIDPChangedEvent
default:
return nil, errors.ThrowInvalidArgumentf(nil, "HANDL-p1582ks", "reduce.wrong.event.type %v", []eventstore.EventType{org.AzureADIDPChangedEventType, instance.AzureADIDPChangedEventType})
}
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),
},
),
)
githubCols := reduceAzureADIDPChangedColumns(idpEvent)
if len(githubCols) > 0 {
ops = append(ops,
crdb.AddUpdateStatement(
githubCols,
[]handler.Condition{
handler.NewCond(AzureADIDCol, idpEvent.ID),
handler.NewCond(AzureADInstanceIDCol, idpEvent.Aggregate().InstanceID),
},
crdb.WithTableSuffix(IDPTemplateAzureADSuffix),
),
)
}
return crdb.NewMultiStatement(
&idpEvent,
ops...,
), nil
}
func (p *idpTemplateProjection) reduceGitHubIDPAdded(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.GitHubIDPAddedEvent
var idpOwnerType domain.IdentityProviderType
@ -1723,6 +1851,26 @@ func reduceJWTIDPChangedColumns(idpEvent idp.JWTIDPChangedEvent) []handler.Colum
return jwtCols
}
func reduceAzureADIDPChangedColumns(idpEvent idp.AzureADIDPChangedEvent) []handler.Column {
azureADCols := make([]handler.Column, 0, 5)
if idpEvent.ClientID != nil {
azureADCols = append(azureADCols, handler.NewCol(AzureADClientIDCol, *idpEvent.ClientID))
}
if idpEvent.ClientSecret != nil {
azureADCols = append(azureADCols, handler.NewCol(AzureADClientSecretCol, *idpEvent.ClientSecret))
}
if idpEvent.Scopes != nil {
azureADCols = append(azureADCols, handler.NewCol(AzureADScopesCol, database.StringArray(idpEvent.Scopes)))
}
if idpEvent.Tenant != nil {
azureADCols = append(azureADCols, handler.NewCol(AzureADTenantCol, *idpEvent.Tenant))
}
if idpEvent.IsEmailVerified != nil {
azureADCols = append(azureADCols, handler.NewCol(AzureADIsEmailVerified, *idpEvent.IsEmailVerified))
}
return azureADCols
}
func reduceGitHubIDPChangedColumns(idpEvent idp.GitHubIDPChangedEvent) []handler.Column {
oauthCols := make([]handler.Column, 0, 3)
if idpEvent.ClientID != nil {

View File

@ -410,6 +410,330 @@ func TestIDPTemplateProjection_reducesOAuth(t *testing.T) {
}
}
func TestIDPTemplateProjection_reducesAzureAD(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 reduceAzureADIDPAdded minimal",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.AzureADIDPAddedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "name",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
}
}`),
), instance.AzureADIDPAddedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceAzureADIDPAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateInsertStmt,
expectedArgs: []interface{}{
"idp-id",
anyArg{},
anyArg{},
uint64(15),
"ro-id",
"instance-id",
domain.IDPStateActive,
"name",
domain.IdentityProviderTypeSystem,
domain.IDPTypeAzureAD,
false,
false,
false,
false,
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates3_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"client_id",
anyArg{},
database.StringArray(nil),
"",
false,
},
},
},
},
},
},
{
name: "instance reduceAzureADIDPAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.AzureADIDPAddedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "name",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"tenant": "tenant",
"isEmailVerified": true,
"scopes": ["profile"],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true
}`),
), instance.AzureADIDPAddedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceAzureADIDPAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateInsertStmt,
expectedArgs: []interface{}{
"idp-id",
anyArg{},
anyArg{},
uint64(15),
"ro-id",
"instance-id",
domain.IDPStateActive,
"name",
domain.IdentityProviderTypeSystem,
domain.IDPTypeAzureAD,
true,
true,
true,
true,
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates3_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"client_id",
anyArg{},
database.StringArray{"profile"},
"tenant",
true,
},
},
},
},
},
},
{
name: "org reduceAzureADIDPAdded",
args: args{
event: getEvent(testEvent(
repository.EventType(org.AzureADIDPAddedEventType),
org.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "name",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"tenant": "tenant",
"isEmailVerified": true,
"scopes": ["profile"],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true
}`),
), org.AzureADIDPAddedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceAzureADIDPAdded,
want: wantReduce{
aggregateType: eventstore.AggregateType("org"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateInsertStmt,
expectedArgs: []interface{}{
"idp-id",
anyArg{},
anyArg{},
uint64(15),
"ro-id",
"instance-id",
domain.IDPStateActive,
"name",
domain.IdentityProviderTypeOrg,
domain.IDPTypeAzureAD,
true,
true,
true,
true,
},
},
{
expectedStmt: "INSERT INTO projections.idp_templates3_azure (idp_id, instance_id, client_id, client_secret, scopes, tenant, is_email_verified) VALUES ($1, $2, $3, $4, $5, $6, $7)",
expectedArgs: []interface{}{
"idp-id",
"instance-id",
"client_id",
anyArg{},
database.StringArray{"profile"},
"tenant",
true,
},
},
},
},
},
},
{
name: "instance reduceAzureADIDPChanged minimal",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.AzureADIDPChangedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"isCreationAllowed": true,
"client_id": "id"
}`),
), instance.AzureADIDPChangedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceAzureADIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateUpdateMinimalStmt,
expectedArgs: []interface{}{
true,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates3_azure SET client_id = $1 WHERE (idp_id = $2) AND (instance_id = $3)",
expectedArgs: []interface{}{
"id",
"idp-id",
"instance-id",
},
},
},
},
},
},
{
name: "instance reduceAzureADIDPChanged",
args: args{
event: getEvent(testEvent(
repository.EventType(instance.AzureADIDPChangedEventType),
instance.AggregateType,
[]byte(`{
"id": "idp-id",
"name": "name",
"client_id": "client_id",
"client_secret": {
"cryptoType": 0,
"algorithm": "RSA-265",
"keyId": "key-id"
},
"tenant": "tenant",
"isEmailVerified": true,
"scopes": ["profile"],
"isCreationAllowed": true,
"isLinkingAllowed": true,
"isAutoCreation": true,
"isAutoUpdate": true
}`),
), instance.AzureADIDPChangedEventMapper),
},
reduce: (&idpTemplateProjection{}).reduceAzureADIDPChanged,
want: wantReduce{
aggregateType: eventstore.AggregateType("instance"),
sequence: 15,
previousSequence: 10,
executer: &testExecuter{
executions: []execution{
{
expectedStmt: idpTemplateUpdateStmt,
expectedArgs: []interface{}{
"name",
true,
true,
true,
true,
anyArg{},
uint64(15),
"idp-id",
"instance-id",
},
},
{
expectedStmt: "UPDATE projections.idp_templates3_azure SET (client_id, client_secret, scopes, tenant, is_email_verified) = ($1, $2, $3, $4, $5) WHERE (idp_id = $6) AND (instance_id = $7)",
expectedArgs: []interface{}{
"client_id",
anyArg{},
database.StringArray{"profile"},
"tenant",
true,
"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_reducesGitHub(t *testing.T) {
type args struct {
event func(t *testing.T) eventstore.Event

View File

@ -0,0 +1,164 @@
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 AzureADIDPAddedEvent struct {
eventstore.BaseEvent `json:"-"`
ID string `json:"id"`
Name string `json:"name,omitempty"`
ClientID string `json:"client_id,omitempty"`
ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Tenant string `json:"tenant,omitempty"`
IsEmailVerified bool `json:"isEmailVerified,omitempty"`
Options
}
func NewAzureADIDPAddedEvent(
base *eventstore.BaseEvent,
id,
name,
clientID string,
clientSecret *crypto.CryptoValue,
scopes []string,
tenant string,
isEmailVerified bool,
options Options,
) *AzureADIDPAddedEvent {
return &AzureADIDPAddedEvent{
BaseEvent: *base,
ID: id,
Name: name,
ClientID: clientID,
ClientSecret: clientSecret,
Scopes: scopes,
Tenant: tenant,
IsEmailVerified: isEmailVerified,
Options: options,
}
}
func (e *AzureADIDPAddedEvent) Data() interface{} {
return e
}
func (e *AzureADIDPAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func AzureADIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &AzureADIDPAddedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "IDP-Grh2g", "unable to unmarshal event")
}
return e, nil
}
type AzureADIDPChangedEvent struct {
eventstore.BaseEvent `json:"-"`
ID string `json:"id"`
Name *string `json:"name,omitempty"`
ClientID *string `json:"client_id,omitempty"`
ClientSecret *crypto.CryptoValue `json:"client_secret,omitempty"`
Scopes []string `json:"scopes,omitempty"`
Tenant *string `json:"tenant,omitempty"`
IsEmailVerified *bool `json:"isEmailVerified,omitempty"`
OptionChanges
}
func NewAzureADIDPChangedEvent(
base *eventstore.BaseEvent,
id string,
changes []AzureADIDPChanges,
) (*AzureADIDPChangedEvent, error) {
if len(changes) == 0 {
return nil, errors.ThrowPreconditionFailed(nil, "IDP-BH3dl", "Errors.NoChangesFound")
}
changedEvent := &AzureADIDPChangedEvent{
BaseEvent: *base,
ID: id,
}
for _, change := range changes {
change(changedEvent)
}
return changedEvent, nil
}
type AzureADIDPChanges func(*AzureADIDPChangedEvent)
func ChangeAzureADName(name string) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.Name = &name
}
}
func ChangeAzureADClientID(clientID string) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.ClientID = &clientID
}
}
func ChangeAzureADClientSecret(clientSecret *crypto.CryptoValue) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.ClientSecret = clientSecret
}
}
func ChangeAzureADOptions(options OptionChanges) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.OptionChanges = options
}
}
func ChangeAzureADScopes(scopes []string) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.Scopes = scopes
}
}
func ChangeAzureADTenant(tenant string) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.Tenant = &tenant
}
}
func ChangeAzureADIsEmailVerified(isEmailVerified bool) func(*AzureADIDPChangedEvent) {
return func(e *AzureADIDPChangedEvent) {
e.IsEmailVerified = &isEmailVerified
}
}
func (e *AzureADIDPChangedEvent) Data() interface{} {
return e
}
func (e *AzureADIDPChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
return nil
}
func AzureADIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e := &AzureADIDPChangedEvent{
BaseEvent: *eventstore.BaseEventFromRepo(event),
}
err := json.Unmarshal(event.Data, e)
if err != nil {
return nil, errors.ThrowInternal(err, "IDP-D3gjzh", "unable to unmarshal event")
}
return e, nil
}

View File

@ -76,6 +76,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, OIDCIDPChangedEventType, OIDCIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, JWTIDPAddedEventType, JWTIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, JWTIDPChangedEventType, JWTIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, AzureADIDPAddedEventType, AzureADIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, AzureADIDPChangedEventType, AzureADIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubIDPAddedEventType, GitHubIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubIDPChangedEventType, GitHubIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubEnterpriseIDPAddedEventType, GitHubEnterpriseIDPAddedEventMapper).

View File

@ -16,6 +16,8 @@ const (
OIDCIDPChangedEventType eventstore.EventType = "instance.idp.oidc.changed"
JWTIDPAddedEventType eventstore.EventType = "instance.idp.jwt.added"
JWTIDPChangedEventType eventstore.EventType = "instance.idp.jwt.changed"
AzureADIDPAddedEventType eventstore.EventType = "instance.idp.azure.added"
AzureADIDPChangedEventType eventstore.EventType = "instance.idp.azure.changed"
GitHubIDPAddedEventType eventstore.EventType = "instance.idp.github.added"
GitHubIDPChangedEventType eventstore.EventType = "instance.idp.github.changed"
GitHubEnterpriseIDPAddedEventType eventstore.EventType = "instance.idp.github_enterprise.added"
@ -271,6 +273,86 @@ func JWTIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error)
return &JWTIDPChangedEvent{JWTIDPChangedEvent: *e.(*idp.JWTIDPChangedEvent)}, nil
}
type AzureADIDPAddedEvent struct {
idp.AzureADIDPAddedEvent
}
func NewAzureADIDPAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID string,
clientSecret *crypto.CryptoValue,
scopes []string,
tenant string,
isEmailVerified bool,
options idp.Options,
) *AzureADIDPAddedEvent {
return &AzureADIDPAddedEvent{
AzureADIDPAddedEvent: *idp.NewAzureADIDPAddedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
AzureADIDPAddedEventType,
),
id,
name,
clientID,
clientSecret,
scopes,
tenant,
isEmailVerified,
options,
),
}
}
func AzureADIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.AzureADIDPAddedEventMapper(event)
if err != nil {
return nil, err
}
return &AzureADIDPAddedEvent{AzureADIDPAddedEvent: *e.(*idp.AzureADIDPAddedEvent)}, nil
}
type AzureADIDPChangedEvent struct {
idp.AzureADIDPChangedEvent
}
func NewAzureADIDPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
changes []idp.AzureADIDPChanges,
) (*AzureADIDPChangedEvent, error) {
changedEvent, err := idp.NewAzureADIDPChangedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
AzureADIDPChangedEventType,
),
id,
changes,
)
if err != nil {
return nil, err
}
return &AzureADIDPChangedEvent{AzureADIDPChangedEvent: *changedEvent}, nil
}
func AzureADIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.AzureADIDPChangedEventMapper(event)
if err != nil {
return nil, err
}
return &AzureADIDPChangedEvent{AzureADIDPChangedEvent: *e.(*idp.AzureADIDPChangedEvent)}, nil
}
type GitHubIDPAddedEvent struct {
idp.GitHubIDPAddedEvent
}

View File

@ -84,6 +84,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
RegisterFilterEventMapper(AggregateType, OIDCIDPChangedEventType, OIDCIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, JWTIDPAddedEventType, JWTIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, JWTIDPChangedEventType, JWTIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, AzureADIDPAddedEventType, AzureADIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, AzureADIDPChangedEventType, AzureADIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubIDPAddedEventType, GitHubIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubIDPChangedEventType, GitHubIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubEnterpriseIDPAddedEventType, GitHubEnterpriseIDPAddedEventMapper).

View File

@ -16,6 +16,8 @@ const (
OIDCIDPChangedEventType eventstore.EventType = "org.idp.oidc.changed"
JWTIDPAddedEventType eventstore.EventType = "org.idp.jwt.added"
JWTIDPChangedEventType eventstore.EventType = "org.idp.jwt.changed"
AzureADIDPAddedEventType eventstore.EventType = "org.idp.azure.added"
AzureADIDPChangedEventType eventstore.EventType = "org.idp.azure.changed"
GitHubIDPAddedEventType eventstore.EventType = "org.idp.github.added"
GitHubIDPChangedEventType eventstore.EventType = "org.idp.github.changed"
GitHubEnterpriseIDPAddedEventType eventstore.EventType = "org.idp.github_enterprise.added"
@ -271,6 +273,86 @@ func JWTIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error)
return &JWTIDPChangedEvent{JWTIDPChangedEvent: *e.(*idp.JWTIDPChangedEvent)}, nil
}
type AzureADIDPAddedEvent struct {
idp.AzureADIDPAddedEvent
}
func NewAzureADIDPAddedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id,
name,
clientID string,
clientSecret *crypto.CryptoValue,
scopes []string,
tenant string,
isEmailVerified bool,
options idp.Options,
) *AzureADIDPAddedEvent {
return &AzureADIDPAddedEvent{
AzureADIDPAddedEvent: *idp.NewAzureADIDPAddedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
AzureADIDPAddedEventType,
),
id,
name,
clientID,
clientSecret,
scopes,
tenant,
isEmailVerified,
options,
),
}
}
func AzureADIDPAddedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.AzureADIDPAddedEventMapper(event)
if err != nil {
return nil, err
}
return &AzureADIDPAddedEvent{AzureADIDPAddedEvent: *e.(*idp.AzureADIDPAddedEvent)}, nil
}
type AzureADIDPChangedEvent struct {
idp.AzureADIDPChangedEvent
}
func NewAzureADIDPChangedEvent(
ctx context.Context,
aggregate *eventstore.Aggregate,
id string,
changes []idp.AzureADIDPChanges,
) (*AzureADIDPChangedEvent, error) {
changedEvent, err := idp.NewAzureADIDPChangedEvent(
eventstore.NewBaseEventForPush(
ctx,
aggregate,
AzureADIDPChangedEventType,
),
id,
changes,
)
if err != nil {
return nil, err
}
return &AzureADIDPChangedEvent{AzureADIDPChangedEvent: *changedEvent}, nil
}
func AzureADIDPChangedEventMapper(event *repository.Event) (eventstore.Event, error) {
e, err := idp.AzureADIDPChangedEventMapper(event)
if err != nil {
return nil, err
}
return &AzureADIDPChangedEvent{AzureADIDPChangedEvent: *e.(*idp.AzureADIDPChangedEvent)}, nil
}
type GitHubIDPAddedEvent struct {
idp.GitHubIDPAddedEvent
}

View File

@ -1,3 +1,4 @@
package idp
type IDPConfig = isIDP_Config
type IsAzureADTenantType = isAzureADTenant_Type

View File

@ -1320,6 +1320,30 @@ service AdminService {
};
}
// Add a new Azure AD identity provider on the instance
rpc AddAzureADProvider(AddAzureADProviderRequest) returns (AddAzureADProviderResponse) {
option (google.api.http) = {
post: "/idps/azure"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "iam.idp.write"
};
}
// Change an existing Azure AD identity provider on the instance
rpc UpdateAzureADProvider(UpdateAzureADProviderRequest) returns (UpdateAzureADProviderResponse) {
option (google.api.http) = {
put: "/idps/azure/{id}"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "iam.idp.write"
};
}
// Add a new GitHub identity provider on the instance
rpc AddGitHubProvider(AddGitHubProviderRequest) returns (AddGitHubProviderResponse) {
option (google.api.http) = {
@ -4525,6 +4549,39 @@ message UpdateJWTProviderResponse {
zitadel.v1.ObjectDetails details = 1;
}
message AddAzureADProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_secret = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
// if not provided the `common` tenant will be used
zitadel.idp.v1.AzureADTenant tenant = 4;
bool email_verified = 5;
repeated string scopes = 6 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 7;
}
message AddAzureADProviderResponse {
zitadel.v1.ObjectDetails details = 1;
string id = 2;
}
message UpdateAzureADProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
// client_secret will only be updated if provided
string client_secret = 4 [(validate.rules).string = {max_len: 200}];
// if not provided the `common` tenant will be used
zitadel.idp.v1.AzureADTenant tenant = 5;
bool email_verified = 6;
repeated string scopes = 7 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 8;
}
message UpdateAzureADProviderResponse {
zitadel.v1.ObjectDetails details = 1;
}
message AddGitHubProviderRequest {
// GitHub will be used as default, if no name is provided
string name = 1 [(validate.rules).string = {max_len: 200}];

View File

@ -271,8 +271,10 @@ message ProviderConfig {
GitHubEnterpriseServerConfig github_es = 8;
GitLabConfig gitlab = 9;
GitLabSelfHostedConfig gitlab_self_hosted = 10;
AzureADConfig azure_ad = 11;
}
}
message OAuthConfig {
string client_id = 1;
string authorization_endpoint = 2;
@ -329,6 +331,13 @@ message LDAPConfig {
Options provider_options = 9;
}
message AzureADConfig {
string client_id = 1;
AzureADTenant tenant = 2;
bool email_verified = 3;
repeated string scopes = 4;
}
message Options {
bool is_linking_allowed = 1;
bool is_creation_allowed = 2;
@ -352,3 +361,15 @@ message LDAPAttributes {
string profile_attribute = 13 [(validate.rules).string = {max_len: 200}];
}
enum AzureADTenantType {
AZURE_AD_TENANT_TYPE_COMMON = 0;
AZURE_AD_TENANT_TYPE_ORGANISATIONS = 1;
AZURE_AD_TENANT_TYPE_CONSUMERS = 2;
}
message AzureADTenant {
oneof type {
AzureADTenantType tenant_type = 1;
string tenant_id = 2;
}
}

View File

@ -6536,6 +6536,30 @@ service ManagementService {
};
}
// Add a new Azure AD identity provider in the organisation
rpc AddAzureADProvider(AddAzureADProviderRequest) returns (AddAzureADProviderResponse) {
option (google.api.http) = {
post: "/idps/azure"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "org.idp.write"
};
}
// Change an existing Azure AD identity provider in the organisation
rpc UpdateAzureADProvider(UpdateAzureADProviderRequest) returns (UpdateAzureADProviderResponse) {
option (google.api.http) = {
put: "/idps/azure/{id}"
body: "*"
};
option (zitadel.v1.auth_option) = {
permission: "org.idp.write"
};
}
// Add a new GitHub identity provider in the organization
rpc AddGitHubProvider(AddGitHubProviderRequest) returns (AddGitHubProviderResponse) {
option (google.api.http) = {
@ -11199,6 +11223,39 @@ message UpdateJWTProviderResponse {
zitadel.v1.ObjectDetails details = 1;
}
message AddAzureADProviderRequest {
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_secret = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
// if not provided the `common` tenant will be used
zitadel.idp.v1.AzureADTenant tenant = 4;
bool email_verified = 5;
repeated string scopes = 6 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 7;
}
message AddAzureADProviderResponse {
zitadel.v1.ObjectDetails details = 1;
string id = 2;
}
message UpdateAzureADProviderRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
string client_id = 3 [(validate.rules).string = {min_len: 1, max_len: 200}];
// client_secret will only be updated if provided
string client_secret = 4 [(validate.rules).string = {max_len: 200}];
// if not provided the `common` tenant will be used
zitadel.idp.v1.AzureADTenant tenant = 5;
bool email_verified = 6;
repeated string scopes = 7 [(validate.rules).repeated = {max_items: 20, items: {string: {min_len: 1, max_len: 100}}}];
zitadel.idp.v1.Options provider_options = 8;
}
message UpdateAzureADProviderResponse {
zitadel.v1.ObjectDetails details = 1;
}
message AddGitHubProviderRequest {
// GitHub will be used as default, if no name is provided
string name = 1 [(validate.rules).string = {max_len: 200}];