From ff9af1704f3cb04332eb9f44fc42a23bc149e70e Mon Sep 17 00:00:00 2001 From: Fabi <38692350+fgerschwiler@users.noreply.github.com> Date: Tue, 6 Jul 2021 16:39:48 +0200 Subject: [PATCH] feat: Extend oidc idp with oauth endpoints (#1980) * feat: add oauth attributes to oidc idp configuration * feat: return idpconfig id on create idp * feat: tests * feat: descriptions * feat: docs * feat: tests --- docs/docs/apis/proto/admin.md | 8 +- docs/docs/apis/proto/idp.md | 2 + docs/docs/apis/proto/management.md | 8 +- go.mod | 1 + go.sum | 1 - internal/api/grpc/admin/idp.go | 2 +- internal/api/grpc/admin/idp_converter.go | 4 + internal/api/grpc/admin/idp_converter_test.go | 48 +++++++----- internal/api/grpc/idp/converter.go | 36 ++++----- internal/api/grpc/management/idp.go | 2 +- internal/api/grpc/management/idp_converter.go | 4 + .../api/grpc/management/idp_converter_test.go | 48 +++++++----- internal/command/iam_converter.go | 2 + internal/command/iam_idp_config.go | 2 + internal/command/iam_idp_config_test.go | 6 ++ internal/command/iam_idp_oidc_config.go | 2 + internal/command/iam_idp_oidc_config_model.go | 8 ++ internal/command/iam_idp_oidc_config_test.go | 18 ++++- internal/command/oidc_config_model.go | 20 +++-- internal/command/org_idp_config.go | 2 + internal/command/org_idp_config_test.go | 6 ++ internal/command/org_idp_oidc_config.go | 2 + internal/command/org_idp_oidc_config_model.go | 8 ++ internal/command/org_idp_oidc_config_test.go | 18 ++++- internal/domain/idp_config.go | 21 +++-- internal/iam/model/idp_config_view.go | 16 ++-- .../iam/repository/view/model/idp_config.go | 77 ++++++++----------- internal/query/converter.go | 2 + internal/query/oidc_config_model.go | 10 +++ internal/repository/iam/idp_oidc_config.go | 6 +- internal/repository/idpconfig/oidc_config.go | 40 +++++++--- internal/repository/org/idp_oidc_config.go | 6 +- .../login/handler/external_login_handler.go | 26 ++++++- internal/ui/login/static/i18n/de.yaml | 2 + internal/ui/login/static/i18n/en.yaml | 3 +- migrations/cockroach/V1.54__oauth_idp.sql | 7 ++ proto/zitadel/admin.proto | 43 ++++++++++- proto/zitadel/idp.proto | 16 ++++ proto/zitadel/management.proto | 42 +++++++++- 39 files changed, 419 insertions(+), 156 deletions(-) create mode 100644 migrations/cockroach/V1.54__oauth_idp.sql diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index a2e6cde824..0a1dc2edd9 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -1072,10 +1072,12 @@ This is an empty request | styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true
| | client_id | string | - | string.min_len: 1
string.max_len: 200
| | client_secret | string | - | string.min_len: 1
string.max_len: 200
| -| issuer | string | - | string.min_len: 1
string.max_len: 200
| +| issuer | string | Fill the issuer if the identity provider is oidc discovery compliant If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead | string.max_len: 200
| | scopes | repeated string | - | | | display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| | username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| +| authorization_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| +| token_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| @@ -2498,12 +2500,14 @@ This is an empty request | Field | Type | Description | Validation | | ----- | ---- | ----------- | ----------- | | idp_id | string | - | string.min_len: 1
string.max_len: 200
| -| issuer | string | - | string.min_len: 1
string.max_len: 200
| +| issuer | string | Fill the issuer if the identity provider is oidc discovery compliant If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead | string.min_len: 1
string.max_len: 200
| | client_id | string | - | string.min_len: 1
string.max_len: 200
| | client_secret | string | - | string.max_len: 200
| | scopes | repeated string | - | | | display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| | username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| +| authorization_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| +| token_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| diff --git a/docs/docs/apis/proto/idp.md b/docs/docs/apis/proto/idp.md index de646c8879..28e95ca36e 100644 --- a/docs/docs/apis/proto/idp.md +++ b/docs/docs/apis/proto/idp.md @@ -100,6 +100,8 @@ title: zitadel/idp.proto | scopes | repeated string | - | | | display_name_mapping | OIDCMappingField | - | | | username_mapping | OIDCMappingField | - | | +| authorization_endpoint | string | - | string.max_len: 500
| +| token_endpoint | string | - | string.max_len: 500
| diff --git a/docs/docs/apis/proto/management.md b/docs/docs/apis/proto/management.md index 4730ebb47a..8df950489b 100644 --- a/docs/docs/apis/proto/management.md +++ b/docs/docs/apis/proto/management.md @@ -3007,10 +3007,12 @@ This is an empty request | styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true
| | client_id | string | - | string.min_len: 1
string.max_len: 200
| | client_secret | string | - | string.min_len: 1
string.max_len: 200
| -| issuer | string | - | string.min_len: 1
string.max_len: 200
| +| issuer | string | Fill the issuer if the identity provider is oidc discovery compliant If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead | string.max_len: 200
| | scopes | repeated string | - | | | display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| | username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| +| authorization_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| +| token_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| @@ -6901,10 +6903,12 @@ This is an empty request | idp_id | string | - | string.min_len: 1
string.max_len: 200
| | client_id | string | - | string.min_len: 1
string.max_len: 200
| | client_secret | string | - | string.max_len: 200
| -| issuer | string | - | string.min_len: 1
string.max_len: 200
| +| issuer | string | Fill the issuer if the identity provider is oidc discovery compliant If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead | string.min_len: 1
string.max_len: 200
| | scopes | repeated string | - | | | display_name_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| | username_mapping | zitadel.idp.v1.OIDCMappingField | - | enum.defined_only: true
| +| authorization_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| +| token_endpoint | string | If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer | string.max_len: 500
| diff --git a/go.mod b/go.mod index d76a37649b..df53035d50 100644 --- a/go.mod +++ b/go.mod @@ -69,6 +69,7 @@ require ( go.opentelemetry.io/otel/exporters/stdout v0.13.0 go.opentelemetry.io/otel/sdk v0.13.0 golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + golang.org/x/oauth2 v0.0.0-20210201163806-010130855d6c golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/text v0.3.6 golang.org/x/tools v0.1.1 diff --git a/go.sum b/go.sum index cd53e0d22f..21f2bb0fec 100644 --- a/go.sum +++ b/go.sum @@ -240,7 +240,6 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.6.1 h1:4CF52PCseTFt4bE+Yk3dIpdVi7XWuPVMhPtm4FaIJPM= github.com/envoyproxy/protoc-gen-validate v0.6.1/go.mod h1:txg5va2Qkip90uYoSKH+nkAAmXrb2j3iq4FLwdrCbXQ= diff --git a/internal/api/grpc/admin/idp.go b/internal/api/grpc/admin/idp.go index 631d6d4c73..a1dc990a33 100644 --- a/internal/api/grpc/admin/idp.go +++ b/internal/api/grpc/admin/idp.go @@ -33,7 +33,7 @@ func (s *Server) AddOIDCIDP(ctx context.Context, req *admin_pb.AddOIDCIDPRequest return nil, err } return &admin_pb.AddOIDCIDPResponse{ - IdpId: config.AggregateID, + IdpId: config.IDPConfigID, Details: object_pb.AddToDetailsPb( config.Sequence, config.ChangeDate, diff --git a/internal/api/grpc/admin/idp_converter.go b/internal/api/grpc/admin/idp_converter.go index c74eff76d1..6e62e3a011 100644 --- a/internal/api/grpc/admin/idp_converter.go +++ b/internal/api/grpc/admin/idp_converter.go @@ -24,6 +24,8 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *admin_pb.AddOIDCIDPRequest) *do ClientID: req.ClientId, ClientSecretString: req.ClientSecret, Issuer: req.Issuer, + AuthorizationEndpoint: req.AuthorizationEndpoint, + TokenEndpoint: req.TokenEndpoint, Scopes: req.Scopes, IDPDisplayNameMapping: idp_grpc.MappingFieldToDomain(req.DisplayNameMapping), UsernameMapping: idp_grpc.MappingFieldToDomain(req.UsernameMapping), @@ -44,6 +46,8 @@ func updateOIDCConfigToDomain(req *admin_pb.UpdateIDPOIDCConfigRequest) *domain. ClientID: req.ClientId, ClientSecretString: req.ClientSecret, Issuer: req.Issuer, + AuthorizationEndpoint: req.AuthorizationEndpoint, + TokenEndpoint: req.TokenEndpoint, Scopes: req.Scopes, IDPDisplayNameMapping: idp_grpc.MappingFieldToDomain(req.DisplayNameMapping), UsernameMapping: idp_grpc.MappingFieldToDomain(req.UsernameMapping), diff --git a/internal/api/grpc/admin/idp_converter_test.go b/internal/api/grpc/admin/idp_converter_test.go index f9389a1c3d..a716e55c12 100644 --- a/internal/api/grpc/admin/idp_converter_test.go +++ b/internal/api/grpc/admin/idp_converter_test.go @@ -20,14 +20,16 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) { name: "all fields filled", args: args{ req: &admin_pb.AddOIDCIDPRequest{ - Name: "ZITADEL", - StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, - ClientId: "test1234", - ClientSecret: "test4321", - Issuer: "zitadel.ch", - Scopes: []string{"email", "profile"}, - DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, - UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + Name: "ZITADEL", + StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, + ClientId: "test1234", + ClientSecret: "test4321", + Issuer: "zitadel.ch", + AuthorizationEndpoint: "https://accounts.zitadel.ch/oauth/v2/authorize", + TokenEndpoint: "https://api.zitadel.ch/oauth/v2/token", + Scopes: []string{"email", "profile"}, + DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, + UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, }, }, }, @@ -60,12 +62,14 @@ func Test_addOIDCIDPRequestToDomainOIDCIDPConfig(t *testing.T) { name: "all fields filled", args: args{ req: &admin_pb.AddOIDCIDPRequest{ - ClientId: "test1234", - ClientSecret: "test4321", - Issuer: "zitadel.ch", - Scopes: []string{"email", "profile"}, - DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, - UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + ClientId: "test1234", + ClientSecret: "test4321", + Issuer: "zitadel.ch", + AuthorizationEndpoint: "https://accounts.zitadel.ch/oauth/v2/authorize", + TokenEndpoint: "https://api.zitadel.ch/oauth/v2/token", + Scopes: []string{"email", "profile"}, + DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, + UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, }, }, }, @@ -126,13 +130,15 @@ func Test_updateOIDCConfigToDomain(t *testing.T) { name: "all fields filled", args: args{ req: &admin_pb.UpdateIDPOIDCConfigRequest{ - IdpId: "4208", - Issuer: "zitadel.ch", - ClientId: "ZITEADEL", - ClientSecret: "i'm so secret", - Scopes: []string{"profile"}, - DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, - UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + IdpId: "4208", + Issuer: "zitadel.ch", + AuthorizationEndpoint: "https://accounts.zitadel.ch/oauth/v2/authorize", + TokenEndpoint: "https://api.zitadel.ch/oauth/v2/token", + ClientId: "ZITEADEL", + ClientSecret: "i'm so secret", + Scopes: []string{"profile"}, + DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, + UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, }, }, }, diff --git a/internal/api/grpc/idp/converter.go b/internal/api/grpc/idp/converter.go index 33edef14e0..d5232b6658 100644 --- a/internal/api/grpc/idp/converter.go +++ b/internal/api/grpc/idp/converter.go @@ -133,11 +133,13 @@ func IDPStylingTypeToPb(stylingType domain.IDPConfigStylingType) idp_pb.IDPStyli func ModelIDPViewToConfigPb(config *iam_model.IDPConfigView) *idp_pb.IDP_OidcConfig { return &idp_pb.IDP_OidcConfig{ OidcConfig: &idp_pb.OIDCConfig{ - ClientId: config.OIDCClientID, - Issuer: config.OIDCIssuer, - Scopes: config.OIDCScopes, - DisplayNameMapping: ModelMappingFieldToPb(config.OIDCIDPDisplayNameMapping), - UsernameMapping: ModelMappingFieldToPb(config.OIDCUsernameMapping), + ClientId: config.OIDCClientID, + Issuer: config.OIDCIssuer, + Scopes: config.OIDCScopes, + DisplayNameMapping: ModelMappingFieldToPb(config.OIDCIDPDisplayNameMapping), + UsernameMapping: ModelMappingFieldToPb(config.OIDCUsernameMapping), + AuthorizationEndpoint: config.OAuthAuthorizationEndpoint, + TokenEndpoint: config.OAuthTokenEndpoint, }, } } @@ -145,23 +147,13 @@ func ModelIDPViewToConfigPb(config *iam_model.IDPConfigView) *idp_pb.IDP_OidcCon func IDPViewToConfigPb(config *domain.IDPConfigView) *idp_pb.IDP_OidcConfig { return &idp_pb.IDP_OidcConfig{ OidcConfig: &idp_pb.OIDCConfig{ - ClientId: config.OIDCClientID, - Issuer: config.OIDCIssuer, - Scopes: config.OIDCScopes, - DisplayNameMapping: MappingFieldToPb(config.OIDCIDPDisplayNameMapping), - UsernameMapping: MappingFieldToPb(config.OIDCUsernameMapping), - }, - } -} - -func OIDCConfigToPb(config *domain.OIDCIDPConfig) *idp_pb.IDP_OidcConfig { - return &idp_pb.IDP_OidcConfig{ - OidcConfig: &idp_pb.OIDCConfig{ - ClientId: config.ClientID, - Issuer: config.Issuer, - Scopes: config.Scopes, - DisplayNameMapping: MappingFieldToPb(config.IDPDisplayNameMapping), - UsernameMapping: MappingFieldToPb(config.UsernameMapping), + ClientId: config.OIDCClientID, + Issuer: config.OIDCIssuer, + AuthorizationEndpoint: config.OAuthAuthorizationEndpoint, + TokenEndpoint: config.OAuthTokenEndpoint, + Scopes: config.OIDCScopes, + DisplayNameMapping: MappingFieldToPb(config.OIDCIDPDisplayNameMapping), + UsernameMapping: MappingFieldToPb(config.OIDCUsernameMapping), }, } } diff --git a/internal/api/grpc/management/idp.go b/internal/api/grpc/management/idp.go index 6ec192040d..60d6619d51 100644 --- a/internal/api/grpc/management/idp.go +++ b/internal/api/grpc/management/idp.go @@ -32,7 +32,7 @@ func (s *Server) AddOrgOIDCIDP(ctx context.Context, req *mgmt_pb.AddOrgOIDCIDPRe return nil, err } return &mgmt_pb.AddOrgOIDCIDPResponse{ - IdpId: config.AggregateID, + IdpId: config.IDPConfigID, Details: object_pb.AddToDetailsPb( config.Sequence, config.ChangeDate, diff --git a/internal/api/grpc/management/idp_converter.go b/internal/api/grpc/management/idp_converter.go index 241e3be779..a3a8125c0d 100644 --- a/internal/api/grpc/management/idp_converter.go +++ b/internal/api/grpc/management/idp_converter.go @@ -24,6 +24,8 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *mgmt_pb.AddOrgOIDCIDPRequest) * ClientID: req.ClientId, ClientSecretString: req.ClientSecret, Issuer: req.Issuer, + AuthorizationEndpoint: req.AuthorizationEndpoint, + TokenEndpoint: req.TokenEndpoint, Scopes: req.Scopes, IDPDisplayNameMapping: idp_grpc.MappingFieldToDomain(req.DisplayNameMapping), UsernameMapping: idp_grpc.MappingFieldToDomain(req.UsernameMapping), @@ -44,6 +46,8 @@ func updateOIDCConfigToDomain(req *mgmt_pb.UpdateOrgIDPOIDCConfigRequest) *domai ClientID: req.ClientId, ClientSecretString: req.ClientSecret, Issuer: req.Issuer, + AuthorizationEndpoint: req.AuthorizationEndpoint, + TokenEndpoint: req.TokenEndpoint, Scopes: req.Scopes, IDPDisplayNameMapping: idp_grpc.MappingFieldToDomain(req.DisplayNameMapping), UsernameMapping: idp_grpc.MappingFieldToDomain(req.UsernameMapping), diff --git a/internal/api/grpc/management/idp_converter_test.go b/internal/api/grpc/management/idp_converter_test.go index b7571d0dae..9db7683d11 100644 --- a/internal/api/grpc/management/idp_converter_test.go +++ b/internal/api/grpc/management/idp_converter_test.go @@ -20,14 +20,16 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) { name: "all fields filled", args: args{ req: &mgmt_pb.AddOrgOIDCIDPRequest{ - Name: "ZITADEL", - StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, - ClientId: "test1234", - ClientSecret: "test4321", - Issuer: "zitadel.ch", - Scopes: []string{"email", "profile"}, - DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, - UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + Name: "ZITADEL", + StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE, + ClientId: "test1234", + ClientSecret: "test4321", + Issuer: "zitadel.ch", + AuthorizationEndpoint: "https://accounts.zitadel.ch/oauth/v2/authorize", + TokenEndpoint: "https://api.zitadel.ch/oauth/v2/token", + Scopes: []string{"email", "profile"}, + DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, + UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, }, }, }, @@ -60,12 +62,14 @@ func Test_addOIDCIDPRequestToDomainOIDCIDPConfig(t *testing.T) { name: "all fields filled", args: args{ req: &mgmt_pb.AddOrgOIDCIDPRequest{ - ClientId: "test1234", - ClientSecret: "test4321", - Issuer: "zitadel.ch", - Scopes: []string{"email", "profile"}, - DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, - UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + ClientId: "test1234", + ClientSecret: "test4321", + Issuer: "zitadel.ch", + AuthorizationEndpoint: "https://accounts.zitadel.ch/oauth/v2/authorize", + TokenEndpoint: "https://api.zitadel.ch/oauth/v2/token", + Scopes: []string{"email", "profile"}, + DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, + UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, }, }, }, @@ -126,13 +130,15 @@ func Test_updateOIDCConfigToDomain(t *testing.T) { name: "all fields filled", args: args{ req: &mgmt_pb.UpdateOrgIDPOIDCConfigRequest{ - IdpId: "4208", - Issuer: "zitadel.ch", - ClientId: "ZITEADEL", - ClientSecret: "i'm so secret", - Scopes: []string{"profile"}, - DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, - UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, + IdpId: "4208", + Issuer: "zitadel.ch", + AuthorizationEndpoint: "https://accounts.zitadel.ch/oauth/v2/authorize", + TokenEndpoint: "https://api.zitadel.ch/oauth/v2/token", + ClientId: "ZITEADEL", + ClientSecret: "i'm so secret", + Scopes: []string{"profile"}, + DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL, + UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME, }, }, }, diff --git a/internal/command/iam_converter.go b/internal/command/iam_converter.go index cbe41a3c7c..e0eafdad2e 100644 --- a/internal/command/iam_converter.go +++ b/internal/command/iam_converter.go @@ -145,6 +145,8 @@ func writeModelToIDPOIDCConfig(wm *OIDCConfigWriteModel) *domain.OIDCIDPConfig { IDPConfigID: wm.IDPConfigID, IDPDisplayNameMapping: wm.IDPDisplayNameMapping, Issuer: wm.Issuer, + AuthorizationEndpoint: wm.AuthorizationEndpoint, + TokenEndpoint: wm.TokenEndpoint, Scopes: wm.Scopes, UsernameMapping: wm.UserNameMapping, } diff --git a/internal/command/iam_idp_config.go b/internal/command/iam_idp_config.go index 22745e1e41..3d1cda2c0a 100644 --- a/internal/command/iam_idp_config.go +++ b/internal/command/iam_idp_config.go @@ -44,6 +44,8 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo config.OIDCConfig.ClientID, idpConfigID, config.OIDCConfig.Issuer, + config.OIDCConfig.AuthorizationEndpoint, + config.OIDCConfig.TokenEndpoint, clientSecret, config.OIDCConfig.IDPDisplayNameMapping, config.OIDCConfig.UsernameMapping, diff --git a/internal/command/iam_idp_config_test.go b/internal/command/iam_idp_config_test.go index 86fced7d52..a900b6efa0 100644 --- a/internal/command/iam_idp_config_test.go +++ b/internal/command/iam_idp_config_test.go @@ -76,6 +76,8 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -102,6 +104,8 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) { OIDCConfig: &domain.OIDCIDPConfig{ ClientID: "clientid1", Issuer: "issuer", + AuthorizationEndpoint: "authorization-endpoint", + TokenEndpoint: "token-endpoint", ClientSecretString: "secret", Scopes: []string{"scope"}, IDPDisplayNameMapping: domain.OIDCMappingFieldEmail, @@ -216,6 +220,8 @@ func TestCommandSide_ChangeDefaultIDPConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", diff --git a/internal/command/iam_idp_oidc_config.go b/internal/command/iam_idp_oidc_config.go index 9838bf3bb7..9db8da5dbf 100644 --- a/internal/command/iam_idp_oidc_config.go +++ b/internal/command/iam_idp_oidc_config.go @@ -27,6 +27,8 @@ func (c *Commands) ChangeDefaultIDPOIDCConfig(ctx context.Context, config *domai config.IDPConfigID, config.ClientID, config.Issuer, + config.AuthorizationEndpoint, + config.TokenEndpoint, config.ClientSecretString, c.idpConfigSecretCrypto, config.IDPDisplayNameMapping, diff --git a/internal/command/iam_idp_oidc_config_model.go b/internal/command/iam_idp_oidc_config_model.go index de91f25b7b..238e98d256 100644 --- a/internal/command/iam_idp_oidc_config_model.go +++ b/internal/command/iam_idp_oidc_config_model.go @@ -90,6 +90,8 @@ func (wm *IAMIDPOIDCConfigWriteModel) NewChangedEvent( idpConfigID, clientID, issuer, + authorizationEndpoint, + tokenEndpoint, clientSecretString string, secretCrypto crypto.Crypto, idpDisplayNameMapping, @@ -113,6 +115,12 @@ func (wm *IAMIDPOIDCConfigWriteModel) NewChangedEvent( if wm.Issuer != issuer { changes = append(changes, idpconfig.ChangeIssuer(issuer)) } + if wm.AuthorizationEndpoint != authorizationEndpoint { + changes = append(changes, idpconfig.ChangeAuthorizationEndpoint(authorizationEndpoint)) + } + if wm.TokenEndpoint != tokenEndpoint { + changes = append(changes, idpconfig.ChangeTokenEndpoint(tokenEndpoint)) + } if idpDisplayNameMapping.Valid() && wm.IDPDisplayNameMapping != idpDisplayNameMapping { changes = append(changes, idpconfig.ChangeIDPDisplayNameMapping(idpDisplayNameMapping)) } diff --git a/internal/command/iam_idp_oidc_config_test.go b/internal/command/iam_idp_oidc_config_test.go index 479b417f17..425cfc791c 100644 --- a/internal/command/iam_idp_oidc_config_test.go +++ b/internal/command/iam_idp_oidc_config_test.go @@ -92,6 +92,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -144,6 +146,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -165,6 +169,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { IDPConfigID: "config1", ClientID: "clientid1", Issuer: "issuer", + AuthorizationEndpoint: "authorization-endpoint", + TokenEndpoint: "token-endpoint", Scopes: []string{"scope"}, IDPDisplayNameMapping: domain.OIDCMappingFieldEmail, UsernameMapping: domain.OIDCMappingFieldEmail, @@ -195,6 +201,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -214,6 +222,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { "config1", "clientid-changed", "issuer-changed", + "authorization-endpoint-changed", + "token-endpoint-changed", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -236,6 +246,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { IDPConfigID: "config1", ClientID: "clientid-changed", Issuer: "issuer-changed", + AuthorizationEndpoint: "authorization-endpoint-changed", + TokenEndpoint: "token-endpoint-changed", ClientSecretString: "secret-changed", Scopes: []string{"scope", "scope2"}, IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName, @@ -251,6 +263,8 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { IDPConfigID: "config1", ClientID: "clientid-changed", Issuer: "issuer-changed", + AuthorizationEndpoint: "authorization-endpoint-changed", + TokenEndpoint: "token-endpoint-changed", Scopes: []string{"scope", "scope2"}, IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName, UsernameMapping: domain.OIDCMappingFieldPreferredLoginName, @@ -278,13 +292,15 @@ func TestCommandSide_ChangeDefaultIDPOIDCConfig(t *testing.T) { } } -func newDefaultIDPOIDCConfigChangedEvent(ctx context.Context, configID, clientID, issuer string, secret *crypto.CryptoValue, displayMapping, usernameMapping domain.OIDCMappingField, scopes []string) *iam.IDPOIDCConfigChangedEvent { +func newDefaultIDPOIDCConfigChangedEvent(ctx context.Context, configID, clientID, issuer, authorizationEndpoint, tokenEndpoint string, secret *crypto.CryptoValue, displayMapping, usernameMapping domain.OIDCMappingField, scopes []string) *iam.IDPOIDCConfigChangedEvent { event, _ := iam.NewIDPOIDCConfigChangedEvent(ctx, &iam.NewAggregate().Aggregate, configID, []idpconfig.OIDCConfigChanges{ idpconfig.ChangeClientID(clientID), idpconfig.ChangeIssuer(issuer), + idpconfig.ChangeAuthorizationEndpoint(authorizationEndpoint), + idpconfig.ChangeTokenEndpoint(tokenEndpoint), idpconfig.ChangeClientSecret(secret), idpconfig.ChangeIDPDisplayNameMapping(displayMapping), idpconfig.ChangeUserNameMapping(usernameMapping), diff --git a/internal/command/oidc_config_model.go b/internal/command/oidc_config_model.go index 6fa34d3ae8..2802af55fb 100644 --- a/internal/command/oidc_config_model.go +++ b/internal/command/oidc_config_model.go @@ -10,11 +10,13 @@ import ( type OIDCConfigWriteModel struct { eventstore.WriteModel - IDPConfigID string - ClientID string - ClientSecret *crypto.CryptoValue - Issuer string - Scopes []string + IDPConfigID string + ClientID string + ClientSecret *crypto.CryptoValue + Issuer string + AuthorizationEndpoint string + TokenEndpoint string + Scopes []string IDPDisplayNameMapping domain.OIDCMappingField UserNameMapping domain.OIDCMappingField @@ -45,6 +47,8 @@ func (wm *OIDCConfigWriteModel) reduceConfigAddedEvent(e *idpconfig.OIDCConfigAd wm.ClientID = e.ClientID wm.ClientSecret = e.ClientSecret wm.Issuer = e.Issuer + wm.AuthorizationEndpoint = e.AuthorizationEndpoint + wm.TokenEndpoint = e.TokenEndpoint wm.Scopes = e.Scopes wm.IDPDisplayNameMapping = e.IDPDisplayNameMapping wm.UserNameMapping = e.UserNameMapping @@ -58,6 +62,12 @@ func (wm *OIDCConfigWriteModel) reduceConfigChangedEvent(e *idpconfig.OIDCConfig if e.Issuer != nil { wm.Issuer = *e.Issuer } + if e.AuthorizationEndpoint != nil { + wm.AuthorizationEndpoint = *e.AuthorizationEndpoint + } + if e.TokenEndpoint != nil { + wm.TokenEndpoint = *e.TokenEndpoint + } if len(e.Scopes) > 0 { wm.Scopes = e.Scopes } diff --git a/internal/command/org_idp_config.go b/internal/command/org_idp_config.go index 9abf73e3e1..6c90438074 100644 --- a/internal/command/org_idp_config.go +++ b/internal/command/org_idp_config.go @@ -47,6 +47,8 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r config.OIDCConfig.ClientID, idpConfigID, config.OIDCConfig.Issuer, + config.OIDCConfig.AuthorizationEndpoint, + config.OIDCConfig.TokenEndpoint, clientSecret, config.OIDCConfig.IDPDisplayNameMapping, config.OIDCConfig.UsernameMapping, diff --git a/internal/command/org_idp_config_test.go b/internal/command/org_idp_config_test.go index 8ffbbad682..781849ea4e 100644 --- a/internal/command/org_idp_config_test.go +++ b/internal/command/org_idp_config_test.go @@ -104,6 +104,8 @@ func TestCommandSide_AddIDPConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -131,6 +133,8 @@ func TestCommandSide_AddIDPConfig(t *testing.T) { OIDCConfig: &domain.OIDCIDPConfig{ ClientID: "clientid1", Issuer: "issuer", + AuthorizationEndpoint: "authorization-endpoint", + TokenEndpoint: "token-endpoint", ClientSecretString: "secret", Scopes: []string{"scope"}, IDPDisplayNameMapping: domain.OIDCMappingFieldEmail, @@ -264,6 +268,8 @@ func TestCommandSide_ChangeIDPConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", diff --git a/internal/command/org_idp_oidc_config.go b/internal/command/org_idp_oidc_config.go index 9e3531dbdc..4e15166cf0 100644 --- a/internal/command/org_idp_oidc_config.go +++ b/internal/command/org_idp_oidc_config.go @@ -30,6 +30,8 @@ func (c *Commands) ChangeIDPOIDCConfig(ctx context.Context, config *domain.OIDCI config.IDPConfigID, config.ClientID, config.Issuer, + config.AuthorizationEndpoint, + config.TokenEndpoint, config.ClientSecretString, c.idpConfigSecretCrypto, config.IDPDisplayNameMapping, diff --git a/internal/command/org_idp_oidc_config_model.go b/internal/command/org_idp_oidc_config_model.go index a88719ffd6..87eb01e3b2 100644 --- a/internal/command/org_idp_oidc_config_model.go +++ b/internal/command/org_idp_oidc_config_model.go @@ -90,6 +90,8 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent( idpConfigID, clientID, issuer, + authorizationEndpoint, + tokenEndpoint, clientSecretString string, secretCrypto crypto.Crypto, idpDisplayNameMapping, @@ -113,6 +115,12 @@ func (wm *IDPOIDCConfigWriteModel) NewChangedEvent( if wm.Issuer != issuer { changes = append(changes, idpconfig.ChangeIssuer(issuer)) } + if wm.AuthorizationEndpoint != authorizationEndpoint { + changes = append(changes, idpconfig.ChangeAuthorizationEndpoint(authorizationEndpoint)) + } + if wm.TokenEndpoint != tokenEndpoint { + changes = append(changes, idpconfig.ChangeTokenEndpoint(tokenEndpoint)) + } if idpDisplayNameMapping.Valid() && wm.IDPDisplayNameMapping != idpDisplayNameMapping { changes = append(changes, idpconfig.ChangeIDPDisplayNameMapping(idpDisplayNameMapping)) } diff --git a/internal/command/org_idp_oidc_config_test.go b/internal/command/org_idp_oidc_config_test.go index 30a94380e6..ec835de87e 100644 --- a/internal/command/org_idp_oidc_config_test.go +++ b/internal/command/org_idp_oidc_config_test.go @@ -112,6 +112,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -165,6 +167,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -186,6 +190,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { IDPConfigID: "config1", ClientID: "clientid1", Issuer: "issuer", + AuthorizationEndpoint: "authorization-endpoint", + TokenEndpoint: "token-endpoint", Scopes: []string{"scope"}, IDPDisplayNameMapping: domain.OIDCMappingFieldEmail, UsernameMapping: domain.OIDCMappingFieldEmail, @@ -217,6 +223,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "clientid1", "config1", "issuer", + "authorization-endpoint", + "token-endpoint", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -237,6 +245,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { "config1", "clientid-changed", "issuer-changed", + "authorization-endpoint-changed", + "token-endpoint-changed", &crypto.CryptoValue{ CryptoType: crypto.TypeEncryption, Algorithm: "enc", @@ -259,6 +269,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { IDPConfigID: "config1", ClientID: "clientid-changed", Issuer: "issuer-changed", + AuthorizationEndpoint: "authorization-endpoint-changed", + TokenEndpoint: "token-endpoint-changed", ClientSecretString: "secret-changed", Scopes: []string{"scope", "scope2"}, IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName, @@ -275,6 +287,8 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { IDPConfigID: "config1", ClientID: "clientid-changed", Issuer: "issuer-changed", + AuthorizationEndpoint: "authorization-endpoint-changed", + TokenEndpoint: "token-endpoint-changed", Scopes: []string{"scope", "scope2"}, IDPDisplayNameMapping: domain.OIDCMappingFieldPreferredLoginName, UsernameMapping: domain.OIDCMappingFieldPreferredLoginName, @@ -302,13 +316,15 @@ func TestCommandSide_ChangeIDPOIDCConfig(t *testing.T) { } } -func newIDPOIDCConfigChangedEvent(ctx context.Context, orgID, configID, clientID, issuer string, secret *crypto.CryptoValue, displayMapping, usernameMapping domain.OIDCMappingField, scopes []string) *org.IDPOIDCConfigChangedEvent { +func newIDPOIDCConfigChangedEvent(ctx context.Context, orgID, configID, clientID, issuer, authorizationEndpoint, tokenEndpoint string, secret *crypto.CryptoValue, displayMapping, usernameMapping domain.OIDCMappingField, scopes []string) *org.IDPOIDCConfigChangedEvent { event, _ := org.NewIDPOIDCConfigChangedEvent(ctx, &org.NewAggregate(orgID, orgID).Aggregate, configID, []idpconfig.OIDCConfigChanges{ idpconfig.ChangeClientID(clientID), idpconfig.ChangeIssuer(issuer), + idpconfig.ChangeAuthorizationEndpoint(authorizationEndpoint), + idpconfig.ChangeTokenEndpoint(tokenEndpoint), idpconfig.ChangeClientSecret(secret), idpconfig.ChangeIDPDisplayNameMapping(displayMapping), idpconfig.ChangeUserNameMapping(usernameMapping), diff --git a/internal/domain/idp_config.go b/internal/domain/idp_config.go index 3561345cf6..063eb17fe5 100644 --- a/internal/domain/idp_config.go +++ b/internal/domain/idp_config.go @@ -1,9 +1,10 @@ package domain import ( + "time" + "github.com/caos/zitadel/internal/crypto" es_models "github.com/caos/zitadel/internal/eventstore/v1/models" - "time" ) type IDPConfig struct { @@ -27,13 +28,15 @@ type IDPConfigView struct { Sequence uint64 IDPProviderType IdentityProviderType - IsOIDC bool - OIDCClientID string - OIDCClientSecret *crypto.CryptoValue - OIDCIssuer string - OIDCScopes []string - OIDCIDPDisplayNameMapping OIDCMappingField - OIDCUsernameMapping OIDCMappingField + IsOIDC bool + OIDCClientID string + OIDCClientSecret *crypto.CryptoValue + OIDCIssuer string + OIDCScopes []string + OIDCIDPDisplayNameMapping OIDCMappingField + OIDCUsernameMapping OIDCMappingField + OAuthAuthorizationEndpoint string + OAuthTokenEndpoint string } type OIDCIDPConfig struct { @@ -43,6 +46,8 @@ type OIDCIDPConfig struct { ClientSecret *crypto.CryptoValue ClientSecretString string Issuer string + AuthorizationEndpoint string + TokenEndpoint string Scopes []string IDPDisplayNameMapping OIDCMappingField UsernameMapping OIDCMappingField diff --git a/internal/iam/model/idp_config_view.go b/internal/iam/model/idp_config_view.go index 948a8726fc..37de4feed4 100644 --- a/internal/iam/model/idp_config_view.go +++ b/internal/iam/model/idp_config_view.go @@ -19,13 +19,15 @@ type IDPConfigView struct { Sequence uint64 IDPProviderType IDPProviderType - IsOIDC bool - OIDCClientID string - OIDCClientSecret *crypto.CryptoValue - OIDCIssuer string - OIDCScopes []string - OIDCIDPDisplayNameMapping OIDCMappingField - OIDCUsernameMapping OIDCMappingField + IsOIDC bool + OIDCClientID string + OIDCClientSecret *crypto.CryptoValue + OIDCIssuer string + OIDCScopes []string + OIDCIDPDisplayNameMapping OIDCMappingField + OIDCUsernameMapping OIDCMappingField + OAuthAuthorizationEndpoint string + OAuthTokenEndpoint string } type IDPConfigSearchRequest struct { diff --git a/internal/iam/repository/view/model/idp_config.go b/internal/iam/repository/view/model/idp_config.go index 52c6020900..5470dc634b 100644 --- a/internal/iam/repository/view/model/idp_config.go +++ b/internal/iam/repository/view/model/idp_config.go @@ -2,17 +2,19 @@ package model import ( "encoding/json" - "github.com/caos/zitadel/internal/crypto" "time" + "github.com/caos/zitadel/internal/crypto" + es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model" org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model" "github.com/caos/logging" + "github.com/lib/pq" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore/v1/models" "github.com/caos/zitadel/internal/iam/model" - "github.com/lib/pq" ) const ( @@ -32,56 +34,39 @@ type IDPConfigView struct { IDPState int32 `json:"-" gorm:"column:idp_state"` IDPProviderType int32 `json:"-" gorm:"column:idp_provider_type"` - IsOIDC bool `json:"-" gorm:"column:is_oidc"` - OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"` - OIDCClientSecret *crypto.CryptoValue `json:"clientSecret" gorm:"column:oidc_client_secret"` - OIDCIssuer string `json:"issuer" gorm:"column:oidc_issuer"` - OIDCScopes pq.StringArray `json:"scopes" gorm:"column:oidc_scopes"` - OIDCIDPDisplayNameMapping int32 `json:"idpDisplayNameMapping" gorm:"column:oidc_idp_display_name_mapping"` - OIDCUsernameMapping int32 `json:"usernameMapping" gorm:"column:oidc_idp_username_mapping"` + IsOIDC bool `json:"-" gorm:"column:is_oidc"` + OIDCClientID string `json:"clientId" gorm:"column:oidc_client_id"` + OIDCClientSecret *crypto.CryptoValue `json:"clientSecret" gorm:"column:oidc_client_secret"` + OIDCIssuer string `json:"issuer" gorm:"column:oidc_issuer"` + OIDCScopes pq.StringArray `json:"scopes" gorm:"column:oidc_scopes"` + OIDCIDPDisplayNameMapping int32 `json:"idpDisplayNameMapping" gorm:"column:oidc_idp_display_name_mapping"` + OIDCUsernameMapping int32 `json:"usernameMapping" gorm:"column:oidc_idp_username_mapping"` + OAuthAuthorizationEndpoint string `json:"authorizationEndpoint" gorm:"column:oauth_authorization_endpoint"` + OAuthTokenEndpoint string `json:"tokenEndpoint" gorm:"column:oauth_token_endpoint"` Sequence uint64 `json:"-" gorm:"column:sequence"` } -func IDPConfigViewFromModel(idp *model.IDPConfigView) *IDPConfigView { - return &IDPConfigView{ - IDPConfigID: idp.IDPConfigID, - AggregateID: idp.AggregateID, - IDPState: int32(idp.State), - Name: idp.Name, - StylingType: int32(idp.StylingType), - Sequence: idp.Sequence, - CreationDate: idp.CreationDate, - ChangeDate: idp.ChangeDate, - IDPProviderType: int32(idp.IDPProviderType), - IsOIDC: idp.IsOIDC, - OIDCClientID: idp.OIDCClientID, - OIDCClientSecret: idp.OIDCClientSecret, - OIDCIssuer: idp.OIDCIssuer, - OIDCScopes: idp.OIDCScopes, - OIDCIDPDisplayNameMapping: int32(idp.OIDCIDPDisplayNameMapping), - OIDCUsernameMapping: int32(idp.OIDCUsernameMapping), - } -} - func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView { return &model.IDPConfigView{ - IDPConfigID: idp.IDPConfigID, - AggregateID: idp.AggregateID, - State: model.IDPConfigState(idp.IDPState), - Name: idp.Name, - StylingType: model.IDPStylingType(idp.StylingType), - Sequence: idp.Sequence, - CreationDate: idp.CreationDate, - ChangeDate: idp.ChangeDate, - IDPProviderType: model.IDPProviderType(idp.IDPProviderType), - IsOIDC: idp.IsOIDC, - OIDCClientID: idp.OIDCClientID, - OIDCClientSecret: idp.OIDCClientSecret, - OIDCIssuer: idp.OIDCIssuer, - OIDCScopes: idp.OIDCScopes, - OIDCIDPDisplayNameMapping: model.OIDCMappingField(idp.OIDCIDPDisplayNameMapping), - OIDCUsernameMapping: model.OIDCMappingField(idp.OIDCUsernameMapping), + IDPConfigID: idp.IDPConfigID, + AggregateID: idp.AggregateID, + State: model.IDPConfigState(idp.IDPState), + Name: idp.Name, + StylingType: model.IDPStylingType(idp.StylingType), + Sequence: idp.Sequence, + CreationDate: idp.CreationDate, + ChangeDate: idp.ChangeDate, + IDPProviderType: model.IDPProviderType(idp.IDPProviderType), + IsOIDC: idp.IsOIDC, + OIDCClientID: idp.OIDCClientID, + OIDCClientSecret: idp.OIDCClientSecret, + OIDCIssuer: idp.OIDCIssuer, + OIDCScopes: idp.OIDCScopes, + OIDCIDPDisplayNameMapping: model.OIDCMappingField(idp.OIDCIDPDisplayNameMapping), + OIDCUsernameMapping: model.OIDCMappingField(idp.OIDCUsernameMapping), + OAuthAuthorizationEndpoint: idp.OAuthAuthorizationEndpoint, + OAuthTokenEndpoint: idp.OAuthTokenEndpoint, } } diff --git a/internal/query/converter.go b/internal/query/converter.go index dc815e0360..71e4f4f66e 100644 --- a/internal/query/converter.go +++ b/internal/query/converter.go @@ -45,6 +45,8 @@ func readModelToIDPConfigView(rm *IAMIDPConfigReadModel) *domain.IDPConfigView { converted.OIDCIssuer = rm.OIDCConfig.Issuer converted.OIDCScopes = rm.OIDCConfig.Scopes converted.OIDCUsernameMapping = rm.OIDCConfig.UserNameMapping + converted.OAuthAuthorizationEndpoint = rm.OIDCConfig.AuthorizationEndpoint + converted.OAuthTokenEndpoint = rm.OIDCConfig.TokenEndpoint } return converted } diff --git a/internal/query/oidc_config_model.go b/internal/query/oidc_config_model.go index b0ee26f9c9..5353d0d176 100644 --- a/internal/query/oidc_config_model.go +++ b/internal/query/oidc_config_model.go @@ -14,6 +14,8 @@ type OIDCConfigReadModel struct { ClientID string ClientSecret *crypto.CryptoValue Issuer string + AuthorizationEndpoint string + TokenEndpoint string Scopes []string IDPDisplayNameMapping domain.OIDCMappingField UserNameMapping domain.OIDCMappingField @@ -37,6 +39,8 @@ func (rm *OIDCConfigReadModel) reduceConfigAddedEvent(e *idpconfig.OIDCConfigAdd rm.ClientID = e.ClientID rm.ClientSecret = e.ClientSecret rm.Issuer = e.Issuer + rm.AuthorizationEndpoint = e.AuthorizationEndpoint + rm.TokenEndpoint = e.TokenEndpoint rm.Scopes = e.Scopes rm.IDPDisplayNameMapping = e.IDPDisplayNameMapping rm.UserNameMapping = e.UserNameMapping @@ -49,6 +53,12 @@ func (rm *OIDCConfigReadModel) reduceConfigChangedEvent(e *idpconfig.OIDCConfigC if e.Issuer != nil { rm.Issuer = *e.Issuer } + if e.AuthorizationEndpoint != nil { + rm.AuthorizationEndpoint = *e.AuthorizationEndpoint + } + if e.TokenEndpoint != nil { + rm.TokenEndpoint = *e.TokenEndpoint + } if len(e.Scopes) > 0 { rm.Scopes = e.Scopes } diff --git a/internal/repository/iam/idp_oidc_config.go b/internal/repository/iam/idp_oidc_config.go index 5bf1a04308..19f8e2d137 100644 --- a/internal/repository/iam/idp_oidc_config.go +++ b/internal/repository/iam/idp_oidc_config.go @@ -24,7 +24,9 @@ func NewIDPOIDCConfigAddedEvent( aggregate *eventstore.Aggregate, clientID, idpConfigID, - issuer string, + issuer, + authorizationEndpoint, + tokenEndpoint string, clientSecret *crypto.CryptoValue, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, @@ -41,6 +43,8 @@ func NewIDPOIDCConfigAddedEvent( clientID, idpConfigID, issuer, + authorizationEndpoint, + tokenEndpoint, clientSecret, idpDisplayNameMapping, userNameMapping, diff --git a/internal/repository/idpconfig/oidc_config.go b/internal/repository/idpconfig/oidc_config.go index ef7edf2150..9f04ab1afb 100644 --- a/internal/repository/idpconfig/oidc_config.go +++ b/internal/repository/idpconfig/oidc_config.go @@ -18,11 +18,13 @@ const ( type OIDCConfigAddedEvent struct { eventstore.BaseEvent `json:"-"` - IDPConfigID string `json:"idpConfigId"` - ClientID string `json:"clientId,omitempty"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` - Issuer string `json:"issuer,omitempty"` - Scopes []string `json:"scopes,omitempty"` + IDPConfigID string `json:"idpConfigId"` + ClientID string `json:"clientId,omitempty"` + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + Issuer string `json:"issuer,omitempty"` + AuthorizationEndpoint string `json:"authorizationEndpoint,omitempty"` + TokenEndpoint string `json:"tokenEndpoint,omitempty"` + Scopes []string `json:"scopes,omitempty"` IDPDisplayNameMapping domain.OIDCMappingField `json:"idpDisplayNameMapping,omitempty"` UserNameMapping domain.OIDCMappingField `json:"usernameMapping,omitempty"` @@ -40,7 +42,9 @@ func NewOIDCConfigAddedEvent( base *eventstore.BaseEvent, clientID, idpConfigID, - issuer string, + issuer, + authorizationEndpoint, + tokenEndpoint string, clientSecret *crypto.CryptoValue, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, @@ -53,6 +57,8 @@ func NewOIDCConfigAddedEvent( ClientID: clientID, ClientSecret: clientSecret, Issuer: issuer, + AuthorizationEndpoint: authorizationEndpoint, + TokenEndpoint: tokenEndpoint, Scopes: scopes, IDPDisplayNameMapping: idpDisplayNameMapping, UserNameMapping: userNameMapping, @@ -77,10 +83,12 @@ type OIDCConfigChangedEvent struct { IDPConfigID string `json:"idpConfigId"` - ClientID *string `json:"clientId,omitempty"` - ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` - Issuer *string `json:"issuer,omitempty"` - Scopes []string `json:"scopes,omitempty"` + ClientID *string `json:"clientId,omitempty"` + ClientSecret *crypto.CryptoValue `json:"clientSecret,omitempty"` + Issuer *string `json:"issuer,omitempty"` + AuthorizationEndpoint *string `json:"authorizationEndpoint,omitempty"` + TokenEndpoint *string `json:"tokenEndpoint,omitempty"` + Scopes []string `json:"scopes,omitempty"` IDPDisplayNameMapping *domain.OIDCMappingField `json:"idpDisplayNameMapping,omitempty"` UserNameMapping *domain.OIDCMappingField `json:"usernameMapping,omitempty"` @@ -132,6 +140,18 @@ func ChangeIssuer(issuer string) func(*OIDCConfigChangedEvent) { } } +func ChangeAuthorizationEndpoint(authorizationEndpoint string) func(*OIDCConfigChangedEvent) { + return func(e *OIDCConfigChangedEvent) { + e.AuthorizationEndpoint = &authorizationEndpoint + } +} + +func ChangeTokenEndpoint(tokenEndpoint string) func(*OIDCConfigChangedEvent) { + return func(e *OIDCConfigChangedEvent) { + e.TokenEndpoint = &tokenEndpoint + } +} + func ChangeIDPDisplayNameMapping(idpDisplayNameMapping domain.OIDCMappingField) func(*OIDCConfigChangedEvent) { return func(e *OIDCConfigChangedEvent) { e.IDPDisplayNameMapping = &idpDisplayNameMapping diff --git a/internal/repository/org/idp_oidc_config.go b/internal/repository/org/idp_oidc_config.go index 8c9e07f323..11bf562cfe 100644 --- a/internal/repository/org/idp_oidc_config.go +++ b/internal/repository/org/idp_oidc_config.go @@ -24,7 +24,9 @@ func NewIDPOIDCConfigAddedEvent( aggregate *eventstore.Aggregate, clientID, idpConfigID, - issuer string, + issuer, + authorizationEndpoint, + tokenEndpoint string, clientSecret *crypto.CryptoValue, idpDisplayNameMapping, userNameMapping domain.OIDCMappingField, @@ -41,6 +43,8 @@ func NewIDPOIDCConfigAddedEvent( clientID, idpConfigID, issuer, + authorizationEndpoint, + tokenEndpoint, clientSecret, idpDisplayNameMapping, userNameMapping, diff --git a/internal/ui/login/handler/external_login_handler.go b/internal/ui/login/handler/external_login_handler.go index 13c89566ba..55642053bd 100644 --- a/internal/ui/login/handler/external_login_handler.go +++ b/internal/ui/login/handler/external_login_handler.go @@ -3,6 +3,8 @@ package handler import ( "github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/pkg/oidc" + "golang.org/x/oauth2" + http_mw "github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" @@ -119,7 +121,29 @@ func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *dom l.renderError(w, r, authReq, err) return nil } - provider, err := rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second))) + if idpConfig.OIDCIssuer != "" { + provider, err := rp.NewRelyingPartyOIDC(idpConfig.OIDCIssuer, idpConfig.OIDCClientID, oidcClientSecret, l.baseURL+callbackEndpoint, idpConfig.OIDCScopes, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second))) + if err != nil { + l.renderError(w, r, authReq, err) + return nil + } + return provider + } + if idpConfig.OAuthAuthorizationEndpoint == "" || idpConfig.OAuthTokenEndpoint == "" { + l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "RP-4n0fs", "Errors.IdentityProvider.InvalidConfig")) + return nil + } + oauth2Config := &oauth2.Config{ + ClientID: idpConfig.OIDCClientID, + ClientSecret: oidcClientSecret, + Endpoint: oauth2.Endpoint{ + AuthURL: idpConfig.OAuthAuthorizationEndpoint, + TokenURL: idpConfig.OAuthTokenEndpoint, + }, + RedirectURL: l.baseURL + callbackEndpoint, + Scopes: idpConfig.OIDCScopes, + } + provider, err := rp.NewRelyingPartyOAuth(oauth2Config, rp.WithVerifierOpts(rp.WithIssuedAtOffset(3*time.Second))) if err != nil { l.renderError(w, r, authReq, err) return nil diff --git a/internal/ui/login/static/i18n/de.yaml b/internal/ui/login/static/i18n/de.yaml index 8969b96c72..a8eb109319 100644 --- a/internal/ui/login/static/i18n/de.yaml +++ b/internal/ui/login/static/i18n/de.yaml @@ -296,5 +296,7 @@ Errors: IDPTypeNotImplemented: IDP Typ ist nicht implementiert NotAllowed: Externer Login Provider ist nicht erlaubt GrantRequired: Der Login an diese Applikation ist nicht möglich. Der Benutzer benötigt mindestens eine Berechtigung an der Applikation. Bitte melde dich bei deinem Administrator. + IdentityProvider: + InvalidConfig: Identitäts Provider Konfiguration ist ungültig optional: (optional) diff --git a/internal/ui/login/static/i18n/en.yaml b/internal/ui/login/static/i18n/en.yaml index 79e8bfbe8f..9bd0fbcfd7 100644 --- a/internal/ui/login/static/i18n/en.yaml +++ b/internal/ui/login/static/i18n/en.yaml @@ -295,6 +295,7 @@ Errors: IDPTypeNotImplemented: IDP Type is not implemented NotAllowed: External Login Provider not allowed GrantRequired: Login not possible. The user is required to have at least one grant on the application. Please contact your administrator. - + IdentityProvider: + InvalidConfig: Identity Provider configuration is invalid optional: (optional) diff --git a/migrations/cockroach/V1.54__oauth_idp.sql b/migrations/cockroach/V1.54__oauth_idp.sql new file mode 100644 index 0000000000..cfbccd9ddf --- /dev/null +++ b/migrations/cockroach/V1.54__oauth_idp.sql @@ -0,0 +1,7 @@ +ALTER TABLE auth.idp_configs ADD COLUMN oauth_authorization_endpoint TEXT; +ALTER TABLE adminapi.idp_configs ADD COLUMN oauth_authorization_endpoint TEXT; +ALTER TABLE management.idp_configs ADD COLUMN oauth_authorization_endpoint TEXT; + +ALTER TABLE auth.idp_configs ADD COLUMN oauth_token_endpoint TEXT; +ALTER TABLE adminapi.idp_configs ADD COLUMN oauth_token_endpoint TEXT; +ALTER TABLE management.idp_configs ADD COLUMN oauth_token_endpoint TEXT; \ No newline at end of file diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index a7ac1c4368..23453c7b6c 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -2281,12 +2281,13 @@ message AddOIDCIDPRequest { max_length: 200; } ]; + // Fill the issuer if the identity provider is oidc discovery compliant + // If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead string issuer = 5 [ - (validate.rules).string = {min_len: 1, max_len: 200}, + (validate.rules).string = {max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"https://accounts.google.com\""; description: "the oidc issuer of the identity provider"; - min_length: 1; max_length: 200; } ]; @@ -2308,6 +2309,24 @@ message AddOIDCIDPRequest { description: "definition which field is mapped to the email of the user"; } ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string authorization_endpoint = 9 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://accounts.google.com/o/oauth2/v2/auth\""; + description: "the oauth2 authorization endpoint of the identity provider"; + max_length: 500; + } + ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string token_endpoint = 10 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://oauth2.googleapis.com/token\""; + description: "the oauth2 token endpoint of the identity provider"; + max_length: 500; + } + ]; } message AddOIDCIDPResponse { @@ -2420,6 +2439,8 @@ message UpdateIDPOIDCConfigRequest { max_length: 200; } ]; + // Fill the issuer if the identity provider is oidc discovery compliant + // If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead string issuer = 2 [ (validate.rules).string = {min_len: 1, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -2462,6 +2483,24 @@ message UpdateIDPOIDCConfigRequest { description: "definition which field is mapped to the email of the user"; } ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string authorization_endpoint = 8 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://accounts.google.com/o/oauth2/v2/auth\""; + description: "the oauth2 authorization endpoint of the identity provider"; + max_length: 500; + } + ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string token_endpoint = 9 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://oauth2.googleapis.com/token\""; + description: "the oauth2 token endpoint of the identity provider"; + max_length: 500; + } + ]; } message UpdateIDPOIDCConfigResponse { diff --git a/proto/zitadel/idp.proto b/proto/zitadel/idp.proto index d6fb1a6884..dbdfc21caa 100644 --- a/proto/zitadel/idp.proto +++ b/proto/zitadel/idp.proto @@ -153,6 +153,22 @@ message OIDCConfig { description: "definition which field is mapped to the email of the user"; } ]; + string authorization_endpoint = 6 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://accounts.google.com/o/oauth2/v2/auth\""; + description: "the oauth2 authorization endpoint of the identity provider"; + max_length: 500; + } + ]; + string token_endpoint = 7 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://oauth2.googleapis.com/token\""; + description: "the oauth2 token endpoint of the identity provider"; + max_length: 500; + } + ]; } enum OIDCMappingField { diff --git a/proto/zitadel/management.proto b/proto/zitadel/management.proto index a6b9a3413e..d21c82bd4e 100644 --- a/proto/zitadel/management.proto +++ b/proto/zitadel/management.proto @@ -4585,8 +4585,10 @@ message AddOrgOIDCIDPRequest { description: "client secret generated by the identity provider"; } ]; + // Fill the issuer if the identity provider is oidc discovery compliant + // If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead string issuer = 5 [ - (validate.rules).string = {min_len: 1, max_len: 200}, + (validate.rules).string = {max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { example: "\"https://accounts.google.com\""; description: "the oidc issuer of the identity provider"; @@ -4610,6 +4612,24 @@ message AddOrgOIDCIDPRequest { description: "definition which field is mapped to the email of the user"; } ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string authorization_endpoint = 9 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://accounts.google.com/o/oauth2/v2/auth\""; + description: "the oauth2 authorization endpoint of the identity provider"; + max_length: 500; + } + ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string token_endpoint = 10 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://oauth2.googleapis.com/token\""; + description: "the oauth2 token endpoint of the identity provider"; + max_length: 500; + } + ]; } message AddOrgOIDCIDPResponse { @@ -4680,6 +4700,8 @@ message UpdateOrgIDPOIDCConfigRequest { description: "client secret generated by the identity provider. If empty the secret is not overwritten"; } ]; + // Fill the issuer if the identity provider is oidc discovery compliant + // If the identity provider is only oauth2 compliant or does not serve a openid configuration, fill the authorization and token endpoint instead string issuer = 4 [ (validate.rules).string = {min_len: 1, max_len: 200}, (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { @@ -4705,6 +4727,24 @@ message UpdateOrgIDPOIDCConfigRequest { description: "definition which field is mapped to the email of the user"; } ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string authorization_endpoint = 8 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://accounts.google.com/o/oauth2/v2/auth\""; + description: "the oauth2 authorization endpoint of the identity provider"; + max_length: 500; + } + ]; + // If the identity provider does not serve an openid configuration, fill the authorization and token endpoint instead of the issuer + string token_endpoint = 9 [ + (validate.rules).string = {max_len: 500}, + (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = { + example: "\"https://oauth2.googleapis.com/token\""; + description: "the oauth2 token endpoint of the identity provider"; + max_length: 500; + } + ]; } message UpdateOrgIDPOIDCConfigResponse {