feat: idp v2 api GetIDPByID (#8425)

# Which Problems Are Solved

GetIDPByID as endpoint in the API v2 so that it can be available for the
new login.

# How the Problems Are Solved

Create GetIDPByID endpoint with IDP v2 API, throught the GetProviderByID
implementation from admin and management API.

# Additional Changes

- Remove the OwnerType attribute from the response, as the information
is available through the resourceOwner.
- correct refs to messages in proto which are used for doc generation
- renaming of elements for API v3

# Additional Context

Closes #8337

---------

Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
Stefan Benz 2024-08-14 20:18:29 +02:00 committed by GitHub
parent 64a3bb3149
commit 3e3d46ac0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 1348 additions and 22 deletions

View File

@ -38,6 +38,7 @@ import (
"github.com/zitadel/zitadel/internal/api/grpc/auth"
feature_v2 "github.com/zitadel/zitadel/internal/api/grpc/feature/v2"
feature_v2beta "github.com/zitadel/zitadel/internal/api/grpc/feature/v2beta"
idp_v2 "github.com/zitadel/zitadel/internal/api/grpc/idp/v2"
"github.com/zitadel/zitadel/internal/api/grpc/management"
oidc_v2 "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2"
oidc_v2beta "github.com/zitadel/zitadel/internal/api/grpc/oidc/v2beta"
@ -437,6 +438,9 @@ func startAPIs(
if err := apis.RegisterService(ctx, feature_v2.CreateServer(commands, queries)); err != nil {
return nil, err
}
if err := apis.RegisterService(ctx, idp_v2.CreateServer(commands, queries, permissionCheck)); err != nil {
return nil, err
}
if err := apis.RegisterService(ctx, action_v3_alpha.CreateServer(config.SystemDefaults, commands, queries, domain.AllFunctions, apis.ListGrpcMethods, apis.ListGrpcServices)); err != nil {
return nil, err
}

View File

@ -356,6 +356,14 @@ module.exports = {
categoryLinkSource: "auto",
},
},
idp_v2: {
specPath: ".artifacts/openapi/zitadel/idp/v2/idp_service.swagger.json",
outputDir: "docs/apis/resources/idp_service_v2",
sidebarOptions: {
groupPathsBy: "tag",
categoryLinkSource: "auto",
},
},
},
},
],

View File

@ -673,12 +673,24 @@ module.exports = {
link: {
type: "generated-index",
title: "Feature Service API",
slug: "/apis/resources/feature_service/v2",
slug: "/apis/resources/feature_service_v2",
description:
'This API is intended to manage features for ZITADEL. Feature settings that are available on multiple "levels", such as instance and organization. The higher level instance acts as a default for the lower level. When a feature is set on multiple levels, the lower level takes precedence. Features can be experimental where ZITADEL will assume a sane default, such as disabled. When over time confidence in such a feature grows, ZITADEL can default to enabling the feature. As a final step we might choose to always enable a feature and remove the setting from this API, reserving the proto field number. Such removal is not considered a breaking change. Setting a removed field will effectively result in a no-op.\n'
},
items: require("./docs/apis/resources/feature_service_v2/sidebar.ts"),
},
{
type: "category",
label: "Identity Provider Lifecycle",
link: {
type: "generated-index",
title: "Identity Provider Service API",
slug: "/apis/resources/idp_service_v2",
description:
'This API is intended to manage identity providers (IdPs) for ZITADEL.\n'
},
items: require("./docs/apis/resources/idp_service_v2/sidebar.ts"),
},
],
},
{

View File

@ -157,7 +157,7 @@ func (s *Server) GetProviderByID(ctx context.Context, req *admin_pb.GetProviderB
if err != nil {
return nil, err
}
idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, instanceIDQuery)
idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, nil, instanceIDQuery)
if err != nil {
return nil, err
}

View File

@ -0,0 +1,369 @@
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
}
}

View File

