Files
zitadel/internal/api/grpc/admin/integration_test/idp_test.go
T
Gayathri VijayanandGitHub e19bb7a173 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
2026-04-15 14:22:45 +02:00

228 lines
6.7 KiB
Go

//go:build integration
package admin_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"
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"
)
func Test_AddZitadelProvider(t *testing.T) {
type args struct {
ctx context.Context
req *admin_pb.AddZitadelProviderRequest
}
tests := []struct {
name string
args args
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{
ctx: AdminCTX,
req: &admin_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: AdminCTX,
req: &admin_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: AdminCTX,
req: &admin_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: AdminCTX,
req: &admin_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: AdminCTX,
req: &admin_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: AdminCTX,
req: &admin_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: AdminCTX,
req: &admin_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: &admin_pb.AddZitadelProviderResponse{
Details: &object_pb.ObjectDetails{
ResourceOwner: Instance.Instance.Id,
},
},
},
{
name: "valid request with instance roles info",
args: args{
ctx: AdminCTX,
req: &admin_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: &admin_pb.AddZitadelProviderResponse{
Details: &object_pb.ObjectDetails{
ResourceOwner: Instance.Instance.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())
})
}
}