Stefan Benz 870e3b1b26
feat: add exclusion of criteria for active idp query (#9040)
# Which Problems Are Solved

To list IDPs for potential linking, we need to filter them. The
GetActiveIdentityProviderResponse should therefore be extended to
provide the IDPConfig or information about whether the IDP is allowed to
be linked or created.

# How the Problems Are Solved

Add parameters to the request to exclude CreationDisallowed and/or
LinkingDisallowed in the query.

# Additional Changes

Added integration tests for the GetGetActiveIdentityProvider endpoint.

# Additional Context

Closes #8981

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
2024-12-18 16:19:05 +00:00

370 lines
12 KiB
Go

package idp
import (
"context"
"github.com/crewjam/saml"
"github.com/muhlemmer/gu"
"google.golang.org/protobuf/types/known/durationpb"
"github.com/zitadel/zitadel/internal/api/grpc/object/v2"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/idp/providers/azuread"
"github.com/zitadel/zitadel/internal/query"
idp_rp "github.com/zitadel/zitadel/internal/repository/idp"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
)
func (s *Server) GetIDPByID(ctx context.Context, req *idp_pb.GetIDPByIDRequest) (*idp_pb.GetIDPByIDResponse, error) {
idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, s.checkPermission)
if err != nil {
return nil, err
}
return &idp_pb.GetIDPByIDResponse{Idp: idpToPb(idp)}, nil
}
func idpToPb(idp *query.IDPTemplate) *idp_pb.IDP {
return &idp_pb.IDP{
Id: idp.ID,
Details: object.DomainToDetailsPb(
&domain.ObjectDetails{
Sequence: idp.Sequence,
EventDate: idp.ChangeDate,
ResourceOwner: idp.ResourceOwner,
}),
State: idpStateToPb(idp.State),
Name: idp.Name,
Type: idpTypeToPb(idp.Type),
Config: configToPb(idp),
}
}
func idpStateToPb(state domain.IDPState) idp_pb.IDPState {
switch state {
case domain.IDPStateActive:
return idp_pb.IDPState_IDP_STATE_ACTIVE
case domain.IDPStateInactive:
return idp_pb.IDPState_IDP_STATE_INACTIVE
case domain.IDPStateUnspecified:
return idp_pb.IDPState_IDP_STATE_UNSPECIFIED
case domain.IDPStateMigrated:
return idp_pb.IDPState_IDP_STATE_MIGRATED
case domain.IDPStateRemoved:
return idp_pb.IDPState_IDP_STATE_REMOVED
default:
return idp_pb.IDPState_IDP_STATE_UNSPECIFIED
}
}
func idpTypeToPb(idpType domain.IDPType) idp_pb.IDPType {
switch idpType {
case domain.IDPTypeOIDC:
return idp_pb.IDPType_IDP_TYPE_OIDC
case domain.IDPTypeJWT:
return idp_pb.IDPType_IDP_TYPE_JWT
case domain.IDPTypeOAuth:
return idp_pb.IDPType_IDP_TYPE_OAUTH
case domain.IDPTypeLDAP:
return idp_pb.IDPType_IDP_TYPE_LDAP
case domain.IDPTypeAzureAD:
return idp_pb.IDPType_IDP_TYPE_AZURE_AD
case domain.IDPTypeGitHub:
return idp_pb.IDPType_IDP_TYPE_GITHUB
case domain.IDPTypeGitHubEnterprise:
return idp_pb.IDPType_IDP_TYPE_GITHUB_ES
case domain.IDPTypeGitLab:
return idp_pb.IDPType_IDP_TYPE_GITLAB
case domain.IDPTypeGitLabSelfHosted:
return idp_pb.IDPType_IDP_TYPE_GITLAB_SELF_HOSTED
case domain.IDPTypeGoogle:
return idp_pb.IDPType_IDP_TYPE_GOOGLE
case domain.IDPTypeApple:
return idp_pb.IDPType_IDP_TYPE_APPLE
case domain.IDPTypeSAML:
return idp_pb.IDPType_IDP_TYPE_SAML
case domain.IDPTypeUnspecified:
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
default:
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
}
}
func configToPb(config *query.IDPTemplate) *idp_pb.IDPConfig {
idpConfig := &idp_pb.IDPConfig{
Options: &idp_pb.Options{
IsLinkingAllowed: config.IsLinkingAllowed,
IsCreationAllowed: config.IsCreationAllowed,
IsAutoCreation: config.IsAutoCreation,
IsAutoUpdate: config.IsAutoUpdate,
AutoLinking: AutoLinkingOptionToPb(config.AutoLinking),
},
}
if config.OAuthIDPTemplate != nil {
oauthConfigToPb(idpConfig, config.OAuthIDPTemplate)
return idpConfig
}
if config.OIDCIDPTemplate != nil {
oidcConfigToPb(idpConfig, config.OIDCIDPTemplate)
return idpConfig
}
if config.JWTIDPTemplate != nil {
jwtConfigToPb(idpConfig, config.JWTIDPTemplate)
return idpConfig
}
if config.AzureADIDPTemplate != nil {
azureConfigToPb(idpConfig, config.AzureADIDPTemplate)
return idpConfig
}
if config.GitHubIDPTemplate != nil {
githubConfigToPb(idpConfig, config.GitHubIDPTemplate)
return idpConfig
}
if config.GitHubEnterpriseIDPTemplate != nil {
githubEnterpriseConfigToPb(idpConfig, config.GitHubEnterpriseIDPTemplate)
return idpConfig
}
if config.GitLabIDPTemplate != nil {
gitlabConfigToPb(idpConfig, config.GitLabIDPTemplate)
return idpConfig
}
if config.GitLabSelfHostedIDPTemplate != nil {
gitlabSelfHostedConfigToPb(idpConfig, config.GitLabSelfHostedIDPTemplate)
return idpConfig
}
if config.GoogleIDPTemplate != nil {
googleConfigToPb(idpConfig, config.GoogleIDPTemplate)
return idpConfig
}
if config.LDAPIDPTemplate != nil {
ldapConfigToPb(idpConfig, config.LDAPIDPTemplate)
return idpConfig
}
if config.AppleIDPTemplate != nil {
appleConfigToPb(idpConfig, config.AppleIDPTemplate)
return idpConfig
}
if config.SAMLIDPTemplate != nil {
samlConfigToPb(idpConfig, config.SAMLIDPTemplate)
return idpConfig
}
return idpConfig
}
func AutoLinkingOptionToPb(linking domain.AutoLinkingOption) idp_pb.AutoLinkingOption {
switch linking {
case domain.AutoLinkingOptionUnspecified:
return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED
case domain.AutoLinkingOptionUsername:
return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME
case domain.AutoLinkingOptionEmail:
return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_EMAIL
default:
return idp_pb.AutoLinkingOption_AUTO_LINKING_OPTION_UNSPECIFIED
}
}
func oauthConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.OAuthIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Oauth{
Oauth: &idp_pb.OAuthConfig{
ClientId: template.ClientID,
AuthorizationEndpoint: template.AuthorizationEndpoint,
TokenEndpoint: template.TokenEndpoint,
UserEndpoint: template.UserEndpoint,
Scopes: template.Scopes,
IdAttribute: template.IDAttribute,
},
}
}
func oidcConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.OIDCIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Oidc{
Oidc: &idp_pb.GenericOIDCConfig{
ClientId: template.ClientID,
Issuer: template.Issuer,
Scopes: template.Scopes,
IsIdTokenMapping: template.IsIDTokenMapping,
},
}
}
func jwtConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.JWTIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Jwt{
Jwt: &idp_pb.JWTConfig{
JwtEndpoint: template.Endpoint,
Issuer: template.Issuer,
KeysEndpoint: template.KeysEndpoint,
HeaderName: template.HeaderName,
},
}
}
func azureConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.AzureADIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_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(idpConfig *idp_pb.IDPConfig, template *query.GitHubIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Github{
Github: &idp_pb.GitHubConfig{
ClientId: template.ClientID,
Scopes: template.Scopes,
},
}
}
func githubEnterpriseConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.GitHubEnterpriseIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_GithubEs{
GithubEs: &idp_pb.GitHubEnterpriseServerConfig{
ClientId: template.ClientID,
AuthorizationEndpoint: template.AuthorizationEndpoint,
TokenEndpoint: template.TokenEndpoint,
UserEndpoint: template.UserEndpoint,
Scopes: template.Scopes,
},
}
}
func gitlabConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.GitLabIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Gitlab{
Gitlab: &idp_pb.GitLabConfig{
ClientId: template.ClientID,
Scopes: template.Scopes,
},
}
}
func gitlabSelfHostedConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.GitLabSelfHostedIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_GitlabSelfHosted{
GitlabSelfHosted: &idp_pb.GitLabSelfHostedConfig{
ClientId: template.ClientID,
Issuer: template.Issuer,
Scopes: template.Scopes,
},
}
}
func googleConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.GoogleIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Google{
Google: &idp_pb.GoogleConfig{
ClientId: template.ClientID,
Scopes: template.Scopes,
},
}
}
func ldapConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.LDAPIDPTemplate) {
var timeout *durationpb.Duration
if template.Timeout != 0 {
timeout = durationpb.New(template.Timeout)
}
idpConfig.Config = &idp_pb.IDPConfig_Ldap{
Ldap: &idp_pb.LDAPConfig{
Servers: template.Servers,
StartTls: template.StartTLS,
BaseDn: template.BaseDN,
BindDn: template.BindDN,
UserBase: template.UserBase,
UserObjectClasses: template.UserObjectClasses,
UserFilters: template.UserFilters,
Timeout: timeout,
Attributes: ldapAttributesToPb(template.LDAPAttributes),
},
}
}
func ldapAttributesToPb(attributes idp_rp.LDAPAttributes) *idp_pb.LDAPAttributes {
return &idp_pb.LDAPAttributes{
IdAttribute: attributes.IDAttribute,
FirstNameAttribute: attributes.FirstNameAttribute,
LastNameAttribute: attributes.LastNameAttribute,
DisplayNameAttribute: attributes.DisplayNameAttribute,
NickNameAttribute: attributes.NickNameAttribute,
PreferredUsernameAttribute: attributes.PreferredUsernameAttribute,
EmailAttribute: attributes.EmailAttribute,
EmailVerifiedAttribute: attributes.EmailVerifiedAttribute,
PhoneAttribute: attributes.PhoneAttribute,
PhoneVerifiedAttribute: attributes.PhoneVerifiedAttribute,
PreferredLanguageAttribute: attributes.PreferredLanguageAttribute,
AvatarUrlAttribute: attributes.AvatarURLAttribute,
ProfileAttribute: attributes.ProfileAttribute,
}
}
func appleConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.AppleIDPTemplate) {
idpConfig.Config = &idp_pb.IDPConfig_Apple{
Apple: &idp_pb.AppleConfig{
ClientId: template.ClientID,
TeamId: template.TeamID,
KeyId: template.KeyID,
Scopes: template.Scopes,
},
}
}
func samlConfigToPb(idpConfig *idp_pb.IDPConfig, template *query.SAMLIDPTemplate) {
nameIDFormat := idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_PERSISTENT
if template.NameIDFormat.Valid {
nameIDFormat = nameIDToPb(template.NameIDFormat.V)
}
idpConfig.Config = &idp_pb.IDPConfig_Saml{
Saml: &idp_pb.SAMLConfig{
MetadataXml: template.Metadata,
Binding: bindingToPb(template.Binding),
WithSignedRequest: template.WithSignedRequest,
NameIdFormat: nameIDFormat,
TransientMappingAttributeName: gu.Ptr(template.TransientMappingAttributeName),
},
}
}
func bindingToPb(binding string) idp_pb.SAMLBinding {
switch binding {
case "":
return idp_pb.SAMLBinding_SAML_BINDING_UNSPECIFIED
case saml.HTTPPostBinding:
return idp_pb.SAMLBinding_SAML_BINDING_POST
case saml.HTTPRedirectBinding:
return idp_pb.SAMLBinding_SAML_BINDING_REDIRECT
case saml.HTTPArtifactBinding:
return idp_pb.SAMLBinding_SAML_BINDING_ARTIFACT
default:
return idp_pb.SAMLBinding_SAML_BINDING_UNSPECIFIED
}
}
func nameIDToPb(format domain.SAMLNameIDFormat) idp_pb.SAMLNameIDFormat {
switch format {
case domain.SAMLNameIDFormatUnspecified:
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_UNSPECIFIED
case domain.SAMLNameIDFormatEmailAddress:
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_EMAIL_ADDRESS
case domain.SAMLNameIDFormatPersistent:
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_PERSISTENT
case domain.SAMLNameIDFormatTransient:
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_TRANSIENT
default:
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_UNSPECIFIED
}
}