mirror of
https://github.com/andwati/zitadel.git
synced 2026-10-28 18:12:16 +00:00
feat: handle Zitadel provider IdP login via login v1 (#12469)
# Which Problems Are Solved This PR adds support for using a ZITADEL IdP provider in the login v1 UI flow and conditionally grants a support-user instance membership based on a ZITADEL-specific project-roles token claim. # How the Problems Are Solved - Introduced ZITADEL IdP handling in the login v1 handler, including extracting the `urn:zitadel:iam:org:project:roles` claim and granting `IAM_OWNER_VIEWER` instance membership when configured orgs match. - Added a new `AddInstanceMemberFromLogin` to assign an instance membership during the login v1 ZITADEL-IdP flow without permission checks (as the authorization is established based on the project roles in the token claim) - Added unit tests # Additional Changes N/A # Additional Context - Closes https://github.com/zitadel/zitadel/issues/12446
This commit is contained in:
@@ -283,12 +283,6 @@ func TestServer_GetIDPByID(t *testing.T) {
|
||||
ClientId: "test-client",
|
||||
Issuer: "zitadel.example.com",
|
||||
Scopes: []string{"email", "profile"},
|
||||
InstanceRolesInfo: []*idp.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Options: &idp.Options{
|
||||
|
||||
@@ -14,7 +14,6 @@ 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"
|
||||
@@ -544,38 +543,22 @@ func signatureAlgorithmToCommand(signatureAlgorithm idp_pb.SAMLSignatureAlgorith
|
||||
|
||||
func addZitadelProviderToCommand(req *mgmt_pb.AddZitadelProviderRequest) command.ZitadelProvider {
|
||||
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: instanceRolesInfoToCommand(req.InstanceRolesInfo),
|
||||
Name: req.Name,
|
||||
Issuer: req.Issuer,
|
||||
ClientID: req.ClientId,
|
||||
ClientSecret: req.ClientSecret,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateZitadelProviderToCommand(req *mgmt_pb.UpdateZitadelProviderRequest) command.ZitadelProvider {
|
||||
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: instanceRolesInfoToCommand(req.InstanceRolesInfo),
|
||||
Name: req.Name,
|
||||
Issuer: req.Issuer,
|
||||
ClientID: req.ClientId,
|
||||
ClientSecret: req.ClientSecret,
|
||||
Scopes: req.Scopes,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func instanceRolesInfoToCommand(reqInstanceRolesInfo []*idp_pb.InstanceRolesInfo) []idp.RolesInfo {
|
||||
if reqInstanceRolesInfo == nil {
|
||||
return nil
|
||||
}
|
||||
instanceRolesInfo := make([]idp.RolesInfo, 0, len(reqInstanceRolesInfo))
|
||||
for _, info := range reqInstanceRolesInfo {
|
||||
instanceRolesInfo = append(instanceRolesInfo, idp.RolesInfo{
|
||||
OrganizationID: info.OrganizationId,
|
||||
OrganizationDomain: info.OrganizationDomain,
|
||||
})
|
||||
}
|
||||
return instanceRolesInfo
|
||||
}
|
||||
|
||||
@@ -207,34 +207,6 @@ func Test_addZitadelProviderToCommand(t *testing.T) {
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields filled",
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
@@ -249,17 +221,6 @@ func Test_addZitadelProviderToCommand(t *testing.T) {
|
||||
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",
|
||||
@@ -272,16 +233,6 @@ func Test_addZitadelProviderToCommand(t *testing.T) {
|
||||
IsLinkingAllowed: false,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
InstanceRolesInfo: []idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -301,35 +252,6 @@ func Test_updateZitadelProviderToCommand(t *testing.T) {
|
||||
req *mgmt_pb.UpdateZitadelProviderRequest
|
||||
want command.ZitadelProvider
|
||||
}{
|
||||
{
|
||||
name: "without instance roles info",
|
||||
req: &mgmt_pb.UpdateZitadelProviderRequest{
|
||||
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: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all fields filled",
|
||||
req: &mgmt_pb.UpdateZitadelProviderRequest{
|
||||
@@ -344,17 +266,6 @@ func Test_updateZitadelProviderToCommand(t *testing.T) {
|
||||
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",
|
||||
@@ -367,16 +278,6 @@ func Test_updateZitadelProviderToCommand(t *testing.T) {
|
||||
IsLinkingAllowed: false,
|
||||
IsAutoUpdate: true,
|
||||
},
|
||||
InstanceRolesInfo: []idp.RolesInfo{
|
||||
{
|
||||
OrganizationID: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationID: "org2",
|
||||
OrganizationDomain: "org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func Test_AddZitadelProvider(t *testing.T) {
|
||||
),
|
||||
},
|
||||
{
|
||||
name: "missing org ID in instance roles info",
|
||||
name: "valid request",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.AddZitadelProviderRequest{
|
||||
@@ -121,87 +121,6 @@ func Test_AddZitadelProvider(t *testing.T) {
|
||||
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{
|
||||
@@ -318,16 +237,6 @@ func Test_UpdateZitadelProvider(t *testing.T) {
|
||||
Issuer: "acme.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile", "openid", "offline_access"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationId: "org2",
|
||||
OrganizationDomain: "org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResponse: &mgmt_pb.UpdateZitadelProviderResponse{
|
||||
@@ -347,16 +256,6 @@ func Test_UpdateZitadelProvider(t *testing.T) {
|
||||
Issuer: "acme.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile", "openid", "offline_access"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
{
|
||||
OrganizationId: "org2",
|
||||
OrganizationDomain: "org2.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -374,12 +273,6 @@ func Test_UpdateZitadelProvider(t *testing.T) {
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsAutoCreation: true,
|
||||
},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org3",
|
||||
OrganizationDomain: "org3.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantResponse: &mgmt_pb.UpdateZitadelProviderResponse{
|
||||
@@ -401,52 +294,6 @@ func Test_UpdateZitadelProvider(t *testing.T) {
|
||||
Issuer: "acme.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: nil, // unset scopes
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org3",
|
||||
OrganizationDomain: "org3.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with instance roles info not set, ok",
|
||||
args: args{
|
||||
ctx: OrgCTX,
|
||||
req: &mgmt_pb.UpdateZitadelProviderRequest{
|
||||
Name: "Zitadel Support IdP updated 2",
|
||||
Issuer: "acme.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "openid"},
|
||||
ProviderOptions: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
InstanceRolesInfo: nil, // instance roles info isn't set -> will be unset by the API
|
||||
},
|
||||
},
|
||||
wantResponse: &mgmt_pb.UpdateZitadelProviderResponse{
|
||||
Details: &object_pb.ObjectDetails{
|
||||
ResourceOwner: Instance.DefaultOrg.Id,
|
||||
},
|
||||
},
|
||||
wantUpdatedProvider: &idp_pb.Provider{
|
||||
State: idp_pb.IDPState_IDP_STATE_ACTIVE,
|
||||
Name: "Zitadel Support IdP updated 2",
|
||||
Owner: idp_pb.IDPOwnerType_IDP_OWNER_TYPE_ORG,
|
||||
Type: idp_pb.ProviderType_PROVIDER_TYPE_ZITADEL,
|
||||
Config: &idp_pb.ProviderConfig{
|
||||
Options: &idp_pb.Options{
|
||||
IsCreationAllowed: true,
|
||||
},
|
||||
Config: &idp_pb.ProviderConfig_Zitadel{
|
||||
Zitadel: &idp_pb.ZitadelConfig{
|
||||
Issuer: "acme.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "openid"},
|
||||
InstanceRolesInfo: nil, // instance roles info isn't set
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -567,12 +414,6 @@ func Test_GetProviderByID(t *testing.T) {
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -676,12 +517,6 @@ func Test_ListProviders(t *testing.T) {
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -731,12 +566,6 @@ func Test_ListProviders(t *testing.T) {
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -777,12 +606,6 @@ func Test_ListProviders(t *testing.T) {
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -807,12 +630,6 @@ func Test_ListProviders(t *testing.T) {
|
||||
Issuer: "zitadel.example.com",
|
||||
ClientId: "test-client",
|
||||
Scopes: []string{"email", "profile"},
|
||||
InstanceRolesInfo: []*idp_pb.InstanceRolesInfo{
|
||||
{
|
||||
OrganizationId: "org1",
|
||||
OrganizationDomain: "org1.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user