@ -0,0 +1,235 @@
//go:build integration
package idp_test
import (
"context"
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/types/known/timestamppb"
"github.com/zitadel/zitadel/internal/integration"
"github.com/zitadel/zitadel/pkg/grpc/idp/v2"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
)
type idpAttr struct {
ID string
Name string
Details *object.Details
}
func TestServer_GetIDPByID(t *testing.T) {
type args struct {
ctx context.Context
req *idp.GetIDPByIDRequest
dep func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr
}
tests := []struct {
name string
args args
want *idp.GetIDPByIDResponse
wantErr bool
}{
{
name: "idp by ID, no id provided",
args: args{
IamCTX,
&idp.GetIDPByIDRequest{
Id: "",
},
func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr {
return nil
},
},
wantErr: true,
},
{
name: "idp by ID, not found",
args: args{
IamCTX,
&idp.GetIDPByIDRequest{
Id: "unknown",
},
func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr {
return nil
},
},
wantErr: true,
},
{
name: "idp by ID, instance, ok",
args: args{
IamCTX,
&idp.GetIDPByIDRequest{},
func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr {
name := fmt.Sprintf("GetIDPByID%d", time.Now().UnixNano())
resp := Tester.AddGenericOAuthIDP(ctx, name)
request.Id = resp.Id
return &idpAttr{
resp.GetId(),
name,
&object.Details{
Sequence: resp.Details.Sequence,
ChangeDate: resp.Details.ChangeDate,
ResourceOwner: resp.Details.ResourceOwner,
}}
},
},
want: &idp.GetIDPByIDResponse{
Idp: &idp.IDP{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
},
State: idp.IDPState_IDP_STATE_ACTIVE,
Type: idp.IDPType_IDP_TYPE_OAUTH,
Config: &idp.IDPConfig{
Config: &idp.IDPConfig_Oauth{
Oauth: &idp.OAuthConfig{
ClientId: "clientID",
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
TokenEndpoint: "https://example.com/oauth/v2/token",
UserEndpoint: "https://api.example.com/user",
Scopes: []string{"openid", "profile", "email"},
IdAttribute: "id",
},
},
Options: &idp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
},
},
},
},
},
{
name: "idp by ID, instance, no permission",
args: args{
UserCTX,
&idp.GetIDPByIDRequest{},
func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr {
name := fmt.Sprintf("GetIDPByID%d", time.Now().UnixNano())
resp := Tester.AddGenericOAuthIDP(IamCTX, name)
request.Id = resp.Id
return &idpAttr{
resp.GetId(),
name,
&object.Details{
Sequence: resp.Details.Sequence,
ChangeDate: resp.Details.ChangeDate,
ResourceOwner: resp.Details.ResourceOwner,
}}
},
},
wantErr: true,
},
{
name: "idp by ID, org, ok",
args: args{
CTX,
&idp.GetIDPByIDRequest{},
func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr {
name := fmt.Sprintf("GetIDPByID%d", time.Now().UnixNano())
resp := Tester.AddOrgGenericOAuthIDP(ctx, name)
request.Id = resp.Id
return &idpAttr{
resp.GetId(),
name,
&object.Details{
Sequence: resp.Details.Sequence,
ChangeDate: resp.Details.ChangeDate,
ResourceOwner: resp.Details.ResourceOwner,
}}
},
},
want: &idp.GetIDPByIDResponse{
Idp: &idp.IDP{
Details: &object.Details{
ChangeDate: timestamppb.Now(),
},
State: idp.IDPState_IDP_STATE_ACTIVE,
Type: idp.IDPType_IDP_TYPE_OAUTH,
Config: &idp.IDPConfig{
Config: &idp.IDPConfig_Oauth{
Oauth: &idp.OAuthConfig{
ClientId: "clientID",
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
TokenEndpoint: "https://example.com/oauth/v2/token",
UserEndpoint: "https://api.example.com/user",
Scopes: []string{"openid", "profile", "email"},
IdAttribute: "id",
},
},
Options: &idp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
},
},
},
},
},
{
name: "idp by ID, org, no permission",
args: args{
UserCTX,
&idp.GetIDPByIDRequest{},
func(ctx context.Context, request *idp.GetIDPByIDRequest) *idpAttr {
name := fmt.Sprintf("GetIDPByID%d", time.Now().UnixNano())
resp := Tester.AddOrgGenericOAuthIDP(CTX, name)
request.Id = resp.Id
return &idpAttr{
resp.GetId(),
name,
&object.Details{
Sequence: resp.Details.Sequence,
ChangeDate: resp.Details.ChangeDate,
ResourceOwner: resp.Details.ResourceOwner,
}}
},
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
idpAttr := tt.args.dep(tt.args.ctx, tt.args.req)
retryDuration := time.Minute
if ctxDeadline, ok := CTX.Deadline(); ok {
retryDuration = time.Until(ctxDeadline)
}
require.EventuallyWithT(t, func(ttt *assert.CollectT) {
got, getErr := Client.GetIDPByID(tt.args.ctx, tt.args.req)
assertErr := assert.NoError
if tt.wantErr {
assertErr = assert.Error
}
assertErr(ttt, getErr)
if getErr != nil {
return
}
// set provided info from creation
tt.want.Idp.Details = idpAttr.Details
tt.want.Idp.Name = idpAttr.Name
tt.want.Idp.Id = idpAttr.ID
// first check for details, mgmt and admin api don't fill the details correctly
integration.AssertDetails(t, tt.want.Idp, got.Idp)
// then set details
tt.want.Idp.Details = got.Idp.Details
// to check the rest of the content
assert.Equal(ttt, tt.want.Idp, got.Idp)
}, retryDuration, time.Second)
})
}
}

