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 }, 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) { func (s *Server) AddGitHubProvider(ctx context.Context, req *admin_pb.AddGitHubProviderRequest) (*admin_pb.AddGitHubProviderResponse, error) {
id, details, err := s.command.AddInstanceGitHubProvider(ctx, addGitHubProviderToCommand(req)) id, details, err := s.command.AddInstanceGitHubProvider(ctx, addGitHubProviderToCommand(req))
if err != nil { 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 { func addGitHubProviderToCommand(req *admin_pb.AddGitHubProviderRequest) command.GitHubProvider {
return command.GitHubProvider{ return command.GitHubProvider{
Name: req.Name, Name: req.Name,

View File

@ -4,6 +4,7 @@ import (
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
"github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/domain"
iam_model "github.com/zitadel/zitadel/internal/iam/model" 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/query"
"github.com/zitadel/zitadel/internal/repository/idp" "github.com/zitadel/zitadel/internal/repository/idp"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/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 { func ProvidersToPb(providers []*query.IDPTemplate) []*idp_pb.Provider {
list := make([]*idp_pb.Provider, len(providers)) list := make([]*idp_pb.Provider, len(providers))
for i, provider := range providers { for i, provider := range providers {
@ -412,6 +440,10 @@ func configToPb(config *query.IDPTemplate) *idp_pb.ProviderConfig {
jwtConfigToPb(providerConfig, config.JWTIDPTemplate) jwtConfigToPb(providerConfig, config.JWTIDPTemplate)
return providerConfig return providerConfig
} }
if config.AzureADIDPTemplate != nil {
azureConfigToPb(providerConfig, config.AzureADIDPTemplate)
return providerConfig
}
if config.GitHubIDPTemplate != nil { if config.GitHubIDPTemplate != nil {
githubConfigToPb(providerConfig, config.GitHubIDPTemplate) githubConfigToPb(providerConfig, config.GitHubIDPTemplate)
return providerConfig 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) { func githubConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.GitHubIDPTemplate) {
providerConfig.Config = &idp_pb.ProviderConfig_Github{ providerConfig.Config = &idp_pb.ProviderConfig_Github{
Github: &idp_pb.GitHubConfig{ Github: &idp_pb.GitHubConfig{

View File

@ -233,6 +233,27 @@ func (s *Server) UpdateJWTProvider(ctx context.Context, req *mgmt_pb.UpdateJWTPr
}, nil }, 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) { 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)) id, details, err := s.command.AddOrgGitHubProvider(ctx, authz.GetCtxData(ctx).OrgID, addGitHubProviderToCommand(req))
if err != nil { 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 { func addGitHubProviderToCommand(req *mgmt_pb.AddGitHubProviderRequest) command.GitHubProvider {
return command.GitHubProvider{ return command.GitHubProvider{
Name: req.Name, Name: req.Name,

View File

@ -18,6 +18,7 @@ import (
"github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/errors"
"github.com/zitadel/zitadel/internal/eventstore/v1/models" "github.com/zitadel/zitadel/internal/eventstore/v1/models"
"github.com/zitadel/zitadel/internal/idp" "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/github"
"github.com/zitadel/zitadel/internal/idp/providers/gitlab" "github.com/zitadel/zitadel/internal/idp/providers/gitlab"
"github.com/zitadel/zitadel/internal/idp/providers/google" "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) provider, err = l.oidcProvider(r.Context(), identityProvider)
case domain.IDPTypeJWT: case domain.IDPTypeJWT:
provider, err = l.jwtProvider(identityProvider) provider, err = l.jwtProvider(identityProvider)
case domain.IDPTypeAzureAD:
provider, err = l.azureProvider(r.Context(), identityProvider)
case domain.IDPTypeGitHub: case domain.IDPTypeGitHub:
provider, err = l.githubProvider(r.Context(), identityProvider) provider, err = l.githubProvider(r.Context(), identityProvider)
case domain.IDPTypeGitHubEnterprise: case domain.IDPTypeGitHubEnterprise:
@ -155,7 +158,6 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
case domain.IDPTypeGoogle: case domain.IDPTypeGoogle:
provider, err = l.googleProvider(r.Context(), identityProvider) provider, err = l.googleProvider(r.Context(), identityProvider)
case domain.IDPTypeLDAP, case domain.IDPTypeLDAP,
domain.IDPTypeAzureAD,
domain.IDPTypeUnspecified: domain.IDPTypeUnspecified:
fallthrough fallthrough
default: default:
@ -212,6 +214,13 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
return return
} }
session = &openid.Session{Provider: provider.(*openid.Provider), Code: data.Code} 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: case domain.IDPTypeGitHub:
provider, err = l.githubProvider(r.Context(), identityProvider) provider, err = l.githubProvider(r.Context(), identityProvider)
if err != nil { 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} session = &openid.Session{Provider: provider.(*google.Provider).Provider, Code: data.Code}
case domain.IDPTypeJWT, case domain.IDPTypeJWT,
domain.IDPTypeLDAP, domain.IDPTypeLDAP,
domain.IDPTypeAzureAD,
domain.IDPTypeUnspecified: domain.IDPTypeUnspecified:
fallthrough fallthrough
default: 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) { func (l *Login) githubProvider(ctx context.Context, identityProvider *query.IDPTemplate) (*github.Provider, error) {
secret, err := crypto.DecryptString(identityProvider.GitHubIDPTemplate.ClientSecret, l.idpConfigAlg) secret, err := crypto.DecryptString(identityProvider.GitHubIDPTemplate.ClientSecret, l.idpConfigAlg)
if err != nil { if err != nil {

View File

@ -38,6 +38,16 @@ type JWTProvider struct {
IDPOptions idp.Options IDPOptions idp.Options
} }
type AzureADProvider struct {
Name string
ClientID string
ClientSecret string
Scopes []string
Tenant string
EmailVerified bool
IDPOptions idp.Options
}
type GitHubProvider struct { type GitHubProvider struct {
Name string Name string
ClientID 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 { type GitHubIDPWriteModel struct {
eventstore.WriteModel eventstore.WriteModel
@ -1049,6 +1154,8 @@ func (wm *IDPRemoveWriteModel) Reduce() error {
wm.reduceAdded(e.ID) wm.reduceAdded(e.ID)
case *idp.JWTIDPAddedEvent: case *idp.JWTIDPAddedEvent:
wm.reduceAdded(e.ID) wm.reduceAdded(e.ID)
case *idp.AzureADIDPAddedEvent:
wm.reduceAdded(e.ID)
case *idp.GitHubIDPAddedEvent: case *idp.GitHubIDPAddedEvent:
wm.reduceAdded(e.ID) wm.reduceAdded(e.ID)
case *idp.GitHubEnterpriseIDPAddedEvent: case *idp.GitHubEnterpriseIDPAddedEvent:

View File

@ -139,6 +139,48 @@ func (c *Commands) UpdateInstanceJWTProvider(ctx context.Context, id string, pro
return pushedEventsToObjectDetails(pushedEvents), nil 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) { func (c *Commands) AddInstanceGitHubProvider(ctx context.Context, provider GitHubProvider) (string, *domain.ObjectDetails, error) {
instanceID := authz.GetInstance(ctx).InstanceID() instanceID := authz.GetInstance(ctx).InstanceID()
instanceAgg := instance.NewAggregate(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 { func (c *Commands) prepareAddInstanceGitHubProvider(a *instance.Aggregate, writeModel *InstanceGitHubIDPWriteModel, provider GitHubProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" { 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) 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 { type InstanceGitHubIDPWriteModel struct {
GitHubIDPWriteModel GitHubIDPWriteModel
} }
@ -726,6 +802,8 @@ func (wm *InstanceIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event)
wm.IDPRemoveWriteModel.AppendEvents(&e.OIDCIDPAddedEvent) wm.IDPRemoveWriteModel.AppendEvents(&e.OIDCIDPAddedEvent)
case *instance.JWTIDPAddedEvent: case *instance.JWTIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.JWTIDPAddedEvent) wm.IDPRemoveWriteModel.AppendEvents(&e.JWTIDPAddedEvent)
case *instance.AzureADIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.AzureADIDPAddedEvent)
case *instance.GitHubIDPAddedEvent: case *instance.GitHubIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.GitHubIDPAddedEvent) wm.IDPRemoveWriteModel.AppendEvents(&e.GitHubIDPAddedEvent)
case *instance.GitHubEnterpriseIDPAddedEvent: case *instance.GitHubEnterpriseIDPAddedEvent:
@ -760,6 +838,7 @@ func (wm *InstanceIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
instance.OAuthIDPAddedEventType, instance.OAuthIDPAddedEventType,
instance.OIDCIDPAddedEventType, instance.OIDCIDPAddedEventType,
instance.JWTIDPAddedEventType, instance.JWTIDPAddedEventType,
instance.AzureADIDPAddedEventType,
instance.GitHubIDPAddedEventType, instance.GitHubIDPAddedEventType,
instance.GitHubEnterpriseIDPAddedEventType, instance.GitHubEnterpriseIDPAddedEventType,
instance.GitLabIDPAddedEventType, 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) { func TestCommandSide_AddInstanceGitHubIDP(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore

View File

@ -131,6 +131,45 @@ func (c *Commands) UpdateOrgJWTProvider(ctx context.Context, resourceOwner, id s
} }
return pushedEventsToObjectDetails(pushedEvents), nil 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) { func (c *Commands) AddOrgGitHubProvider(ctx context.Context, resourceOwner string, provider GitHubProvider) (string, *domain.ObjectDetails, error) {
orgAgg := org.NewAggregate(resourceOwner) 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 { func (c *Commands) prepareAddOrgGitHubProvider(a *org.Aggregate, writeModel *OrgGitHubIDPWriteModel, provider GitHubProvider) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" { 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) 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 { type OrgGitHubIDPWriteModel struct {
GitHubIDPWriteModel GitHubIDPWriteModel
} }
@ -732,6 +812,8 @@ func (wm *OrgIDPRemoveWriteModel) AppendEvents(events ...eventstore.Event) {
wm.IDPRemoveWriteModel.AppendEvents(&e.OIDCIDPAddedEvent) wm.IDPRemoveWriteModel.AppendEvents(&e.OIDCIDPAddedEvent)
case *org.JWTIDPAddedEvent: case *org.JWTIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.JWTIDPAddedEvent) wm.IDPRemoveWriteModel.AppendEvents(&e.JWTIDPAddedEvent)
case *org.AzureADIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.AzureADIDPAddedEvent)
case *org.GitHubIDPAddedEvent: case *org.GitHubIDPAddedEvent:
wm.IDPRemoveWriteModel.AppendEvents(&e.GitHubIDPAddedEvent) wm.IDPRemoveWriteModel.AppendEvents(&e.GitHubIDPAddedEvent)
case *org.GitHubEnterpriseIDPAddedEvent: case *org.GitHubEnterpriseIDPAddedEvent:
@ -766,6 +848,7 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
org.OAuthIDPAddedEventType, org.OAuthIDPAddedEventType,
org.OIDCIDPAddedEventType, org.OIDCIDPAddedEventType,
org.JWTIDPAddedEventType, org.JWTIDPAddedEventType,
org.AzureADIDPAddedEventType,
org.GitHubIDPAddedEventType, org.GitHubIDPAddedEventType,
org.GitHubEnterpriseIDPAddedEventType, org.GitHubEnterpriseIDPAddedEventType,
org.GitLabIDPAddedEventType, 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) { func TestCommandSide_AddOrgGitHubIDP(t *testing.T) {
type fields struct { type fields struct {
eventstore *eventstore.Eventstore eventstore *eventstore.Eventstore

View File

@ -15,7 +15,7 @@ import (
const ( const (
authURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize" authURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/authorize"
tokenURLTemplate string = "https://login.microsoftonline.com/%s/oauth2/v2.0/token" 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 // 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). // New creates an AzureAD provider using the [oauth.Provider] (OAuth 2.0 generic provider).
// By default, it uses the [CommonTenant] and unverified emails. // 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{ provider := &Provider{
tenant: CommonTenant, tenant: CommonTenant,
options: make([]oauth.ProviderOpts, 0), options: make([]oauth.ProviderOpts, 0),
@ -81,7 +81,7 @@ func New(name, clientID, clientSecret, redirectURI string, opts ...ProviderOptio
for _, opt := range opts { for _, opt := range opts {
opt(provider) 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( rp, err := oauth.New(
config, config,
name, name,
@ -121,34 +121,38 @@ func newConfig(tenant TenantType, clientID, secret, callbackURL string, scopes [
// AzureAD does not return an `email_verified` claim. // AzureAD does not return an `email_verified` claim.
// The verification can be automatically activated on the provider ([WithEmailVerified]) // The verification can be automatically activated on the provider ([WithEmailVerified])
type User struct { type User struct {
Sub string `json:"sub"` ID string `json:"id"`
FamilyName string `json:"family_name"` BusinessPhones []domain.PhoneNumber `json:"businessPhones"`
GivenName string `json:"given_name"` DisplayName string `json:"displayName"`
Name string `json:"name"` FirstName string `json:"givenName"`
PreferredUsername string `json:"preferred_username"` JobTitle string `json:"jobTitle"`
Email domain.EmailAddress `json:"email"` Email domain.EmailAddress `json:"mail"`
Picture string `json:"picture"` MobilePhone domain.PhoneNumber `json:"mobilePhone"`
OfficeLocation string `json:"officeLocation"`
PreferredLanguage string `json:"preferredLanguage"`
LastName string `json:"surname"`
UserPrincipalName string `json:"userPrincipalName"`
isEmailVerified bool isEmailVerified bool
} }
// GetID is an implementation of the [idp.User] interface. // GetID is an implementation of the [idp.User] interface.
func (u *User) GetID() string { func (u *User) GetID() string {
return u.Sub return u.ID
} }
// GetFirstName is an implementation of the [idp.User] interface. // GetFirstName is an implementation of the [idp.User] interface.
func (u *User) GetFirstName() string { func (u *User) GetFirstName() string {
return u.GivenName return u.FirstName
} }
// GetLastName is an implementation of the [idp.User] interface. // GetLastName is an implementation of the [idp.User] interface.
func (u *User) GetLastName() string { func (u *User) GetLastName() string {
return u.FamilyName return u.LastName
} }
// GetDisplayName is an implementation of the [idp.User] interface. // GetDisplayName is an implementation of the [idp.User] interface.
func (u *User) GetDisplayName() string { func (u *User) GetDisplayName() string {
return u.Name return u.DisplayName
} }
// GetNickname is an implementation of the [idp.User] interface. // 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. // GetPreferredUsername is an implementation of the [idp.User] interface.
func (u *User) GetPreferredUsername() string { func (u *User) GetPreferredUsername() string {
return u.PreferredUsername return u.UserPrincipalName
} }
// GetEmail is an implementation of the [idp.User] interface. // GetEmail is an implementation of the [idp.User] interface.
func (u *User) GetEmail() domain.EmailAddress { 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 return u.Email
} }
@ -188,10 +197,8 @@ func (u *User) IsPhoneVerified() bool {
} }
// GetPreferredLanguage is an implementation of the [idp.User] interface. // 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 { func (u *User) GetPreferredLanguage() language.Tag {
// AzureAD does not provide the user's language return language.Make(u.PreferredLanguage)
return language.Und
} }
// GetProfile is an implementation of the [idp.User] interface. // 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. // GetAvatarURL is an implementation of the [idp.User] interface.
func (u *User) GetAvatarURL() string { 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/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/zitadel/oidc/v2/pkg/client/rp" "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"
"github.com/zitadel/zitadel/internal/idp/providers/oauth" "github.com/zitadel/zitadel/internal/idp/providers/oauth"
@ -19,6 +20,7 @@ func TestProvider_BeginAuth(t *testing.T) {
clientID string clientID string
clientSecret string clientSecret string
redirectURI string redirectURI string
scopes []string
options []ProviderOptions options []ProviderOptions
} }
tests := []struct { tests := []struct {
@ -34,7 +36,7 @@ func TestProvider_BeginAuth(t *testing.T) {
redirectURI: "redirectURI", redirectURI: "redirectURI",
}, },
want: &oidc.Session{ 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{ 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) a := assert.New(t)
r := require.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) r.NoError(err)
session, err := provider.BeginAuth(context.Background(), "testState") session, err := provider.BeginAuth(context.Background(), "testState")
@ -74,6 +91,7 @@ func TestProvider_Options(t *testing.T) {
clientID string clientID string
clientSecret string clientSecret string
redirectURI string redirectURI string
scopes []string
options []ProviderOptions options []ProviderOptions
} }
type want struct { type want struct {
@ -98,6 +116,7 @@ func TestProvider_Options(t *testing.T) {
clientID: "clientID", clientID: "clientID",
clientSecret: "clientSecret", clientSecret: "clientSecret",
redirectURI: "redirectURI", redirectURI: "redirectURI",
scopes: nil,
options: nil, options: nil,
}, },
want: want{ want: want{
@ -146,7 +165,7 @@ func TestProvider_Options(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
a := assert.New(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) require.NoError(t, err)
a.Equal(tt.want.name, provider.Name()) a.Equal(tt.want.name, provider.Name())

View File

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

View File

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

View File

@ -49,6 +49,13 @@ var (
` projections.idp_templates3_jwt.jwt_endpoint,` + ` projections.idp_templates3_jwt.jwt_endpoint,` +
` projections.idp_templates3_jwt.keys_endpoint,` + ` projections.idp_templates3_jwt.keys_endpoint,` +
` projections.idp_templates3_jwt.header_name,` + ` 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 // github
` projections.idp_templates3_github.idp_id,` + ` projections.idp_templates3_github.idp_id,` +
` projections.idp_templates3_github.client_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_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_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_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 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_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` + ` 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", "jwt_endpoint",
"keys_endpoint", "keys_endpoint",
"header_name", "header_name",
// azure
"idp_id",
"client_id",
"client_secret",
"scopes",
"tenant",
"is_email_verified",
// github config // github config
"idp_id", "idp_id",
"client_id", "client_id",
@ -234,6 +249,13 @@ var (
` projections.idp_templates3_jwt.jwt_endpoint,` + ` projections.idp_templates3_jwt.jwt_endpoint,` +
` projections.idp_templates3_jwt.keys_endpoint,` + ` projections.idp_templates3_jwt.keys_endpoint,` +
` projections.idp_templates3_jwt.header_name,` + ` 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 // github
` projections.idp_templates3_github.idp_id,` + ` projections.idp_templates3_github.idp_id,` +
` projections.idp_templates3_github.client_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_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_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_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 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_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` + ` 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", "jwt_endpoint",
"keys_endpoint", "keys_endpoint",
"header_name", "header_name",
// azure
"idp_id",
"client_id",
"client_secret",
"scopes",
"tenant",
"is_email_verified",
// github config // github config
"idp_id", "idp_id",
"client_id", "client_id",
@ -460,6 +490,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -583,6 +620,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -703,6 +747,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"jwt", "jwt",
"keys", "keys",
"header", "header",
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -823,6 +874,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
"idp-id", "idp-id",
"client_id", "client_id",
@ -942,6 +1000,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1061,6 +1126,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1181,6 +1253,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1300,6 +1379,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1438,6 +1524,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1587,6 +1680,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1734,6 +1834,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1856,6 +1963,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -1944,6 +2058,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -2032,6 +2153,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -2120,6 +2248,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
nil, nil,
nil, nil,
nil, nil,
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,
@ -2208,6 +2343,13 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
"jwt", "jwt",
"keys", "keys",
"header", "header",
// azure
nil,
nil,
nil,
nil,
nil,
nil,
// github // github
nil, nil,
nil, nil,

View File

@ -21,6 +21,7 @@ const (
IDPTemplateOAuthTable = IDPTemplateTable + "_" + IDPTemplateOAuthSuffix IDPTemplateOAuthTable = IDPTemplateTable + "_" + IDPTemplateOAuthSuffix
IDPTemplateOIDCTable = IDPTemplateTable + "_" + IDPTemplateOIDCSuffix IDPTemplateOIDCTable = IDPTemplateTable + "_" + IDPTemplateOIDCSuffix
IDPTemplateJWTTable = IDPTemplateTable + "_" + IDPTemplateJWTSuffix IDPTemplateJWTTable = IDPTemplateTable + "_" + IDPTemplateJWTSuffix
IDPTemplateAzureADTable = IDPTemplateTable + "_" + IDPTemplateAzureADSuffix
IDPTemplateGitHubTable = IDPTemplateTable + "_" + IDPTemplateGitHubSuffix IDPTemplateGitHubTable = IDPTemplateTable + "_" + IDPTemplateGitHubSuffix
IDPTemplateGitHubEnterpriseTable = IDPTemplateTable + "_" + IDPTemplateGitHubEnterpriseSuffix IDPTemplateGitHubEnterpriseTable = IDPTemplateTable + "_" + IDPTemplateGitHubEnterpriseSuffix
IDPTemplateGitLabTable = IDPTemplateTable + "_" + IDPTemplateGitLabSuffix IDPTemplateGitLabTable = IDPTemplateTable + "_" + IDPTemplateGitLabSuffix
@ -31,6 +32,7 @@ const (
IDPTemplateOAuthSuffix = "oauth2" IDPTemplateOAuthSuffix = "oauth2"
IDPTemplateOIDCSuffix = "oidc" IDPTemplateOIDCSuffix = "oidc"
IDPTemplateJWTSuffix = "jwt" IDPTemplateJWTSuffix = "jwt"
IDPTemplateAzureADSuffix = "azure"
IDPTemplateGitHubSuffix = "github" IDPTemplateGitHubSuffix = "github"
IDPTemplateGitHubEnterpriseSuffix = "github_enterprise" IDPTemplateGitHubEnterpriseSuffix = "github_enterprise"
IDPTemplateGitLabSuffix = "gitlab" IDPTemplateGitLabSuffix = "gitlab"
@ -78,6 +80,14 @@ const (
JWTKeysEndpointCol = "keys_endpoint" JWTKeysEndpointCol = "keys_endpoint"
JWTHeaderNameCol = "header_name" 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" GitHubIDCol = "idp_id"
GitHubInstanceIDCol = "instance_id" GitHubInstanceIDCol = "instance_id"
GitHubClientIDCol = "client_id" GitHubClientIDCol = "client_id"
@ -206,6 +216,19 @@ func newIDPTemplateProjection(ctx context.Context, config crdb.StatementHandlerC
IDPTemplateJWTSuffix, IDPTemplateJWTSuffix,
crdb.WithForeignKey(crdb.NewForeignKeyOfPublicKeys()), 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.NewSuffixedTable([]*crdb.Column{
crdb.NewColumn(GitHubIDCol, crdb.ColumnTypeText), crdb.NewColumn(GitHubIDCol, crdb.ColumnTypeText),
crdb.NewColumn(GitHubInstanceIDCol, crdb.ColumnTypeText), crdb.NewColumn(GitHubInstanceIDCol, crdb.ColumnTypeText),
@ -352,6 +375,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
Event: instance.IDPJWTConfigChangedEventType, Event: instance.IDPJWTConfigChangedEventType,
Reduce: p.reduceOldJWTConfigChanged, Reduce: p.reduceOldJWTConfigChanged,
}, },
{
Event: instance.AzureADIDPAddedEventType,
Reduce: p.reduceAzureADIDPAdded,
},
{
Event: instance.AzureADIDPChangedEventType,
Reduce: p.reduceAzureADIDPChanged,
},
{ {
Event: instance.GitHubIDPAddedEventType, Event: instance.GitHubIDPAddedEventType,
Reduce: p.reduceGitHubIDPAdded, Reduce: p.reduceGitHubIDPAdded,
@ -429,7 +460,6 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
Event: org.OIDCIDPChangedEventType, Event: org.OIDCIDPChangedEventType,
Reduce: p.reduceOIDCIDPChanged, Reduce: p.reduceOIDCIDPChanged,
}, },
{ {
Event: org.JWTIDPAddedEventType, Event: org.JWTIDPAddedEventType,
Reduce: p.reduceJWTIDPAdded, Reduce: p.reduceJWTIDPAdded,
@ -462,6 +492,14 @@ func (p *idpTemplateProjection) reducers() []handler.AggregateReducer {
Event: org.IDPJWTConfigChangedEventType, Event: org.IDPJWTConfigChangedEventType,
Reduce: p.reduceOldJWTConfigChanged, Reduce: p.reduceOldJWTConfigChanged,
}, },
{
Event: org.AzureADIDPAddedEventType,
Reduce: p.reduceAzureADIDPAdded,
},
{
Event: org.AzureADIDPChangedEventType,
Reduce: p.reduceAzureADIDPChanged,
},
{ {
Event: org.GitHubIDPAddedEventType, Event: org.GitHubIDPAddedEventType,
Reduce: p.reduceGitHubIDPAdded, Reduce: p.reduceGitHubIDPAdded,
@ -1049,6 +1087,96 @@ func (p *idpTemplateProjection) reduceOldJWTConfigChanged(event eventstore.Event
), nil ), 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) { func (p *idpTemplateProjection) reduceGitHubIDPAdded(event eventstore.Event) (*handler.Statement, error) {
var idpEvent idp.GitHubIDPAddedEvent var idpEvent idp.GitHubIDPAddedEvent
var idpOwnerType domain.IdentityProviderType var idpOwnerType domain.IdentityProviderType
@ -1723,6 +1851,26 @@ func reduceJWTIDPChangedColumns(idpEvent idp.JWTIDPChangedEvent) []handler.Colum
return jwtCols 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 { func reduceGitHubIDPChangedColumns(idpEvent idp.GitHubIDPChangedEvent) []handler.Column {
oauthCols := make([]handler.Column, 0, 3) oauthCols := make([]handler.Column, 0, 3)
if idpEvent.ClientID != nil { 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) { func TestIDPTemplateProjection_reducesGitHub(t *testing.T) {
type args struct { type args struct {
event func(t *testing.T) eventstore.Event 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, OIDCIDPChangedEventType, OIDCIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, JWTIDPAddedEventType, JWTIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, JWTIDPAddedEventType, JWTIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, JWTIDPChangedEventType, JWTIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, JWTIDPChangedEventType, JWTIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, AzureADIDPAddedEventType, AzureADIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, AzureADIDPChangedEventType, AzureADIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubIDPAddedEventType, GitHubIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, GitHubIDPAddedEventType, GitHubIDPAddedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubIDPChangedEventType, GitHubIDPChangedEventMapper). RegisterFilterEventMapper(AggregateType, GitHubIDPChangedEventType, GitHubIDPChangedEventMapper).
RegisterFilterEventMapper(AggregateType, GitHubEnterpriseIDPAddedEventType, GitHubEnterpriseIDPAddedEventMapper). RegisterFilterEventMapper(AggregateType, GitHubEnterpriseIDPAddedEventType, GitHubEnterpriseIDPAddedEventMapper).

View File

@ -16,6 +16,8 @@ const (
OIDCIDPChangedEventType eventstore.EventType = "instance.idp.oidc.changed" OIDCIDPChangedEventType eventstore.EventType = "instance.idp.oidc.changed"
JWTIDPAddedEventType eventstore.EventType = "instance.idp.jwt.added" JWTIDPAddedEventType eventstore.EventType = "instance.idp.jwt.added"
JWTIDPChangedEventType eventstore.EventType = "instance.idp.jwt.changed" 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" GitHubIDPAddedEventType eventstore.EventType = "instance.idp.github.added"
GitHubIDPChangedEventType eventstore.EventType = "instance.idp.github.changed" GitHubIDPChangedEventType eventstore.EventType = "instance.idp.github.changed"
GitHubEnterpriseIDPAddedEventType eventstore.EventType = "instance.idp.github_enterprise.added" 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 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 { type GitHubIDPAddedEvent struct {
idp.GitHubIDPAddedEvent idp.GitHubIDPAddedEvent
} }

View File

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

View File

@ -16,6 +16,8 @@ const (
OIDCIDPChangedEventType eventstore.EventType = "org.idp.oidc.changed" OIDCIDPChangedEventType eventstore.EventType = "org.idp.oidc.changed"
JWTIDPAddedEventType eventstore.EventType = "org.idp.jwt.added" JWTIDPAddedEventType eventstore.EventType = "org.idp.jwt.added"
JWTIDPChangedEventType eventstore.EventType = "org.idp.jwt.changed" 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" GitHubIDPAddedEventType eventstore.EventType = "org.idp.github.added"
GitHubIDPChangedEventType eventstore.EventType = "org.idp.github.changed" GitHubIDPChangedEventType eventstore.EventType = "org.idp.github.changed"
GitHubEnterpriseIDPAddedEventType eventstore.EventType = "org.idp.github_enterprise.added" 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 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 { type GitHubIDPAddedEvent struct {
idp.GitHubIDPAddedEvent idp.GitHubIDPAddedEvent
} }

View File

@ -1,3 +1,4 @@
package idp package idp
type IDPConfig = isIDP_Config 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 // Add a new GitHub identity provider on the instance
rpc AddGitHubProvider(AddGitHubProviderRequest) returns (AddGitHubProviderResponse) { rpc AddGitHubProvider(AddGitHubProviderRequest) returns (AddGitHubProviderResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -4525,6 +4549,39 @@ message UpdateJWTProviderResponse {
zitadel.v1.ObjectDetails details = 1; 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 { message AddGitHubProviderRequest {
// GitHub will be used as default, if no name is provided // GitHub will be used as default, if no name is provided
string name = 1 [(validate.rules).string = {max_len: 200}]; string name = 1 [(validate.rules).string = {max_len: 200}];

View File

@ -271,8 +271,10 @@ message ProviderConfig {
GitHubEnterpriseServerConfig github_es = 8; GitHubEnterpriseServerConfig github_es = 8;
GitLabConfig gitlab = 9; GitLabConfig gitlab = 9;
GitLabSelfHostedConfig gitlab_self_hosted = 10; GitLabSelfHostedConfig gitlab_self_hosted = 10;
AzureADConfig azure_ad = 11;
} }
} }
message OAuthConfig { message OAuthConfig {
string client_id = 1; string client_id = 1;
string authorization_endpoint = 2; string authorization_endpoint = 2;
@ -329,6 +331,13 @@ message LDAPConfig {
Options provider_options = 9; Options provider_options = 9;
} }
message AzureADConfig {
string client_id = 1;
AzureADTenant tenant = 2;
bool email_verified = 3;
repeated string scopes = 4;
}
message Options { message Options {
bool is_linking_allowed = 1; bool is_linking_allowed = 1;
bool is_creation_allowed = 2; bool is_creation_allowed = 2;
@ -352,3 +361,15 @@ message LDAPAttributes {
string profile_attribute = 13 [(validate.rules).string = {max_len: 200}]; 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 // Add a new GitHub identity provider in the organization
rpc AddGitHubProvider(AddGitHubProviderRequest) returns (AddGitHubProviderResponse) { rpc AddGitHubProvider(AddGitHubProviderRequest) returns (AddGitHubProviderResponse) {
option (google.api.http) = { option (google.api.http) = {
@ -11199,6 +11223,39 @@ message UpdateJWTProviderResponse {
zitadel.v1.ObjectDetails details = 1; 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 { message AddGitHubProviderRequest {
// GitHub will be used as default, if no name is provided // GitHub will be used as default, if no name is provided
string name = 1 [(validate.rules).string = {max_len: 200}]; string name = 1 [(validate.rules).string = {max_len: 200}];