mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 19:37:24 +00:00
feat: jwt as idp (#2363)
* feat: jwt idp * feat: command side * feat: add tests * fill idp views with jwt idps and return apis * add jwtEndpoint to jwt idp * begin jwt request handling * merge * handle jwt idp * cleanup * fixes * autoregister * get token from specific header name * error handling * fix texts * handle renderExternalNotFoundOption Co-authored-by: fabi <fabienne.gerschwiler@gmail.com>
This commit is contained in:
parent
4e1d42259c
commit
b6b5b1b782
@ -118,6 +118,18 @@ Adds a new oidc identity provider configuration the IAM
|
||||
POST: /idps/oidc
|
||||
|
||||
|
||||
### AddJWTIDP
|
||||
|
||||
> **rpc** AddJWTIDP([AddJWTIDPRequest](#addjwtidprequest))
|
||||
[AddJWTIDPResponse](#addjwtidpresponse)
|
||||
|
||||
Adds a new jwt identity provider configuration the IAM
|
||||
|
||||
|
||||
|
||||
POST: /idps/jwt
|
||||
|
||||
|
||||
### UpdateIDP
|
||||
|
||||
> **rpc** UpdateIDP([UpdateIDPRequest](#updateidprequest))
|
||||
@ -182,6 +194,19 @@ all fields are updated. If no value is provided the field will be empty afterwar
|
||||
PUT: /idps/{idp_id}/oidc_config
|
||||
|
||||
|
||||
### UpdateIDPJWTConfig
|
||||
|
||||
> **rpc** UpdateIDPJWTConfig([UpdateIDPJWTConfigRequest](#updateidpjwtconfigrequest))
|
||||
[UpdateIDPJWTConfigResponse](#updateidpjwtconfigresponse)
|
||||
|
||||
Updates the jwt configuration of the specified idp
|
||||
all fields are updated. If no value is provided the field will be empty afterwards.
|
||||
|
||||
|
||||
|
||||
PUT: /idps/{idp_id}/jwt_config
|
||||
|
||||
|
||||
### GetDefaultFeatures
|
||||
|
||||
> **rpc** GetDefaultFeatures([GetDefaultFeaturesRequest](#getdefaultfeaturesrequest))
|
||||
@ -1165,6 +1190,35 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### AddJWTIDPRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true<br /> |
|
||||
| jwt_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| issuer | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| keys_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| header_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| auto_register | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### AddJWTIDPResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| idp_id | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### AddMultiFactorToLoginPolicyRequest
|
||||
|
||||
|
||||
@ -2851,6 +2905,32 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### UpdateIDPJWTConfigRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| idp_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| jwt_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| issuer | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| keys_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| header_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateIDPJWTConfigResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateIDPOIDCConfigRequest
|
||||
|
||||
|
||||
|
@ -22,6 +22,7 @@ title: zitadel/idp.proto
|
||||
| styling_type | IDPStylingType | - | |
|
||||
| owner | IDPOwnerType | - | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.oidc_config | OIDCConfig | - | |
|
||||
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.jwt_config | JWTConfig | - | |
|
||||
| auto_register | bool | - | |
|
||||
|
||||
|
||||
@ -90,6 +91,20 @@ title: zitadel/idp.proto
|
||||
|
||||
|
||||
|
||||
### JWTConfig
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| jwt_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| issuer | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| keys_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| header_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### OIDCConfig
|
||||
|
||||
|
||||
@ -162,7 +177,8 @@ authorization framework of the identity provider
|
||||
| Name | Number | Description |
|
||||
| ---- | ------ | ----------- |
|
||||
| IDP_TYPE_UNSPECIFIED | 0 | - |
|
||||
| IDP_TYPE_OIDC | 1 | PLANNED: IDP_TYPE_SAML |
|
||||
| IDP_TYPE_OIDC | 1 | - |
|
||||
| IDP_TYPE_JWT | 3 | PLANNED: IDP_TYPE_SAML |
|
||||
|
||||
|
||||
|
||||
|
@ -2595,6 +2595,18 @@ Provider must be OIDC compliant
|
||||
POST: /idps/oidc
|
||||
|
||||
|
||||
### AddOrgJWTIDP
|
||||
|
||||
> **rpc** AddOrgJWTIDP([AddOrgJWTIDPRequest](#addorgjwtidprequest))
|
||||
[AddOrgJWTIDPResponse](#addorgjwtidpresponse)
|
||||
|
||||
Add a new jwt identity provider configuration in the organisation
|
||||
|
||||
|
||||
|
||||
POST: /idps/jwt
|
||||
|
||||
|
||||
### DeactivateOrgIDP
|
||||
|
||||
> **rpc** DeactivateOrgIDP([DeactivateOrgIDPRequest](#deactivateorgidprequest))
|
||||
@ -2659,6 +2671,18 @@ Change OIDC identity provider configuration of the organisation
|
||||
PUT: /idps/{idp_id}/oidc_config
|
||||
|
||||
|
||||
### UpdateOrgIDPJWTConfig
|
||||
|
||||
> **rpc** UpdateOrgIDPJWTConfig([UpdateOrgIDPJWTConfigRequest](#updateorgidpjwtconfigrequest))
|
||||
[UpdateOrgIDPJWTConfigResponse](#updateorgidpjwtconfigresponse)
|
||||
|
||||
Change JWT identity provider configuration of the organisation
|
||||
|
||||
|
||||
|
||||
PUT: /idps/{idp_id}/jwt_config
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3117,6 +3141,35 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### AddOrgJWTIDPRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| styling_type | zitadel.idp.v1.IDPStylingType | - | enum.defined_only: true<br /> |
|
||||
| jwt_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| issuer | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| keys_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| header_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| auto_register | bool | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### AddOrgJWTIDPResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
| idp_id | string | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### AddOrgMemberRequest
|
||||
|
||||
|
||||
@ -7343,6 +7396,32 @@ This is an empty request
|
||||
|
||||
|
||||
|
||||
### UpdateOrgIDPJWTConfigRequest
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| idp_id | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| jwt_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| issuer | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| keys_endpoint | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
| header_name | string | - | string.min_len: 1<br /> string.max_len: 200<br /> |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateOrgIDPJWTConfigResponse
|
||||
|
||||
|
||||
|
||||
| Field | Type | Description | Validation |
|
||||
| ----- | ---- | ----------- | ----------- |
|
||||
| details | zitadel.v1.ObjectDetails | - | |
|
||||
|
||||
|
||||
|
||||
|
||||
### UpdateOrgIDPOIDCConfigRequest
|
||||
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -84,7 +85,9 @@ func (i *IDPConfig) processIDPConfig(event *es_models.Event) (err error) {
|
||||
err = idp.AppendEvent(iam_model.IDPProviderTypeSystem, event)
|
||||
case model.IDPConfigChanged,
|
||||
model.OIDCIDPConfigAdded,
|
||||
model.OIDCIDPConfigChanged:
|
||||
model.OIDCIDPConfigChanged,
|
||||
es_models.EventType(iam.IDPJWTConfigAddedEventType),
|
||||
es_models.EventType(iam.IDPJWTConfigChangedEventType):
|
||||
err = idp.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -42,6 +42,21 @@ func (s *Server) AddOIDCIDP(ctx context.Context, req *admin_pb.AddOIDCIDPRequest
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddJWTIDP(ctx context.Context, req *admin_pb.AddJWTIDPRequest) (*admin_pb.AddJWTIDPResponse, error) {
|
||||
config, err := s.command.AddDefaultIDPConfig(ctx, addJWTIDPRequestToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.AddJWTIDPResponse{
|
||||
IdpId: config.IDPConfigID,
|
||||
Details: object_pb.AddToDetailsPb(
|
||||
config.Sequence,
|
||||
config.ChangeDate,
|
||||
config.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateIDP(ctx context.Context, req *admin_pb.UpdateIDPRequest) (*admin_pb.UpdateIDPResponse, error) {
|
||||
config, err := s.command.ChangeDefaultIDPConfig(ctx, updateIDPToDomain(req))
|
||||
if err != nil {
|
||||
@ -101,3 +116,17 @@ func (s *Server) UpdateIDPOIDCConfig(ctx context.Context, req *admin_pb.UpdateID
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateIDPJWTConfig(ctx context.Context, req *admin_pb.UpdateIDPJWTConfigRequest) (*admin_pb.UpdateIDPJWTConfigResponse, error) {
|
||||
config, err := s.command.ChangeDefaultIDPJWTConfig(ctx, updateJWTConfigToDomain(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &admin_pb.UpdateIDPJWTConfigResponse{
|
||||
Details: object_pb.ChangeToDetailsPb(
|
||||
config.Sequence,
|
||||
config.ChangeDate,
|
||||
config.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
@ -31,6 +31,25 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *admin_pb.AddOIDCIDPRequest) *do
|
||||
}
|
||||
}
|
||||
|
||||
func addJWTIDPRequestToDomain(req *admin_pb.AddJWTIDPRequest) *domain.IDPConfig {
|
||||
return &domain.IDPConfig{
|
||||
Name: req.Name,
|
||||
JWTConfig: addJWTIDPRequestToDomainJWTIDPConfig(req),
|
||||
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
|
||||
Type: domain.IDPConfigTypeJWT,
|
||||
AutoRegister: req.AutoRegister,
|
||||
}
|
||||
}
|
||||
|
||||
func addJWTIDPRequestToDomainJWTIDPConfig(req *admin_pb.AddJWTIDPRequest) *domain.JWTIDPConfig {
|
||||
return &domain.JWTIDPConfig{
|
||||
JWTEndpoint: req.JwtEndpoint,
|
||||
Issuer: req.Issuer,
|
||||
KeysEndpoint: req.KeysEndpoint,
|
||||
HeaderName: req.HeaderName,
|
||||
}
|
||||
}
|
||||
|
||||
func updateIDPToDomain(req *admin_pb.UpdateIDPRequest) *domain.IDPConfig {
|
||||
return &domain.IDPConfig{
|
||||
IDPConfigID: req.IdpId,
|
||||
@ -52,6 +71,16 @@ func updateOIDCConfigToDomain(req *admin_pb.UpdateIDPOIDCConfigRequest) *domain.
|
||||
}
|
||||
}
|
||||
|
||||
func updateJWTConfigToDomain(req *admin_pb.UpdateIDPJWTConfigRequest) *domain.JWTIDPConfig {
|
||||
return &domain.JWTIDPConfig{
|
||||
IDPConfigID: req.IdpId,
|
||||
JWTEndpoint: req.JwtEndpoint,
|
||||
Issuer: req.Issuer,
|
||||
KeysEndpoint: req.KeysEndpoint,
|
||||
HeaderName: req.HeaderName,
|
||||
}
|
||||
}
|
||||
|
||||
func listIDPsToModel(req *admin_pb.ListIDPsRequest) *iam_model.IDPConfigSearchRequest {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &iam_model.IDPConfigSearchRequest{
|
||||
|
@ -46,6 +46,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
|
||||
"OIDCConfig.AuthorizationEndpoint",
|
||||
"OIDCConfig.TokenEndpoint",
|
||||
"Type", //TODO: default (0) is oidc
|
||||
"JWTConfig",
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -113,6 +114,7 @@ func Test_updateIDPToDomain(t *testing.T) {
|
||||
test.AssertFieldsMapped(t, got,
|
||||
"ObjectRoot",
|
||||
"OIDCConfig",
|
||||
"JWTConfig",
|
||||
"State",
|
||||
"Type", //TODO: type should not be changeable
|
||||
)
|
||||
|
@ -59,7 +59,7 @@ func ExternalIDPViewToLoginPolicyLinkPb(link *iam_model.IDPProviderView) *idp_pb
|
||||
return &idp_pb.IDPLoginPolicyLink{
|
||||
IdpId: link.IDPConfigID,
|
||||
IdpName: link.Name,
|
||||
IdpType: idp_pb.IDPType_IDP_TYPE_OIDC,
|
||||
IdpType: IDPTypeToPb(link.IDPConfigType),
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,7 +79,20 @@ func ExternalIDPViewToUserLinkPb(link *user_model.ExternalIDPView) *idp_pb.IDPUs
|
||||
ProvidedUserId: link.ExternalUserID,
|
||||
ProvidedUserName: link.UserDisplayName,
|
||||
//TODO: as soon as saml is implemented we need to switch here
|
||||
IdpType: idp_pb.IDPType_IDP_TYPE_OIDC,
|
||||
//IdpType: IDPTypeToPb(link.Type),
|
||||
}
|
||||
}
|
||||
|
||||
func IDPTypeToPb(idpType iam_model.IdpConfigType) idp_pb.IDPType {
|
||||
switch idpType {
|
||||
case iam_model.IDPConfigTypeOIDC:
|
||||
return idp_pb.IDPType_IDP_TYPE_OIDC
|
||||
case iam_model.IDPConfigTypeSAML:
|
||||
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
|
||||
case iam_model.IDPConfigTypeJWT:
|
||||
return idp_pb.IDPType_IDP_TYPE_JWT
|
||||
default:
|
||||
return idp_pb.IDPType_IDP_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,26 +145,45 @@ 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),
|
||||
func ModelIDPViewToConfigPb(config *iam_model.IDPConfigView) idp_pb.IDPConfig {
|
||||
if config.IsOIDC {
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &idp_pb.IDP_JwtConfig{
|
||||
JwtConfig: &idp_pb.JWTConfig{
|
||||
JwtEndpoint: config.JWTEndpoint,
|
||||
Issuer: config.JWTIssuer,
|
||||
KeysEndpoint: config.JWTKeysEndpoint,
|
||||
HeaderName: config.JWTHeaderName,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
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 IDPViewToConfigPb(config *domain.IDPConfigView) idp_pb.IDPConfig {
|
||||
if config.IsOIDC {
|
||||
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),
|
||||
},
|
||||
}
|
||||
}
|
||||
return &idp_pb.IDP_JwtConfig{
|
||||
JwtConfig: &idp_pb.JWTConfig{
|
||||
JwtEndpoint: config.JWTEndpoint,
|
||||
Issuer: config.JWTIssuer,
|
||||
KeysEndpoint: config.JWTKeysEndpoint,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -40,6 +40,22 @@ func (s *Server) AddOrgOIDCIDP(ctx context.Context, req *mgmt_pb.AddOrgOIDCIDPRe
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddOrgJWTIDP(ctx context.Context, req *mgmt_pb.AddOrgJWTIDPRequest) (*mgmt_pb.AddOrgJWTIDPResponse, error) {
|
||||
config, err := s.command.AddIDPConfig(ctx, addJWTIDPRequestToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddOrgJWTIDPResponse{
|
||||
IdpId: config.IDPConfigID,
|
||||
Details: object_pb.AddToDetailsPb(
|
||||
config.Sequence,
|
||||
config.ChangeDate,
|
||||
config.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) DeactivateOrgIDP(ctx context.Context, req *mgmt_pb.DeactivateOrgIDPRequest) (*mgmt_pb.DeactivateOrgIDPResponse, error) {
|
||||
objectDetails, err := s.command.DeactivateIDPConfig(ctx, req.IdpId, authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
@ -96,3 +112,17 @@ func (s *Server) UpdateOrgIDPOIDCConfig(ctx context.Context, req *mgmt_pb.Update
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) UpdateOrgIDPJWTConfig(ctx context.Context, req *mgmt_pb.UpdateOrgIDPJWTConfigRequest) (*mgmt_pb.UpdateOrgIDPJWTConfigResponse, error) {
|
||||
config, err := s.command.ChangeIDPJWTConfig(ctx, updateJWTConfigToDomain(req), authz.GetCtxData(ctx).OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.UpdateOrgIDPJWTConfigResponse{
|
||||
Details: object_pb.ChangeToDetailsPb(
|
||||
config.Sequence,
|
||||
config.ChangeDate,
|
||||
config.ResourceOwner,
|
||||
),
|
||||
}, nil
|
||||
}
|
||||
|
@ -12,10 +12,11 @@ import (
|
||||
|
||||
func addOIDCIDPRequestToDomain(req *mgmt_pb.AddOrgOIDCIDPRequest) *domain.IDPConfig {
|
||||
return &domain.IDPConfig{
|
||||
Name: req.Name,
|
||||
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
|
||||
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
|
||||
Type: domain.IDPConfigTypeOIDC,
|
||||
Name: req.Name,
|
||||
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
|
||||
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
|
||||
Type: domain.IDPConfigTypeOIDC,
|
||||
AutoRegister: req.AutoRegister,
|
||||
}
|
||||
}
|
||||
|
||||
@ -30,6 +31,25 @@ func addOIDCIDPRequestToDomainOIDCIDPConfig(req *mgmt_pb.AddOrgOIDCIDPRequest) *
|
||||
}
|
||||
}
|
||||
|
||||
func addJWTIDPRequestToDomain(req *mgmt_pb.AddOrgJWTIDPRequest) *domain.IDPConfig {
|
||||
return &domain.IDPConfig{
|
||||
Name: req.Name,
|
||||
JWTConfig: addJWTIDPRequestToDomainJWTIDPConfig(req),
|
||||
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
|
||||
Type: domain.IDPConfigTypeJWT,
|
||||
AutoRegister: req.AutoRegister,
|
||||
}
|
||||
}
|
||||
|
||||
func addJWTIDPRequestToDomainJWTIDPConfig(req *mgmt_pb.AddOrgJWTIDPRequest) *domain.JWTIDPConfig {
|
||||
return &domain.JWTIDPConfig{
|
||||
JWTEndpoint: req.JwtEndpoint,
|
||||
Issuer: req.Issuer,
|
||||
KeysEndpoint: req.KeysEndpoint,
|
||||
HeaderName: req.HeaderName,
|
||||
}
|
||||
}
|
||||
|
||||
func updateIDPToDomain(req *mgmt_pb.UpdateOrgIDPRequest) *domain.IDPConfig {
|
||||
return &domain.IDPConfig{
|
||||
IDPConfigID: req.IdpId,
|
||||
@ -51,6 +71,16 @@ func updateOIDCConfigToDomain(req *mgmt_pb.UpdateOrgIDPOIDCConfigRequest) *domai
|
||||
}
|
||||
}
|
||||
|
||||
func updateJWTConfigToDomain(req *mgmt_pb.UpdateOrgIDPJWTConfigRequest) *domain.JWTIDPConfig {
|
||||
return &domain.JWTIDPConfig{
|
||||
IDPConfigID: req.IdpId,
|
||||
JWTEndpoint: req.JwtEndpoint,
|
||||
Issuer: req.Issuer,
|
||||
KeysEndpoint: req.KeysEndpoint,
|
||||
HeaderName: req.HeaderName,
|
||||
}
|
||||
}
|
||||
|
||||
func listIDPsToModel(req *mgmt_pb.ListOrgIDPsRequest) *iam_model.IDPConfigSearchRequest {
|
||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||
return &iam_model.IDPConfigSearchRequest{
|
||||
|
@ -46,7 +46,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
|
||||
"OIDCConfig.AuthorizationEndpoint",
|
||||
"OIDCConfig.TokenEndpoint",
|
||||
"Type", //TODO: default (0) is oidc
|
||||
"AutoRegister",
|
||||
"JWTConfig",
|
||||
)
|
||||
})
|
||||
}
|
||||
@ -114,6 +114,7 @@ func Test_updateIDPToDomain(t *testing.T) {
|
||||
test.AssertFieldsMapped(t, got,
|
||||
"ObjectRoot",
|
||||
"OIDCConfig",
|
||||
"JWTConfig",
|
||||
"State",
|
||||
"Type", //TODO: type should not be changeable
|
||||
)
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
iam_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||
"github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -88,7 +90,9 @@ func (i *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, eve
|
||||
err = idp.AppendEvent(providerType, event)
|
||||
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
||||
model.OIDCIDPConfigAdded, iam_es_model.OIDCIDPConfigAdded,
|
||||
model.OIDCIDPConfigChanged, iam_es_model.OIDCIDPConfigChanged:
|
||||
model.OIDCIDPConfigChanged, iam_es_model.OIDCIDPConfigChanged,
|
||||
es_models.EventType(org.IDPJWTConfigAddedEventType), es_models.EventType(iam.IDPJWTConfigAddedEventType),
|
||||
es_models.EventType(org.IDPJWTConfigChangedEventType), es_models.EventType(iam.IDPJWTConfigChangedEventType):
|
||||
err = idp.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -153,6 +153,17 @@ func writeModelToIDPOIDCConfig(wm *OIDCConfigWriteModel) *domain.OIDCIDPConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func writeModelToIDPJWTConfig(wm *JWTConfigWriteModel) *domain.JWTIDPConfig {
|
||||
return &domain.JWTIDPConfig{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
IDPConfigID: wm.IDPConfigID,
|
||||
JWTEndpoint: wm.JWTEndpoint,
|
||||
Issuer: wm.Issuer,
|
||||
KeysEndpoint: wm.KeysEndpoint,
|
||||
HeaderName: wm.HeaderName,
|
||||
}
|
||||
}
|
||||
|
||||
func writeModelToIDPProvider(wm *IdentityProviderWriteModel) *domain.IDPProvider {
|
||||
return &domain.IDPProvider{
|
||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||
|
@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
@ -13,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
|
||||
if config.OIDCConfig == nil {
|
||||
if config.OIDCConfig == nil && config.JWTConfig == nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "IAM-eUpQU", "Errors.idp.config.notset")
|
||||
}
|
||||
|
||||
@ -23,11 +24,6 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
|
||||
}
|
||||
addedConfig := NewIAMIDPConfigWriteModel(idpConfigID)
|
||||
|
||||
clientSecret, err := crypto.Encrypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
iamAgg := IAMAggregateFromWriteModel(&addedConfig.WriteModel)
|
||||
events := []eventstore.EventPusher{
|
||||
iam_repo.NewIDPConfigAddedEvent(
|
||||
@ -39,7 +35,14 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
|
||||
config.StylingType,
|
||||
config.AutoRegister,
|
||||
),
|
||||
iam_repo.NewIDPOIDCConfigAddedEvent(
|
||||
}
|
||||
if config.OIDCConfig != nil {
|
||||
clientSecret, err := crypto.Encrypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events = append(events, iam_repo.NewIDPOIDCConfigAddedEvent(
|
||||
ctx,
|
||||
iamAgg,
|
||||
config.OIDCConfig.ClientID,
|
||||
@ -51,9 +54,18 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
|
||||
config.OIDCConfig.IDPDisplayNameMapping,
|
||||
config.OIDCConfig.UsernameMapping,
|
||||
config.OIDCConfig.Scopes...,
|
||||
),
|
||||
))
|
||||
} else if config.JWTConfig != nil {
|
||||
events = append(events, iam_repo.NewIDPJWTConfigAddedEvent(
|
||||
ctx,
|
||||
iamAgg,
|
||||
idpConfigID,
|
||||
config.JWTConfig.JWTEndpoint,
|
||||
config.JWTConfig.Issuer,
|
||||
config.JWTConfig.KeysEndpoint,
|
||||
config.JWTConfig.HeaderName,
|
||||
))
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -129,6 +129,65 @@ func TestCommandSide_AddDefaultIDPConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config jwt add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeOIDC,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name1", "IAM")),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "config1"),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.IDPConfig{
|
||||
Name: "name1",
|
||||
StylingType: domain.IDPConfigStylingTypeGoogle,
|
||||
JWTConfig: &domain.JWTIDPConfig{
|
||||
JWTEndpoint: "jwt-endpoint",
|
||||
Issuer: "issuer",
|
||||
KeysEndpoint: "keys-endpoint",
|
||||
HeaderName: "auth",
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.IDPConfig{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
},
|
||||
IDPConfigID: "config1",
|
||||
Name: "name1",
|
||||
StylingType: domain.IDPConfigStylingTypeGoogle,
|
||||
State: domain.IDPConfigStateActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
49
internal/command/iam_idp_jwt_config.go
Normal file
49
internal/command/iam_idp_jwt_config.go
Normal file
@ -0,0 +1,49 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func (c *Commands) ChangeDefaultIDPJWTConfig(ctx context.Context, config *domain.JWTIDPConfig) (*domain.JWTIDPConfig, error) {
|
||||
if config.IDPConfigID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "IAM-m9322", "Errors.IDMissing")
|
||||
}
|
||||
existingConfig := NewIAMIDPJWTConfigWriteModel(config.IDPConfigID)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, existingConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existingConfig.State == domain.IDPConfigStateRemoved || existingConfig.State == domain.IDPConfigStateUnspecified {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "IAM-2m00d", "Errors.IAM.IDPConfig.AlreadyExists")
|
||||
}
|
||||
|
||||
iamAgg := IAMAggregateFromWriteModel(&existingConfig.WriteModel)
|
||||
changedEvent, hasChanged, err := existingConfig.NewChangedEvent(
|
||||
ctx,
|
||||
iamAgg,
|
||||
config.IDPConfigID,
|
||||
config.JWTEndpoint,
|
||||
config.Issuer,
|
||||
config.KeysEndpoint,
|
||||
config.HeaderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "IAM-3n9gg", "Errors.IAM.IDPConfig.NotChanged")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingConfig, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return writeModelToIDPJWTConfig(&existingConfig.JWTConfigWriteModel), nil
|
||||
}
|
116
internal/command/iam_idp_jwt_config_model.go
Normal file
116
internal/command/iam_idp_jwt_config_model.go
Normal file
@ -0,0 +1,116 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
)
|
||||
|
||||
type IAMIDPJWTConfigWriteModel struct {
|
||||
JWTConfigWriteModel
|
||||
}
|
||||
|
||||
func NewIAMIDPJWTConfigWriteModel(idpConfigID string) *IAMIDPJWTConfigWriteModel {
|
||||
return &IAMIDPJWTConfigWriteModel{
|
||||
JWTConfigWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: domain.IAMID,
|
||||
ResourceOwner: domain.IAMID,
|
||||
},
|
||||
IDPConfigID: idpConfigID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IAMIDPJWTConfigWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *iam.IDPJWTConfigAddedEvent:
|
||||
if wm.IDPConfigID != e.IDPConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.JWTConfigAddedEvent)
|
||||
case *iam.IDPJWTConfigChangedEvent:
|
||||
if wm.IDPConfigID != e.IDPConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.JWTConfigChangedEvent)
|
||||
case *iam.IDPConfigReactivatedEvent:
|
||||
if wm.IDPConfigID != e.ConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.IDPConfigReactivatedEvent)
|
||||
case *iam.IDPConfigDeactivatedEvent:
|
||||
if wm.IDPConfigID != e.ConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.IDPConfigDeactivatedEvent)
|
||||
case *iam.IDPConfigRemovedEvent:
|
||||
if wm.IDPConfigID != e.ConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.IDPConfigRemovedEvent)
|
||||
default:
|
||||
wm.JWTConfigWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IAMIDPJWTConfigWriteModel) Reduce() error {
|
||||
if err := wm.JWTConfigWriteModel.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *IAMIDPJWTConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(iam.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
iam.IDPJWTConfigAddedEventType,
|
||||
iam.IDPJWTConfigChangedEventType,
|
||||
iam.IDPConfigReactivatedEventType,
|
||||
iam.IDPConfigDeactivatedEventType,
|
||||
iam.IDPConfigRemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *IAMIDPJWTConfigWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName string,
|
||||
) (*iam.IDPJWTConfigChangedEvent, bool, error) {
|
||||
|
||||
changes := make([]idpconfig.JWTConfigChanges, 0)
|
||||
if wm.JWTEndpoint != jwtEndpoint {
|
||||
changes = append(changes, idpconfig.ChangeJWTEndpoint(jwtEndpoint))
|
||||
}
|
||||
if wm.Issuer != issuer {
|
||||
changes = append(changes, idpconfig.ChangeJWTIssuer(issuer))
|
||||
}
|
||||
if wm.KeysEndpoint != keysEndpoint {
|
||||
changes = append(changes, idpconfig.ChangeKeysEndpoint(keysEndpoint))
|
||||
}
|
||||
if wm.HeaderName != headerName {
|
||||
changes = append(changes, idpconfig.ChangeHeaderName(headerName))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
changeEvent, err := iam.NewIDPJWTConfigChangedEvent(ctx, aggregate, idpConfigID, changes)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return changeEvent, true, nil
|
||||
}
|
264
internal/command/iam_idp_jwt_config_test.go
Normal file
264
internal/command/iam_idp_jwt_config_test.go
Normal file
@ -0,0 +1,264 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
)
|
||||
|
||||
func TestCommandSide_ChangeDefaultIDPJWTConfig(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
config *domain.JWTIDPConfig
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.JWTIDPConfig
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "invalid config, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config removed, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeJWT,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPConfigRemovedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"name",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeJWT,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
JWTEndpoint: "jwt-endpoint",
|
||||
Issuer: "issuer",
|
||||
KeysEndpoint: "keys-endpoint",
|
||||
HeaderName: "auth",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config jwt add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeJWT,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
iam.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&iam.NewAggregate().Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newDefaultIDPJWTConfigChangedEvent(context.Background(),
|
||||
"config1",
|
||||
"jwt-endpoint-changed",
|
||||
"issuer-changed",
|
||||
"keys-endpoint-changed",
|
||||
"auth-changed",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
JWTEndpoint: "jwt-endpoint-changed",
|
||||
Issuer: "issuer-changed",
|
||||
KeysEndpoint: "keys-endpoint-changed",
|
||||
HeaderName: "auth-changed",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.JWTIDPConfig{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "IAM",
|
||||
ResourceOwner: "IAM",
|
||||
},
|
||||
IDPConfigID: "config1",
|
||||
JWTEndpoint: "jwt-endpoint-changed",
|
||||
Issuer: "issuer-changed",
|
||||
KeysEndpoint: "keys-endpoint-changed",
|
||||
HeaderName: "auth-changed",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigSecretCrypto: tt.fields.secretCrypto,
|
||||
}
|
||||
got, err := r.ChangeDefaultIDPJWTConfig(tt.args.ctx, tt.args.config)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newDefaultIDPJWTConfigChangedEvent(ctx context.Context, configID, jwtEndpoint, issuer, keysEndpoint, headerName string) *iam.IDPJWTConfigChangedEvent {
|
||||
event, _ := iam.NewIDPJWTConfigChangedEvent(ctx,
|
||||
&iam.NewAggregate().Aggregate,
|
||||
configID,
|
||||
[]idpconfig.JWTConfigChanges{
|
||||
idpconfig.ChangeJWTEndpoint(jwtEndpoint),
|
||||
idpconfig.ChangeJWTIssuer(issuer),
|
||||
idpconfig.ChangeKeysEndpoint(keysEndpoint),
|
||||
idpconfig.ChangeHeaderName(headerName),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}
|
61
internal/command/jwt_config_model.go
Normal file
61
internal/command/jwt_config_model.go
Normal file
@ -0,0 +1,61 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
)
|
||||
|
||||
type JWTConfigWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
IDPConfigID string
|
||||
JWTEndpoint string
|
||||
Issuer string
|
||||
KeysEndpoint string
|
||||
HeaderName string
|
||||
State domain.IDPConfigState
|
||||
}
|
||||
|
||||
func (wm *JWTConfigWriteModel) Reduce() error {
|
||||
for _, event := range wm.Events {
|
||||
switch e := event.(type) {
|
||||
case *idpconfig.JWTConfigAddedEvent:
|
||||
wm.reduceConfigAddedEvent(e)
|
||||
case *idpconfig.JWTConfigChangedEvent:
|
||||
wm.reduceConfigChangedEvent(e)
|
||||
case *idpconfig.IDPConfigDeactivatedEvent:
|
||||
wm.State = domain.IDPConfigStateInactive
|
||||
case *idpconfig.IDPConfigReactivatedEvent:
|
||||
wm.State = domain.IDPConfigStateActive
|
||||
case *idpconfig.IDPConfigRemovedEvent:
|
||||
wm.State = domain.IDPConfigStateRemoved
|
||||
}
|
||||
}
|
||||
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *JWTConfigWriteModel) reduceConfigAddedEvent(e *idpconfig.JWTConfigAddedEvent) {
|
||||
wm.IDPConfigID = e.IDPConfigID
|
||||
wm.JWTEndpoint = e.JWTEndpoint
|
||||
wm.Issuer = e.Issuer
|
||||
wm.KeysEndpoint = e.KeysEndpoint
|
||||
wm.HeaderName = e.HeaderName
|
||||
wm.State = domain.IDPConfigStateActive
|
||||
}
|
||||
|
||||
func (wm *JWTConfigWriteModel) reduceConfigChangedEvent(e *idpconfig.JWTConfigChangedEvent) {
|
||||
if e.JWTEndpoint != nil {
|
||||
wm.JWTEndpoint = *e.JWTEndpoint
|
||||
}
|
||||
if e.Issuer != nil {
|
||||
wm.Issuer = *e.Issuer
|
||||
}
|
||||
if e.KeysEndpoint != nil {
|
||||
wm.KeysEndpoint = *e.KeysEndpoint
|
||||
}
|
||||
if e.HeaderName != nil {
|
||||
wm.HeaderName = *e.HeaderName
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
@ -16,7 +17,7 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-0j8gs", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if config.OIDCConfig == nil {
|
||||
if config.OIDCConfig == nil && config.JWTConfig == nil {
|
||||
return nil, errors.ThrowInvalidArgument(nil, "Org-eUpQU", "Errors.idp.config.notset")
|
||||
}
|
||||
|
||||
@ -26,11 +27,6 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
||||
}
|
||||
addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, resourceOwner)
|
||||
|
||||
clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&addedConfig.WriteModel)
|
||||
events := []eventstore.EventPusher{
|
||||
org_repo.NewIDPConfigAddedEvent(
|
||||
@ -42,7 +38,13 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
||||
config.StylingType,
|
||||
config.AutoRegister,
|
||||
),
|
||||
org_repo.NewIDPOIDCConfigAddedEvent(
|
||||
}
|
||||
if config.OIDCConfig != nil {
|
||||
clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
events = append(events, org_repo.NewIDPOIDCConfigAddedEvent(
|
||||
ctx,
|
||||
orgAgg,
|
||||
config.OIDCConfig.ClientID,
|
||||
@ -53,7 +55,17 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
||||
clientSecret,
|
||||
config.OIDCConfig.IDPDisplayNameMapping,
|
||||
config.OIDCConfig.UsernameMapping,
|
||||
config.OIDCConfig.Scopes...),
|
||||
config.OIDCConfig.Scopes...))
|
||||
} else if config.JWTConfig != nil {
|
||||
events = append(events, org_repo.NewIDPJWTConfigAddedEvent(
|
||||
ctx,
|
||||
orgAgg,
|
||||
idpConfigID,
|
||||
config.JWTConfig.JWTEndpoint,
|
||||
config.JWTConfig.Issuer,
|
||||
config.JWTConfig.KeysEndpoint,
|
||||
config.JWTConfig.HeaderName,
|
||||
))
|
||||
}
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||
if err != nil {
|
||||
|
@ -159,6 +159,66 @@ func TestCommandSide_AddIDPConfig(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config jwt add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
org.NewIDPConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeOIDC,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
},
|
||||
uniqueConstraintsFromEventConstraint(idpconfig.NewAddIDPConfigNameUniqueConstraint("name1", "org1")),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "config1"),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
config: &domain.IDPConfig{
|
||||
Name: "name1",
|
||||
StylingType: domain.IDPConfigStylingTypeGoogle,
|
||||
JWTConfig: &domain.JWTIDPConfig{
|
||||
JWTEndpoint: "jwt-endpoint",
|
||||
Issuer: "issuer",
|
||||
KeysEndpoint: "keys-endpoint",
|
||||
HeaderName: "auth",
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
want: &domain.IDPConfig{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
IDPConfigID: "config1",
|
||||
Name: "name1",
|
||||
StylingType: domain.IDPConfigStylingTypeGoogle,
|
||||
State: domain.IDPConfigStateActive,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
53
internal/command/org_idp_jwt_config.go
Normal file
53
internal/command/org_idp_jwt_config.go
Normal file
@ -0,0 +1,53 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func (c *Commands) ChangeIDPJWTConfig(ctx context.Context, config *domain.JWTIDPConfig, resourceOwner string) (*domain.JWTIDPConfig, error) {
|
||||
if resourceOwner == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-ff8NF", "Errors.ResourceOwnerMissing")
|
||||
}
|
||||
if config.IDPConfigID == "" {
|
||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-2n99f", "Errors.IDMissing")
|
||||
}
|
||||
existingConfig := NewOrgIDPJWTConfigWriteModel(config.IDPConfigID, resourceOwner)
|
||||
err := c.eventstore.FilterToQueryReducer(ctx, existingConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existingConfig.State == domain.IDPConfigStateRemoved || existingConfig.State == domain.IDPConfigStateUnspecified {
|
||||
return nil, caos_errs.ThrowNotFound(nil, "Org-67J9d", "Errors.Org.IDPConfig.AlreadyExists")
|
||||
}
|
||||
|
||||
orgAgg := OrgAggregateFromWriteModel(&existingConfig.WriteModel)
|
||||
changedEvent, hasChanged, err := existingConfig.NewChangedEvent(
|
||||
ctx,
|
||||
orgAgg,
|
||||
config.IDPConfigID,
|
||||
config.JWTEndpoint,
|
||||
config.Issuer,
|
||||
config.KeysEndpoint,
|
||||
config.HeaderName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !hasChanged {
|
||||
return nil, caos_errs.ThrowPreconditionFailed(nil, "Org-2k9fs", "Errors.Org.IDPConfig.NotChanged")
|
||||
}
|
||||
|
||||
pushedEvents, err := c.eventstore.PushEvents(ctx, changedEvent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = AppendAndReduce(existingConfig, pushedEvents...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return writeModelToIDPJWTConfig(&existingConfig.JWTConfigWriteModel), nil
|
||||
}
|
115
internal/command/org_idp_jwt_config_model.go
Normal file
115
internal/command/org_idp_jwt_config_model.go
Normal file
@ -0,0 +1,115 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
type IDPJWTConfigWriteModel struct {
|
||||
JWTConfigWriteModel
|
||||
}
|
||||
|
||||
func NewOrgIDPJWTConfigWriteModel(idpConfigID, orgID string) *IDPJWTConfigWriteModel {
|
||||
return &IDPJWTConfigWriteModel{
|
||||
JWTConfigWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
IDPConfigID: idpConfigID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IDPJWTConfigWriteModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.IDPJWTConfigAddedEvent:
|
||||
if wm.IDPConfigID != e.IDPConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.JWTConfigAddedEvent)
|
||||
case *org.IDPJWTConfigChangedEvent:
|
||||
if wm.IDPConfigID != e.IDPConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.JWTConfigChangedEvent)
|
||||
case *org.IDPConfigReactivatedEvent:
|
||||
if wm.IDPConfigID != e.ConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.IDPConfigReactivatedEvent)
|
||||
case *org.IDPConfigDeactivatedEvent:
|
||||
if wm.IDPConfigID != e.ConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.IDPConfigDeactivatedEvent)
|
||||
case *org.IDPConfigRemovedEvent:
|
||||
if wm.IDPConfigID != e.ConfigID {
|
||||
continue
|
||||
}
|
||||
wm.JWTConfigWriteModel.AppendEvents(&e.IDPConfigRemovedEvent)
|
||||
default:
|
||||
wm.JWTConfigWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *IDPJWTConfigWriteModel) Reduce() error {
|
||||
if err := wm.JWTConfigWriteModel.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
return wm.WriteModel.Reduce()
|
||||
}
|
||||
|
||||
func (wm *IDPJWTConfigWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
org.IDPJWTConfigAddedEventType,
|
||||
org.IDPJWTConfigChangedEventType,
|
||||
org.IDPConfigReactivatedEventType,
|
||||
org.IDPConfigDeactivatedEventType,
|
||||
org.IDPConfigRemovedEventType).
|
||||
Builder()
|
||||
}
|
||||
|
||||
func (wm *IDPJWTConfigWriteModel) NewChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName string,
|
||||
) (*org.IDPJWTConfigChangedEvent, bool, error) {
|
||||
|
||||
changes := make([]idpconfig.JWTConfigChanges, 0)
|
||||
if wm.JWTEndpoint != jwtEndpoint {
|
||||
changes = append(changes, idpconfig.ChangeJWTEndpoint(jwtEndpoint))
|
||||
}
|
||||
if wm.Issuer != issuer {
|
||||
changes = append(changes, idpconfig.ChangeJWTIssuer(issuer))
|
||||
}
|
||||
if wm.KeysEndpoint != keysEndpoint {
|
||||
changes = append(changes, idpconfig.ChangeKeysEndpoint(keysEndpoint))
|
||||
}
|
||||
if wm.HeaderName != headerName {
|
||||
changes = append(changes, idpconfig.ChangeHeaderName(headerName))
|
||||
}
|
||||
if len(changes) == 0 {
|
||||
return nil, false, nil
|
||||
}
|
||||
changeEvent, err := org.NewIDPJWTConfigChangedEvent(ctx, aggregate, idpConfigID, changes)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return changeEvent, true, nil
|
||||
}
|
288
internal/command/org_idp_jwt_config_test.go
Normal file
288
internal/command/org_idp_jwt_config_test.go
Normal file
@ -0,0 +1,288 @@
|
||||
package command
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
)
|
||||
|
||||
func TestCommandSide_ChangeIDPJWTConfig(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore *eventstore.Eventstore
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type (
|
||||
args struct {
|
||||
ctx context.Context
|
||||
config *domain.JWTIDPConfig
|
||||
resourceOwner string
|
||||
}
|
||||
)
|
||||
type res struct {
|
||||
want *domain.JWTIDPConfig
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
name: "resourceowner missing, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid config, error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsErrorInvalidArgument,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config not existing, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config removed, not found error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewIDPConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeJWT,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewIDPConfigRemovedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"name",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsNotFound,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "no changes, precondition error",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewIDPConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeJWT,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
JWTEndpoint: "jwt-endpoint",
|
||||
Issuer: "issuer",
|
||||
KeysEndpoint: "keys-endpoint",
|
||||
HeaderName: "auth",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
err: caos_errs.IsPreconditionFailed,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "idp config jwt add, ok",
|
||||
fields: fields{
|
||||
eventstore: eventstoreExpect(
|
||||
t,
|
||||
expectFilter(
|
||||
eventFromEventPusher(
|
||||
org.NewIDPConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"name1",
|
||||
domain.IDPConfigTypeJWT,
|
||||
domain.IDPConfigStylingTypeGoogle,
|
||||
false,
|
||||
),
|
||||
),
|
||||
eventFromEventPusher(
|
||||
org.NewIDPJWTConfigAddedEvent(context.Background(),
|
||||
&org.NewAggregate("org1", "org1").Aggregate,
|
||||
"config1",
|
||||
"jwt-endpoint",
|
||||
"issuer",
|
||||
"keys-endpoint",
|
||||
"auth",
|
||||
),
|
||||
),
|
||||
),
|
||||
expectPush(
|
||||
[]*repository.Event{
|
||||
eventFromEventPusher(
|
||||
newIDPJWTConfigChangedEvent(context.Background(),
|
||||
"org1",
|
||||
"config1",
|
||||
"jwt-endpoint-changed",
|
||||
"issuer-changed",
|
||||
"keys-endpoint-changed",
|
||||
"auth-changed",
|
||||
),
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
config: &domain.JWTIDPConfig{
|
||||
IDPConfigID: "config1",
|
||||
JWTEndpoint: "jwt-endpoint-changed",
|
||||
Issuer: "issuer-changed",
|
||||
KeysEndpoint: "keys-endpoint-changed",
|
||||
HeaderName: "auth-changed",
|
||||
},
|
||||
resourceOwner: "org1",
|
||||
},
|
||||
res: res{
|
||||
want: &domain.JWTIDPConfig{
|
||||
ObjectRoot: models.ObjectRoot{
|
||||
AggregateID: "org1",
|
||||
ResourceOwner: "org1",
|
||||
},
|
||||
IDPConfigID: "config1",
|
||||
JWTEndpoint: "jwt-endpoint-changed",
|
||||
Issuer: "issuer-changed",
|
||||
KeysEndpoint: "keys-endpoint-changed",
|
||||
HeaderName: "auth-changed",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
r := &Commands{
|
||||
eventstore: tt.fields.eventstore,
|
||||
idpConfigSecretCrypto: tt.fields.secretCrypto,
|
||||
}
|
||||
got, err := r.ChangeIDPJWTConfig(tt.args.ctx, tt.args.config, tt.args.resourceOwner)
|
||||
if tt.res.err == nil {
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
if tt.res.err != nil && !tt.res.err(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
if tt.res.err == nil {
|
||||
assert.Equal(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newIDPJWTConfigChangedEvent(ctx context.Context, orgID, configID, jwtEndpoint, issuer, keysEndpoint, headerName string) *org.IDPJWTConfigChangedEvent {
|
||||
event, _ := org.NewIDPJWTConfigChangedEvent(ctx,
|
||||
&org.NewAggregate(orgID, orgID).Aggregate,
|
||||
configID,
|
||||
[]idpconfig.JWTConfigChanges{
|
||||
idpconfig.ChangeJWTEndpoint(jwtEndpoint),
|
||||
idpconfig.ChangeJWTIssuer(issuer),
|
||||
idpconfig.ChangeKeysEndpoint(keysEndpoint),
|
||||
idpconfig.ChangeHeaderName(headerName),
|
||||
},
|
||||
)
|
||||
return event
|
||||
}
|
@ -15,6 +15,7 @@ type IDPConfig struct {
|
||||
StylingType IDPConfigStylingType
|
||||
State IDPConfigState
|
||||
OIDCConfig *OIDCIDPConfig
|
||||
JWTConfig *JWTIDPConfig
|
||||
AutoRegister bool
|
||||
}
|
||||
|
||||
@ -39,6 +40,10 @@ type IDPConfigView struct {
|
||||
OIDCUsernameMapping OIDCMappingField
|
||||
OAuthAuthorizationEndpoint string
|
||||
OAuthTokenEndpoint string
|
||||
|
||||
JWTEndpoint string
|
||||
JWTIssuer string
|
||||
JWTKeysEndpoint string
|
||||
}
|
||||
|
||||
type OIDCIDPConfig struct {
|
||||
@ -55,11 +60,21 @@ type OIDCIDPConfig struct {
|
||||
UsernameMapping OIDCMappingField
|
||||
}
|
||||
|
||||
type JWTIDPConfig struct {
|
||||
es_models.ObjectRoot
|
||||
IDPConfigID string
|
||||
JWTEndpoint string
|
||||
Issuer string
|
||||
KeysEndpoint string
|
||||
HeaderName string
|
||||
}
|
||||
|
||||
type IDPConfigType int32
|
||||
|
||||
const (
|
||||
IDPConfigTypeOIDC IDPConfigType = iota
|
||||
IDPConfigTypeSAML
|
||||
IDPConfigTypeJWT
|
||||
|
||||
//count is for validation
|
||||
idpConfigTypeCount
|
||||
|
@ -7,12 +7,13 @@ import (
|
||||
|
||||
type IDPConfig struct {
|
||||
es_models.ObjectRoot
|
||||
IDPConfigID string
|
||||
Type IdpConfigType
|
||||
Name string
|
||||
StylingType IDPStylingType
|
||||
State IDPConfigState
|
||||
OIDCConfig *OIDCIDPConfig
|
||||
IDPConfigID string
|
||||
Type IdpConfigType
|
||||
Name string
|
||||
StylingType IDPStylingType
|
||||
State IDPConfigState
|
||||
OIDCConfig *OIDCIDPConfig
|
||||
JWTIDPConfig *JWTIDPConfig
|
||||
}
|
||||
|
||||
type OIDCIDPConfig struct {
|
||||
@ -27,11 +28,20 @@ type OIDCIDPConfig struct {
|
||||
UsernameMapping OIDCMappingField
|
||||
}
|
||||
|
||||
type JWTIDPConfig struct {
|
||||
es_models.ObjectRoot
|
||||
IDPConfigID string
|
||||
JWTEndpoint string
|
||||
Issuer string
|
||||
KeysEndpoint string
|
||||
}
|
||||
|
||||
type IdpConfigType int32
|
||||
|
||||
const (
|
||||
IDPConfigTypeOIDC IdpConfigType = iota
|
||||
IDPConfigTypeSAML
|
||||
IDPConfigTypeJWT
|
||||
)
|
||||
|
||||
type IDPConfigState int32
|
||||
|
@ -29,6 +29,10 @@ type IDPConfigView struct {
|
||||
OIDCUsernameMapping OIDCMappingField
|
||||
OAuthAuthorizationEndpoint string
|
||||
OAuthTokenEndpoint string
|
||||
JWTEndpoint string
|
||||
JWTIssuer string
|
||||
JWTKeysEndpoint string
|
||||
JWTHeaderName string
|
||||
}
|
||||
|
||||
type IDPConfigSearchRequest struct {
|
||||
|
@ -100,6 +100,8 @@ func idpConfigTypeToDomain(idpType IdpConfigType) domain.IDPConfigType {
|
||||
return domain.IDPConfigTypeOIDC
|
||||
case IDPConfigTypeSAML:
|
||||
return domain.IDPConfigTypeSAML
|
||||
case IDPConfigTypeJWT:
|
||||
return domain.IDPConfigTypeJWT
|
||||
default:
|
||||
return domain.IDPConfigTypeOIDC
|
||||
}
|
||||
|
@ -5,6 +5,8 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
|
||||
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||
org_es_model "github.com/caos/zitadel/internal/org/repository/eventsourcing/model"
|
||||
@ -44,12 +46,15 @@ type IDPConfigView struct {
|
||||
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"`
|
||||
JWTEndpoint string `json:"jwtEndpoint" gorm:"jwt_endpoint"`
|
||||
JWTKeysEndpoint string `json:"keysEndpoint" gorm:"jwt_keys_endpoint"`
|
||||
JWTHeaderName string `json:"headerName" gorm:"jwt_header_name"`
|
||||
|
||||
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||
}
|
||||
|
||||
func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
||||
return &model.IDPConfigView{
|
||||
view := &model.IDPConfigView{
|
||||
IDPConfigID: idp.IDPConfigID,
|
||||
AggregateID: idp.AggregateID,
|
||||
State: model.IDPConfigState(idp.IDPState),
|
||||
@ -63,13 +68,21 @@ func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
||||
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,
|
||||
}
|
||||
if idp.IsOIDC {
|
||||
view.OIDCIssuer = idp.OIDCIssuer
|
||||
return view
|
||||
}
|
||||
view.JWTEndpoint = idp.JWTEndpoint
|
||||
view.JWTIssuer = idp.OIDCIssuer
|
||||
view.JWTKeysEndpoint = idp.JWTKeysEndpoint
|
||||
view.JWTHeaderName = idp.JWTHeaderName
|
||||
return view
|
||||
}
|
||||
|
||||
func IdpConfigViewsToModel(idps []*IDPConfigView) []*model.IDPConfigView {
|
||||
@ -93,7 +106,9 @@ func (i *IDPConfigView) AppendEvent(providerType model.IDPProviderType, event *m
|
||||
i.IsOIDC = true
|
||||
err = i.SetData(event)
|
||||
case es_model.OIDCIDPConfigChanged, org_es_model.OIDCIDPConfigChanged,
|
||||
es_model.IDPConfigChanged, org_es_model.IDPConfigChanged:
|
||||
es_model.IDPConfigChanged, org_es_model.IDPConfigChanged,
|
||||
models.EventType(org.IDPJWTConfigAddedEventType), models.EventType(iam.IDPJWTConfigAddedEventType),
|
||||
models.EventType(org.IDPJWTConfigChangedEventType), models.EventType(iam.IDPJWTConfigChangedEventType):
|
||||
err = i.SetData(event)
|
||||
case es_model.IDPConfigDeactivated, org_es_model.IDPConfigDeactivated:
|
||||
i.IDPState = int32(model.IDPConfigStateInactive)
|
||||
|
@ -3,6 +3,8 @@ package handler
|
||||
import (
|
||||
"github.com/caos/logging"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
||||
"github.com/caos/zitadel/internal/repository/iam"
|
||||
"github.com/caos/zitadel/internal/repository/org"
|
||||
|
||||
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
||||
@ -89,7 +91,9 @@ func (m *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, eve
|
||||
err = idp.AppendEvent(providerType, event)
|
||||
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
||||
model.OIDCIDPConfigAdded, iam_es_model.OIDCIDPConfigAdded,
|
||||
model.OIDCIDPConfigChanged, iam_es_model.OIDCIDPConfigChanged:
|
||||
model.OIDCIDPConfigChanged, iam_es_model.OIDCIDPConfigChanged,
|
||||
es_models.EventType(org.IDPJWTConfigAddedEventType), es_models.EventType(iam.IDPJWTConfigAddedEventType),
|
||||
es_models.EventType(org.IDPJWTConfigChangedEventType), es_models.EventType(iam.IDPJWTConfigChangedEventType):
|
||||
err = idp.SetData(event)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -48,6 +48,11 @@ func readModelToIDPConfigView(rm *IAMIDPConfigReadModel) *domain.IDPConfigView {
|
||||
converted.OAuthAuthorizationEndpoint = rm.OIDCConfig.AuthorizationEndpoint
|
||||
converted.OAuthTokenEndpoint = rm.OIDCConfig.TokenEndpoint
|
||||
}
|
||||
if rm.JWTConfig != nil {
|
||||
converted.JWTEndpoint = rm.JWTConfig.JWTEndpoint
|
||||
converted.JWTIssuer = rm.JWTConfig.Issuer
|
||||
converted.JWTKeysEndpoint = rm.JWTConfig.KeysEndpoint
|
||||
}
|
||||
return converted
|
||||
}
|
||||
|
||||
@ -138,14 +143,20 @@ func readModelToIDPConfigs(rm *IAMIDPConfigsReadModel) []*model.IDPConfig {
|
||||
}
|
||||
|
||||
func readModelToIDPConfig(rm *IAMIDPConfigReadModel) *model.IDPConfig {
|
||||
return &model.IDPConfig{
|
||||
config := &model.IDPConfig{
|
||||
ObjectRoot: readModelToObjectRoot(rm.ReadModel),
|
||||
OIDCConfig: readModelToIDPOIDCConfig(rm.OIDCConfig),
|
||||
IDPConfigID: rm.ConfigID,
|
||||
Name: rm.Name,
|
||||
State: model.IDPConfigState(rm.State),
|
||||
StylingType: model.IDPStylingType(rm.StylingType),
|
||||
}
|
||||
if rm.OIDCConfig != nil {
|
||||
config.OIDCConfig = readModelToIDPOIDCConfig(rm.OIDCConfig)
|
||||
}
|
||||
if rm.JWTConfig != nil {
|
||||
config.JWTIDPConfig = readModelToIDPJWTConfig(rm.JWTConfig)
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func readModelToIDPOIDCConfig(rm *OIDCConfigReadModel) *model.OIDCIDPConfig {
|
||||
@ -162,6 +173,16 @@ func readModelToIDPOIDCConfig(rm *OIDCConfigReadModel) *model.OIDCIDPConfig {
|
||||
}
|
||||
}
|
||||
|
||||
func readModelToIDPJWTConfig(rm *JWTConfigReadModel) *model.JWTIDPConfig {
|
||||
return &model.JWTIDPConfig{
|
||||
ObjectRoot: readModelToObjectRoot(rm.ReadModel),
|
||||
IDPConfigID: rm.IDPConfigID,
|
||||
JWTEndpoint: rm.JWTEndpoint,
|
||||
Issuer: rm.Issuer,
|
||||
KeysEndpoint: rm.KeysEndpoint,
|
||||
}
|
||||
}
|
||||
|
||||
func readModelToObjectRoot(readModel eventstore.ReadModel) models.ObjectRoot {
|
||||
return models.ObjectRoot{
|
||||
AggregateID: readModel.AggregateID,
|
||||
|
@ -36,6 +36,10 @@ func (rm *IAMIDPConfigReadModel) AppendEvents(events ...eventstore.EventReader)
|
||||
rm.IDPConfigReadModel.AppendEvents(&e.OIDCConfigAddedEvent)
|
||||
case *iam.IDPOIDCConfigChangedEvent:
|
||||
rm.IDPConfigReadModel.AppendEvents(&e.OIDCConfigChangedEvent)
|
||||
case *iam.IDPJWTConfigAddedEvent:
|
||||
rm.IDPConfigReadModel.AppendEvents(&e.JWTConfigAddedEvent)
|
||||
case *iam.IDPJWTConfigChangedEvent:
|
||||
rm.IDPConfigReadModel.AppendEvents(&e.JWTConfigChangedEvent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ type IDPConfigReadModel struct {
|
||||
ProviderType domain.IdentityProviderType
|
||||
|
||||
OIDCConfig *OIDCConfigReadModel
|
||||
JWTConfig *JWTConfigReadModel
|
||||
}
|
||||
|
||||
func NewIDPConfigReadModel(configID string) *IDPConfigReadModel {
|
||||
@ -45,6 +46,13 @@ func (rm *IDPConfigReadModel) AppendEvents(events ...eventstore.EventReader) {
|
||||
case *idpconfig.OIDCConfigChangedEvent:
|
||||
rm.ReadModel.AppendEvents(e)
|
||||
rm.OIDCConfig.AppendEvents(event)
|
||||
case *idpconfig.JWTConfigAddedEvent:
|
||||
rm.JWTConfig = &JWTConfigReadModel{}
|
||||
rm.ReadModel.AppendEvents(e)
|
||||
rm.JWTConfig.AppendEvents(event)
|
||||
case *idpconfig.JWTConfigChangedEvent:
|
||||
rm.ReadModel.AppendEvents(e)
|
||||
rm.JWTConfig.AppendEvents(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,6 +78,11 @@ func (rm *IDPConfigReadModel) Reduce() error {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if rm.JWTConfig != nil {
|
||||
if err := rm.JWTConfig.Reduce(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return rm.ReadModel.Reduce()
|
||||
}
|
||||
|
||||
|
47
internal/query/jwt_config_model.go
Normal file
47
internal/query/jwt_config_model.go
Normal file
@ -0,0 +1,47 @@
|
||||
package query
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
)
|
||||
|
||||
type JWTConfigReadModel struct {
|
||||
eventstore.ReadModel
|
||||
|
||||
IDPConfigID string
|
||||
JWTEndpoint string
|
||||
Issuer string
|
||||
KeysEndpoint string
|
||||
}
|
||||
|
||||
func (rm *JWTConfigReadModel) Reduce() error {
|
||||
for _, event := range rm.Events {
|
||||
switch e := event.(type) {
|
||||
case *idpconfig.JWTConfigAddedEvent:
|
||||
rm.reduceConfigAddedEvent(e)
|
||||
case *idpconfig.JWTConfigChangedEvent:
|
||||
rm.reduceConfigChangedEvent(e)
|
||||
}
|
||||
}
|
||||
|
||||
return rm.ReadModel.Reduce()
|
||||
}
|
||||
|
||||
func (rm *JWTConfigReadModel) reduceConfigAddedEvent(e *idpconfig.JWTConfigAddedEvent) {
|
||||
rm.IDPConfigID = e.IDPConfigID
|
||||
rm.JWTEndpoint = e.JWTEndpoint
|
||||
rm.Issuer = e.Issuer
|
||||
rm.KeysEndpoint = e.KeysEndpoint
|
||||
}
|
||||
|
||||
func (rm *JWTConfigReadModel) reduceConfigChangedEvent(e *idpconfig.JWTConfigChangedEvent) {
|
||||
if e.JWTEndpoint != nil {
|
||||
rm.JWTEndpoint = *e.JWTEndpoint
|
||||
}
|
||||
if e.Issuer != nil {
|
||||
rm.Issuer = *e.Issuer
|
||||
}
|
||||
if e.KeysEndpoint != nil {
|
||||
rm.KeysEndpoint = *e.KeysEndpoint
|
||||
}
|
||||
}
|
@ -47,6 +47,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(IDPConfigReactivatedEventType, IDPConfigReactivatedEventMapper).
|
||||
RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper).
|
||||
RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
|
||||
RegisterFilterEventMapper(IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
|
||||
RegisterFilterEventMapper(IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
|
||||
RegisterFilterEventMapper(LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper).
|
||||
RegisterFilterEventMapper(LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper).
|
||||
RegisterFilterEventMapper(LoginPolicyIDPProviderCascadeRemovedEventType, IdentityProviderCascadeRemovedEventMapper).
|
||||
|
86
internal/repository/iam/idp_jwt_config.go
Normal file
86
internal/repository/iam/idp_jwt_config.go
Normal file
@ -0,0 +1,86 @@
|
||||
package iam
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
IDPJWTConfigAddedEventType eventstore.EventType = "iam.idp." + idpconfig.JWTConfigAddedEventType
|
||||
IDPJWTConfigChangedEventType eventstore.EventType = "iam.idp." + idpconfig.JWTConfigChangedEventType
|
||||
)
|
||||
|
||||
type IDPJWTConfigAddedEvent struct {
|
||||
idpconfig.JWTConfigAddedEvent
|
||||
}
|
||||
|
||||
func NewIDPJWTConfigAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName string,
|
||||
) *IDPJWTConfigAddedEvent {
|
||||
return &IDPJWTConfigAddedEvent{
|
||||
JWTConfigAddedEvent: *idpconfig.NewJWTConfigAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
IDPJWTConfigAddedEventType,
|
||||
),
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func IDPJWTConfigAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := idpconfig.JWTConfigAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IDPJWTConfigAddedEvent{JWTConfigAddedEvent: *e.(*idpconfig.JWTConfigAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type IDPJWTConfigChangedEvent struct {
|
||||
idpconfig.JWTConfigChangedEvent
|
||||
}
|
||||
|
||||
func NewIDPJWTConfigChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
idpConfigID string,
|
||||
changes []idpconfig.JWTConfigChanges,
|
||||
) (*IDPJWTConfigChangedEvent, error) {
|
||||
changeEvent, err := idpconfig.NewJWTConfigChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
IDPJWTConfigChangedEventType),
|
||||
idpConfigID,
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IDPJWTConfigChangedEvent{JWTConfigChangedEvent: *changeEvent}, nil
|
||||
}
|
||||
|
||||
func IDPJWTConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := idpconfig.JWTConfigChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IDPJWTConfigChangedEvent{JWTConfigChangedEvent: *e.(*idpconfig.JWTConfigChangedEvent)}, nil
|
||||
}
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
const (
|
||||
IDPOIDCConfigAddedEventType eventstore.EventType = "iam.idp." + idpconfig.OIDCConfigAddedEventType
|
||||
IDPOIDCConfigChangedEventType eventstore.EventType = "iam.idp." + idpconfig.ConfigChangedEventType
|
||||
IDPOIDCConfigChangedEventType eventstore.EventType = "iam.idp." + idpconfig.OIDCConfigChangedEventType
|
||||
)
|
||||
|
||||
type IDPOIDCConfigAddedEvent struct {
|
||||
|
140
internal/repository/idpconfig/jwt_config.go
Normal file
140
internal/repository/idpconfig/jwt_config.go
Normal file
@ -0,0 +1,140 @@
|
||||
package idpconfig
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
)
|
||||
|
||||
const (
|
||||
JWTConfigAddedEventType eventstore.EventType = "jwt.config.added"
|
||||
JWTConfigChangedEventType eventstore.EventType = "jwt.config.changed"
|
||||
)
|
||||
|
||||
type JWTConfigAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
IDPConfigID string `json:"idpConfigId"`
|
||||
JWTEndpoint string `json:"jwtEndpoint,omitempty"`
|
||||
Issuer string `json:"issuer,omitempty"`
|
||||
KeysEndpoint string `json:"keysEndpoint,omitempty"`
|
||||
HeaderName string `json:"headerName,omitempty"`
|
||||
}
|
||||
|
||||
func (e *JWTConfigAddedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *JWTConfigAddedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewJWTConfigAddedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName string,
|
||||
) *JWTConfigAddedEvent {
|
||||
return &JWTConfigAddedEvent{
|
||||
BaseEvent: *base,
|
||||
IDPConfigID: idpConfigID,
|
||||
JWTEndpoint: jwtEndpoint,
|
||||
Issuer: issuer,
|
||||
KeysEndpoint: keysEndpoint,
|
||||
HeaderName: headerName,
|
||||
}
|
||||
}
|
||||
|
||||
func JWTConfigAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &JWTConfigAddedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "JWT-m0fwf", "unable to unmarshal event")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
||||
|
||||
type JWTConfigChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
IDPConfigID string `json:"idpConfigId"`
|
||||
|
||||
JWTEndpoint *string `json:"jwtEndpoint,omitempty"`
|
||||
Issuer *string `json:"issuer,omitempty"`
|
||||
KeysEndpoint *string `json:"keysEndpoint,omitempty"`
|
||||
HeaderName *string `json:"headerName,omitempty"`
|
||||
}
|
||||
|
||||
func (e *JWTConfigChangedEvent) Data() interface{} {
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *JWTConfigChangedEvent) UniqueConstraints() []*eventstore.EventUniqueConstraint {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewJWTConfigChangedEvent(
|
||||
base *eventstore.BaseEvent,
|
||||
idpConfigID string,
|
||||
changes []JWTConfigChanges,
|
||||
) (*JWTConfigChangedEvent, error) {
|
||||
if len(changes) == 0 {
|
||||
return nil, errors.ThrowPreconditionFailed(nil, "IDPCONFIG-fn93s", "Errors.NoChangesFound")
|
||||
}
|
||||
changeEvent := &JWTConfigChangedEvent{
|
||||
BaseEvent: *base,
|
||||
IDPConfigID: idpConfigID,
|
||||
}
|
||||
for _, change := range changes {
|
||||
change(changeEvent)
|
||||
}
|
||||
return changeEvent, nil
|
||||
}
|
||||
|
||||
type JWTConfigChanges func(*JWTConfigChangedEvent)
|
||||
|
||||
func ChangeJWTEndpoint(jwtEndpoint string) func(*JWTConfigChangedEvent) {
|
||||
return func(e *JWTConfigChangedEvent) {
|
||||
e.JWTEndpoint = &jwtEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeJWTIssuer(issuer string) func(*JWTConfigChangedEvent) {
|
||||
return func(e *JWTConfigChangedEvent) {
|
||||
e.Issuer = &issuer
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeKeysEndpoint(keysEndpoint string) func(*JWTConfigChangedEvent) {
|
||||
return func(e *JWTConfigChangedEvent) {
|
||||
e.KeysEndpoint = &keysEndpoint
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeHeaderName(headerName string) func(*JWTConfigChangedEvent) {
|
||||
return func(e *JWTConfigChangedEvent) {
|
||||
e.HeaderName = &headerName
|
||||
}
|
||||
}
|
||||
|
||||
func JWTConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e := &JWTConfigChangedEvent{
|
||||
BaseEvent: *eventstore.BaseEventFromRepo(event),
|
||||
}
|
||||
|
||||
err := json.Unmarshal(event.Data, e)
|
||||
if err != nil {
|
||||
return nil, errors.ThrowInternal(err, "JWT-fk3fs", "unable to unmarshal event")
|
||||
}
|
||||
|
||||
return e, nil
|
||||
}
|
@ -11,8 +11,8 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
OIDCConfigAddedEventType eventstore.EventType = "oidc.config.added"
|
||||
ConfigChangedEventType eventstore.EventType = "oidc.config.changed"
|
||||
OIDCConfigAddedEventType eventstore.EventType = "oidc.config.added"
|
||||
OIDCConfigChangedEventType eventstore.EventType = "oidc.config.changed"
|
||||
)
|
||||
|
||||
type OIDCConfigAddedEvent struct {
|
||||
|
@ -75,6 +75,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
||||
RegisterFilterEventMapper(IDPConfigReactivatedEventType, IDPConfigReactivatedEventMapper).
|
||||
RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper).
|
||||
RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
|
||||
RegisterFilterEventMapper(IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
|
||||
RegisterFilterEventMapper(IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
|
||||
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
|
||||
RegisterFilterEventMapper(FeaturesRemovedEventType, FeaturesRemovedEventMapper)
|
||||
}
|
||||
|
87
internal/repository/org/idp_jwt_config.go
Normal file
87
internal/repository/org/idp_jwt_config.go
Normal file
@ -0,0 +1,87 @@
|
||||
package org
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore/repository"
|
||||
"github.com/caos/zitadel/internal/repository/idpconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
IDPJWTConfigAddedEventType eventstore.EventType = "org.idp." + idpconfig.JWTConfigAddedEventType
|
||||
IDPJWTConfigChangedEventType eventstore.EventType = "org.idp." + idpconfig.JWTConfigChangedEventType
|
||||
)
|
||||
|
||||
type IDPJWTConfigAddedEvent struct {
|
||||
idpconfig.JWTConfigAddedEvent
|
||||
}
|
||||
|
||||
func NewIDPJWTConfigAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName string,
|
||||
) *IDPJWTConfigAddedEvent {
|
||||
|
||||
return &IDPJWTConfigAddedEvent{
|
||||
JWTConfigAddedEvent: *idpconfig.NewJWTConfigAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
IDPJWTConfigAddedEventType,
|
||||
),
|
||||
idpConfigID,
|
||||
jwtEndpoint,
|
||||
issuer,
|
||||
keysEndpoint,
|
||||
headerName,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func IDPJWTConfigAddedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := idpconfig.JWTConfigAddedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IDPJWTConfigAddedEvent{JWTConfigAddedEvent: *e.(*idpconfig.JWTConfigAddedEvent)}, nil
|
||||
}
|
||||
|
||||
type IDPJWTConfigChangedEvent struct {
|
||||
idpconfig.JWTConfigChangedEvent
|
||||
}
|
||||
|
||||
func NewIDPJWTConfigChangedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
idpConfigID string,
|
||||
changes []idpconfig.JWTConfigChanges,
|
||||
) (*IDPJWTConfigChangedEvent, error) {
|
||||
changeEvent, err := idpconfig.NewJWTConfigChangedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
IDPJWTConfigChangedEventType),
|
||||
idpConfigID,
|
||||
changes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &IDPJWTConfigChangedEvent{JWTConfigChangedEvent: *changeEvent}, nil
|
||||
}
|
||||
|
||||
func IDPJWTConfigChangedEventMapper(event *repository.Event) (eventstore.EventReader, error) {
|
||||
e, err := idpconfig.JWTConfigChangedEventMapper(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &IDPJWTConfigChangedEvent{JWTConfigChangedEvent: *e.(*idpconfig.JWTConfigChangedEvent)}, nil
|
||||
}
|
@ -12,7 +12,7 @@ import (
|
||||
|
||||
const (
|
||||
IDPOIDCConfigAddedEventType eventstore.EventType = "org.idp." + idpconfig.OIDCConfigAddedEventType
|
||||
IDPOIDCConfigChangedEventType eventstore.EventType = "org.idp." + idpconfig.ConfigChangedEventType
|
||||
IDPOIDCConfigChangedEventType eventstore.EventType = "org.idp." + idpconfig.OIDCConfigChangedEventType
|
||||
)
|
||||
|
||||
type IDPOIDCConfigAddedEvent struct {
|
||||
|
@ -1,14 +1,16 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"net/http"
|
||||
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
|
||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||
)
|
||||
|
||||
const (
|
||||
queryAuthRequestID = "authRequestID"
|
||||
queryUserAgentID = "userAgentID"
|
||||
)
|
||||
|
||||
func (l *Login) getAuthRequest(r *http.Request) (*domain.AuthRequest, error) {
|
||||
|
@ -1,7 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -79,7 +81,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
||||
return
|
||||
}
|
||||
if !idpConfig.IsOIDC {
|
||||
l.renderError(w, r, authReq, caos_errors.ThrowInternal(nil, "LOGIN-Rio9s", "Errors.User.ExternalIDP.IDPTypeNotImplemented"))
|
||||
l.handleJWTAuthorize(w, r, authReq, idpConfig)
|
||||
return
|
||||
}
|
||||
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
||||
@ -90,6 +92,29 @@ func (l *Login) handleOIDCAuthorize(w http.ResponseWriter, r *http.Request, auth
|
||||
http.Redirect(w, r, rp.AuthURL(authReq.ID, provider, rp.WithPrompt(oidc.PromptSelectAccount)), http.StatusFound)
|
||||
}
|
||||
|
||||
func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView) {
|
||||
redirect, err := url.Parse(idpConfig.JWTEndpoint)
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
q := redirect.Query()
|
||||
q.Set(queryAuthRequestID, authReq.ID)
|
||||
userAgentID, ok := http_mw.UserAgentIDFromCtx(r.Context())
|
||||
if !ok {
|
||||
l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
|
||||
return
|
||||
}
|
||||
nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(userAgentID))
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
q.Set(queryUserAgentID, base64.RawURLEncoding.EncodeToString(nonce))
|
||||
redirect.RawQuery = q.Encode()
|
||||
http.Redirect(w, r, redirect.String(), http.StatusFound)
|
||||
}
|
||||
|
||||
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(externalIDPCallbackData)
|
||||
err := l.getParseData(r, data)
|
||||
@ -108,13 +133,17 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
||||
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
if idpConfig.IsOIDC {
|
||||
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
||||
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
|
||||
if err != nil {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
|
||||
}
|
||||
l.handleExternalUserAuthenticated(w, r, authReq, idpConfig, userAgentID, tokens)
|
||||
l.renderError(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "RP-asff2", "Errors.ExternalIDP.IDPTypeNotImplemented"))
|
||||
return
|
||||
}
|
||||
|
||||
func (l *Login) getRPConfig(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView, callbackEndpoint string) rp.RelyingParty {
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
|
||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
caos_errors "github.com/caos/zitadel/internal/errors"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
||||
@ -73,7 +72,7 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !idpConfig.IsOIDC {
|
||||
l.renderError(w, r, authReq, caos_errors.ThrowInternal(nil, "LOGIN-Rio9s", "Errors.User.ExternalIDP.IDPTypeNotImplemented"))
|
||||
l.handleJWTAuthorize(w, r, authReq, idpConfig)
|
||||
return
|
||||
}
|
||||
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
|
||||
|
203
internal/ui/login/handler/jwt_handler.go
Normal file
203
internal/ui/login/handler/jwt_handler.go
Normal file
@ -0,0 +1,203 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/caos/oidc/pkg/client/rp"
|
||||
"github.com/caos/oidc/pkg/oidc"
|
||||
http_util "github.com/caos/zitadel/internal/api/http"
|
||||
"github.com/caos/zitadel/internal/domain"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||
)
|
||||
|
||||
type jwtRequest struct {
|
||||
AuthRequestID string `schema:"authRequestID"`
|
||||
UserAgentID string `schema:"userAgentID"`
|
||||
}
|
||||
|
||||
func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(jwtRequest)
|
||||
err := l.getParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
if data.AuthRequestID == "" || data.UserAgentID == "" {
|
||||
l.renderError(w, r, nil, errors.ThrowInvalidArgument(nil, "LOGIN-adfzz", "Errors.AuthRequest.MissingParameters"))
|
||||
return
|
||||
}
|
||||
id, err := base64.RawURLEncoding.DecodeString(data.UserAgentID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
userAgentID, err := l.IDPConfigAesCrypto.DecryptString(id, l.IDPConfigAesCrypto.EncryptionKeyID())
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.AuthRequestID, userAgentID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if idpConfig.IsOIDC {
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
l.handleJWTExtraction(w, r, authReq, idpConfig)
|
||||
}
|
||||
|
||||
func (l *Login) handleJWTExtraction(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest, idpConfig *iam_model.IDPConfigView) {
|
||||
token, err := getToken(r, idpConfig.JWTHeaderName)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
tokenClaims, err := validateToken(r.Context(), token, idpConfig)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
tokens := &oidc.Tokens{IDToken: token, IDTokenClaims: tokenClaims}
|
||||
externalUser := l.mapTokenToLoginUser(tokens, idpConfig)
|
||||
err = l.authRepo.CheckExternalUserLogin(r.Context(), authReq.ID, authReq.AgentID, externalUser, domain.BrowserInfoFromRequest(r))
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
err = nil
|
||||
}
|
||||
if !idpConfig.AutoRegister {
|
||||
l.renderExternalNotFoundOption(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
authReq, err = l.authRepo.AuthRequestByID(r.Context(), authReq.ID, authReq.AgentID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
resourceOwner := l.getOrgID(authReq)
|
||||
orgIamPolicy, err := l.getOrgIamPolicy(r, resourceOwner)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
user, externalIDP := l.mapExternalUserToLoginUser(orgIamPolicy, authReq.LinkingUsers[len(authReq.LinkingUsers)-1], idpConfig)
|
||||
err = l.authRepo.AutoRegisterExternalUser(setContext(r.Context(), resourceOwner), user, externalIDP, nil, authReq.ID, authReq.AgentID, resourceOwner, domain.BrowserInfoFromRequest(r))
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
redirect, err := l.redirectToJWTCallback(authReq)
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, redirect, http.StatusFound)
|
||||
}
|
||||
|
||||
func (l *Login) redirectToJWTCallback(authReq *domain.AuthRequest) (string, error) {
|
||||
redirect, err := url.Parse(l.baseURL + EndpointJWTCallback)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
q := redirect.Query()
|
||||
q.Set(queryAuthRequestID, authReq.ID)
|
||||
nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(authReq.AgentID))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
q.Set(queryUserAgentID, base64.RawURLEncoding.EncodeToString(nonce))
|
||||
redirect.RawQuery = q.Encode()
|
||||
return redirect.String(), nil
|
||||
}
|
||||
|
||||
func (l *Login) handleJWTCallback(w http.ResponseWriter, r *http.Request) {
|
||||
data := new(jwtRequest)
|
||||
err := l.getParseData(r, data)
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
id, err := base64.RawURLEncoding.DecodeString(data.UserAgentID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
userAgentID, err := l.IDPConfigAesCrypto.DecryptString(id, l.IDPConfigAesCrypto.EncryptionKeyID())
|
||||
if err != nil {
|
||||
l.renderError(w, r, nil, err)
|
||||
return
|
||||
}
|
||||
authReq, err := l.authRepo.AuthRequestByID(r.Context(), data.AuthRequestID, userAgentID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
idpConfig, err := l.authRepo.GetIDPConfigByID(r.Context(), authReq.SelectedIDPConfigID)
|
||||
if err != nil {
|
||||
l.renderError(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
if idpConfig.IsOIDC {
|
||||
l.renderLogin(w, r, authReq, err)
|
||||
return
|
||||
}
|
||||
l.renderNextStep(w, r, authReq)
|
||||
}
|
||||
|
||||
func validateToken(ctx context.Context, token string, config *iam_model.IDPConfigView) (oidc.IDTokenClaims, error) {
|
||||
offset := 3 * time.Second
|
||||
maxAge := time.Hour
|
||||
claims := oidc.EmptyIDTokenClaims()
|
||||
payload, err := oidc.ParseToken(token, claims)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = oidc.CheckIssuer(claims, config.JWTIssuer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
keySet := rp.NewRemoteKeySet(http.DefaultClient, config.JWTKeysEndpoint)
|
||||
if err = oidc.CheckSignature(ctx, token, payload, claims, nil, keySet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !claims.GetExpiration().IsZero() {
|
||||
if err = oidc.CheckExpiration(claims, offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if !claims.GetIssuedAt().IsZero() {
|
||||
if err = oidc.CheckIssuedAt(claims, maxAge, offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func getToken(r *http.Request, headerName string) (string, error) {
|
||||
if headerName == "" {
|
||||
headerName = http_util.Authorization
|
||||
}
|
||||
auth := r.Header.Get(headerName)
|
||||
if auth == "" {
|
||||
return "", errors.ThrowInvalidArgument(nil, "LOGIN-adh42", "Errors.AuthRequest.TokenNotFound")
|
||||
}
|
||||
return strings.TrimPrefix(auth, oidc.PrefixBearer), nil
|
||||
}
|
@ -13,6 +13,8 @@ const (
|
||||
EndpointLogin = "/login"
|
||||
EndpointExternalLogin = "/login/externalidp"
|
||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||
EndpointJWTCallback = "/login/jwt/callback"
|
||||
EndpointPasswordlessLogin = "/login/passwordless"
|
||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
||||
@ -53,6 +55,8 @@ func CreateRouter(login *Login, staticDir http.FileSystem, interceptors ...mux.M
|
||||
router.HandleFunc(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointJWTAuthorize, login.handleJWTRequest).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointJWTCallback, login.handleJWTCallback).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)
|
||||
router.HandleFunc(EndpointPasswordlessRegistration, login.handlePasswordlessRegistration).Methods(http.MethodGet)
|
||||
router.HandleFunc(EndpointPasswordlessRegistration, login.handlePasswordlessRegistrationCheck).Methods(http.MethodPost)
|
||||
|
@ -298,7 +298,10 @@ Errors:
|
||||
AuthRequest:
|
||||
NotFound: AuthRequest konnte nicht gefunden werden
|
||||
UserAgentNotCorresponding: User Agent stimmt nicht überein
|
||||
UserAgentNotFound: User Agent ID nicht gefunden
|
||||
TokenNotFound: Token nicht gefunden
|
||||
RequestTypeNotSupported: Requesttyp wird nicht unterstürzt
|
||||
MissingParameters: Benötigte Parameter fehlen
|
||||
User:
|
||||
NotFound: Benutzer konnte nicht gefunden werden
|
||||
Inactive: Benutzer ist inaktiv
|
||||
|
@ -299,7 +299,10 @@ Errors:
|
||||
AuthRequest:
|
||||
NotFound: Could not find authrequest
|
||||
UserAgentNotCorresponding: User Agent does not correspond
|
||||
UserAgentNotFound: User Agent ID not found
|
||||
TokenNotFound: Token not found
|
||||
RequestTypeNotSupported: Request type is not supported
|
||||
MissingParameters: Required parameters missing
|
||||
User:
|
||||
NotFound: User could not be found
|
||||
Inactive: User is inactive
|
||||
|
11
migrations/cockroach/V1.71__jwt_idp.sql
Normal file
11
migrations/cockroach/V1.71__jwt_idp.sql
Normal file
@ -0,0 +1,11 @@
|
||||
ALTER TABLE auth.idp_configs ADD COLUMN jwt_endpoint TEXT;
|
||||
ALTER TABLE auth.idp_configs ADD COLUMN jwt_keys_endpoint TEXT;
|
||||
ALTER TABLE auth.idp_configs ADD COLUMN jwt_header_name TEXT;
|
||||
|
||||
ALTER TABLE adminapi.idp_configs ADD COLUMN jwt_endpoint TEXT;
|
||||
ALTER TABLE adminapi.idp_configs ADD COLUMN jwt_keys_endpoint TEXT;
|
||||
ALTER TABLE adminapi.idp_configs ADD COLUMN jwt_header_name TEXT;
|
||||
|
||||
ALTER TABLE management.idp_configs ADD COLUMN jwt_endpoint TEXT;
|
||||
ALTER TABLE management.idp_configs ADD COLUMN jwt_keys_endpoint TEXT;
|
||||
ALTER TABLE management.idp_configs ADD COLUMN jwt_header_name TEXT;
|
3
pkg/grpc/idp/idp.go
Normal file
3
pkg/grpc/idp/idp.go
Normal file
@ -0,0 +1,3 @@
|
||||
package idp
|
||||
|
||||
type IDPConfig = isIDP_Config
|
@ -401,6 +401,41 @@ service AdminService {
|
||||
};
|
||||
}
|
||||
|
||||
// Adds a new jwt identity provider configuration the IAM
|
||||
rpc AddJWTIDP(AddJWTIDPRequest) returns (AddJWTIDPResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/idps/jwt";
|
||||
body: "*";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.idp.write";
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "identity provider";
|
||||
tags: "jwt";
|
||||
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "idp created";
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
key: "400";
|
||||
value: {
|
||||
description: "invalid argument";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/rpcStatus";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
//Updates the specified idp
|
||||
// all fields are updated. If no value is provided the field will be empty afterwards.
|
||||
rpc UpdateIDP(UpdateIDPRequest) returns (UpdateIDPResponse) {
|
||||
@ -598,7 +633,53 @@ service AdminService {
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//Updates the jwt configuration of the specified idp
|
||||
// all fields are updated. If no value is provided the field will be empty afterwards.
|
||||
rpc UpdateIDPJWTConfig(UpdateIDPJWTConfigRequest) returns (UpdateIDPJWTConfigResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/idps/{idp_id}/jwt_config";
|
||||
body: "*";
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "iam.idp.write";
|
||||
};
|
||||
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
|
||||
tags: "identity provider";
|
||||
tags: "jwt";
|
||||
responses: {
|
||||
key: "200";
|
||||
value: {
|
||||
description: "jwt config updated";
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
key: "400";
|
||||
value: {
|
||||
description: "invalid argument";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/rpcStatus";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
key: "409";
|
||||
value: {
|
||||
description: "precondition failed";
|
||||
schema: {
|
||||
json_schema: {
|
||||
ref: "#/definitions/rpcStatus";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
rpc GetDefaultFeatures(GetDefaultFeaturesRequest) returns (GetDefaultFeaturesResponse) {
|
||||
option(google.api.http) = {
|
||||
get: "/features"
|
||||
@ -2436,6 +2517,64 @@ message AddOIDCIDPResponse {
|
||||
string idp_id = 2;
|
||||
}
|
||||
|
||||
message AddJWTIDPRequest {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema: {
|
||||
required: ["name", "issuer", "keys_endpoint"]
|
||||
};
|
||||
};
|
||||
|
||||
string name = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"google\"";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.IDPStylingType styling_type = 2 [
|
||||
(validate.rules).enum = {defined_only: true},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "some identity providers specify the styling of the button to their login";
|
||||
}
|
||||
];
|
||||
string jwt_endpoint = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://custom.com/auth/jwt\"";
|
||||
description: "the endpoint where the jwt can be extracted";
|
||||
}
|
||||
];
|
||||
string issuer = 4 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.custom.com\"";
|
||||
description: "the issuer of the jwt (for validation)";
|
||||
}
|
||||
];
|
||||
string keys_endpoint = 5 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.custom.com/keys\"";
|
||||
description: "the endpoint to the key (JWK) which are used to sign the JWT with";
|
||||
}
|
||||
];
|
||||
string header_name = 6 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"x-auth-token\"";
|
||||
description: "the name of the header where the JWT is sent in, default is authorization";
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
bool auto_register = 7;
|
||||
}
|
||||
|
||||
message AddJWTIDPResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
string idp_id = 2;
|
||||
}
|
||||
|
||||
message UpdateIDPRequest {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema: {
|
||||
@ -2590,6 +2729,62 @@ message UpdateIDPOIDCConfigResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message UpdateIDPJWTConfigRequest {
|
||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||
json_schema: {
|
||||
required: ["idp_id", "issuer", "keys_endpoint"]
|
||||
};
|
||||
};
|
||||
|
||||
string idp_id = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629023906488334\"";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string jwt_endpoint = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://custom.com/auth/jwt\"";
|
||||
description: "the endpoint where the jwt can be extracted";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string issuer = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.custom.com\"";
|
||||
description: "the issuer of the jwt (for validation)";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string keys_endpoint = 4 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.custom.com/keys\"";
|
||||
description: "the endpoint to the key (JWK) which are used to sign the JWT with";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string header_name = 5 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"x-auth-token\"";
|
||||
description: "the name of the header where the JWT is sent in, default is authorization";
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message UpdateIDPJWTConfigResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
message GetDefaultFeaturesRequest {}
|
||||
|
||||
message GetDefaultFeaturesResponse {
|
||||
@ -3637,4 +3832,4 @@ message FailedEvent {
|
||||
example: "\"ID=EXAMP-ID3ER Message=Example message\"";
|
||||
}
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ message IDP {
|
||||
];
|
||||
oneof config {
|
||||
OIDCConfig oidc_config = 7;
|
||||
JWTConfig jwt_config = 9;
|
||||
}
|
||||
bool auto_register = 8;
|
||||
}
|
||||
@ -115,6 +116,7 @@ enum IDPType {
|
||||
IDP_TYPE_UNSPECIFIED = 0;
|
||||
IDP_TYPE_OIDC = 1;
|
||||
//PLANNED: IDP_TYPE_SAML
|
||||
IDP_TYPE_JWT = 3;
|
||||
}
|
||||
|
||||
// the owner of the identity provider.
|
||||
@ -162,6 +164,38 @@ enum OIDCMappingField {
|
||||
OIDC_MAPPING_FIELD_EMAIL = 2;
|
||||
}
|
||||
|
||||
|
||||
message JWTConfig {
|
||||
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\"";
|
||||
description: "the endpoint where the jwt can be extracted";
|
||||
}
|
||||
];
|
||||
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\"";
|
||||
description: "the issuer of the jwt (for validation)";
|
||||
}
|
||||
];
|
||||
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\"";
|
||||
description: "the endpoint to the key (JWK) which are used to sign the JWT with";
|
||||
}
|
||||
];
|
||||
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\"";
|
||||
description: "the name of the header where the JWT is sent in, default is authorization";
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message IDPIDQuery {
|
||||
string id = 1 [
|
||||
(validate.rules).string = {max_len: 200},
|
||||
|
@ -2617,6 +2617,19 @@ service ManagementService {
|
||||
};
|
||||
}
|
||||
|
||||
// Add a new jwt identity provider configuration in the organisation
|
||||
rpc AddOrgJWTIDP(AddOrgJWTIDPRequest) returns (AddOrgJWTIDPResponse) {
|
||||
option (google.api.http) = {
|
||||
post: "/idps/jwt"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "org.idp.write"
|
||||
feature: "login_policy.idp"
|
||||
};
|
||||
}
|
||||
|
||||
// Deactivate identity provider configuration
|
||||
// Users will not be able to use this provider for login (e.g Google, Microsoft, AD, etc)
|
||||
// Returns error if already deactivated
|
||||
@ -2684,6 +2697,19 @@ service ManagementService {
|
||||
feature: "login_policy.idp"
|
||||
};
|
||||
}
|
||||
|
||||
// Change JWT identity provider configuration of the organisation
|
||||
rpc UpdateOrgIDPJWTConfig(UpdateOrgIDPJWTConfigRequest) returns (UpdateOrgIDPJWTConfigResponse) {
|
||||
option (google.api.http) = {
|
||||
put: "/idps/{idp_id}/jwt_config"
|
||||
body: "*"
|
||||
};
|
||||
|
||||
option (zitadel.v1.auth_option) = {
|
||||
permission: "org.idp.write"
|
||||
feature: "login_policy.idp"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
//This is an empty request
|
||||
@ -4892,6 +4918,62 @@ message AddOrgOIDCIDPResponse {
|
||||
string idp_id = 2;
|
||||
}
|
||||
|
||||
message AddOrgJWTIDPRequest {
|
||||
string name = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"google\"";
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.IDPStylingType styling_type = 2 [
|
||||
(validate.rules).enum = {defined_only: true},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "some identity providers specify the styling of the button to their login";
|
||||
}
|
||||
];
|
||||
string jwt_endpoint = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.google.com\"";
|
||||
description: "the endpoint where the jwt can be extracted";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string issuer = 4 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.google.com\"";
|
||||
description: "the issuer of the jwt (for validation)";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string keys_endpoint = 5 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.google.com/keys\"";
|
||||
description: "the endpoint to the key (JWK) which are used to sign the JWT with";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string header_name = 6 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"x-auth-token\"";
|
||||
description: "the name of the header where the JWT is sent in, default is authorization";
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
bool auto_register = 7;
|
||||
}
|
||||
|
||||
message AddOrgJWTIDPResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
string idp_id = 2;
|
||||
}
|
||||
|
||||
message DeactivateOrgIDPRequest {
|
||||
string idp_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
}
|
||||
@ -4986,3 +5068,52 @@ message UpdateOrgIDPOIDCConfigRequest {
|
||||
message UpdateOrgIDPOIDCConfigResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
||||
|
||||
message UpdateOrgIDPJWTConfigRequest {
|
||||
string idp_id = 1 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"69629023906488334\"";
|
||||
}
|
||||
];
|
||||
string jwt_endpoint = 2 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.google.com\"";
|
||||
description: "the endpoint where the jwt can be extracted";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string issuer = 3 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.google.com\"";
|
||||
description: "the issuer of the jwt (for validation)";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string keys_endpoint = 4 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://accounts.google.com/keys\"";
|
||||
description: "the endpoint to the key (JWK) which are used to sign the JWT with";
|
||||
min_length: 1;
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
string header_name = 5 [
|
||||
(validate.rules).string = {min_len: 1, max_len: 200},
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"x-auth-token\"";
|
||||
description: "the name of the header where the JWT is sent in, default is authorization";
|
||||
max_length: 200;
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
message UpdateOrgIDPJWTConfigResponse {
|
||||
zitadel.v1.ObjectDetails details = 1;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user