View File

@ -0,0 +1,56 @@
package idp
import (
"google.golang.org/grpc"
"github.com/zitadel/zitadel/internal/api/authz"
"github.com/zitadel/zitadel/internal/api/grpc/server"
"github.com/zitadel/zitadel/internal/command"
"github.com/zitadel/zitadel/internal/domain"
"github.com/zitadel/zitadel/internal/query"
"github.com/zitadel/zitadel/pkg/grpc/idp/v2"
)
var _ idp.IdentityProviderServiceServer = (*Server)(nil)
type Server struct {
idp.UnimplementedIdentityProviderServiceServer
command *command.Commands
query *query.Queries
checkPermission domain.PermissionCheck
}
type Config struct{}
func CreateServer(
command *command.Commands,
query *query.Queries,
checkPermission domain.PermissionCheck,
) *Server {
return &Server{
command: command,
query: query,
checkPermission: checkPermission,
}
}
func (s *Server) RegisterServer(grpcServer *grpc.Server) {
idp.RegisterIdentityProviderServiceServer(grpcServer, s)
}
func (s *Server) AppName() string {
return idp.IdentityProviderService_ServiceDesc.ServiceName
}
func (s *Server) MethodPrefix() string {
return idp.IdentityProviderService_ServiceDesc.ServiceName
}
func (s *Server) AuthMethods() authz.MethodMapping {
return idp.IdentityProviderService_AuthMethods
}
func (s *Server) RegisterGateway() server.RegisterGatewayFunc {
return idp.RegisterIdentityProviderServiceHandler
}

View File

@ -0,0 +1,40 @@
//go:build integration
package idp_test
import (
"context"
"os"
"testing"
"time"
"github.com/zitadel/zitadel/internal/integration"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
)
var (
CTX context.Context
IamCTX context.Context
UserCTX context.Context
SystemCTX context.Context
ErrCTX context.Context
Tester *integration.Tester
Client idp_pb.IdentityProviderServiceClient
)
func TestMain(m *testing.M) {
os.Exit(func() int {
ctx, errCtx, cancel := integration.Contexts(time.Hour)
defer cancel()
Tester = integration.NewTester(ctx)
defer Tester.Done()
UserCTX = Tester.WithAuthorization(ctx, integration.Login)
IamCTX = Tester.WithAuthorization(ctx, integration.IAMOwner)
SystemCTX = Tester.WithAuthorization(ctx, integration.SystemUser)
CTX, ErrCTX = Tester.WithAuthorization(ctx, integration.OrgOwner), errCtx
Client = Tester.Client.IDPv2
return m.Run()
}())
}

View File

@ -149,7 +149,7 @@ func (s *Server) GetProviderByID(ctx context.Context, req *mgmt_pb.GetProviderBy
if err != nil {
return nil, err
}
idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, orgIDQuery)
idp, err := s.query.IDPTemplateByID(ctx, true, req.Id, false, nil, orgIDQuery)
if err != nil {
return nil, err
}

View File

