mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 22:57:23 +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
|
POST: /idps/oidc
|
||||||
|
|
||||||
|
|
||||||
|
### AddJWTIDP
|
||||||
|
|
||||||
|
> **rpc** AddJWTIDP([AddJWTIDPRequest](#addjwtidprequest))
|
||||||
|
[AddJWTIDPResponse](#addjwtidpresponse)
|
||||||
|
|
||||||
|
Adds a new jwt identity provider configuration the IAM
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /idps/jwt
|
||||||
|
|
||||||
|
|
||||||
### UpdateIDP
|
### UpdateIDP
|
||||||
|
|
||||||
> **rpc** UpdateIDP([UpdateIDPRequest](#updateidprequest))
|
> **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
|
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
|
### GetDefaultFeatures
|
||||||
|
|
||||||
> **rpc** GetDefaultFeatures([GetDefaultFeaturesRequest](#getdefaultfeaturesrequest))
|
> **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
|
### 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
|
### UpdateIDPOIDCConfigRequest
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ title: zitadel/idp.proto
|
|||||||
| styling_type | IDPStylingType | - | |
|
| styling_type | IDPStylingType | - | |
|
||||||
| owner | IDPOwnerType | - | |
|
| 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.oidc_config | OIDCConfig | - | |
|
||||||
|
| [**oneof**](https://developers.google.com/protocol-buffers/docs/proto3#oneof) config.jwt_config | JWTConfig | - | |
|
||||||
| auto_register | bool | - | |
|
| 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
|
### OIDCConfig
|
||||||
|
|
||||||
|
|
||||||
@ -162,7 +177,8 @@ authorization framework of the identity provider
|
|||||||
| Name | Number | Description |
|
| Name | Number | Description |
|
||||||
| ---- | ------ | ----------- |
|
| ---- | ------ | ----------- |
|
||||||
| IDP_TYPE_UNSPECIFIED | 0 | - |
|
| 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
|
POST: /idps/oidc
|
||||||
|
|
||||||
|
|
||||||
|
### AddOrgJWTIDP
|
||||||
|
|
||||||
|
> **rpc** AddOrgJWTIDP([AddOrgJWTIDPRequest](#addorgjwtidprequest))
|
||||||
|
[AddOrgJWTIDPResponse](#addorgjwtidpresponse)
|
||||||
|
|
||||||
|
Add a new jwt identity provider configuration in the organisation
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
POST: /idps/jwt
|
||||||
|
|
||||||
|
|
||||||
### DeactivateOrgIDP
|
### DeactivateOrgIDP
|
||||||
|
|
||||||
> **rpc** DeactivateOrgIDP([DeactivateOrgIDPRequest](#deactivateorgidprequest))
|
> **rpc** DeactivateOrgIDP([DeactivateOrgIDPRequest](#deactivateorgidprequest))
|
||||||
@ -2659,6 +2671,18 @@ Change OIDC identity provider configuration of the organisation
|
|||||||
PUT: /idps/{idp_id}/oidc_config
|
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
|
### 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
|
### UpdateOrgIDPOIDCConfigRequest
|
||||||
|
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import (
|
|||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
"github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/model"
|
||||||
|
"github.com/caos/zitadel/internal/repository/iam"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -84,7 +85,9 @@ func (i *IDPConfig) processIDPConfig(event *es_models.Event) (err error) {
|
|||||||
err = idp.AppendEvent(iam_model.IDPProviderTypeSystem, event)
|
err = idp.AppendEvent(iam_model.IDPProviderTypeSystem, event)
|
||||||
case model.IDPConfigChanged,
|
case model.IDPConfigChanged,
|
||||||
model.OIDCIDPConfigAdded,
|
model.OIDCIDPConfigAdded,
|
||||||
model.OIDCIDPConfigChanged:
|
model.OIDCIDPConfigChanged,
|
||||||
|
es_models.EventType(iam.IDPJWTConfigAddedEventType),
|
||||||
|
es_models.EventType(iam.IDPJWTConfigChangedEventType):
|
||||||
err = idp.SetData(event)
|
err = idp.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -42,6 +42,21 @@ func (s *Server) AddOIDCIDP(ctx context.Context, req *admin_pb.AddOIDCIDPRequest
|
|||||||
}, nil
|
}, 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) {
|
func (s *Server) UpdateIDP(ctx context.Context, req *admin_pb.UpdateIDPRequest) (*admin_pb.UpdateIDPResponse, error) {
|
||||||
config, err := s.command.ChangeDefaultIDPConfig(ctx, updateIDPToDomain(req))
|
config, err := s.command.ChangeDefaultIDPConfig(ctx, updateIDPToDomain(req))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -101,3 +116,17 @@ func (s *Server) UpdateIDPOIDCConfig(ctx context.Context, req *admin_pb.UpdateID
|
|||||||
),
|
),
|
||||||
}, nil
|
}, 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 {
|
func updateIDPToDomain(req *admin_pb.UpdateIDPRequest) *domain.IDPConfig {
|
||||||
return &domain.IDPConfig{
|
return &domain.IDPConfig{
|
||||||
IDPConfigID: req.IdpId,
|
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 {
|
func listIDPsToModel(req *admin_pb.ListIDPsRequest) *iam_model.IDPConfigSearchRequest {
|
||||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||||
return &iam_model.IDPConfigSearchRequest{
|
return &iam_model.IDPConfigSearchRequest{
|
||||||
|
@ -46,6 +46,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
|
|||||||
"OIDCConfig.AuthorizationEndpoint",
|
"OIDCConfig.AuthorizationEndpoint",
|
||||||
"OIDCConfig.TokenEndpoint",
|
"OIDCConfig.TokenEndpoint",
|
||||||
"Type", //TODO: default (0) is oidc
|
"Type", //TODO: default (0) is oidc
|
||||||
|
"JWTConfig",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -113,6 +114,7 @@ func Test_updateIDPToDomain(t *testing.T) {
|
|||||||
test.AssertFieldsMapped(t, got,
|
test.AssertFieldsMapped(t, got,
|
||||||
"ObjectRoot",
|
"ObjectRoot",
|
||||||
"OIDCConfig",
|
"OIDCConfig",
|
||||||
|
"JWTConfig",
|
||||||
"State",
|
"State",
|
||||||
"Type", //TODO: type should not be changeable
|
"Type", //TODO: type should not be changeable
|
||||||
)
|
)
|
||||||
|
@ -59,7 +59,7 @@ func ExternalIDPViewToLoginPolicyLinkPb(link *iam_model.IDPProviderView) *idp_pb
|
|||||||
return &idp_pb.IDPLoginPolicyLink{
|
return &idp_pb.IDPLoginPolicyLink{
|
||||||
IdpId: link.IDPConfigID,
|
IdpId: link.IDPConfigID,
|
||||||
IdpName: link.Name,
|
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,
|
ProvidedUserId: link.ExternalUserID,
|
||||||
ProvidedUserName: link.UserDisplayName,
|
ProvidedUserName: link.UserDisplayName,
|
||||||
//TODO: as soon as saml is implemented we need to switch here
|
//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,7 +145,8 @@ func IDPStylingTypeToPb(stylingType domain.IDPConfigStylingType) idp_pb.IDPStyli
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ModelIDPViewToConfigPb(config *iam_model.IDPConfigView) *idp_pb.IDP_OidcConfig {
|
func ModelIDPViewToConfigPb(config *iam_model.IDPConfigView) idp_pb.IDPConfig {
|
||||||
|
if config.IsOIDC {
|
||||||
return &idp_pb.IDP_OidcConfig{
|
return &idp_pb.IDP_OidcConfig{
|
||||||
OidcConfig: &idp_pb.OIDCConfig{
|
OidcConfig: &idp_pb.OIDCConfig{
|
||||||
ClientId: config.OIDCClientID,
|
ClientId: config.OIDCClientID,
|
||||||
@ -142,9 +156,19 @@ func ModelIDPViewToConfigPb(config *iam_model.IDPConfigView) *idp_pb.IDP_OidcCon
|
|||||||
UsernameMapping: ModelMappingFieldToPb(config.OIDCUsernameMapping),
|
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 {
|
func IDPViewToConfigPb(config *domain.IDPConfigView) idp_pb.IDPConfig {
|
||||||
|
if config.IsOIDC {
|
||||||
return &idp_pb.IDP_OidcConfig{
|
return &idp_pb.IDP_OidcConfig{
|
||||||
OidcConfig: &idp_pb.OIDCConfig{
|
OidcConfig: &idp_pb.OIDCConfig{
|
||||||
ClientId: config.OIDCClientID,
|
ClientId: config.OIDCClientID,
|
||||||
@ -154,6 +178,14 @@ func IDPViewToConfigPb(config *domain.IDPConfigView) *idp_pb.IDP_OidcConfig {
|
|||||||
UsernameMapping: MappingFieldToPb(config.OIDCUsernameMapping),
|
UsernameMapping: MappingFieldToPb(config.OIDCUsernameMapping),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
return &idp_pb.IDP_JwtConfig{
|
||||||
|
JwtConfig: &idp_pb.JWTConfig{
|
||||||
|
JwtEndpoint: config.JWTEndpoint,
|
||||||
|
Issuer: config.JWTIssuer,
|
||||||
|
KeysEndpoint: config.JWTKeysEndpoint,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func FieldNameToModel(fieldName idp_pb.IDPFieldName) iam_model.IDPConfigSearchKey {
|
func FieldNameToModel(fieldName idp_pb.IDPFieldName) iam_model.IDPConfigSearchKey {
|
||||||
|
@ -40,6 +40,22 @@ func (s *Server) AddOrgOIDCIDP(ctx context.Context, req *mgmt_pb.AddOrgOIDCIDPRe
|
|||||||
),
|
),
|
||||||
}, nil
|
}, 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) {
|
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)
|
objectDetails, err := s.command.DeactivateIDPConfig(ctx, req.IdpId, authz.GetCtxData(ctx).OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -96,3 +112,17 @@ func (s *Server) UpdateOrgIDPOIDCConfig(ctx context.Context, req *mgmt_pb.Update
|
|||||||
),
|
),
|
||||||
}, nil
|
}, 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
|
||||||
|
}
|
||||||
|
@ -16,6 +16,7 @@ func addOIDCIDPRequestToDomain(req *mgmt_pb.AddOrgOIDCIDPRequest) *domain.IDPCon
|
|||||||
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
|
OIDCConfig: addOIDCIDPRequestToDomainOIDCIDPConfig(req),
|
||||||
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
|
StylingType: idp_grpc.IDPStylingTypeToDomain(req.StylingType),
|
||||||
Type: domain.IDPConfigTypeOIDC,
|
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 {
|
func updateIDPToDomain(req *mgmt_pb.UpdateOrgIDPRequest) *domain.IDPConfig {
|
||||||
return &domain.IDPConfig{
|
return &domain.IDPConfig{
|
||||||
IDPConfigID: req.IdpId,
|
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 {
|
func listIDPsToModel(req *mgmt_pb.ListOrgIDPsRequest) *iam_model.IDPConfigSearchRequest {
|
||||||
offset, limit, asc := object.ListQueryToModel(req.Query)
|
offset, limit, asc := object.ListQueryToModel(req.Query)
|
||||||
return &iam_model.IDPConfigSearchRequest{
|
return &iam_model.IDPConfigSearchRequest{
|
||||||
|
@ -46,7 +46,7 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
|
|||||||
"OIDCConfig.AuthorizationEndpoint",
|
"OIDCConfig.AuthorizationEndpoint",
|
||||||
"OIDCConfig.TokenEndpoint",
|
"OIDCConfig.TokenEndpoint",
|
||||||
"Type", //TODO: default (0) is oidc
|
"Type", //TODO: default (0) is oidc
|
||||||
"AutoRegister",
|
"JWTConfig",
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -114,6 +114,7 @@ func Test_updateIDPToDomain(t *testing.T) {
|
|||||||
test.AssertFieldsMapped(t, got,
|
test.AssertFieldsMapped(t, got,
|
||||||
"ObjectRoot",
|
"ObjectRoot",
|
||||||
"OIDCConfig",
|
"OIDCConfig",
|
||||||
|
"JWTConfig",
|
||||||
"State",
|
"State",
|
||||||
"Type", //TODO: type should not be changeable
|
"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_es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
iam_view_model "github.com/caos/zitadel/internal/iam/repository/view/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/org/repository/eventsourcing/model"
|
||||||
|
"github.com/caos/zitadel/internal/repository/iam"
|
||||||
|
"github.com/caos/zitadel/internal/repository/org"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -88,7 +90,9 @@ func (i *IDPConfig) processIdpConfig(providerType iam_model.IDPProviderType, eve
|
|||||||
err = idp.AppendEvent(providerType, event)
|
err = idp.AppendEvent(providerType, event)
|
||||||
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
||||||
model.OIDCIDPConfigAdded, iam_es_model.OIDCIDPConfigAdded,
|
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)
|
err = idp.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
func writeModelToIDPProvider(wm *IdentityProviderWriteModel) *domain.IDPProvider {
|
||||||
return &domain.IDPProvider{
|
return &domain.IDPProvider{
|
||||||
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
ObjectRoot: writeModelToObjectRoot(wm.WriteModel),
|
||||||
|
@ -2,6 +2,7 @@ package command
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
@ -13,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPConfig) (*domain.IDPConfig, error) {
|
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")
|
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)
|
addedConfig := NewIAMIDPConfigWriteModel(idpConfigID)
|
||||||
|
|
||||||
clientSecret, err := crypto.Encrypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
iamAgg := IAMAggregateFromWriteModel(&addedConfig.WriteModel)
|
iamAgg := IAMAggregateFromWriteModel(&addedConfig.WriteModel)
|
||||||
events := []eventstore.EventPusher{
|
events := []eventstore.EventPusher{
|
||||||
iam_repo.NewIDPConfigAddedEvent(
|
iam_repo.NewIDPConfigAddedEvent(
|
||||||
@ -39,7 +35,14 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
|
|||||||
config.StylingType,
|
config.StylingType,
|
||||||
config.AutoRegister,
|
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,
|
ctx,
|
||||||
iamAgg,
|
iamAgg,
|
||||||
config.OIDCConfig.ClientID,
|
config.OIDCConfig.ClientID,
|
||||||
@ -51,9 +54,18 @@ func (c *Commands) AddDefaultIDPConfig(ctx context.Context, config *domain.IDPCo
|
|||||||
config.OIDCConfig.IDPDisplayNameMapping,
|
config.OIDCConfig.IDPDisplayNameMapping,
|
||||||
config.OIDCConfig.UsernameMapping,
|
config.OIDCConfig.UsernameMapping,
|
||||||
config.OIDCConfig.Scopes...,
|
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...)
|
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||||
"github.com/caos/zitadel/internal/eventstore"
|
"github.com/caos/zitadel/internal/eventstore"
|
||||||
@ -16,7 +17,7 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
|||||||
if resourceOwner == "" {
|
if resourceOwner == "" {
|
||||||
return nil, caos_errs.ThrowInvalidArgument(nil, "Org-0j8gs", "Errors.ResourceOwnerMissing")
|
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")
|
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)
|
addedConfig := NewOrgIDPConfigWriteModel(idpConfigID, resourceOwner)
|
||||||
|
|
||||||
clientSecret, err := crypto.Crypt([]byte(config.OIDCConfig.ClientSecretString), c.idpConfigSecretCrypto)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
orgAgg := OrgAggregateFromWriteModel(&addedConfig.WriteModel)
|
orgAgg := OrgAggregateFromWriteModel(&addedConfig.WriteModel)
|
||||||
events := []eventstore.EventPusher{
|
events := []eventstore.EventPusher{
|
||||||
org_repo.NewIDPConfigAddedEvent(
|
org_repo.NewIDPConfigAddedEvent(
|
||||||
@ -42,7 +38,13 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
|||||||
config.StylingType,
|
config.StylingType,
|
||||||
config.AutoRegister,
|
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,
|
ctx,
|
||||||
orgAgg,
|
orgAgg,
|
||||||
config.OIDCConfig.ClientID,
|
config.OIDCConfig.ClientID,
|
||||||
@ -53,7 +55,17 @@ func (c *Commands) AddIDPConfig(ctx context.Context, config *domain.IDPConfig, r
|
|||||||
clientSecret,
|
clientSecret,
|
||||||
config.OIDCConfig.IDPDisplayNameMapping,
|
config.OIDCConfig.IDPDisplayNameMapping,
|
||||||
config.OIDCConfig.UsernameMapping,
|
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...)
|
pushedEvents, err := c.eventstore.PushEvents(ctx, events...)
|
||||||
if err != nil {
|
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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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
|
StylingType IDPConfigStylingType
|
||||||
State IDPConfigState
|
State IDPConfigState
|
||||||
OIDCConfig *OIDCIDPConfig
|
OIDCConfig *OIDCIDPConfig
|
||||||
|
JWTConfig *JWTIDPConfig
|
||||||
AutoRegister bool
|
AutoRegister bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +40,10 @@ type IDPConfigView struct {
|
|||||||
OIDCUsernameMapping OIDCMappingField
|
OIDCUsernameMapping OIDCMappingField
|
||||||
OAuthAuthorizationEndpoint string
|
OAuthAuthorizationEndpoint string
|
||||||
OAuthTokenEndpoint string
|
OAuthTokenEndpoint string
|
||||||
|
|
||||||
|
JWTEndpoint string
|
||||||
|
JWTIssuer string
|
||||||
|
JWTKeysEndpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
type OIDCIDPConfig struct {
|
type OIDCIDPConfig struct {
|
||||||
@ -55,11 +60,21 @@ type OIDCIDPConfig struct {
|
|||||||
UsernameMapping OIDCMappingField
|
UsernameMapping OIDCMappingField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JWTIDPConfig struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
IDPConfigID string
|
||||||
|
JWTEndpoint string
|
||||||
|
Issuer string
|
||||||
|
KeysEndpoint string
|
||||||
|
HeaderName string
|
||||||
|
}
|
||||||
|
|
||||||
type IDPConfigType int32
|
type IDPConfigType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IDPConfigTypeOIDC IDPConfigType = iota
|
IDPConfigTypeOIDC IDPConfigType = iota
|
||||||
IDPConfigTypeSAML
|
IDPConfigTypeSAML
|
||||||
|
IDPConfigTypeJWT
|
||||||
|
|
||||||
//count is for validation
|
//count is for validation
|
||||||
idpConfigTypeCount
|
idpConfigTypeCount
|
||||||
|
@ -13,6 +13,7 @@ type IDPConfig struct {
|
|||||||
StylingType IDPStylingType
|
StylingType IDPStylingType
|
||||||
State IDPConfigState
|
State IDPConfigState
|
||||||
OIDCConfig *OIDCIDPConfig
|
OIDCConfig *OIDCIDPConfig
|
||||||
|
JWTIDPConfig *JWTIDPConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type OIDCIDPConfig struct {
|
type OIDCIDPConfig struct {
|
||||||
@ -27,11 +28,20 @@ type OIDCIDPConfig struct {
|
|||||||
UsernameMapping OIDCMappingField
|
UsernameMapping OIDCMappingField
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type JWTIDPConfig struct {
|
||||||
|
es_models.ObjectRoot
|
||||||
|
IDPConfigID string
|
||||||
|
JWTEndpoint string
|
||||||
|
Issuer string
|
||||||
|
KeysEndpoint string
|
||||||
|
}
|
||||||
|
|
||||||
type IdpConfigType int32
|
type IdpConfigType int32
|
||||||
|
|
||||||
const (
|
const (
|
||||||
IDPConfigTypeOIDC IdpConfigType = iota
|
IDPConfigTypeOIDC IdpConfigType = iota
|
||||||
IDPConfigTypeSAML
|
IDPConfigTypeSAML
|
||||||
|
IDPConfigTypeJWT
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDPConfigState int32
|
type IDPConfigState int32
|
||||||
|
@ -29,6 +29,10 @@ type IDPConfigView struct {
|
|||||||
OIDCUsernameMapping OIDCMappingField
|
OIDCUsernameMapping OIDCMappingField
|
||||||
OAuthAuthorizationEndpoint string
|
OAuthAuthorizationEndpoint string
|
||||||
OAuthTokenEndpoint string
|
OAuthTokenEndpoint string
|
||||||
|
JWTEndpoint string
|
||||||
|
JWTIssuer string
|
||||||
|
JWTKeysEndpoint string
|
||||||
|
JWTHeaderName string
|
||||||
}
|
}
|
||||||
|
|
||||||
type IDPConfigSearchRequest struct {
|
type IDPConfigSearchRequest struct {
|
||||||
|
@ -100,6 +100,8 @@ func idpConfigTypeToDomain(idpType IdpConfigType) domain.IDPConfigType {
|
|||||||
return domain.IDPConfigTypeOIDC
|
return domain.IDPConfigTypeOIDC
|
||||||
case IDPConfigTypeSAML:
|
case IDPConfigTypeSAML:
|
||||||
return domain.IDPConfigTypeSAML
|
return domain.IDPConfigTypeSAML
|
||||||
|
case IDPConfigTypeJWT:
|
||||||
|
return domain.IDPConfigTypeJWT
|
||||||
default:
|
default:
|
||||||
return domain.IDPConfigTypeOIDC
|
return domain.IDPConfigTypeOIDC
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/caos/zitadel/internal/crypto"
|
"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"
|
es_model "github.com/caos/zitadel/internal/iam/repository/eventsourcing/model"
|
||||||
org_es_model "github.com/caos/zitadel/internal/org/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"`
|
OIDCUsernameMapping int32 `json:"usernameMapping" gorm:"column:oidc_idp_username_mapping"`
|
||||||
OAuthAuthorizationEndpoint string `json:"authorizationEndpoint" gorm:"column:oauth_authorization_endpoint"`
|
OAuthAuthorizationEndpoint string `json:"authorizationEndpoint" gorm:"column:oauth_authorization_endpoint"`
|
||||||
OAuthTokenEndpoint string `json:"tokenEndpoint" gorm:"column:oauth_token_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"`
|
Sequence uint64 `json:"-" gorm:"column:sequence"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
||||||
return &model.IDPConfigView{
|
view := &model.IDPConfigView{
|
||||||
IDPConfigID: idp.IDPConfigID,
|
IDPConfigID: idp.IDPConfigID,
|
||||||
AggregateID: idp.AggregateID,
|
AggregateID: idp.AggregateID,
|
||||||
State: model.IDPConfigState(idp.IDPState),
|
State: model.IDPConfigState(idp.IDPState),
|
||||||
@ -63,13 +68,21 @@ func IDPConfigViewToModel(idp *IDPConfigView) *model.IDPConfigView {
|
|||||||
IsOIDC: idp.IsOIDC,
|
IsOIDC: idp.IsOIDC,
|
||||||
OIDCClientID: idp.OIDCClientID,
|
OIDCClientID: idp.OIDCClientID,
|
||||||
OIDCClientSecret: idp.OIDCClientSecret,
|
OIDCClientSecret: idp.OIDCClientSecret,
|
||||||
OIDCIssuer: idp.OIDCIssuer,
|
|
||||||
OIDCScopes: idp.OIDCScopes,
|
OIDCScopes: idp.OIDCScopes,
|
||||||
OIDCIDPDisplayNameMapping: model.OIDCMappingField(idp.OIDCIDPDisplayNameMapping),
|
OIDCIDPDisplayNameMapping: model.OIDCMappingField(idp.OIDCIDPDisplayNameMapping),
|
||||||
OIDCUsernameMapping: model.OIDCMappingField(idp.OIDCUsernameMapping),
|
OIDCUsernameMapping: model.OIDCMappingField(idp.OIDCUsernameMapping),
|
||||||
OAuthAuthorizationEndpoint: idp.OAuthAuthorizationEndpoint,
|
OAuthAuthorizationEndpoint: idp.OAuthAuthorizationEndpoint,
|
||||||
OAuthTokenEndpoint: idp.OAuthTokenEndpoint,
|
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 {
|
func IdpConfigViewsToModel(idps []*IDPConfigView) []*model.IDPConfigView {
|
||||||
@ -93,7 +106,9 @@ func (i *IDPConfigView) AppendEvent(providerType model.IDPProviderType, event *m
|
|||||||
i.IsOIDC = true
|
i.IsOIDC = true
|
||||||
err = i.SetData(event)
|
err = i.SetData(event)
|
||||||
case es_model.OIDCIDPConfigChanged, org_es_model.OIDCIDPConfigChanged,
|
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)
|
err = i.SetData(event)
|
||||||
case es_model.IDPConfigDeactivated, org_es_model.IDPConfigDeactivated:
|
case es_model.IDPConfigDeactivated, org_es_model.IDPConfigDeactivated:
|
||||||
i.IDPState = int32(model.IDPConfigStateInactive)
|
i.IDPState = int32(model.IDPConfigStateInactive)
|
||||||
|
@ -3,6 +3,8 @@ package handler
|
|||||||
import (
|
import (
|
||||||
"github.com/caos/logging"
|
"github.com/caos/logging"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1"
|
"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"
|
es_models "github.com/caos/zitadel/internal/eventstore/v1/models"
|
||||||
"github.com/caos/zitadel/internal/eventstore/v1/query"
|
"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)
|
err = idp.AppendEvent(providerType, event)
|
||||||
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
case model.IDPConfigChanged, iam_es_model.IDPConfigChanged,
|
||||||
model.OIDCIDPConfigAdded, iam_es_model.OIDCIDPConfigAdded,
|
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)
|
err = idp.SetData(event)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -48,6 +48,11 @@ func readModelToIDPConfigView(rm *IAMIDPConfigReadModel) *domain.IDPConfigView {
|
|||||||
converted.OAuthAuthorizationEndpoint = rm.OIDCConfig.AuthorizationEndpoint
|
converted.OAuthAuthorizationEndpoint = rm.OIDCConfig.AuthorizationEndpoint
|
||||||
converted.OAuthTokenEndpoint = rm.OIDCConfig.TokenEndpoint
|
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
|
return converted
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,14 +143,20 @@ func readModelToIDPConfigs(rm *IAMIDPConfigsReadModel) []*model.IDPConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func readModelToIDPConfig(rm *IAMIDPConfigReadModel) *model.IDPConfig {
|
func readModelToIDPConfig(rm *IAMIDPConfigReadModel) *model.IDPConfig {
|
||||||
return &model.IDPConfig{
|
config := &model.IDPConfig{
|
||||||
ObjectRoot: readModelToObjectRoot(rm.ReadModel),
|
ObjectRoot: readModelToObjectRoot(rm.ReadModel),
|
||||||
OIDCConfig: readModelToIDPOIDCConfig(rm.OIDCConfig),
|
|
||||||
IDPConfigID: rm.ConfigID,
|
IDPConfigID: rm.ConfigID,
|
||||||
Name: rm.Name,
|
Name: rm.Name,
|
||||||
State: model.IDPConfigState(rm.State),
|
State: model.IDPConfigState(rm.State),
|
||||||
StylingType: model.IDPStylingType(rm.StylingType),
|
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 {
|
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 {
|
func readModelToObjectRoot(readModel eventstore.ReadModel) models.ObjectRoot {
|
||||||
return models.ObjectRoot{
|
return models.ObjectRoot{
|
||||||
AggregateID: readModel.AggregateID,
|
AggregateID: readModel.AggregateID,
|
||||||
|
@ -36,6 +36,10 @@ func (rm *IAMIDPConfigReadModel) AppendEvents(events ...eventstore.EventReader)
|
|||||||
rm.IDPConfigReadModel.AppendEvents(&e.OIDCConfigAddedEvent)
|
rm.IDPConfigReadModel.AppendEvents(&e.OIDCConfigAddedEvent)
|
||||||
case *iam.IDPOIDCConfigChangedEvent:
|
case *iam.IDPOIDCConfigChangedEvent:
|
||||||
rm.IDPConfigReadModel.AppendEvents(&e.OIDCConfigChangedEvent)
|
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
|
ProviderType domain.IdentityProviderType
|
||||||
|
|
||||||
OIDCConfig *OIDCConfigReadModel
|
OIDCConfig *OIDCConfigReadModel
|
||||||
|
JWTConfig *JWTConfigReadModel
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIDPConfigReadModel(configID string) *IDPConfigReadModel {
|
func NewIDPConfigReadModel(configID string) *IDPConfigReadModel {
|
||||||
@ -45,6 +46,13 @@ func (rm *IDPConfigReadModel) AppendEvents(events ...eventstore.EventReader) {
|
|||||||
case *idpconfig.OIDCConfigChangedEvent:
|
case *idpconfig.OIDCConfigChangedEvent:
|
||||||
rm.ReadModel.AppendEvents(e)
|
rm.ReadModel.AppendEvents(e)
|
||||||
rm.OIDCConfig.AppendEvents(event)
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if rm.JWTConfig != nil {
|
||||||
|
if err := rm.JWTConfig.Reduce(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return rm.ReadModel.Reduce()
|
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(IDPConfigReactivatedEventType, IDPConfigReactivatedEventMapper).
|
||||||
RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper).
|
RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper).
|
||||||
RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
|
RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
|
||||||
|
RegisterFilterEventMapper(IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
|
||||||
|
RegisterFilterEventMapper(IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
|
||||||
RegisterFilterEventMapper(LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper).
|
RegisterFilterEventMapper(LoginPolicyIDPProviderAddedEventType, IdentityProviderAddedEventMapper).
|
||||||
RegisterFilterEventMapper(LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper).
|
RegisterFilterEventMapper(LoginPolicyIDPProviderRemovedEventType, IdentityProviderRemovedEventMapper).
|
||||||
RegisterFilterEventMapper(LoginPolicyIDPProviderCascadeRemovedEventType, IdentityProviderCascadeRemovedEventMapper).
|
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 (
|
const (
|
||||||
IDPOIDCConfigAddedEventType eventstore.EventType = "iam.idp." + idpconfig.OIDCConfigAddedEventType
|
IDPOIDCConfigAddedEventType eventstore.EventType = "iam.idp." + idpconfig.OIDCConfigAddedEventType
|
||||||
IDPOIDCConfigChangedEventType eventstore.EventType = "iam.idp." + idpconfig.ConfigChangedEventType
|
IDPOIDCConfigChangedEventType eventstore.EventType = "iam.idp." + idpconfig.OIDCConfigChangedEventType
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDPOIDCConfigAddedEvent struct {
|
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
|
||||||
|
}
|
@ -12,7 +12,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
OIDCConfigAddedEventType eventstore.EventType = "oidc.config.added"
|
OIDCConfigAddedEventType eventstore.EventType = "oidc.config.added"
|
||||||
ConfigChangedEventType eventstore.EventType = "oidc.config.changed"
|
OIDCConfigChangedEventType eventstore.EventType = "oidc.config.changed"
|
||||||
)
|
)
|
||||||
|
|
||||||
type OIDCConfigAddedEvent struct {
|
type OIDCConfigAddedEvent struct {
|
||||||
|
@ -75,6 +75,8 @@ func RegisterEventMappers(es *eventstore.Eventstore) {
|
|||||||
RegisterFilterEventMapper(IDPConfigReactivatedEventType, IDPConfigReactivatedEventMapper).
|
RegisterFilterEventMapper(IDPConfigReactivatedEventType, IDPConfigReactivatedEventMapper).
|
||||||
RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper).
|
RegisterFilterEventMapper(IDPOIDCConfigAddedEventType, IDPOIDCConfigAddedEventMapper).
|
||||||
RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
|
RegisterFilterEventMapper(IDPOIDCConfigChangedEventType, IDPOIDCConfigChangedEventMapper).
|
||||||
|
RegisterFilterEventMapper(IDPJWTConfigAddedEventType, IDPJWTConfigAddedEventMapper).
|
||||||
|
RegisterFilterEventMapper(IDPJWTConfigChangedEventType, IDPJWTConfigChangedEventMapper).
|
||||||
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
|
RegisterFilterEventMapper(FeaturesSetEventType, FeaturesSetEventMapper).
|
||||||
RegisterFilterEventMapper(FeaturesRemovedEventType, FeaturesRemovedEventMapper)
|
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 (
|
const (
|
||||||
IDPOIDCConfigAddedEventType eventstore.EventType = "org.idp." + idpconfig.OIDCConfigAddedEventType
|
IDPOIDCConfigAddedEventType eventstore.EventType = "org.idp." + idpconfig.OIDCConfigAddedEventType
|
||||||
IDPOIDCConfigChangedEventType eventstore.EventType = "org.idp." + idpconfig.ConfigChangedEventType
|
IDPOIDCConfigChangedEventType eventstore.EventType = "org.idp." + idpconfig.OIDCConfigChangedEventType
|
||||||
)
|
)
|
||||||
|
|
||||||
type IDPOIDCConfigAddedEvent struct {
|
type IDPOIDCConfigAddedEvent struct {
|
||||||
|
@ -1,14 +1,16 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/caos/zitadel/internal/domain"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/caos/zitadel/internal/domain"
|
||||||
|
|
||||||
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
queryAuthRequestID = "authRequestID"
|
queryAuthRequestID = "authRequestID"
|
||||||
|
queryUserAgentID = "userAgentID"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (l *Login) getAuthRequest(r *http.Request) (*domain.AuthRequest, error) {
|
func (l *Login) getAuthRequest(r *http.Request) (*domain.AuthRequest, error) {
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -79,7 +81,7 @@ func (l *Login) handleIDP(w http.ResponseWriter, r *http.Request, authReq *domai
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !idpConfig.IsOIDC {
|
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
|
return
|
||||||
}
|
}
|
||||||
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
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)
|
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) {
|
func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Request) {
|
||||||
data := new(externalIDPCallbackData)
|
data := new(externalIDPCallbackData)
|
||||||
err := l.getParseData(r, data)
|
err := l.getParseData(r, data)
|
||||||
@ -108,6 +133,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
|||||||
l.renderError(w, r, authReq, err)
|
l.renderError(w, r, authReq, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if idpConfig.IsOIDC {
|
||||||
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
provider := l.getRPConfig(w, r, authReq, idpConfig, EndpointExternalLoginCallback)
|
||||||
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
|
tokens, err := rp.CodeExchange(r.Context(), data.Code, provider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,6 +141,9 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
|||||||
return
|
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 {
|
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"
|
http_mw "github.com/caos/zitadel/internal/api/http/middleware"
|
||||||
"github.com/caos/zitadel/internal/domain"
|
"github.com/caos/zitadel/internal/domain"
|
||||||
caos_errors "github.com/caos/zitadel/internal/errors"
|
|
||||||
iam_model "github.com/caos/zitadel/internal/iam/model"
|
iam_model "github.com/caos/zitadel/internal/iam/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -73,7 +72,7 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !idpConfig.IsOIDC {
|
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
|
return
|
||||||
}
|
}
|
||||||
l.handleOIDCAuthorize(w, r, authReq, idpConfig, EndpointExternalRegisterCallback)
|
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"
|
EndpointLogin = "/login"
|
||||||
EndpointExternalLogin = "/login/externalidp"
|
EndpointExternalLogin = "/login/externalidp"
|
||||||
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
EndpointExternalLoginCallback = "/login/externalidp/callback"
|
||||||
|
EndpointJWTAuthorize = "/login/jwt/authorize"
|
||||||
|
EndpointJWTCallback = "/login/jwt/callback"
|
||||||
EndpointPasswordlessLogin = "/login/passwordless"
|
EndpointPasswordlessLogin = "/login/passwordless"
|
||||||
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
EndpointPasswordlessRegistration = "/login/passwordless/init"
|
||||||
EndpointPasswordlessPrompt = "/login/passwordless/prompt"
|
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(EndpointLogin, login.handleLogin).Methods(http.MethodGet, http.MethodPost)
|
||||||
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
router.HandleFunc(EndpointExternalLogin, login.handleExternalLogin).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointExternalLoginCallback, login.handleExternalLoginCallback).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(EndpointPasswordlessLogin, login.handlePasswordlessVerification).Methods(http.MethodPost)
|
||||||
router.HandleFunc(EndpointPasswordlessRegistration, login.handlePasswordlessRegistration).Methods(http.MethodGet)
|
router.HandleFunc(EndpointPasswordlessRegistration, login.handlePasswordlessRegistration).Methods(http.MethodGet)
|
||||||
router.HandleFunc(EndpointPasswordlessRegistration, login.handlePasswordlessRegistrationCheck).Methods(http.MethodPost)
|
router.HandleFunc(EndpointPasswordlessRegistration, login.handlePasswordlessRegistrationCheck).Methods(http.MethodPost)
|
||||||
|
@ -298,7 +298,10 @@ Errors:
|
|||||||
AuthRequest:
|
AuthRequest:
|
||||||
NotFound: AuthRequest konnte nicht gefunden werden
|
NotFound: AuthRequest konnte nicht gefunden werden
|
||||||
UserAgentNotCorresponding: User Agent stimmt nicht überein
|
UserAgentNotCorresponding: User Agent stimmt nicht überein
|
||||||
|
UserAgentNotFound: User Agent ID nicht gefunden
|
||||||
|
TokenNotFound: Token nicht gefunden
|
||||||
RequestTypeNotSupported: Requesttyp wird nicht unterstürzt
|
RequestTypeNotSupported: Requesttyp wird nicht unterstürzt
|
||||||
|
MissingParameters: Benötigte Parameter fehlen
|
||||||
User:
|
User:
|
||||||
NotFound: Benutzer konnte nicht gefunden werden
|
NotFound: Benutzer konnte nicht gefunden werden
|
||||||
Inactive: Benutzer ist inaktiv
|
Inactive: Benutzer ist inaktiv
|
||||||
|
@ -299,7 +299,10 @@ Errors:
|
|||||||
AuthRequest:
|
AuthRequest:
|
||||||
NotFound: Could not find authrequest
|
NotFound: Could not find authrequest
|
||||||
UserAgentNotCorresponding: User Agent does not correspond
|
UserAgentNotCorresponding: User Agent does not correspond
|
||||||
|
UserAgentNotFound: User Agent ID not found
|
||||||
|
TokenNotFound: Token not found
|
||||||
RequestTypeNotSupported: Request type is not supported
|
RequestTypeNotSupported: Request type is not supported
|
||||||
|
MissingParameters: Required parameters missing
|
||||||
User:
|
User:
|
||||||
NotFound: User could not be found
|
NotFound: User could not be found
|
||||||
Inactive: User is inactive
|
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
|
//Updates the specified idp
|
||||||
// all fields are updated. If no value is provided the field will be empty afterwards.
|
// all fields are updated. If no value is provided the field will be empty afterwards.
|
||||||
rpc UpdateIDP(UpdateIDPRequest) returns (UpdateIDPResponse) {
|
rpc UpdateIDP(UpdateIDPRequest) returns (UpdateIDPResponse) {
|
||||||
@ -599,6 +634,52 @@ 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) {
|
rpc GetDefaultFeatures(GetDefaultFeaturesRequest) returns (GetDefaultFeaturesResponse) {
|
||||||
option(google.api.http) = {
|
option(google.api.http) = {
|
||||||
get: "/features"
|
get: "/features"
|
||||||
@ -2436,6 +2517,64 @@ message AddOIDCIDPResponse {
|
|||||||
string idp_id = 2;
|
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 {
|
message UpdateIDPRequest {
|
||||||
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_schema) = {
|
||||||
json_schema: {
|
json_schema: {
|
||||||
@ -2590,6 +2729,62 @@ message UpdateIDPOIDCConfigResponse {
|
|||||||
zitadel.v1.ObjectDetails details = 1;
|
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 GetDefaultFeaturesRequest {}
|
||||||
|
|
||||||
message GetDefaultFeaturesResponse {
|
message GetDefaultFeaturesResponse {
|
||||||
|
@ -37,6 +37,7 @@ message IDP {
|
|||||||
];
|
];
|
||||||
oneof config {
|
oneof config {
|
||||||
OIDCConfig oidc_config = 7;
|
OIDCConfig oidc_config = 7;
|
||||||
|
JWTConfig jwt_config = 9;
|
||||||
}
|
}
|
||||||
bool auto_register = 8;
|
bool auto_register = 8;
|
||||||
}
|
}
|
||||||
@ -115,6 +116,7 @@ enum IDPType {
|
|||||||
IDP_TYPE_UNSPECIFIED = 0;
|
IDP_TYPE_UNSPECIFIED = 0;
|
||||||
IDP_TYPE_OIDC = 1;
|
IDP_TYPE_OIDC = 1;
|
||||||
//PLANNED: IDP_TYPE_SAML
|
//PLANNED: IDP_TYPE_SAML
|
||||||
|
IDP_TYPE_JWT = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// the owner of the identity provider.
|
// the owner of the identity provider.
|
||||||
@ -162,6 +164,38 @@ enum OIDCMappingField {
|
|||||||
OIDC_MAPPING_FIELD_EMAIL = 2;
|
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 {
|
message IDPIDQuery {
|
||||||
string id = 1 [
|
string id = 1 [
|
||||||
(validate.rules).string = {max_len: 200},
|
(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
|
// Deactivate identity provider configuration
|
||||||
// Users will not be able to use this provider for login (e.g Google, Microsoft, AD, etc)
|
// Users will not be able to use this provider for login (e.g Google, Microsoft, AD, etc)
|
||||||
// Returns error if already deactivated
|
// Returns error if already deactivated
|
||||||
@ -2684,6 +2697,19 @@ service ManagementService {
|
|||||||
feature: "login_policy.idp"
|
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
|
//This is an empty request
|
||||||
@ -4892,6 +4918,62 @@ message AddOrgOIDCIDPResponse {
|
|||||||
string idp_id = 2;
|
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 {
|
message DeactivateOrgIDPRequest {
|
||||||
string idp_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
string idp_id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||||
}
|
}
|
||||||
@ -4986,3 +5068,52 @@ message UpdateOrgIDPOIDCConfigRequest {
|
|||||||
message UpdateOrgIDPOIDCConfigResponse {
|
message UpdateOrgIDPOIDCConfigResponse {
|
||||||
zitadel.v1.ObjectDetails details = 1;
|
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