mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-18 05:52:51 +00:00
feat: implement AddZitadelProvider in ManagementService (#12056)
# Which Problems Are Solved This PR adds implementation to add a Zitadel IdP at the organization-level. # How the Problems Are Solved - Added handling/converters for the `AddZitadelProvider` endpoint in `ManagementService` in the server layer - Registered a new `org.idp.zitadel.added` event for org-level Zitadel providers - Added `AddOrgZitadelProvider` command to validate the request and push `org.idp.zitadel.added` event to the eventstore - Added the `org.idp.zitadel.added` event to the projection reducer - Added unit and integration tests # Additional Changes added more tests for the ZitadelProvider in AdminService # Additional Context - Closes https://github.com/zitadel/zitadel/issues/11823 - Follow-up for PRs https://github.com/zitadel/zitadel/pull/12018, https://github.com/zitadel/zitadel/pull/12020, https://github.com/zitadel/zitadel/pull/12055
This commit is contained in:
@@ -1,8 +1,6 @@
|
||||
package admin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/muhlemmer/gu"
|
||||
dsig "github.com/russellhaering/goxmldsig"
|
||||
@@ -553,10 +551,11 @@ func addZitadelProviderToCommand(req *admin_pb.AddZitadelProviderRequest) comman
|
||||
instanceRolesInfo := make([]idp.RolesInfo, 0, len(req.InstanceRolesInfo))
|
||||
for _, info := range req.InstanceRolesInfo {
|
||||
instanceRolesInfo = append(instanceRolesInfo, idp.RolesInfo{
|
||||
OrganizationID: strings.TrimSpace(info.OrganizationId),
|
||||
OrganizationDomain: strings.TrimSpace(info.OrganizationDomain),
|
||||
OrganizationID: info.OrganizationId,
|
||||
OrganizationDomain: info.OrganizationDomain,
|
||||
})
|
||||
}
|
||||
|
||||
return command.ZitadelProvider{
|
||||
Name: req.Name,
|
||||
Issuer: req.Issuer,
|
||||
|
||||
@@ -207,6 +207,35 @@ func Test_addZitadelProviderToCommand(t *testing.T) {
|
||||
req *admin_pb.AddZitadelProviderRequest
|
||||
want command.ZitadelProvider
|
||||
}{
|
||||
{
|
||||
name: "without instance roles info",
|
||||
req: &admin_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile", "urn:zitadel:iam:org:project:roles"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: 0,
|
||||
},
|
||||
},
|
||||
want: command.ZitadelProvider{
|
||||
Name: "Zitadel Support IdP",
|
||||
ClientID: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile", "urn:zitadel:iam:org:project:roles"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: false,
|
||||
IsLinkingAllowed: false,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
InstanceRolesInfo: []idp.RolesInfo{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields filled",
|
||||
req: &admin_pb.AddZitadelProviderRequest{
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
|
||||
@@ -28,6 +29,34 @@ func Test_AddZitadelProvider(t *testing.T) {
|
||||
wantErr error
|
||||
wantResponse *admin_pb.AddZitadelProviderResponse
|
||||
}{
|
||||
{
|
||||
name: "no permissions, error",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
|
||||
req: &admin_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(codes.NotFound, "membership not found (AUTHZ-cdgFk)"),
|
||||
},
|
||||
{
|
||||
name: "insufficient permissions, error",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeLogin),
|
||||
req: &admin_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(codes.PermissionDenied, "No matching permissions found (AUTH-5mWD2)"),
|
||||
},
|
||||
{
|
||||
name: "missing required field: name",
|
||||
args: args{
|
||||
|
||||
@@ -458,3 +458,14 @@ func (s *Server) DeleteProvider(ctx context.Context, req *mgmt_pb.DeleteProvider
|
||||
Details: object_pb.DomainToChangeDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Server) AddZitadelProvider(ctx context.Context, req *mgmt_pb.AddZitadelProviderRequest) (*mgmt_pb.AddZitadelProviderResponse, error) {
|
||||
id, details, err := s.command.AddOrgZitadelProvider(ctx, authz.GetCtxData(ctx).OrgID, addZitadelProviderToCommand(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &mgmt_pb.AddZitadelProviderResponse{
|
||||
Id: id,
|
||||
Details: object_pb.DomainToAddDetailsPb(details),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/v1/models"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
@@ -538,3 +539,23 @@ func signatureAlgorithmToCommand(signatureAlgorithm idp_pb.SAMLSignatureAlgorith
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func addZitadelProviderToCommand(req *mgmt_pb.AddZitadelProviderRequest) command.ZitadelProvider {
|
||||
instanceRolesInfo := make([]idp.RolesInfo, 0, len(req.InstanceRolesInfo))
|
||||
for _, info := range req.InstanceRolesInfo {
|
||||
instanceRolesInfo = append(instanceRolesInfo, idp.RolesInfo{
|
||||
OrganizationID: info.OrganizationId,
|
||||
OrganizationDomain: info.OrganizationDomain,
|
||||
})
|
||||
}
|
||||
|
||||
return command.ZitadelProvider{
|
||||
Name: req.Name,
|
||||
Issuer: req.Issuer,
|
||||
ClientID: req.ClientId,
|
||||
ClientSecret: req.ClientSecret,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
InstanceRolesInfo: instanceRolesInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,13 @@ package management
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/test"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
)
|
||||
|
||||
@@ -23,13 +26,13 @@ func Test_addOIDCIDPRequestToDomain(t *testing.T) {
|
||||
args: args{
|
||||
req: &mgmt_pb.AddOrgOIDCIDPRequest{
|
||||
Name: "ZITADEL",
|
||||
StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
StylingType: idp_pb.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
ClientId: "test1234",
|
||||
ClientSecret: "test4321",
|
||||
Issuer: "zitadel.ch",
|
||||
Scopes: []string{"email", "profile"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
|
||||
DisplayNameMapping: idp_pb.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp_pb.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
|
||||
AutoRegister: true,
|
||||
},
|
||||
},
|
||||
@@ -70,8 +73,8 @@ func Test_addOIDCIDPRequestToDomainOIDCIDPConfig(t *testing.T) {
|
||||
ClientSecret: "test4321",
|
||||
Issuer: "zitadel.ch",
|
||||
Scopes: []string{"email", "profile"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
|
||||
DisplayNameMapping: idp_pb.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp_pb.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -104,7 +107,7 @@ func Test_updateIDPToDomain(t *testing.T) {
|
||||
req: &mgmt_pb.UpdateOrgIDPRequest{
|
||||
IdpId: "13523",
|
||||
Name: "new name",
|
||||
StylingType: idp.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
StylingType: idp_pb.IDPStylingType_STYLING_TYPE_GOOGLE,
|
||||
AutoRegister: true,
|
||||
},
|
||||
},
|
||||
@@ -141,8 +144,8 @@ func Test_updateOIDCConfigToDomain(t *testing.T) {
|
||||
ClientId: "ZITEADEL",
|
||||
ClientSecret: "i'm so secret",
|
||||
Scopes: []string{"profile"},
|
||||
DisplayNameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
|
||||
DisplayNameMapping: idp_pb.OIDCMappingField_OIDC_MAPPING_FIELD_EMAIL,
|
||||
UsernameMapping: idp_pb.OIDCMappingField_OIDC_MAPPING_FIELD_PREFERRED_USERNAME,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -164,7 +167,7 @@ func Test_signatureAlgorithmToCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
signatureAlgorithm idp.SAMLSignatureAlgorithm
|
||||
signatureAlgorithm idp_pb.SAMLSignatureAlgorithm
|
||||
wantSignatureAlgorithm string
|
||||
}{
|
||||
{
|
||||
@@ -174,17 +177,17 @@ func Test_signatureAlgorithmToCommand(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "RSA_SHA1",
|
||||
signatureAlgorithm: idp.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA1,
|
||||
signatureAlgorithm: idp_pb.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA1,
|
||||
wantSignatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1",
|
||||
},
|
||||
{
|
||||
name: "RSA_SHA256",
|
||||
signatureAlgorithm: idp.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA256,
|
||||
signatureAlgorithm: idp_pb.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA256,
|
||||
wantSignatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
|
||||
},
|
||||
{
|
||||
name: "RSA_SHA512",
|
||||
signatureAlgorithm: idp.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA512,
|
||||
signatureAlgorithm: idp_pb.SAMLSignatureAlgorithm_SAML_SIGNATURE_RSA_SHA512,
|
||||
wantSignatureAlgorithm: "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512",
|
||||
},
|
||||
}
|
||||
@@ -196,3 +199,98 @@ func Test_signatureAlgorithmToCommand(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_addZitadelProviderToCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
tests := []struct {
|
||||
name string
|
||||
req *mgmt_pb.AddZitadelProviderRequest
|
||||
want command.ZitadelProvider
|
||||
}{
|
||||
{
|
||||
name: "without instance roles info",
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile", "urn:zitadel:iam:org:project:roles"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: 0,
|
||||
},
|
||||
},
|
||||
want: command.ZitadelProvider{
|
||||
Name: "Zitadel Support IdP",
|
||||
ClientID: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile", "urn:zitadel:iam:org:project:roles"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: false,
|
||||
IsLinkingAllowed: false,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
InstanceRolesInfo: []idp.RolesInfo{},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields filled",
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile", "urn:zitadel:iam:org:project:roles"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsLinkingAllowed: false,
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: false,
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: 0,
|
||||
},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
|
||||
OrganizationId: "org2",
|
||||
OrganizationDomain: "org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
want: command.ZitadelProvider{
|
||||
Name: "Zitadel Support IdP",
|
||||
ClientID: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile", "urn:zitadel:iam:org:project:roles"},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsAutoCreation: false,
|
||||
IsLinkingAllowed: false,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
InstanceRolesInfo: []idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
got := addZitadelProviderToCommand(tt.req)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,233 @@
|
||||
//go:build integration
|
||||
|
||||
package management_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
idp_pb "github.com/zitadel/zitadel/pkg/grpc/idp"
|
||||
mgmt_pb "github.com/zitadel/zitadel/pkg/grpc/management"
|
||||
object_pb "github.com/zitadel/zitadel/pkg/grpc/object"
|
||||
)
|
||||
|
||||
func Test_AddZitadelProvider(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
req *mgmt_pb.AddZitadelProviderRequest
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr error
|
||||
wantResponse *mgmt_pb.AddZitadelProviderResponse
|
||||
}{
|
||||
{
|
||||
name: "no permissions, error",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeNoPermission),
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(codes.NotFound, "membership not found (AUTHZ-cdgFk)"),
|
||||
},
|
||||
{
|
||||
name: "insufficient permissions, error",
|
||||
args: args{
|
||||
ctx: Instance.WithAuthorizationToken(CTX, integration.UserTypeLogin),
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(codes.PermissionDenied, "No matching permissions found (AUTH-5mWD2)"),
|
||||
},
|
||||
{
|
||||
name: "missing required field: name",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{},
|
||||
},
|
||||
wantErr: status.Error(codes.InvalidArgument, "invalid AddZitadelProviderRequest.Name: value length must be between 1 and 200 runes, inclusive"),
|
||||
},
|
||||
{
|
||||
name: "missing required field: issuer",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(codes.InvalidArgument, "invalid AddZitadelProviderRequest.Issuer: value length must be between 1 and 200 runes, inclusive"),
|
||||
},
|
||||
{
|
||||
name: "missing required field: client_id",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(codes.InvalidArgument, "invalid AddZitadelProviderRequest.ClientId: value length must be between 1 and 200 runes, inclusive"),
|
||||
},
|
||||
{
|
||||
name: "missing required field: client_secret",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(
|
||||
codes.InvalidArgument,
|
||||
"invalid AddZitadelProviderRequest.ClientSecret: value length must be between 1 and 1000 runes, inclusive",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "missing org ID in instance roles info",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(
|
||||
codes.InvalidArgument,
|
||||
"invalid AddZitadelProviderRequest.InstanceRolesInfo[0]: embedded message failed validation | caused by: invalid InstanceRolesInfo.OrganizationId: value length must be between 1 and 200 runes, inclusive",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "missing org domain in instance roles info",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationId: "org2",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: status.Error(
|
||||
codes.InvalidArgument,
|
||||
"invalid AddZitadelProviderRequest.InstanceRolesInfo[1]: embedded message failed validation | caused by: invalid InstanceRolesInfo.OrganizationDomain: value length must be between 1 and 200 runes, inclusive",
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "valid request without instance roles info",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResponse: &mgmt_pb.AddZitadelProviderResponse{
|
||||
Details: &object_pb.ObjectDetails{
|
||||
ResourceOwner: Instance.DefaultOrg.Id,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid request with instance roles info",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP",
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
ClientSecret: "test-secret",
|
||||
Scopes: []string{"email", "profile"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResponse: &mgmt_pb.AddZitadelProviderResponse{
|
||||
Details: &object_pb.ObjectDetails{
|
||||
ResourceOwner: Instance.DefaultOrg.Id,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
before := time.Now()
|
||||
got, err := Client.AddZitadelProvider(tt.args.ctx, tt.args.req)
|
||||
after := time.Now()
|
||||
if tt.wantErr != nil {
|
||||
require.Error(t, err)
|
||||
grpcStatus, ok := status.FromError(err)
|
||||
require.True(t, ok)
|
||||
assert.Equal(t, status.Code(tt.wantErr), grpcStatus.Code())
|
||||
assert.Equal(t, status.Convert(tt.wantErr).Message(), grpcStatus.Message())
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
assert.NotNil(t, got)
|
||||
assert.NotEmpty(t, got.GetId())
|
||||
assert.WithinRange(t, got.GetDetails().GetCreationDate().AsTime(), before, after)
|
||||
assert.Equal(t, tt.wantResponse.GetDetails().GetResourceOwner(), got.GetDetails().GetResourceOwner())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1946,7 +1946,7 @@ func (c *Commands) AddInstanceZitadelProvider(ctx context.Context, provider Zita
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
err = c.validateZitadelProvider(ctx, &provider)
|
||||
err = c.validateInstanceZitadelProvider(&provider)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
@@ -1996,7 +1996,7 @@ func (c *Commands) prepareAddInstanceZitadelProvider(a *instance.Aggregate, writ
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) validateZitadelProvider(ctx context.Context, provider *ZitadelProvider) error {
|
||||
func (c *Commands) validateInstanceZitadelProvider(provider *ZitadelProvider) error {
|
||||
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "INST-Sgtj5", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
@@ -6094,6 +6094,46 @@ func TestCommandSide_AddInstanceZitadelIDP(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok, without instance roles info",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
instance.NewZitadelIDPAddedEvent(context.Background(), &instance.NewAggregate("instance1").Aggregate,
|
||||
"id1",
|
||||
"name",
|
||||
"issuer",
|
||||
"clientID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("clientSecret"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
nil,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: authz.WithInstanceID(context.Background(), "instance1"),
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
Issuer: "issuer",
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "instance1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
|
||||
@@ -1910,3 +1910,87 @@ func (c *Commands) prepareDeleteOrgProvider(a *org.Aggregate, resourceOwner, id
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) AddOrgZitadelProvider(ctx context.Context, resourceOwner string, provider ZitadelProvider) (string, *domain.ObjectDetails, error) {
|
||||
id, err := c.idGenerator.Next()
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
err = c.validateOrgZitadelProvider(&provider)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
orgAgg := org.NewAggregate(resourceOwner)
|
||||
writeModel := NewZitadelOrgIDPWriteModel(resourceOwner, id)
|
||||
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, c.prepareAddOrgZitadelProvider(orgAgg, writeModel, provider)) //nolint:staticcheck
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
pushedEvents, err := c.eventstore.Push(ctx, cmds...)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
return id, pushedEventsToObjectDetails(pushedEvents), nil
|
||||
}
|
||||
|
||||
func (c *Commands) prepareAddOrgZitadelProvider(a *org.Aggregate, writeModel *OrgZitadelIDPWriteModel, provider ZitadelProvider) preparation.Validation {
|
||||
return func() (preparation.CreateCommands, error) {
|
||||
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
|
||||
events, err := filter(ctx, writeModel.Query())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writeModel.AppendEvents(events...)
|
||||
if err = writeModel.Reduce(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
secret, err := crypto.Encrypt([]byte(provider.ClientSecret), c.idpConfigEncryption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []eventstore.Command{
|
||||
org.NewZitadelIDPAddedEvent(
|
||||
ctx,
|
||||
&a.Aggregate,
|
||||
writeModel.ID,
|
||||
provider.Name,
|
||||
provider.Issuer,
|
||||
provider.ClientID,
|
||||
secret,
|
||||
provider.Scopes,
|
||||
provider.IDPOptions,
|
||||
provider.InstanceRolesInfo,
|
||||
),
|
||||
}, nil
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Commands) validateOrgZitadelProvider(provider *ZitadelProvider) error {
|
||||
if provider.Name = strings.TrimSpace(provider.Name); provider.Name == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-c3EC3W", "Errors.Invalid.Argument")
|
||||
}
|
||||
if provider.Issuer = strings.TrimSpace(provider.Issuer); provider.Issuer == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-TYAbiK", "Errors.Invalid.Argument")
|
||||
}
|
||||
if provider.ClientID = strings.TrimSpace(provider.ClientID); provider.ClientID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-feyb3A", "Errors.Invalid.Argument")
|
||||
}
|
||||
if provider.ClientSecret = strings.TrimSpace(provider.ClientSecret); provider.ClientSecret == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-DxaSb9", "Errors.Invalid.Argument")
|
||||
}
|
||||
|
||||
for i := range provider.InstanceRolesInfo {
|
||||
provider.InstanceRolesInfo[i].OrganizationID = strings.TrimSpace(provider.InstanceRolesInfo[i].OrganizationID)
|
||||
if provider.InstanceRolesInfo[i].OrganizationID == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-M0QmjB", "Errors.Invalid.Argument")
|
||||
}
|
||||
provider.InstanceRolesInfo[i].OrganizationDomain = strings.TrimSpace(provider.InstanceRolesInfo[i].OrganizationDomain)
|
||||
if provider.InstanceRolesInfo[i].OrganizationDomain == "" {
|
||||
return zerrors.ThrowInvalidArgument(nil, "ORG-HAELEz", "Errors.Invalid.Argument")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1046,3 +1046,43 @@ func (wm *OrgIDPRemoveWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
EventData(map[string]interface{}{"idpConfigId": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
type OrgZitadelIDPWriteModel struct {
|
||||
ZitadelIDPWriteModel
|
||||
}
|
||||
|
||||
func NewZitadelOrgIDPWriteModel(orgID, id string) *OrgZitadelIDPWriteModel {
|
||||
return &OrgZitadelIDPWriteModel{
|
||||
ZitadelIDPWriteModel{
|
||||
WriteModel: eventstore.WriteModel{
|
||||
AggregateID: orgID,
|
||||
ResourceOwner: orgID,
|
||||
},
|
||||
ID: id,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgZitadelIDPWriteModel) AppendEvents(events ...eventstore.Event) {
|
||||
for _, event := range events {
|
||||
switch e := event.(type) {
|
||||
case *org.ZitadelIDPAddedEvent:
|
||||
wm.ZitadelIDPWriteModel.AppendEvents(&e.ZitadelIDPAddedEvent)
|
||||
default:
|
||||
wm.ZitadelIDPWriteModel.AppendEvents(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wm *OrgZitadelIDPWriteModel) Query() *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
|
||||
ResourceOwner(wm.ResourceOwner).
|
||||
AddQuery().
|
||||
AggregateTypes(org.AggregateType).
|
||||
AggregateIDs(wm.AggregateID).
|
||||
EventTypes(
|
||||
org.ZitadelIDPAddedEventType,
|
||||
).
|
||||
EventData(map[string]interface{}{"id": wm.ID}).
|
||||
Builder()
|
||||
}
|
||||
|
||||
@@ -6026,3 +6026,298 @@ func TestCommandSide_RegenerateOrgSAMLProviderCertificate(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandSide_AddOrgZitadelIDP(t *testing.T) {
|
||||
type fields struct {
|
||||
eventstore func(*testing.T) *eventstore.Eventstore
|
||||
idGenerator id.Generator
|
||||
secretCrypto crypto.EncryptionAlgorithm
|
||||
}
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
resourceOwner string
|
||||
provider ZitadelProvider
|
||||
}
|
||||
type res struct {
|
||||
id string
|
||||
want *domain.ObjectDetails
|
||||
err func(error) bool
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
res res
|
||||
}{
|
||||
{
|
||||
"invalid name",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "ORG-c3EC3W", "Errors.Invalid.Argument"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid issuer",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "ORG-TYAbiK", "Errors.Invalid.Argument"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientID",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
Issuer: "issuer",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "ORG-feyb3A", "Errors.Invalid.Argument"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
"invalid clientSecret",
|
||||
fields{
|
||||
eventstore: expectEventstore(),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
},
|
||||
args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
Issuer: "issuer",
|
||||
ClientID: "clientID",
|
||||
},
|
||||
},
|
||||
res{
|
||||
err: func(err error) bool {
|
||||
return errors.Is(err, zerrors.ThrowInvalidArgument(nil, "ORG-DxaSb9", "Errors.Invalid.Argument"))
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok, without instance roles info",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
org.NewZitadelIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"name",
|
||||
"issuer",
|
||||
"clientID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("clientSecret"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
nil,
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
Issuer: "issuer",
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
org.NewZitadelIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"name",
|
||||
"issuer",
|
||||
"clientID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("clientSecret"),
|
||||
},
|
||||
nil,
|
||||
idp.Options{},
|
||||
[]idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "example-org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "example-org2.com",
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
Issuer: "issuer",
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
InstanceRolesInfo: []idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "example-org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "example-org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ok all set",
|
||||
fields: fields{
|
||||
eventstore: expectEventstore(
|
||||
expectFilter(),
|
||||
expectPush(
|
||||
org.NewZitadelIDPAddedEvent(context.Background(), &org.NewAggregate("org1").Aggregate,
|
||||
"id1",
|
||||
"name",
|
||||
"issuer",
|
||||
"clientID",
|
||||
&crypto.CryptoValue{
|
||||
CryptoType: crypto.TypeEncryption,
|
||||
Algorithm: "enc",
|
||||
KeyID: "id",
|
||||
Crypted: []byte("clientSecret"),
|
||||
},
|
||||
[]string{openid.ScopeOpenID},
|
||||
idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
[]idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "example-org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "example-org2.com",
|
||||
},
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
idGenerator: id_mock.NewIDGeneratorExpectIDs(t, "id1"),
|
||||
secretCrypto: crypto.CreateMockEncryptionAlg(gomock.NewController(t)),
|
||||
},
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
resourceOwner: "org1",
|
||||
provider: ZitadelProvider{
|
||||
Name: "name",
|
||||
Issuer: "issuer",
|
||||
ClientID: "clientID",
|
||||
ClientSecret: "clientSecret",
|
||||
Scopes: []string{openid.ScopeOpenID},
|
||||
IDPOptions: idp.Options{
|
||||
IsCreationAllowed: true,
|
||||
IsLinkingAllowed: true,
|
||||
IsAutoCreation: true,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
InstanceRolesInfo: []idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "example-org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "example-org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
res: res{
|
||||
id: "id1",
|
||||
want: &domain.ObjectDetails{ResourceOwner: "org1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &Commands{
|
||||
eventstore: tt.fields.eventstore(t),
|
||||
idGenerator: tt.fields.idGenerator,
|
||||
idpConfigEncryption: tt.fields.secretCrypto,
|
||||
}
|
||||
id, got, err := c.AddOrgZitadelProvider(tt.args.ctx, tt.args.resourceOwner, tt.args.provider)
|
||||
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.id, id)
|
||||
assertObjectDetails(t, tt.res.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,6 +694,10 @@ func (p *idpTemplateProjection) Reducers() []handler.AggregateReducer {
|
||||
Event: org.SAMLIDPChangedEventType,
|
||||
Reduce: p.reduceSAMLIDPChanged,
|
||||
},
|
||||
{
|
||||
Event: org.ZitadelIDPAddedEventType,
|
||||
Reduce: p.reduceZitadelIDPAdded,
|
||||
},
|
||||
{
|
||||
Event: org.IDPConfigRemovedEventType,
|
||||
Reduce: p.reduceIDPConfigRemoved,
|
||||
@@ -2574,9 +2578,11 @@ func (p *idpTemplateProjection) reduceZitadelIDPAdded(event eventstore.Event) (*
|
||||
case *instance.ZitadelIDPAddedEvent:
|
||||
idpEvent = e.ZitadelIDPAddedEvent
|
||||
idpOwnerType = domain.IdentityProviderTypeSystem
|
||||
// todo (@grvijayan): org-level ZitadelIDPAddedEvent in the next PRs
|
||||
case *org.ZitadelIDPAddedEvent:
|
||||
idpEvent = e.ZitadelIDPAddedEvent
|
||||
idpOwnerType = domain.IdentityProviderTypeOrg
|
||||
default:
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ZCpH41", "reduce.wrong.event.type %v", []eventstore.EventType{instance.ZitadelIDPAddedEventType})
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-ZCpH41", "reduce.wrong.event.type %v", []eventstore.EventType{instance.ZitadelIDPAddedEventType, org.ZitadelIDPAddedEventType})
|
||||
}
|
||||
|
||||
zitadelIDPCols := []handler.Column{
|
||||
|
||||
@@ -4659,6 +4659,149 @@ func TestIDPTemplateProjection_reducesZitadel(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceZitadelIDPAdded without instance roles info",
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
org.ZitadelIDPAddedEventType,
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"name": "idp-name",
|
||||
"issuer": "issuer",
|
||||
"clientId": "client_id",
|
||||
"clientSecret": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["profile"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true,
|
||||
"autoLinkingOption": 1
|
||||
}`),
|
||||
), eventstore.GenericEventMapper[org.ZitadelIDPAddedEvent]),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceZitadelIDPAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateInsertStmt,
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
domain.IDPStateActive,
|
||||
"idp-name",
|
||||
domain.IdentityProviderTypeOrg,
|
||||
domain.IDPTypeZitadel,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.AutoLinkingOptionUsername,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates6_zitadel (idp_id, instance_id, issuer, client_id, client_secret, scopes) VALUES ($1, $2, $3, $4, $5, $6)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
"issuer",
|
||||
"client_id",
|
||||
anyArg{},
|
||||
database.TextArray[string]{"profile"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org reduceZitadelIDPAdded with instance roles info",
|
||||
args: args{
|
||||
event: getEvent(
|
||||
testEvent(
|
||||
org.ZitadelIDPAddedEventType,
|
||||
org.AggregateType,
|
||||
[]byte(`{
|
||||
"id": "idp-id",
|
||||
"name": "idp-name",
|
||||
"issuer": "issuer",
|
||||
"clientId": "client_id",
|
||||
"clientSecret": {
|
||||
"cryptoType": 0,
|
||||
"algorithm": "RSA-265",
|
||||
"keyId": "key-id"
|
||||
},
|
||||
"scopes": ["profile"],
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
"isAutoCreation": true,
|
||||
"isAutoUpdate": true,
|
||||
"autoLinkingOption": 1,
|
||||
"instanceRolesInfo": [{
|
||||
"organizationId": "org1",
|
||||
"organizationDomain": "org1.com"
|
||||
},
|
||||
{
|
||||
"organizationId": "org2",
|
||||
"organizationDomain": "org2.com"
|
||||
}]
|
||||
}`),
|
||||
), eventstore.GenericEventMapper[org.ZitadelIDPAddedEvent]),
|
||||
},
|
||||
reduce: (&idpTemplateProjection{}).reduceZitadelIDPAdded,
|
||||
want: wantReduce{
|
||||
aggregateType: eventstore.AggregateType("org"),
|
||||
sequence: 15,
|
||||
executer: &testExecuter{
|
||||
executions: []execution{
|
||||
{
|
||||
expectedStmt: idpTemplateInsertStmt,
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
anyArg{},
|
||||
anyArg{},
|
||||
uint64(15),
|
||||
"ro-id",
|
||||
"instance-id",
|
||||
domain.IDPStateActive,
|
||||
"idp-name",
|
||||
domain.IdentityProviderTypeOrg,
|
||||
domain.IDPTypeZitadel,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
domain.AutoLinkingOptionUsername,
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates6_zitadel (idp_id, instance_id, issuer, client_id, client_secret, scopes, instance_roles_info) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
"issuer",
|
||||
"client_id",
|
||||
anyArg{},
|
||||
database.TextArray[string]{"profile"},
|
||||
[]byte(`[{"organizationId":"org1","organizationDomain":"org1.com"},{"organizationId":"org2","organizationDomain":"org2.com"}]`),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
@@ -103,6 +103,7 @@ func init() {
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, AppleIDPChangedEventType, AppleIDPChangedEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SAMLIDPAddedEventType, SAMLIDPAddedEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, SAMLIDPChangedEventType, SAMLIDPChangedEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, ZitadelIDPAddedEventType, eventstore.GenericEventMapper[ZitadelIDPAddedEvent])
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, IDPRemovedEventType, IDPRemovedEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, TriggerActionsSetEventType, TriggerActionsSetEventMapper)
|
||||
eventstore.RegisterFilterEventMapper(AggregateType, TriggerActionsCascadeRemovedEventType, TriggerActionsCascadeRemovedEventMapper)
|
||||
|
||||
@@ -38,6 +38,7 @@ const (
|
||||
SAMLIDPAddedEventType eventstore.EventType = "org.idp.saml.added"
|
||||
SAMLIDPChangedEventType eventstore.EventType = "org.idp.saml.changed"
|
||||
IDPRemovedEventType eventstore.EventType = "org.idp.removed"
|
||||
ZitadelIDPAddedEventType eventstore.EventType = "org.idp.zitadel.added"
|
||||
)
|
||||
|
||||
type OAuthIDPAddedEvent struct {
|
||||
@@ -1129,3 +1130,42 @@ func IDPRemovedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
|
||||
return &IDPRemovedEvent{RemovedEvent: *e.(*idp.RemovedEvent)}, nil
|
||||
}
|
||||
|
||||
type ZitadelIDPAddedEvent struct {
|
||||
idp.ZitadelIDPAddedEvent
|
||||
}
|
||||
|
||||
func NewZitadelIDPAddedEvent(
|
||||
ctx context.Context,
|
||||
aggregate *eventstore.Aggregate,
|
||||
id,
|
||||
name,
|
||||
issuer,
|
||||
clientID string,
|
||||
clientSecret *crypto.CryptoValue,
|
||||
scopes []string,
|
||||
options idp.Options,
|
||||
instanceRolesInfo []idp.RolesInfo,
|
||||
) *ZitadelIDPAddedEvent {
|
||||
return &ZitadelIDPAddedEvent{
|
||||
ZitadelIDPAddedEvent: *idp.NewZitadelIDPAddedEvent(
|
||||
eventstore.NewBaseEventForPush(
|
||||
ctx,
|
||||
aggregate,
|
||||
ZitadelIDPAddedEventType,
|
||||
),
|
||||
id,
|
||||
name,
|
||||
issuer,
|
||||
clientID,
|
||||
clientSecret,
|
||||
scopes,
|
||||
options,
|
||||
instanceRolesInfo,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (e *ZitadelIDPAddedEvent) SetBaseEvent(event *eventstore.BaseEvent) {
|
||||
e.BaseEvent = *event
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user