@ -18,7 +18,7 @@ func (l *Login) getOrgDomainPolicy(r *http.Request, orgID string) (*query.Domain
}
func (l *Login) getIDPByID(r *http.Request, id string) (*query.IDPTemplate, error) {
return l.query.IDPTemplateByID(r.Context(), false, id, false)
return l.query.IDPTemplateByID(r.Context(), false, id, false, nil)
}
func (l *Login) getLoginPolicy(r *http.Request, orgID string) (*query.LoginPolicy, error) {

View File

@ -33,4 +33,6 @@ const (
PermissionUserCredentialWrite = "user.credential.write"
PermissionSessionWrite = "session.write"
PermissionSessionDelete = "session.delete"
PermissionIDPRead = "iam.idp.read"
PermissionOrgIDPRead = "org.idp.read"
)

View File

@ -24,22 +24,24 @@ import (
"github.com/zitadel/zitadel/internal/idp/providers/ldap"
openid "github.com/zitadel/zitadel/internal/idp/providers/oidc"
"github.com/zitadel/zitadel/internal/idp/providers/saml"
"github.com/zitadel/zitadel/internal/repository/idp"
idp_rp "github.com/zitadel/zitadel/internal/repository/idp"
"github.com/zitadel/zitadel/pkg/grpc/admin"
"github.com/zitadel/zitadel/pkg/grpc/auth"
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
feature_v2beta "github.com/zitadel/zitadel/pkg/grpc/feature/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/idp"
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp/v2"
mgmt "github.com/zitadel/zitadel/pkg/grpc/management"
object "github.com/zitadel/zitadel/pkg/grpc/object/v2"
"github.com/zitadel/zitadel/pkg/grpc/object/v2"
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
oidc_pb_v2beta "github.com/zitadel/zitadel/pkg/grpc/oidc/v2beta"
org "github.com/zitadel/zitadel/pkg/grpc/org/v2"
"github.com/zitadel/zitadel/pkg/grpc/org/v2"
org_v2beta "github.com/zitadel/zitadel/pkg/grpc/org/v2beta"
action "github.com/zitadel/zitadel/pkg/grpc/resources/action/v3alpha"
webkey_v3alpha "github.com/zitadel/zitadel/pkg/grpc/resources/webkey/v3alpha"
session "github.com/zitadel/zitadel/pkg/grpc/session/v2"
"github.com/zitadel/zitadel/pkg/grpc/session/v2"
session_v2beta "github.com/zitadel/zitadel/pkg/grpc/session/v2beta"
settings "github.com/zitadel/zitadel/pkg/grpc/settings/v2"
"github.com/zitadel/zitadel/pkg/grpc/settings/v2"
settings_v2beta "github.com/zitadel/zitadel/pkg/grpc/settings/v2beta"
"github.com/zitadel/zitadel/pkg/grpc/system"
user_pb "github.com/zitadel/zitadel/pkg/grpc/user"
@ -69,6 +71,7 @@ type Client struct {
FeatureV2 feature.FeatureServiceClient
UserSchemaV3 schema.UserSchemaServiceClient
WebKeyV3Alpha webkey_v3alpha.ZITADELWebKeysClient
IDPv2 idp_pb.IdentityProviderServiceClient
}
func newClient(cc *grpc.ClientConn) Client {
@ -93,6 +96,7 @@ func newClient(cc *grpc.ClientConn) Client {
FeatureV2: feature.NewFeatureServiceClient(cc),
UserSchemaV3: schema.NewUserSchemaServiceClient(cc),
WebKeyV3Alpha: webkey_v3alpha.NewZITADELWebKeysClient(cc),
IDPv2: idp_pb.NewIdentityProviderServiceClient(cc),
}
}
@ -367,6 +371,28 @@ func (s *Tester) SetUserPassword(ctx context.Context, userID, password string, c
return resp.GetDetails()
}
func (s *Tester) AddGenericOAuthIDP(ctx context.Context, name string) *admin.AddGenericOAuthProviderResponse {
resp, err := s.Client.Admin.AddGenericOAuthProvider(ctx, &admin.AddGenericOAuthProviderRequest{
Name: name,
ClientId: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
TokenEndpoint: "https://example.com/oauth/v2/token",
UserEndpoint: "https://api.example.com/user",
Scopes: []string{"openid", "profile", "email"},
IdAttribute: "id",
ProviderOptions: &idp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
},
})
logging.OnError(err).Fatal("create generic OAuth idp")
return resp
}
func (s *Tester) AddGenericOAuthProvider(t *testing.T, ctx context.Context) string {
ctx = authz.WithInstance(ctx, s.Instance)
id, _, err := s.Commands.AddInstanceGenericOAuthProvider(ctx, command.GenericOAuthProvider{
@ -378,7 +404,7 @@ func (s *Tester) AddGenericOAuthProvider(t *testing.T, ctx context.Context) stri
UserEndpoint: "https://api.example.com/user",
Scopes: []string{"openid", "profile", "email"},
IDAttribute: "id",
IDPOptions: idp.Options{
IDPOptions: idp_rp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
@ -389,6 +415,28 @@ func (s *Tester) AddGenericOAuthProvider(t *testing.T, ctx context.Context) stri
return id
}
func (s *Tester) AddOrgGenericOAuthIDP(ctx context.Context, name string) *mgmt.AddGenericOAuthProviderResponse {
resp, err := s.Client.Mgmt.AddGenericOAuthProvider(ctx, &mgmt.AddGenericOAuthProviderRequest{
Name: name,
ClientId: "clientID",
ClientSecret: "clientSecret",
AuthorizationEndpoint: "https://example.com/oauth/v2/authorize",
TokenEndpoint: "https://example.com/oauth/v2/token",
UserEndpoint: "https://api.example.com/user",
Scopes: []string{"openid", "profile", "email"},
IdAttribute: "id",
ProviderOptions: &idp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
IsAutoUpdate: true,
AutoLinking: idp.AutoLinkingOption_AUTO_LINKING_OPTION_USERNAME,
},
})
logging.OnError(err).Fatal("create generic OAuth idp")
return resp
}
func (s *Tester) AddOrgGenericOAuthProvider(t *testing.T, ctx context.Context, orgID string) string {
ctx = authz.WithInstance(ctx, s.Instance)
id, _, err := s.Commands.AddOrgGenericOAuthProvider(ctx, orgID,
@ -401,7 +449,7 @@ func (s *Tester) AddOrgGenericOAuthProvider(t *testing.T, ctx context.Context, o
UserEndpoint: "https://api.example.com/user",
Scopes: []string{"openid", "profile", "email"},
IDAttribute: "id",
IDPOptions: idp.Options{
IDPOptions: idp_rp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
@ -417,7 +465,7 @@ func (s *Tester) AddSAMLProvider(t *testing.T, ctx context.Context) string {
id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{
Name: "saml-idp",
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
IDPOptions: idp.Options{
IDPOptions: idp_rp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
@ -435,7 +483,7 @@ func (s *Tester) AddSAMLRedirectProvider(t *testing.T, ctx context.Context, tran
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
TransientMappingAttributeName: transientMappingAttributeName,
IDPOptions: idp.Options{
IDPOptions: idp_rp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,
@ -452,7 +500,7 @@ func (s *Tester) AddSAMLPostProvider(t *testing.T, ctx context.Context) string {
Name: "saml-idp-post",
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
IDPOptions: idp.Options{
IDPOptions: idp_rp.Options{
IsLinkingAllowed: true,
IsCreationAllowed: true,
IsAutoCreation: true,

View File

@ -712,8 +712,29 @@ var (
}
)
// IDPTemplateByID searches for the requested id
func (q *Queries) IDPTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (template *IDPTemplate, err error) {
// IDPTemplateByID searches for the requested id with permission check if necessary
func (q *Queries) IDPTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, permissionCheck domain.PermissionCheck, queries ...SearchQuery) (template *IDPTemplate, err error) {
idp, err := q.idpTemplateByID(ctx, shouldTriggerBulk, id, withOwnerRemoved, queries...)
if err != nil {
return nil, err
}
if permissionCheck != nil {
switch idp.OwnerType {
case domain.IdentityProviderTypeSystem:
if err := permissionCheck(ctx, domain.PermissionIDPRead, idp.ResourceOwner, idp.ID); err != nil {
return nil, err
}
case domain.IdentityProviderTypeOrg:
if err := permissionCheck(ctx, domain.PermissionOrgIDPRead, idp.ResourceOwner, idp.ID); err != nil {
return nil, err
}
}
}
return idp, nil
}
// idpTemplateByID searches for the requested id
func (q *Queries) idpTemplateByID(ctx context.Context, shouldTriggerBulk bool, id string, withOwnerRemoved bool, queries ...SearchQuery) (template *IDPTemplate, err error) {
ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }()

4
pkg/grpc/idp/v2/idp.go Normal file
View File

@ -0,0 +1,4 @@
package idp
type IsIDPConfig = isIDPConfig_Config
type IsAzureADTenantType = isAzureADTenant_Type

View File

@ -0,0 +1,391 @@
syntax = "proto3";
package zitadel.idp.v2;
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "zitadel/object/v2/object.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "google/protobuf/duration.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/idp/v2;idp";
message IDP {
// Unique identifier for the identity provider.
string id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"69629023906488334\"";
}
];
zitadel.object.v2.Details details = 2;
// Current state of the identity provider.
IDPState state = 3;
string name = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"Google\"";
}
];
// Type of the identity provider, for example OIDC, JWT, LDAP and SAML.
IDPType type = 5;
// Configuration for the type of the identity provider.
IDPConfig config = 6;
}
enum IDPState {
IDP_STATE_UNSPECIFIED = 0;
IDP_STATE_ACTIVE = 1;
IDP_STATE_INACTIVE = 2;
IDP_STATE_REMOVED = 3;
IDP_STATE_MIGRATED = 4;
}
enum IDPType {
IDP_TYPE_UNSPECIFIED = 0;
IDP_TYPE_OIDC = 1;
IDP_TYPE_JWT = 2;
IDP_TYPE_LDAP = 3;
IDP_TYPE_OAUTH = 4;
IDP_TYPE_AZURE_AD = 5;
IDP_TYPE_GITHUB = 6;
IDP_TYPE_GITHUB_ES = 7;
IDP_TYPE_GITLAB = 8;
IDP_TYPE_GITLAB_SELF_HOSTED = 9;
IDP_TYPE_GOOGLE = 10;
IDP_TYPE_APPLE = 11;
IDP_TYPE_SAML = 12;
}
enum SAMLBinding {
SAML_BINDING_UNSPECIFIED = 0;
SAML_BINDING_POST = 1;
SAML_BINDING_REDIRECT = 2;
SAML_BINDING_ARTIFACT = 3;
}
enum SAMLNameIDFormat {
SAML_NAME_ID_FORMAT_UNSPECIFIED = 0;
SAML_NAME_ID_FORMAT_EMAIL_ADDRESS = 1;
SAML_NAME_ID_FORMAT_PERSISTENT = 2;
SAML_NAME_ID_FORMAT_TRANSIENT = 3;
}
message IDPConfig {
Options options = 1;
oneof config {
LDAPConfig ldap = 2;
GoogleConfig google = 3;
OAuthConfig oauth = 4;
GenericOIDCConfig oidc = 5;
JWTConfig jwt = 6;
GitHubConfig github = 7;
GitHubEnterpriseServerConfig github_es = 8;
GitLabConfig gitlab = 9;
GitLabSelfHostedConfig gitlab_self_hosted = 10;
AzureADConfig azure_ad = 11;
AppleConfig apple = 12;
SAMLConfig saml = 13;
}
}
message JWTConfig {
// The endpoint where the JWT can be extracted.
string jwt_endpoint = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com\"";
}
];
// The issuer of the JWT (for validation).
string issuer = 2 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com\"";
}
];
// The endpoint to the key (JWK) which is used to sign the JWT with.
string keys_endpoint = 3 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com/keys\"";
}
];
// The name of the header where the JWT is sent in, default is authorization.
string header_name = 4 [
(validate.rules).string = {min_len: 1, max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"x-auth-token\"";
}
];
}
message OAuthConfig {
// Client id generated by the identity provider.
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// The endpoint where ZITADEL send the user to authenticate.
string authorization_endpoint = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com/o/oauth2/v2/auth\"";
}
];
// The endpoint where ZITADEL can get the token.
string token_endpoint = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://oauth2.googleapis.com/token\"";
}
];
// The endpoint where ZITADEL can get the user information.
string user_endpoint = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://openidconnect.googleapis.com/v1/userinfo\"";
}
];
// The scopes requested by ZITADEL during the request on the identity provider.
repeated string scopes = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
// Defines how the attribute is called where ZITADEL can get the id of the user.
string id_attribute = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"user_id\"";
}
];
}
message GenericOIDCConfig {
// The OIDC issuer of the identity provider.
string issuer = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"https://accounts.google.com/\"";
}
];
// Client id generated by the identity provider.
string client_id = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// The scopes requested by ZITADEL during the request on the identity provider.
repeated string scopes = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
// If true, provider information get mapped from the id token, not from the userinfo endpoint.
bool is_id_token_mapping = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "true";
}
];
}
message GitHubConfig {
// The client ID of the GitHub App.
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// The scopes requested by ZITADEL during the request to GitHub.
repeated string scopes = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
}
message GitHubEnterpriseServerConfig {
// The client ID of the GitHub App.
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
string authorization_endpoint = 2;
string token_endpoint = 3;
string user_endpoint = 4;
// The scopes requested by ZITADEL during the request to GitHub.
repeated string scopes = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
}
message GoogleConfig {
// Client id of the Google application.
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// The scopes requested by ZITADEL during the request to Google.
repeated string scopes = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
}
message GitLabConfig {
// Client id of the GitLab application.
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// The scopes requested by ZITADEL during the request to GitLab.
repeated string scopes = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
}
message GitLabSelfHostedConfig {
string issuer = 1;
// Client id of the GitLab application.
string client_id = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// The scopes requested by ZITADEL during the request to GitLab.
repeated string scopes = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\"]";
}
];
}
message LDAPConfig {
repeated string servers = 1;
bool start_tls = 2;
string base_dn = 3;
string bind_dn = 4;
string user_base = 5;
repeated string user_object_classes = 6;
repeated string user_filters = 7;
google.protobuf.Duration timeout = 8;
LDAPAttributes attributes = 9;
}
message SAMLConfig {
// Metadata of the SAML identity provider.
bytes metadata_xml = 1;
// Binding which defines the type of communication with the identity provider.
SAMLBinding binding = 2;
// Boolean which defines if the authentication requests are signed.
bool with_signed_request = 3;
// `nameid-format` for the SAML Request.
SAMLNameIDFormat name_id_format = 4;
// Optional name of the attribute, which will be used to map the user
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
optional string transient_mapping_attribute_name = 5;
}
message AzureADConfig {
// Client id of the Azure AD application
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"client-id\"";
}
];
// Defines what user accounts should be able to login (Personal, Organizational, All).
AzureADTenant tenant = 2;
// Azure AD doesn't send if the email has been verified. Enable this if the user email should always be added verified in ZITADEL (no verification emails will be sent).
bool email_verified = 3;
// The scopes requested by ZITADEL during the request to Azure AD.
repeated string scopes = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"openid\", \"profile\", \"email\", \"User.Read\"]";
}
];
}
message Options {
// Enable if users should be able to link an existing ZITADEL user with an external account.
bool is_linking_allowed = 1;
// Enable if users should be able to create a new account in ZITADEL when using an external account.
bool is_creation_allowed = 2;
// Enable if a new account in ZITADEL should be created automatically when login with an external account.
bool is_auto_creation = 3;
// Enable if a the ZITADEL account fields should be updated automatically on each login.
bool is_auto_update = 4;
// Enable if users should get prompted to link an existing ZITADEL user to an external account if the selected attribute matches.
AutoLinkingOption auto_linking = 5 ;
}
enum AutoLinkingOption {
// AUTO_LINKING_OPTION_UNSPECIFIED disables the auto linking prompt.
AUTO_LINKING_OPTION_UNSPECIFIED = 0;
// AUTO_LINKING_OPTION_USERNAME will use the username of the external user to check for a corresponding ZITADEL user.
AUTO_LINKING_OPTION_USERNAME = 1;
// AUTO_LINKING_OPTION_EMAIL will use the email of the external user to check for a corresponding ZITADEL user with the same verified email
// Note that in case multiple users match, no prompt will be shown.
AUTO_LINKING_OPTION_EMAIL = 2;
}
message LDAPAttributes {
string id_attribute = 1 [(validate.rules).string = {max_len: 200}];
string first_name_attribute = 2 [(validate.rules).string = {max_len: 200}];
string last_name_attribute = 3 [(validate.rules).string = {max_len: 200}];
string display_name_attribute = 4 [(validate.rules).string = {max_len: 200}];
string nick_name_attribute = 5 [(validate.rules).string = {max_len: 200}];
string preferred_username_attribute = 6 [(validate.rules).string = {max_len: 200}];
string email_attribute = 7 [(validate.rules).string = {max_len: 200}];
string email_verified_attribute = 8 [(validate.rules).string = {max_len: 200}];
string phone_attribute = 9 [(validate.rules).string = {max_len: 200}];
string phone_verified_attribute = 10 [(validate.rules).string = {max_len: 200}];
string preferred_language_attribute = 11 [(validate.rules).string = {max_len: 200}];
string avatar_url_attribute = 12 [(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;
}
}
message AppleConfig {
// Client id (App ID or Service ID) provided by Apple.
string client_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"com.client.id\"";
}
];
// Team ID provided by Apple.
string team_id = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"ALT03JV3OS\"";
}
];
// ID of the private key generated by Apple.
string key_id = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"OGKDK25KD\"";
}
];
// The scopes requested by ZITADEL during the request to Apple.
repeated string scopes = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"name\", \"email\"]";
}
];
}

View File

@ -0,0 +1,136 @@
syntax = "proto3";
package zitadel.idp.v2;
import "zitadel/protoc_gen_zitadel/v2/options.proto";
import "zitadel/object/v2/object.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/idp/v2/idp.proto";
option go_package = "github.com/zitadel/zitadel/pkg/grpc/idp/v2;idp";
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
info: {
title: "Identity Provider Service";
version: "2.0";
description: "This API is intended to manage identity providers (IdPs) in a ZITADEL instance.";
contact:{
name: "ZITADEL"
url: "https://zitadel.com"
email: "hi@zitadel.com"
}
license: {
name: "Apache 2.0",
url: "https://github.com/zitadel/zitadel/blob/main/LICENSE";
};
};
schemes: HTTPS;
schemes: HTTP;
consumes: "application/json";
consumes: "application/grpc";
produces: "application/json";
produces: "application/grpc";
consumes: "application/grpc-web+proto";
produces: "application/grpc-web+proto";
host: "$CUSTOM-DOMAIN";
base_path: "/";
external_docs: {
description: "Detailed information about ZITADEL",
url: "https://zitadel.com/docs"
}
security_definitions: {
security: {
key: "OAuth2";
value: {
type: TYPE_OAUTH2;
flow: FLOW_ACCESS_CODE;
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
scopes: {
scope: {
key: "openid";
value: "openid";
}
scope: {
key: "urn:zitadel:iam:org:project:id:zitadel:aud";
value: "urn:zitadel:iam:org:project:id:zitadel:aud";
}
}
}
}
}
security: {
security_requirement: {
key: "OAuth2";
value: {
scope: "openid";
scope: "urn:zitadel:iam:org:project:id:zitadel:aud";
}
}
}
responses: {
key: "403";
value: {
description: "Returned when the user does not have permission to access the resource.";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
}
}
}
}
responses: {
key: "404";
value: {
description: "Returned when the resource does not exist.";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
}
}
}
}
};
service IdentityProviderService {
// Get identity provider (IdP) by ID
//
// Returns an identity provider (social/enterprise login) by its ID, which can be of the type Google, AzureAD, etc.
rpc GetIDPByID (GetIDPByIDRequest) returns (GetIDPByIDResponse) {
option (google.api.http) = {
get: "/v2/idps/{id}"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
responses: {
key: "200"
value: {
description: "OK";
}
};
};
}
}
message GetIDPByIDRequest {
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
}
message GetIDPByIDResponse {
zitadel.idp.v2.IDP idp = 1;
}

View File

@ -47,7 +47,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
consumes: "application/grpc-web+proto";
produces: "application/grpc-web+proto";
host: "${ZITADEL_DOMAIN}";
host: "$CUSTOM-DOMAIN";
base_path: "/resources/v3alpha/actions";
external_docs: {
@ -60,8 +60,8 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
value: {
type: TYPE_OAUTH2;
flow: FLOW_ACCESS_CODE;
authorization_url: "${ZITADEL_DOMAIN}/oauth/v2/authorize";
token_url: "${ZITADEL_DOMAIN}/oauth/v2/token";
authorization_url: "$CUSTOM-DOMAIN/oauth/v2/authorize";
token_url: "$CUSTOM-DOMAIN/oauth/v2/token";
scopes: {
scope: {
key: "openid";
@ -135,7 +135,7 @@ service ZITADELActions {
description: "Target successfully created";
schema: {
json_schema: {
ref: "#/definitions/CreateTargetResponse";
ref: "#/definitions/v3alphaCreateTargetResponse";
}
}
};
@ -278,7 +278,7 @@ service ZITADELActions {
description: "Execution successfully updated or left unchanged";
schema: {
json_schema: {
ref: "#/definitions/SetExecutionResponse";
ref: "#/definitions/v3alphaSetExecutionResponse";
}
}
};

View File

@ -42,7 +42,7 @@ option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_swagger) = {
consumes: "application/grpc-web+proto";
produces: "application/grpc-web+proto";
host: "${ZITADEL_DOMAIN}";
host: "$CUSTOM-DOMAIN";
base_path: "/resources/v3alpha/web_keys";
external_docs: {