mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-04 23:45:07 +00:00
feat(saml): allow setting nameid-format and alternative mapping for transient format (#7979)
# Which Problems Are Solved ZITADEL currently always uses `urn:oasis:names:tc:SAML:2.0:nameid-format:persistent` in SAML requests, relying on the IdP to respect that flag and always return a peristent nameid in order to be able to map the external user with an existing user (idp link) in ZITADEL. In case the IdP however returns a `urn:oasis:names:tc:SAML:2.0:nameid-format:transient` (transient) nameid, the attribute will differ between each request and it will not be possible to match existing users. # How the Problems Are Solved This PR adds the following two options on SAML IdP: - **nameIDFormat**: allows to set the nameid-format used in the SAML Request - **transientMappingAttributeName**: allows to set an attribute name, which will be used instead of the nameid itself in case the returned nameid-format is transient # Additional Changes To reduce impact on current installations, the `idp_templates6_saml` table is altered with the two added columns by a setup job. New installations will automatically get the table with the two columns directly. All idp unit tests are updated to use `expectEventstore` instead of the deprecated `eventstoreExpect`. # Additional Context Closes #7483 Closes #7743 --------- Co-authored-by: peintnermax <max@caos.ch> Co-authored-by: Stefan Benz <46600784+stebenz@users.noreply.github.com>
This commit is contained in:
parent
12be21a3ff
commit
e57a9b57c8
27
cmd/setup/27.go
Normal file
27
cmd/setup/27.go
Normal file
@ -0,0 +1,27 @@
|
||||
package setup
|
||||
|
||||
import (
|
||||
"context"
|
||||
_ "embed"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
)
|
||||
|
||||
var (
|
||||
//go:embed 27.sql
|
||||
addSAMLNameIDFormat string
|
||||
)
|
||||
|
||||
type IDPTemplate6SAMLNameIDFormat struct {
|
||||
dbClient *database.DB
|
||||
}
|
||||
|
||||
func (mig *IDPTemplate6SAMLNameIDFormat) Execute(ctx context.Context, _ eventstore.Event) error {
|
||||
_, err := mig.dbClient.ExecContext(ctx, addSAMLNameIDFormat)
|
||||
return err
|
||||
}
|
||||
|
||||
func (mig *IDPTemplate6SAMLNameIDFormat) String() string {
|
||||
return "26_idp_templates6_add_saml_name_id_format"
|
||||
}
|
2
cmd/setup/27.sql
Normal file
2
cmd/setup/27.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE IF EXISTS projections.idp_templates6_saml ADD COLUMN IF NOT EXISTS name_id_format SMALLINT;
|
||||
ALTER TABLE IF EXISTS projections.idp_templates6_saml ADD COLUMN IF NOT EXISTS transient_mapping_attribute_name TEXT;
|
@ -109,6 +109,7 @@ type Steps struct {
|
||||
s24AddActorToAuthTokens *AddActorToAuthTokens
|
||||
s25User11AddLowerFieldsToVerifiedEmail *User11AddLowerFieldsToVerifiedEmail
|
||||
s26AuthUsers3 *AuthUsers3
|
||||
s27IDPTemplate6SAMLNameIDFormat *IDPTemplate6SAMLNameIDFormat
|
||||
}
|
||||
|
||||
func MustNewSteps(v *viper.Viper) *Steps {
|
||||
|
@ -139,6 +139,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
steps.s24AddActorToAuthTokens = &AddActorToAuthTokens{dbClient: queryDBClient}
|
||||
steps.s25User11AddLowerFieldsToVerifiedEmail = &User11AddLowerFieldsToVerifiedEmail{dbClient: esPusherDBClient}
|
||||
steps.s26AuthUsers3 = &AuthUsers3{dbClient: esPusherDBClient}
|
||||
steps.s27IDPTemplate6SAMLNameIDFormat = &IDPTemplate6SAMLNameIDFormat{dbClient: esPusherDBClient}
|
||||
|
||||
err = projection.Create(ctx, projectionDBClient, eventstoreClient, config.Projections, nil, nil, nil)
|
||||
logging.OnError(err).Fatal("unable to start projections")
|
||||
@ -190,6 +191,7 @@ func Setup(config *Config, steps *Steps, masterKey string) {
|
||||
steps.s18AddLowerFieldsToLoginNames,
|
||||
steps.s21AddBlockFieldToLimits,
|
||||
steps.s25User11AddLowerFieldsToVerifiedEmail,
|
||||
steps.s27IDPTemplate6SAMLNameIDFormat,
|
||||
} {
|
||||
mustExecuteMigration(ctx, eventstoreClient, step, "migration failed")
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
.option-form {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
.checkbox-desc {
|
||||
|
@ -70,6 +70,28 @@
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="showOptional">
|
||||
<div class="transient-info">
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.SAML.NAMEIDFORMAT' | translate }}</cnsl-label>
|
||||
<mat-select formControlName="nameIdFormat" [compareWith]="compareNameIDFormat">
|
||||
<mat-option *ngFor="let nameIdFormat of nameIDFormatValues" [value]="nameIdFormat">{{
|
||||
nameIdFormat
|
||||
}}</mat-option>
|
||||
</mat-select>
|
||||
</cnsl-form-field>
|
||||
|
||||
<cnsl-info-section>
|
||||
<div>
|
||||
<p class="transient-info-desc">{{ 'IDP.SAML.TRANSIENTMAPPINGATTRIBUTENAME_DESC' | translate }}</p>
|
||||
</div>
|
||||
|
||||
<cnsl-form-field class="formfield">
|
||||
<cnsl-label>{{ 'IDP.SAML.TRANSIENTMAPPINGATTRIBUTENAME' | translate }}</cnsl-label>
|
||||
<input cnslInput formControlName="transientMappingAttributeName" />
|
||||
</cnsl-form-field>
|
||||
</cnsl-info-section>
|
||||
</div>
|
||||
|
||||
<cnsl-provider-options
|
||||
[initialOptions]="provider?.config?.options"
|
||||
(optionsChanged)="options = $event"
|
||||
|
@ -1,3 +1,12 @@
|
||||
.metadata-xml {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.transient-info {
|
||||
max-width: 500px;
|
||||
|
||||
.transient-info-desc {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,12 @@
|
||||
import { Component, Injector, Type } from '@angular/core';
|
||||
import { Location } from '@angular/common';
|
||||
import { AutoLinkingOption, Options, Provider, SAMLBinding } from '../../../proto/generated/zitadel/idp_pb';
|
||||
import {
|
||||
AutoLinkingOption,
|
||||
Options,
|
||||
Provider,
|
||||
SAMLBinding,
|
||||
SAMLNameIDFormat,
|
||||
} from '../../../proto/generated/zitadel/idp_pb';
|
||||
import { AbstractControl, FormGroup, UntypedFormControl, UntypedFormGroup } from '@angular/forms';
|
||||
import { PolicyComponentServiceType } from '../../policies/policy-component-types.enum';
|
||||
import { ManagementService } from '../../../services/mgmt.service';
|
||||
@ -46,6 +52,7 @@ export class ProviderSamlSpComponent {
|
||||
// DEPRECATED: use service$ instead
|
||||
private service!: ManagementService | AdminService;
|
||||
bindingValues: string[] = Object.keys(SAMLBinding);
|
||||
nameIDFormatValues: string[] = Object.keys(SAMLNameIDFormat);
|
||||
|
||||
public justCreated$: BehaviorSubject<string> = new BehaviorSubject<string>('');
|
||||
public justActivated$ = new BehaviorSubject<boolean>(false);
|
||||
@ -118,6 +125,8 @@ export class ProviderSamlSpComponent {
|
||||
metadataUrl: new UntypedFormControl('', []),
|
||||
binding: new UntypedFormControl(this.bindingValues[0], [requiredValidator]),
|
||||
withSignedRequest: new UntypedFormControl(true, [requiredValidator]),
|
||||
nameIdFormat: new UntypedFormControl(SAMLNameIDFormat.SAML_NAME_ID_FORMAT_PERSISTENT, []),
|
||||
transientMappingAttributeName: new UntypedFormControl('', []),
|
||||
},
|
||||
atLeastOneIsFilled('metadataXml', 'metadataUrl'),
|
||||
);
|
||||
@ -196,8 +205,12 @@ export class ProviderSamlSpComponent {
|
||||
req.setWithSignedRequest(this.withSignedRequest?.value);
|
||||
// @ts-ignore
|
||||
req.setBinding(SAMLBinding[this.binding?.value]);
|
||||
// @ts-ignore
|
||||
req.setNameIdFormat(SAMLNameIDFormat[this.nameIDFormat?.value]);
|
||||
req.setTransientMappingAttributeName(this.transientMapping?.value);
|
||||
req.setProviderOptions(this.options);
|
||||
|
||||
console.log(req);
|
||||
this.loading = true;
|
||||
this.service
|
||||
.updateSAMLProvider(req)
|
||||
@ -229,6 +242,11 @@ export class ProviderSamlSpComponent {
|
||||
// @ts-ignore
|
||||
req.setBinding(SAMLBinding[this.binding?.value]);
|
||||
req.setWithSignedRequest(this.withSignedRequest?.value);
|
||||
if (this.nameIDFormat) {
|
||||
// @ts-ignore
|
||||
req.setNameIdFormat(SAMLNameIDFormat[this.nameIDFormat.value]);
|
||||
}
|
||||
req.setTransientMappingAttributeName(this.transientMapping?.value);
|
||||
this.loading = true;
|
||||
this.service
|
||||
.addSAMLProvider(req)
|
||||
@ -279,6 +297,14 @@ export class ProviderSamlSpComponent {
|
||||
return false;
|
||||
}
|
||||
|
||||
compareNameIDFormat(value: string, index: number) {
|
||||
console.log(value, index);
|
||||
if (value) {
|
||||
return value === Object.keys(SAMLNameIDFormat)[index];
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private get name(): AbstractControl | null {
|
||||
return this.form.get('name');
|
||||
}
|
||||
@ -298,4 +324,12 @@ export class ProviderSamlSpComponent {
|
||||
private get withSignedRequest(): AbstractControl | null {
|
||||
return this.form.get('withSignedRequest');
|
||||
}
|
||||
|
||||
private get nameIDFormat(): AbstractControl | null {
|
||||
return this.form.get('nameIdFormat');
|
||||
}
|
||||
|
||||
private get transientMapping(): AbstractControl | null {
|
||||
return this.form.get('transientMappingAttributeName');
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@
|
||||
|
||||
.formfield {
|
||||
display: block;
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
|
||||
&.pwd {
|
||||
display: none;
|
||||
@ -132,7 +132,7 @@
|
||||
}
|
||||
|
||||
.string-list-component-wrapper {
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.identity-provider-content {
|
||||
@ -144,7 +144,7 @@
|
||||
}
|
||||
|
||||
.identity-provider-2-col {
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
column-gap: 1rem;
|
||||
@ -160,7 +160,7 @@
|
||||
.flex-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
max-width: 400px;
|
||||
max-width: 500px;
|
||||
|
||||
.formfield {
|
||||
flex: 1;
|
||||
|
@ -2044,7 +2044,7 @@
|
||||
"DESCRIPTION": "Enter the credentials for your Apple Provider"
|
||||
},
|
||||
"SAML": {
|
||||
"TITLE": "Sign in with Saml SP",
|
||||
"TITLE": "Sign in with SAML SP",
|
||||
"DESCRIPTION": "Enter the credentials for your SAML Provider"
|
||||
}
|
||||
},
|
||||
@ -2177,7 +2177,10 @@
|
||||
"METADATAXML": "Metadata Xml",
|
||||
"METADATAURL": "Metadata URL",
|
||||
"BINDING": "Binding",
|
||||
"SIGNEDREQUEST": "Signed Request"
|
||||
"SIGNEDREQUEST": "Signed Request",
|
||||
"NAMEIDFORMAT": "NameID Format",
|
||||
"TRANSIENTMAPPINGATTRIBUTENAME": "Custom Mapping Attribute Name",
|
||||
"TRANSIENTMAPPINGATTRIBUTENAME_DESC": "Alternative attribute name to map the user in case the `nameid-format` returned is `transient`, e.g. `http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress`"
|
||||
},
|
||||
"TOAST": {
|
||||
"SAVED": "Successfully saved.",
|
||||
|
@ -2044,7 +2044,7 @@
|
||||
"DESCRIPTION": "Voer de inloggegevens in voor uw Apple Provider"
|
||||
},
|
||||
"SAML": {
|
||||
"TITLE": "Log in met Saml SP",
|
||||
"TITLE": "Log in met SAML SP",
|
||||
"DESCRIPTION": "Voer de inloggegevens in voor uw SAML Provider"
|
||||
}
|
||||
},
|
||||
|
@ -2,6 +2,7 @@ package admin
|
||||
|
||||
import (
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp"
|
||||
"github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
@ -469,24 +470,36 @@ func updateAppleProviderToCommand(req *admin_pb.UpdateAppleProviderRequest) comm
|
||||
}
|
||||
|
||||
func addSAMLProviderToCommand(req *admin_pb.AddSAMLProviderRequest) command.SAMLProvider {
|
||||
var nameIDFormat *domain.SAMLNameIDFormat
|
||||
if req.NameIdFormat != nil {
|
||||
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
|
||||
}
|
||||
return command.SAMLProvider{
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
NameIDFormat: nameIDFormat,
|
||||
TransientMappingAttributeName: req.GetTransientMappingAttributeName(),
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateSAMLProviderToCommand(req *admin_pb.UpdateSAMLProviderRequest) command.SAMLProvider {
|
||||
var nameIDFormat *domain.SAMLNameIDFormat
|
||||
if req.NameIdFormat != nil {
|
||||
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
|
||||
}
|
||||
return command.SAMLProvider{
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
NameIDFormat: nameIDFormat,
|
||||
TransientMappingAttributeName: req.GetTransientMappingAttributeName(),
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ package idp
|
||||
|
||||
import (
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/muhlemmer/gu"
|
||||
"google.golang.org/protobuf/types/known/durationpb"
|
||||
|
||||
obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object"
|
||||
@ -339,6 +340,21 @@ func azureADTenantTypeToCommand(tenantType idp_pb.AzureADTenantType) azuread.Ten
|
||||
}
|
||||
}
|
||||
|
||||
func SAMLNameIDFormatToDomain(format idp_pb.SAMLNameIDFormat) domain.SAMLNameIDFormat {
|
||||
switch format {
|
||||
case idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_UNSPECIFIED:
|
||||
return domain.SAMLNameIDFormatUnspecified
|
||||
case idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_EMAIL_ADDRESS:
|
||||
return domain.SAMLNameIDFormatEmailAddress
|
||||
case idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_PERSISTENT:
|
||||
return domain.SAMLNameIDFormatPersistent
|
||||
case idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_TRANSIENT:
|
||||
return domain.SAMLNameIDFormatTransient
|
||||
default:
|
||||
return domain.SAMLNameIDFormatUnspecified
|
||||
}
|
||||
}
|
||||
|
||||
func ProvidersToPb(providers []*query.IDPTemplate) []*idp_pb.Provider {
|
||||
list := make([]*idp_pb.Provider, len(providers))
|
||||
for i, provider := range providers {
|
||||
@ -639,11 +655,17 @@ func appleConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.Appl
|
||||
}
|
||||
|
||||
func samlConfigToPb(providerConfig *idp_pb.ProviderConfig, template *query.SAMLIDPTemplate) {
|
||||
nameIDFormat := idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_PERSISTENT
|
||||
if template.NameIDFormat.Valid {
|
||||
nameIDFormat = nameIDToPb(template.NameIDFormat.V)
|
||||
}
|
||||
providerConfig.Config = &idp_pb.ProviderConfig_Saml{
|
||||
Saml: &idp_pb.SAMLConfig{
|
||||
MetadataXml: template.Metadata,
|
||||
Binding: bindingToPb(template.Binding),
|
||||
WithSignedRequest: template.WithSignedRequest,
|
||||
MetadataXml: template.Metadata,
|
||||
Binding: bindingToPb(template.Binding),
|
||||
WithSignedRequest: template.WithSignedRequest,
|
||||
NameIdFormat: nameIDFormat,
|
||||
TransientMappingAttributeName: gu.Ptr(template.TransientMappingAttributeName),
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -662,3 +684,18 @@ func bindingToPb(binding string) idp_pb.SAMLBinding {
|
||||
return idp_pb.SAMLBinding_SAML_BINDING_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func nameIDToPb(format domain.SAMLNameIDFormat) idp_pb.SAMLNameIDFormat {
|
||||
switch format {
|
||||
case domain.SAMLNameIDFormatUnspecified:
|
||||
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_UNSPECIFIED
|
||||
case domain.SAMLNameIDFormatEmailAddress:
|
||||
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_EMAIL_ADDRESS
|
||||
case domain.SAMLNameIDFormatPersistent:
|
||||
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_PERSISTENT
|
||||
case domain.SAMLNameIDFormatTransient:
|
||||
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_TRANSIENT
|
||||
default:
|
||||
return idp_pb.SAMLNameIDFormat_SAML_NAME_ID_FORMAT_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/muhlemmer/gu"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
idp_grpc "github.com/zitadel/zitadel/internal/api/grpc/idp"
|
||||
@ -462,24 +463,36 @@ func updateAppleProviderToCommand(req *mgmt_pb.UpdateAppleProviderRequest) comma
|
||||
}
|
||||
|
||||
func addSAMLProviderToCommand(req *mgmt_pb.AddSAMLProviderRequest) command.SAMLProvider {
|
||||
var nameIDFormat *domain.SAMLNameIDFormat
|
||||
if req.NameIdFormat != nil {
|
||||
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
|
||||
}
|
||||
return command.SAMLProvider{
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
NameIDFormat: nameIDFormat,
|
||||
TransientMappingAttributeName: req.GetTransientMappingAttributeName(),
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
func updateSAMLProviderToCommand(req *mgmt_pb.UpdateSAMLProviderRequest) command.SAMLProvider {
|
||||
var nameIDFormat *domain.SAMLNameIDFormat
|
||||
if req.NameIdFormat != nil {
|
||||
nameIDFormat = gu.Ptr(idp_grpc.SAMLNameIDFormatToDomain(req.GetNameIdFormat()))
|
||||
}
|
||||
return command.SAMLProvider{
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
Name: req.Name,
|
||||
Metadata: req.GetMetadataXml(),
|
||||
MetadataURL: req.GetMetadataUrl(),
|
||||
Binding: bindingToCommand(req.Binding),
|
||||
WithSignedRequest: req.WithSignedRequest,
|
||||
NameIDFormat: nameIDFormat,
|
||||
TransientMappingAttributeName: req.GetTransientMappingAttributeName(),
|
||||
IDPOptions: idp_grpc.OptionsToCommand(req.ProviderOptions),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1883,7 +1883,7 @@ func TestServer_StartIdentityProviderIntent(t *testing.T) {
|
||||
orgResp := Tester.CreateOrganization(IamCTX, fmt.Sprintf("NotDefaultOrg%d", time.Now().UnixNano()), fmt.Sprintf("%d@mouse.com", time.Now().UnixNano()))
|
||||
notDefaultOrgIdpID := Tester.AddOrgGenericOAuthProvider(t, CTX, orgResp.OrganizationId)
|
||||
samlIdpID := Tester.AddSAMLProvider(t, CTX)
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX)
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX, "")
|
||||
samlPostIdpID := Tester.AddSAMLPostProvider(t, CTX)
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
|
@ -229,11 +229,6 @@ func (h *Handler) handleACS(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
sp, err := samlProvider.GetSP()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
intent, err := h.commands.GetActiveIntent(ctx, data.RelayState)
|
||||
if err != nil {
|
||||
@ -245,10 +240,10 @@ func (h *Handler) handleACS(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
session := saml2.Session{
|
||||
ServiceProvider: sp,
|
||||
RequestID: intent.RequestID,
|
||||
Request: r,
|
||||
session, err := saml2.NewSession(samlProvider, intent.RequestID, r)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
idpUser, err := session.FetchUser(r.Context())
|
||||
|
@ -52,7 +52,7 @@ func TestMain(m *testing.M) {
|
||||
}
|
||||
|
||||
func TestServer_SAMLCertificate(t *testing.T) {
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX)
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX, "")
|
||||
oauthIdpID := Tester.AddGenericOAuthProvider(t, CTX)
|
||||
|
||||
type args struct {
|
||||
@ -109,7 +109,7 @@ func TestServer_SAMLCertificate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_SAMLMetadata(t *testing.T) {
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX)
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX, "")
|
||||
oauthIdpID := Tester.AddGenericOAuthProvider(t, CTX)
|
||||
|
||||
type args struct {
|
||||
@ -167,7 +167,7 @@ func TestServer_SAMLMetadata(t *testing.T) {
|
||||
|
||||
func TestServer_SAMLACS(t *testing.T) {
|
||||
userHuman := Tester.CreateHumanUser(CTX)
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX)
|
||||
samlRedirectIdpID := Tester.AddSAMLRedirectProvider(t, CTX, "urn:oid:0.9.2342.19200300.100.1.1") // the username is set in urn:oid:0.9.2342.19200300.100.1.1
|
||||
externalUserID := "test1"
|
||||
linkedExternalUserID := "test2"
|
||||
Tester.CreateUserIDPlink(CTX, userHuman.UserId, linkedExternalUserID, samlRedirectIdpID, linkedExternalUserID)
|
||||
@ -180,13 +180,15 @@ func TestServer_SAMLACS(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
successURL string
|
||||
failureURL string
|
||||
idpID string
|
||||
username string
|
||||
intentID string
|
||||
response string
|
||||
ctx context.Context
|
||||
successURL string
|
||||
failureURL string
|
||||
idpID string
|
||||
username string
|
||||
nameID string
|
||||
nameIDFormat string
|
||||
intentID string
|
||||
response string
|
||||
}
|
||||
type want struct {
|
||||
successful bool
|
||||
@ -201,12 +203,14 @@ func TestServer_SAMLACS(t *testing.T) {
|
||||
{
|
||||
name: "intent invalid",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
intentID: "notexisting",
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
nameID: externalUserID,
|
||||
nameIDFormat: string(saml.PersistentNameIDFormat),
|
||||
intentID: "notexisting",
|
||||
},
|
||||
want: want{
|
||||
successful: false,
|
||||
@ -217,12 +221,14 @@ func TestServer_SAMLACS(t *testing.T) {
|
||||
{
|
||||
name: "response invalid",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
response: "invalid",
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
nameID: externalUserID,
|
||||
nameIDFormat: string(saml.PersistentNameIDFormat),
|
||||
response: "invalid",
|
||||
},
|
||||
want: want{
|
||||
successful: false,
|
||||
@ -232,11 +238,13 @@ func TestServer_SAMLACS(t *testing.T) {
|
||||
{
|
||||
name: "saml flow redirect, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
nameID: externalUserID,
|
||||
nameIDFormat: string(saml.PersistentNameIDFormat),
|
||||
},
|
||||
want: want{
|
||||
successful: true,
|
||||
@ -246,11 +254,45 @@ func TestServer_SAMLACS(t *testing.T) {
|
||||
{
|
||||
name: "saml flow redirect with link, ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: linkedExternalUserID,
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: linkedExternalUserID,
|
||||
nameID: linkedExternalUserID,
|
||||
nameIDFormat: string(saml.PersistentNameIDFormat),
|
||||
},
|
||||
want: want{
|
||||
successful: true,
|
||||
user: userHuman.UserId,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "saml flow redirect (transient), ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: externalUserID,
|
||||
nameID: "genericID",
|
||||
nameIDFormat: string(saml.TransientNameIDFormat),
|
||||
},
|
||||
want: want{
|
||||
successful: true,
|
||||
user: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "saml flow redirect with link (transient), ok",
|
||||
args: args{
|
||||
ctx: CTX,
|
||||
successURL: "https://example.com/success",
|
||||
failureURL: "https://example.com/failure",
|
||||
idpID: samlRedirectIdpID,
|
||||
username: linkedExternalUserID,
|
||||
nameID: "genericID",
|
||||
nameIDFormat: string(saml.TransientNameIDFormat),
|
||||
},
|
||||
want: want{
|
||||
successful: true,
|
||||
@ -287,7 +329,7 @@ func TestServer_SAMLACS(t *testing.T) {
|
||||
relayState = tt.args.intentID
|
||||
}
|
||||
callbackURL := http_util.BuildOrigin(Tester.Host(), Tester.Server.Config.ExternalSecure) + "/idps/" + tt.args.idpID + "/saml/acs"
|
||||
response := createResponse(t, idp, samlRequest, tt.args.username)
|
||||
response := createResponse(t, idp, samlRequest, tt.args.nameID, tt.args.nameIDFormat, tt.args.username)
|
||||
//test purposes, use defined response
|
||||
if tt.args.response != "" {
|
||||
response = tt.args.response
|
||||
@ -432,14 +474,16 @@ func getIDP(zitadelBaseURL string, idpIDs []string, user1, user2 string) (*saml.
|
||||
return &idpServer.IDP, nil
|
||||
}
|
||||
|
||||
func createResponse(t *testing.T, idp *saml.IdentityProvider, req *http.Request, username string) string {
|
||||
func createResponse(t *testing.T, idp *saml.IdentityProvider, req *http.Request, nameID, nameIDFormat, username string) string {
|
||||
authnReq, err := saml.NewIdpAuthnRequest(idp, req)
|
||||
assert.NoError(t, authnReq.Validate())
|
||||
|
||||
err = idp.AssertionMaker.MakeAssertion(authnReq, &saml.Session{
|
||||
CreateTime: time.Now().UTC(),
|
||||
Index: "",
|
||||
NameID: username,
|
||||
CreateTime: time.Now().UTC(),
|
||||
Index: "",
|
||||
NameID: nameID,
|
||||
NameIDFormat: nameIDFormat,
|
||||
UserName: username,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
err = authnReq.MakeResponse()
|
||||
|
@ -319,12 +319,11 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque
|
||||
l.externalAuthFailed(w, r, authReq, nil, nil, err)
|
||||
return
|
||||
}
|
||||
sp, err := provider.(*saml.Provider).GetSP()
|
||||
session, err = saml.NewSession(provider.(*saml.Provider), authReq.SAMLRequestID, r)
|
||||
if err != nil {
|
||||
l.externalAuthFailed(w, r, authReq, nil, nil, err)
|
||||
return
|
||||
}
|
||||
session = &saml.Session{ServiceProvider: sp, RequestID: authReq.SAMLRequestID, Request: r}
|
||||
case domain.IDPTypeJWT,
|
||||
domain.IDPTypeLDAP,
|
||||
domain.IDPTypeUnspecified:
|
||||
@ -1029,13 +1028,19 @@ func (l *Login) samlProvider(ctx context.Context, identityProvider *query.IDPTem
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts := make([]saml.ProviderOpts, 0, 2)
|
||||
opts := make([]saml.ProviderOpts, 0, 6)
|
||||
if identityProvider.SAMLIDPTemplate.WithSignedRequest {
|
||||
opts = append(opts, saml.WithSignedRequest())
|
||||
}
|
||||
if identityProvider.SAMLIDPTemplate.Binding != "" {
|
||||
opts = append(opts, saml.WithBinding(identityProvider.SAMLIDPTemplate.Binding))
|
||||
}
|
||||
if identityProvider.SAMLIDPTemplate.NameIDFormat.Valid {
|
||||
opts = append(opts, saml.WithNameIDFormat(identityProvider.SAMLIDPTemplate.NameIDFormat.V))
|
||||
}
|
||||
if identityProvider.SAMLIDPTemplate.TransientMappingAttributeName != "" {
|
||||
opts = append(opts, saml.WithTransientMappingAttributeName(identityProvider.SAMLIDPTemplate.TransientMappingAttributeName))
|
||||
}
|
||||
opts = append(opts,
|
||||
saml.WithEntityID(http_utils.BuildOrigin(authz.GetInstance(ctx).RequestedHost(), l.externalSecure)+"/idps/"+identityProvider.ID+"/saml/metadata"),
|
||||
saml.WithCustomRequestTracker(
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/command/preparation"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -110,12 +111,14 @@ type LDAPProvider struct {
|
||||
}
|
||||
|
||||
type SAMLProvider struct {
|
||||
Name string
|
||||
Metadata []byte
|
||||
MetadataURL string
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
IDPOptions idp.Options
|
||||
Name string
|
||||
Metadata []byte
|
||||
MetadataURL string
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
NameIDFormat *domain.SAMLNameIDFormat
|
||||
TransientMappingAttributeName string
|
||||
IDPOptions idp.Options
|
||||
}
|
||||
|
||||
type AppleProvider struct {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
@ -631,6 +632,8 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
[]byte("certificate"),
|
||||
"",
|
||||
false,
|
||||
gu.Ptr(domain.SAMLNameIDFormatUnspecified),
|
||||
"",
|
||||
rep_idp.Options{},
|
||||
)),
|
||||
),
|
||||
@ -649,6 +652,8 @@ func TestCommands_AuthFromProvider_SAML(t *testing.T) {
|
||||
}, []byte("-----BEGIN CERTIFICATE-----\nMIIC2zCCAcOgAwIBAgIIAy/jm1gAAdEwDQYJKoZIhvcNAQELBQAwEjEQMA4GA1UE\nChMHWklUQURFTDAeFw0yMzA4MzAwNzExMTVaFw0yNDA4MjkwNzExMTVaMBIxEDAO\nBgNVBAoTB1pJVEFERUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDE\nd3TztGgSb3LBVZn8f60NbFCyZW+F9HPiMCr9F9T45Zc0fgmMwxId0WzRD5Y/3yc1\ndHJzt+Bsxvw12aUHbIPiothqk3lINoFzl2H/cSfIW3nehKyNOUqdBQ8B4mvaqH81\njTjoJ/JTJAwzglHk6JAWjhOyx9aep1yBqYa3QASeTaW9sxkpB0Co1L2UPNhuMwZq\n8RA9NkTfmYVcVBeNqihler5MhruFtqrv+J0ftwc1stw8uCN89ADyr4Ni+e+FeWar\nQs9Bkfc6KLF/5IXa9HCsHNPaaoYPY6I6RSaG4/DKoSKIEe1/GSVG1FTpZ8trUZxv\nU+xXS6gEalXcrJsiX8aXAgMBAAGjNTAzMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUE\nDDAKBggrBgEFBQcDATAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQCx\n/dRNIj0N/16zJhZR/ahkc2AkvDXYxyr4JRT5wK9GQDNl/oaX3debRuSi/tfaXFIX\naJA6PxM4J49ZaiEpLrKfxMz5kAhjKchCBEMcH3mGt+iNZH7EOyTvHjpGrP2OZrsh\nO17yrvN3HuQxIU6roJlqtZz2iAADsoPtwOO4D7hupm9XTMkSnAmlMWOo/q46Jz89\n1sMxB+dXmH/zV0wgwh0omZfLV0u89mvdq269VhcjNBpBYSnN1ccqYWd5iwziob3I\nvaavGHGfkbvRUn/tKftYuTK30q03R+e9YbmlWZ0v695owh2e/apCzowQsCKfSVC8\nOxVyt5XkHq1tWwVyBmFp\n-----END CERTIFICATE-----\n"),
|
||||
"",
|
||||
false,
|
||||
gu.Ptr(domain.SAMLNameIDFormatUnspecified),
|
||||
"",
|
||||
rep_idp.Options{},
|
||||
)),
|
||||
),
|
||||
|
@ -1726,13 +1726,15 @@ func (wm *AppleIDPWriteModel) GetProviderOptions() idp.Options {
|
||||
type SAMLIDPWriteModel struct {
|
||||
eventstore.WriteModel
|
||||
|
||||
Name string
|
||||
ID string
|
||||
Metadata []byte
|
||||
Key *crypto.CryptoValue
|
||||
Certificate []byte
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
Name string
|
||||
ID string
|
||||
Metadata []byte
|
||||
Key *crypto.CryptoValue
|
||||
Certificate []byte
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
NameIDFormat *domain.SAMLNameIDFormat
|
||||
TransientMappingAttributeName string
|
||||
idp.Options
|
||||
|
||||
State domain.IDPState
|
||||
@ -1759,6 +1761,8 @@ func (wm *SAMLIDPWriteModel) reduceAddedEvent(e *idp.SAMLIDPAddedEvent) {
|
||||
wm.Certificate = e.Certificate
|
||||
wm.Binding = e.Binding
|
||||
wm.WithSignedRequest = e.WithSignedRequest
|
||||
wm.NameIDFormat = e.NameIDFormat
|
||||
wm.TransientMappingAttributeName = e.TransientMappingAttributeName
|
||||
wm.Options = e.Options
|
||||
wm.State = domain.IDPStateActive
|
||||
}
|
||||
@ -1782,6 +1786,12 @@ func (wm *SAMLIDPWriteModel) reduceChangedEvent(e *idp.SAMLIDPChangedEvent) {
|
||||
if e.WithSignedRequest != nil {
|
||||
wm.WithSignedRequest = *e.WithSignedRequest
|
||||
}
|
||||
if e.NameIDFormat != nil {
|
||||
wm.NameIDFormat = e.NameIDFormat
|
||||
}
|
||||
if e.TransientMappingAttributeName != nil {
|
||||
wm.TransientMappingAttributeName = *e.TransientMappingAttributeName
|
||||
}
|
||||
wm.Options.ReduceChanges(e.OptionChanges)
|
||||
}
|
||||
|
||||
@ -1793,6 +1803,8 @@ func (wm *SAMLIDPWriteModel) NewChanges(
|
||||
secretCrypto crypto.EncryptionAlgorithm,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
nameIDFormat *domain.SAMLNameIDFormat,
|
||||
transientMappingAttributeName string,
|
||||
options idp.Options,
|
||||
) ([]idp.SAMLIDPChanges, error) {
|
||||
changes := make([]idp.SAMLIDPChanges, 0)
|
||||
@ -1818,6 +1830,12 @@ func (wm *SAMLIDPWriteModel) NewChanges(
|
||||
if wm.WithSignedRequest != withSignedRequest {
|
||||
changes = append(changes, idp.ChangeSAMLWithSignedRequest(withSignedRequest))
|
||||
}
|
||||
if wm.NameIDFormat != nameIDFormat {
|
||||
changes = append(changes, idp.ChangeSAMLNameIDFormat(nameIDFormat))
|
||||
}
|
||||
if wm.TransientMappingAttributeName != transientMappingAttributeName {
|
||||
changes = append(changes, idp.ChangeSAMLTransientMappingAttributeName(transientMappingAttributeName))
|
||||
}
|
||||
opts := wm.Options.Changes(options)
|
||||
if !opts.IsZero() {
|
||||
changes = append(changes, idp.ChangeSAMLOptions(opts))
|
||||
@ -1850,6 +1868,12 @@ func (wm *SAMLIDPWriteModel) ToProvider(callbackURL string, idpAlg crypto.Encryp
|
||||
if wm.Binding != "" {
|
||||
opts = append(opts, saml2.WithBinding(wm.Binding))
|
||||
}
|
||||
if wm.NameIDFormat != nil {
|
||||
opts = append(opts, saml2.WithNameIDFormat(*wm.NameIDFormat))
|
||||
}
|
||||
if wm.TransientMappingAttributeName != "" {
|
||||
opts = append(opts, saml2.WithTransientMappingAttributeName(wm.TransientMappingAttributeName))
|
||||
}
|
||||
opts = append(opts, saml2.WithCustomRequestTracker(
|
||||
requesttracker.New(
|
||||
addRequest,
|
||||
|
@ -1763,6 +1763,8 @@ func (c *Commands) prepareAddInstanceSAMLProvider(a *instance.Aggregate, writeMo
|
||||
cert,
|
||||
provider.Binding,
|
||||
provider.WithSignedRequest,
|
||||
provider.NameIDFormat,
|
||||
provider.TransientMappingAttributeName,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
@ -1811,6 +1813,8 @@ func (c *Commands) prepareUpdateInstanceSAMLProvider(a *instance.Aggregate, writ
|
||||
c.idpConfigEncryption,
|
||||
provider.Binding,
|
||||
provider.WithSignedRequest,
|
||||
provider.NameIDFormat,
|
||||
provider.TransientMappingAttributeName,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
@ -1854,6 +1858,8 @@ func (c *Commands) prepareRegenerateInstanceSAMLProviderCertificate(a *instance.
|
||||
c.idpConfigEncryption,
|
||||
writeModel.Binding,
|
||||
writeModel.WithSignedRequest,
|
||||
writeModel.NameIDFormat,
|
||||
writeModel.TransientMappingAttributeName,
|
||||
writeModel.Options,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
@ -915,6 +916,8 @@ func (wm *InstanceSAMLIDPWriteModel) NewChangedEvent(
|
||||
secretCrypto crypto.EncryptionAlgorithm,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
nameIDFormat *domain.SAMLNameIDFormat,
|
||||
transientMappingAttributeName string,
|
||||
options idp.Options,
|
||||
) (*instance.SAMLIDPChangedEvent, error) {
|
||||
changes, err := wm.SAMLIDPWriteModel.NewChanges(
|
||||
@ -925,6 +928,8 @@ func (wm *InstanceSAMLIDPWriteModel) NewChangedEvent(
|
||||
secretCrypto,
|
||||
binding,
|
||||
withSignedRequest,
|
||||
nameIDFormat,
|
||||
transientMappingAttributeName,
|
||||
options,
|
||||
)
|
||||
if err != nil || len(changes) == 0 {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1746,6 +1746,8 @@ func (c *Commands) prepareAddOrgSAMLProvider(a *org.Aggregate, writeModel *OrgSA
|
||||
cert,
|
||||
provider.Binding,
|
||||
provider.WithSignedRequest,
|
||||
provider.NameIDFormat,
|
||||
provider.TransientMappingAttributeName,
|
||||
provider.IDPOptions,
|
||||
),
|
||||
}, nil
|
||||
@ -1794,6 +1796,8 @@ func (c *Commands) prepareUpdateOrgSAMLProvider(a *org.Aggregate, writeModel *Or
|
||||
c.idpConfigEncryption,
|
||||
provider.Binding,
|
||||
provider.WithSignedRequest,
|
||||
provider.NameIDFormat,
|
||||
provider.TransientMappingAttributeName,
|
||||
provider.IDPOptions,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
@ -1837,6 +1841,8 @@ func (c *Commands) prepareRegenerateOrgSAMLProviderCertificate(a *org.Aggregate,
|
||||
c.idpConfigEncryption,
|
||||
writeModel.Binding,
|
||||
writeModel.WithSignedRequest,
|
||||
writeModel.NameIDFormat,
|
||||
writeModel.TransientMappingAttributeName,
|
||||
writeModel.Options,
|
||||
)
|
||||
if err != nil || event == nil {
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
"github.com/zitadel/zitadel/internal/repository/org"
|
||||
@ -927,6 +928,8 @@ func (wm *OrgSAMLIDPWriteModel) NewChangedEvent(
|
||||
secretCrypto crypto.EncryptionAlgorithm,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
nameIDFormat *domain.SAMLNameIDFormat,
|
||||
transientMappingAttributeName string,
|
||||
options idp.Options,
|
||||
) (*org.SAMLIDPChangedEvent, error) {
|
||||
changes, err := wm.SAMLIDPWriteModel.NewChanges(
|
||||
@ -937,6 +940,8 @@ func (wm *OrgSAMLIDPWriteModel) NewChangedEvent(
|
||||
secretCrypto,
|
||||
binding,
|
||||
withSignedRequest,
|
||||
nameIDFormat,
|
||||
transientMappingAttributeName,
|
||||
options,
|
||||
)
|
||||
if err != nil || len(changes) == 0 {
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -134,3 +134,12 @@ const (
|
||||
AutoLinkingOptionUsername
|
||||
AutoLinkingOptionEmail
|
||||
)
|
||||
|
||||
type SAMLNameIDFormat uint8
|
||||
|
||||
const (
|
||||
SAMLNameIDFormatUnspecified SAMLNameIDFormat = iota
|
||||
SAMLNameIDFormatEmailAddress
|
||||
SAMLNameIDFormatPersistent
|
||||
SAMLNameIDFormatTransient
|
||||
)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package saml
|
||||
|
||||
import (
|
||||
"github.com/crewjam/saml"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
@ -20,8 +19,8 @@ func NewUser() *UserMapper {
|
||||
return &UserMapper{Attributes: map[string][]string{}}
|
||||
}
|
||||
|
||||
func (u *UserMapper) SetID(id *saml.NameID) {
|
||||
u.ID = id.Value
|
||||
func (u *UserMapper) SetID(id string) {
|
||||
u.ID = id
|
||||
}
|
||||
|
||||
// GetID is an implementation of the [idp.User] interface.
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -26,7 +27,9 @@ type Provider struct {
|
||||
|
||||
spOptions *samlsp.Options
|
||||
|
||||
binding string
|
||||
binding string
|
||||
nameIDFormat saml.NameIDFormat
|
||||
transientMappingAttributeName string
|
||||
|
||||
isLinkingAllowed bool
|
||||
isCreationAllowed bool
|
||||
@ -77,6 +80,18 @@ func WithBinding(binding string) ProviderOpts {
|
||||
}
|
||||
}
|
||||
|
||||
func WithNameIDFormat(format domain.SAMLNameIDFormat) ProviderOpts {
|
||||
return func(p *Provider) {
|
||||
p.nameIDFormat = nameIDFormatFromDomain(format)
|
||||
}
|
||||
}
|
||||
|
||||
func WithTransientMappingAttributeName(attribute string) ProviderOpts {
|
||||
return func(p *Provider) {
|
||||
p.transientMappingAttributeName = attribute
|
||||
}
|
||||
}
|
||||
|
||||
func WithCustomRequestTracker(tracker samlsp.RequestTracker) ProviderOpts {
|
||||
return func(p *Provider) {
|
||||
p.requestTracker = tracker
|
||||
@ -124,6 +139,8 @@ func New(
|
||||
name: name,
|
||||
spOptions: &opts,
|
||||
Certificate: certificate,
|
||||
// the library uses transient as default, which does not make sense for federating accounts
|
||||
nameIDFormat: saml.PersistentNameIDFormat,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(provider)
|
||||
@ -156,10 +173,7 @@ func (p *Provider) GetSP() (*samlsp.Middleware, error) {
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "SAML-qee09ffuq5", "Errors.Intent.IDPInvalid")
|
||||
}
|
||||
// the library uses transient as default, which we currently can't handle (https://github.com/zitadel/zitadel/discussions/7421)
|
||||
// for the moment we'll use persistent (for those who actually use it from the saml request) and add an option
|
||||
// later on to specify on the provider: https://github.com/zitadel/zitadel/issues/7743
|
||||
sp.ServiceProvider.AuthnNameIDFormat = saml.PersistentNameIDFormat
|
||||
sp.ServiceProvider.AuthnNameIDFormat = p.nameIDFormat
|
||||
if p.requestTracker != nil {
|
||||
sp.RequestTracker = p.requestTracker
|
||||
}
|
||||
@ -180,3 +194,22 @@ func (p *Provider) BeginAuth(ctx context.Context, state string, _ ...idp.Paramet
|
||||
state: state,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *Provider) TransientMappingAttributeName() string {
|
||||
return p.transientMappingAttributeName
|
||||
}
|
||||
|
||||
func nameIDFormatFromDomain(format domain.SAMLNameIDFormat) saml.NameIDFormat {
|
||||
switch format {
|
||||
case domain.SAMLNameIDFormatUnspecified:
|
||||
return saml.UnspecifiedNameIDFormat
|
||||
case domain.SAMLNameIDFormatEmailAddress:
|
||||
return saml.EmailAddressNameIDFormat
|
||||
case domain.SAMLNameIDFormatPersistent:
|
||||
return saml.PersistentNameIDFormat
|
||||
case domain.SAMLNameIDFormatTransient:
|
||||
return saml.TransientNameIDFormat
|
||||
default:
|
||||
return saml.UnspecifiedNameIDFormat
|
||||
}
|
||||
}
|
||||
|
@ -3,10 +3,12 @@ package saml
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/crewjam/saml"
|
||||
"github.com/crewjam/saml/samlsp"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/idp/providers/saml/requesttracker"
|
||||
)
|
||||
|
||||
@ -20,16 +22,18 @@ func TestProvider_Options(t *testing.T) {
|
||||
options []ProviderOpts
|
||||
}
|
||||
type want struct {
|
||||
err bool
|
||||
name string
|
||||
linkingAllowed bool
|
||||
creationAllowed bool
|
||||
autoCreation bool
|
||||
autoUpdate bool
|
||||
binding string
|
||||
withSignedRequest bool
|
||||
requesttracker samlsp.RequestTracker
|
||||
entityID string
|
||||
err bool
|
||||
name string
|
||||
linkingAllowed bool
|
||||
creationAllowed bool
|
||||
autoCreation bool
|
||||
autoUpdate bool
|
||||
binding string
|
||||
nameIDFormat saml.NameIDFormat
|
||||
transientMappingAttributeName string
|
||||
withSignedRequest bool
|
||||
requesttracker samlsp.RequestTracker
|
||||
entityID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@ -103,10 +107,11 @@ func TestProvider_Options(t *testing.T) {
|
||||
creationAllowed: false,
|
||||
autoCreation: false,
|
||||
autoUpdate: false,
|
||||
nameIDFormat: saml.PersistentNameIDFormat,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "all true",
|
||||
name: "all set / true",
|
||||
fields: fields{
|
||||
name: "saml",
|
||||
key: []byte("-----BEGIN RSA PRIVATE KEY-----\nMIIEogIBAAKCAQEAxHd087RoEm9ywVWZ/H+tDWxQsmVvhfRz4jAq/RfU+OWXNH4J\njMMSHdFs0Q+WP98nNXRyc7fgbMb8NdmlB2yD4qLYapN5SDaBc5dh/3EnyFt53oSs\njTlKnQUPAeJr2qh/NY046CfyUyQMM4JR5OiQFo4TssfWnqdcgamGt0AEnk2lvbMZ\nKQdAqNS9lDzYbjMGavEQPTZE35mFXFQXjaooZXq+TIa7hbaq7/idH7cHNbLcPLgj\nfPQA8q+DYvnvhXlmq0LPQZH3Oiixf+SF2vRwrBzT2mqGD2OiOkUmhuPwyqEiiBHt\nfxklRtRU6WfLa1Gcb1PsV0uoBGpV3KybIl/GlwIDAQABAoIBAEQjDduLgOCL6Gem\n0X3hpdnW6/HC/jed/Sa//9jBECq2LYeWAqff64ON40hqOHi0YvvGA/+gEOSI6mWe\nsv5tIxxRz+6+cLybsq+tG96kluCE4TJMHy/nY7orS/YiWbd+4odnEApr+D3fbZ/b\nnZ1fDsHTyn8hkYx6jLmnWsJpIHDp7zxD76y7k2Bbg6DZrCGiVxngiLJk23dvz79W\np03lHLM7XE92aFwXQmhfxHGxrbuoB/9eY4ai5IHp36H4fw0vL6NXdNQAo/bhe0p9\nAYB7y0ZumF8Hg0Z/BmMeEzLy6HrYB+VE8cO93pNjhSyH+p2yDB/BlUyTiRLQAoM0\nVTmOZXECgYEA7NGlzpKNhyQEJihVqt0MW0LhKIO/xbBn+XgYfX6GpqPa/ucnMx5/\nVezpl3gK8IU4wPUhAyXXAHJiqNBcEeyxrw0MXLujDVMJgYaLysCLJdvMVgoY08mS\nK5IQivpbozpf4+0y3mOnA+Sy1kbfxv2X8xiWLODRQW3f3q/xoklwOR8CgYEA1GEe\nfaibOFTQAYcIVj77KXtBfYZsX3EGAyfAN9O7cKHq5oaxVstwnF47WxpuVtoKZxCZ\nbNm9D5WvQ9b+Ztpioe42tzwE7Bff/Osj868GcDdRPK7nFlh9N2yVn/D514dOYVwR\n4MBr1KrJzgRWt4QqS4H+to1GzudDTSNlG7gnK4kCgYBUi6AbOHzoYzZL/RhgcJwp\ntJ23nhmH1Su5h2OO4e3mbhcP66w19sxU+8iFN+kH5zfUw26utgKk+TE5vXExQQRK\nT2k7bg2PAzcgk80ybD0BHhA8I0yrx4m0nmfjhe/TPVLgh10iwgbtP+eM0i6v1vc5\nZWyvxu9N4ZEL6lpkqr0y1wKBgG/NAIQd8jhhTW7Aav8cAJQBsqQl038avJOEpYe+\nCnpsgoAAf/K0/f8TDCQVceh+t+MxtdK7fO9rWOxZjWsPo8Si5mLnUaAHoX4/OpnZ\nlYYVWMqdOEFnK+O1Yb7k2GFBdV2DXlX2dc1qavntBsls5ecB89id3pyk2aUN8Pf6\npYQhAoGAMGtrHFely9wyaxI0RTCyfmJbWZHGVGkv6ELK8wneJjdjl82XOBUGCg5q\naRCrTZ3dPitKwrUa6ibJCIFCIziiriBmjDvTHzkMvoJEap2TVxYNDR6IfINVsQ57\nlOsiC4A2uGq4Lbfld+gjoplJ5GX6qXtTgZ6m7eo0y7U6zm2tkN0=\n-----END RSA PRIVATE KEY-----\n"),
|
||||
@ -121,18 +126,22 @@ func TestProvider_Options(t *testing.T) {
|
||||
WithSignedRequest(),
|
||||
WithCustomRequestTracker(&requesttracker.RequestTracker{}),
|
||||
WithEntityID("entityID"),
|
||||
WithNameIDFormat(domain.SAMLNameIDFormatTransient),
|
||||
WithTransientMappingAttributeName("attribute"),
|
||||
},
|
||||
},
|
||||
want: want{
|
||||
name: "saml",
|
||||
linkingAllowed: true,
|
||||
creationAllowed: true,
|
||||
autoCreation: true,
|
||||
autoUpdate: true,
|
||||
binding: "binding",
|
||||
withSignedRequest: true,
|
||||
requesttracker: &requesttracker.RequestTracker{},
|
||||
entityID: "entityID",
|
||||
name: "saml",
|
||||
linkingAllowed: true,
|
||||
creationAllowed: true,
|
||||
autoCreation: true,
|
||||
autoUpdate: true,
|
||||
binding: "binding",
|
||||
entityID: "entityID",
|
||||
nameIDFormat: saml.TransientNameIDFormat,
|
||||
transientMappingAttributeName: "attribute",
|
||||
withSignedRequest: true,
|
||||
requesttracker: &requesttracker.RequestTracker{},
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -152,6 +161,8 @@ func TestProvider_Options(t *testing.T) {
|
||||
a.Equal(tt.want.autoCreation, provider.IsAutoCreation())
|
||||
a.Equal(tt.want.autoUpdate, provider.IsAutoUpdate())
|
||||
a.Equal(tt.want.binding, provider.binding)
|
||||
a.Equal(tt.want.nameIDFormat, provider.nameIDFormat)
|
||||
a.Equal(tt.want.transientMappingAttributeName, provider.transientMappingAttributeName)
|
||||
a.Equal(tt.want.withSignedRequest, provider.spOptions.SignRequest)
|
||||
a.Equal(tt.want.requesttracker, provider.requestTracker)
|
||||
a.Equal(tt.want.entityID, provider.spOptions.EntityID)
|
||||
|
@ -17,8 +17,9 @@ var _ idp.Session = (*Session)(nil)
|
||||
|
||||
// Session is the [idp.Session] implementation for the SAML provider.
|
||||
type Session struct {
|
||||
ServiceProvider *samlsp.Middleware
|
||||
state string
|
||||
ServiceProvider *samlsp.Middleware
|
||||
state string
|
||||
TransientMappingAttributeName string
|
||||
|
||||
RequestID string
|
||||
Request *http.Request
|
||||
@ -26,6 +27,19 @@ type Session struct {
|
||||
Assertion *saml.Assertion
|
||||
}
|
||||
|
||||
func NewSession(provider *Provider, requestID string, request *http.Request) (*Session, error) {
|
||||
sp, err := provider.GetSP()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &Session{
|
||||
ServiceProvider: sp,
|
||||
TransientMappingAttributeName: provider.TransientMappingAttributeName(),
|
||||
RequestID: requestID,
|
||||
Request: request,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetAuth implements the [idp.Session] interface.
|
||||
func (s *Session) GetAuth(ctx context.Context) (string, bool) {
|
||||
url, _ := url.Parse(s.state)
|
||||
@ -56,8 +70,17 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return nil, zerrors.ThrowInvalidArgument(err, "SAML-nuo0vphhh9", "Errors.Intent.ResponseInvalid")
|
||||
}
|
||||
|
||||
nameID := s.Assertion.Subject.NameID
|
||||
userMapper := NewUser()
|
||||
userMapper.SetID(s.Assertion.Subject.NameID)
|
||||
// use the nameID as default mapping id
|
||||
userMapper.SetID(nameID.Value)
|
||||
if nameID.Format == string(saml.TransientNameIDFormat) {
|
||||
mappingID, err := s.transientMappingID()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
userMapper.SetID(mappingID)
|
||||
}
|
||||
for _, statement := range s.Assertion.AttributeStatements {
|
||||
for _, attribute := range statement.Attributes {
|
||||
values := make([]string, len(attribute.Values))
|
||||
@ -70,6 +93,21 @@ func (s *Session) FetchUser(ctx context.Context) (user idp.User, err error) {
|
||||
return userMapper, nil
|
||||
}
|
||||
|
||||
func (s *Session) transientMappingID() (string, error) {
|
||||
for _, statement := range s.Assertion.AttributeStatements {
|
||||
for _, attribute := range statement.Attributes {
|
||||
if attribute.Name != s.TransientMappingAttributeName {
|
||||
continue
|
||||
}
|
||||
if len(attribute.Values) != 1 {
|
||||
return "", zerrors.ThrowInvalidArgument(nil, "SAML-Soij4", "Errors.Intent.MissingSingleMappingAttribute")
|
||||
}
|
||||
return attribute.Values[0].Value, nil
|
||||
}
|
||||
}
|
||||
return "", zerrors.ThrowInvalidArgument(nil, "SAML-swwg2", "Errors.Intent.MissingSingleMappingAttribute")
|
||||
}
|
||||
|
||||
type TempResponseWriter struct {
|
||||
header http.Header
|
||||
content *bytes.Buffer
|
||||
|
File diff suppressed because one or more lines are too long
@ -380,12 +380,13 @@ func (s *Tester) AddSAMLProvider(t *testing.T, ctx context.Context) string {
|
||||
return id
|
||||
}
|
||||
|
||||
func (s *Tester) AddSAMLRedirectProvider(t *testing.T, ctx context.Context) string {
|
||||
func (s *Tester) AddSAMLRedirectProvider(t *testing.T, ctx context.Context, transientMappingAttributeName string) string {
|
||||
ctx = authz.WithInstance(ctx, s.Instance)
|
||||
id, _, err := s.Server.Commands.AddInstanceSAMLProvider(ctx, command.SAMLProvider{
|
||||
Name: "saml-idp-redirect",
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
||||
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
Name: "saml-idp-redirect",
|
||||
Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
||||
Metadata: []byte("<EntityDescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" validUntil=\"2023-09-16T09:00:32.986Z\" cacheDuration=\"PT48H\" entityID=\"http://localhost:8000/metadata\">\n <IDPSSODescriptor xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n <KeyDescriptor use=\"signing\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n </KeyDescriptor>\n <KeyDescriptor use=\"encryption\">\n <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Data xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n <X509Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\">MIIDBzCCAe+gAwIBAgIJAPr/Mrlc8EGhMA0GCSqGSIb3DQEBBQUAMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTAeFw0xNTEyMjgxOTE5NDVaFw0yNTEyMjUxOTE5NDVaMBoxGDAWBgNVBAMMD3d3dy5leGFtcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANDoWzLos4LWxTn8Gyu2lEbl4WcelUbgLN5zYm4ron8Ahs+rvcsu2zkdD/s6jdGJI8WqJKhYK2u61ygnXgAZqC6ggtFPnBpizcDzjgND2g+aucSoUODHt67f0fQuAmupN/zp5MZysJ6IHLJnYLNpfJYk96lRz9ODnO1Mpqtr9PWxm+pz7nzq5F0vRepkgpcRxv6ufQBjlrFytccyEVdXrvFtkjXcnhVVNSR4kHuOOMS6D7pebSJ1mrCmshbD5SX1jXPBKFPAjozYX6PxqLxUx1Y4faFEf4MBBVcInyB4oURNB2s59hEEi2jq9izNE7EbEK6BY5sEhoCPl9m32zE6ljkCAwEAAaNQME4wHQYDVR0OBBYEFB9ZklC1Ork2zl56zg08ei7ss/+iMB8GA1UdIwQYMBaAFB9ZklC1Ork2zl56zg08ei7ss/+iMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAAVoTSQ5pAirw8OR9FZ1bRSuTDhY9uxzl/OL7lUmsv2cMNeCB3BRZqm3mFt+cwN8GsH6f3uvNONIhgFpTGN5LEcXQz89zJEzB+qaHqmbFpHQl/sx2B8ezNgT/882H2IH00dXESEfy/+1gHg2pxjGnhRBN6el/gSaDiySIMKbilDrffuvxiCfbpPN0NRRiPJhd2ay9KuL/RxQRl1gl9cHaWiouWWba1bSBb2ZPhv2rPMUsFo98ntkGCObDX6Y1SpkqmoTbrsbGFsTG2DLxnvr4GdN1BSr0Uu/KV3adj47WkXVPeMYQti/bQmxQB8tRFhrw80qakTLUzreO96WzlBBMtY=</X509Certificate>\n </X509Data>\n </KeyInfo>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes128-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes192-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#aes256-cbc\"></EncryptionMethod>\n <EncryptionMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p\"></EncryptionMethod>\n </KeyDescriptor>\n <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\" Location=\"http://localhost:8000/sso\"></SingleSignOnService>\n </IDPSSODescriptor>\n</EntityDescriptor>"),
|
||||
TransientMappingAttributeName: transientMappingAttributeName,
|
||||
IDPOptions: idp.Options{
|
||||
IsLinkingAllowed: true,
|
||||
IsCreationAllowed: true,
|
||||
|
@ -155,12 +155,14 @@ type AppleIDPTemplate struct {
|
||||
}
|
||||
|
||||
type SAMLIDPTemplate struct {
|
||||
IDPID string
|
||||
Metadata []byte
|
||||
Key *crypto.CryptoValue
|
||||
Certificate []byte
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
IDPID string
|
||||
Metadata []byte
|
||||
Key *crypto.CryptoValue
|
||||
Certificate []byte
|
||||
Binding string
|
||||
WithSignedRequest bool
|
||||
NameIDFormat sql.Null[domain.SAMLNameIDFormat]
|
||||
TransientMappingAttributeName string
|
||||
}
|
||||
|
||||
var (
|
||||
@ -700,6 +702,14 @@ var (
|
||||
name: projection.SAMLWithSignedRequestCol,
|
||||
table: samlIdpTemplateTable,
|
||||
}
|
||||
SAMLNameIDFormatCol = Column{
|
||||
name: projection.SAMLNameIDFormatCol,
|
||||
table: samlIdpTemplateTable,
|
||||
}
|
||||
SAMLTransientMappingAttributeNameCol = Column{
|
||||
name: projection.SAMLTransientMappingAttributeName,
|
||||
table: samlIdpTemplateTable,
|
||||
}
|
||||
)
|
||||
|
||||
// IDPTemplateByID searches for the requested id
|
||||
@ -883,6 +893,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
SAMLCertificateCol.identifier(),
|
||||
SAMLBindingCol.identifier(),
|
||||
SAMLWithSignedRequestCol.identifier(),
|
||||
SAMLNameIDFormatCol.identifier(),
|
||||
SAMLTransientMappingAttributeNameCol.identifier(),
|
||||
// ldap
|
||||
LDAPIDCol.identifier(),
|
||||
LDAPServersCol.identifier(),
|
||||
@ -997,6 +1009,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
var samlCertificate []byte
|
||||
samlBinding := sql.NullString{}
|
||||
samlWithSignedRequest := sql.NullBool{}
|
||||
samlNameIDFormat := sql.Null[domain.SAMLNameIDFormat]{}
|
||||
samlTransientMappingAttributeName := sql.NullString{}
|
||||
|
||||
ldapID := sql.NullString{}
|
||||
ldapServers := database.TextArray[string]{}
|
||||
@ -1109,6 +1123,8 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
&samlCertificate,
|
||||
&samlBinding,
|
||||
&samlWithSignedRequest,
|
||||
&samlNameIDFormat,
|
||||
&samlTransientMappingAttributeName,
|
||||
// ldap
|
||||
&ldapID,
|
||||
&ldapServers,
|
||||
@ -1237,12 +1253,14 @@ func prepareIDPTemplateByIDQuery(ctx context.Context, db prepareDatabase) (sq.Se
|
||||
}
|
||||
if samlID.Valid {
|
||||
idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{
|
||||
IDPID: samlID.String,
|
||||
Metadata: samlMetadata,
|
||||
Key: samlKey,
|
||||
Certificate: samlCertificate,
|
||||
Binding: samlBinding.String,
|
||||
WithSignedRequest: samlWithSignedRequest.Bool,
|
||||
IDPID: samlID.String,
|
||||
Metadata: samlMetadata,
|
||||
Key: samlKey,
|
||||
Certificate: samlCertificate,
|
||||
Binding: samlBinding.String,
|
||||
WithSignedRequest: samlWithSignedRequest.Bool,
|
||||
NameIDFormat: samlNameIDFormat,
|
||||
TransientMappingAttributeName: samlTransientMappingAttributeName.String,
|
||||
}
|
||||
}
|
||||
if ldapID.Valid {
|
||||
@ -1370,6 +1388,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
SAMLCertificateCol.identifier(),
|
||||
SAMLBindingCol.identifier(),
|
||||
SAMLWithSignedRequestCol.identifier(),
|
||||
SAMLNameIDFormatCol.identifier(),
|
||||
SAMLTransientMappingAttributeNameCol.identifier(),
|
||||
// ldap
|
||||
LDAPIDCol.identifier(),
|
||||
LDAPServersCol.identifier(),
|
||||
@ -1489,6 +1509,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
var samlCertificate []byte
|
||||
samlBinding := sql.NullString{}
|
||||
samlWithSignedRequest := sql.NullBool{}
|
||||
samlNameIDFormat := sql.Null[domain.SAMLNameIDFormat]{}
|
||||
samlTransientMappingAttributeName := sql.NullString{}
|
||||
|
||||
ldapID := sql.NullString{}
|
||||
ldapServers := database.TextArray[string]{}
|
||||
@ -1601,6 +1623,8 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
&samlCertificate,
|
||||
&samlBinding,
|
||||
&samlWithSignedRequest,
|
||||
&samlNameIDFormat,
|
||||
&samlTransientMappingAttributeName,
|
||||
// ldap
|
||||
&ldapID,
|
||||
&ldapServers,
|
||||
@ -1728,12 +1752,14 @@ func prepareIDPTemplatesQuery(ctx context.Context, db prepareDatabase) (sq.Selec
|
||||
}
|
||||
if samlID.Valid {
|
||||
idpTemplate.SAMLIDPTemplate = &SAMLIDPTemplate{
|
||||
IDPID: samlID.String,
|
||||
Metadata: samlMetadata,
|
||||
Key: samlKey,
|
||||
Certificate: samlCertificate,
|
||||
Binding: samlBinding.String,
|
||||
WithSignedRequest: samlWithSignedRequest.Bool,
|
||||
IDPID: samlID.String,
|
||||
Metadata: samlMetadata,
|
||||
Key: samlKey,
|
||||
Certificate: samlCertificate,
|
||||
Binding: samlBinding.String,
|
||||
WithSignedRequest: samlWithSignedRequest.Bool,
|
||||
NameIDFormat: samlNameIDFormat,
|
||||
TransientMappingAttributeName: samlTransientMappingAttributeName.String,
|
||||
}
|
||||
}
|
||||
if ldapID.Valid {
|
||||
|
@ -95,6 +95,8 @@ var (
|
||||
` projections.idp_templates6_saml.certificate,` +
|
||||
` projections.idp_templates6_saml.binding,` +
|
||||
` projections.idp_templates6_saml.with_signed_request,` +
|
||||
` projections.idp_templates6_saml.name_id_format,` +
|
||||
` projections.idp_templates6_saml.transient_mapping_attribute_name,` +
|
||||
// ldap
|
||||
` projections.idp_templates6_ldap2.idp_id,` +
|
||||
` projections.idp_templates6_ldap2.servers,` +
|
||||
@ -220,6 +222,8 @@ var (
|
||||
"certificate",
|
||||
"binding",
|
||||
"with_signed_request",
|
||||
"name_id_format",
|
||||
"transient_mapping_attribute_name",
|
||||
// ldap config
|
||||
"idp_id",
|
||||
"servers",
|
||||
@ -331,6 +335,8 @@ var (
|
||||
` projections.idp_templates6_saml.certificate,` +
|
||||
` projections.idp_templates6_saml.binding,` +
|
||||
` projections.idp_templates6_saml.with_signed_request,` +
|
||||
` projections.idp_templates6_saml.name_id_format,` +
|
||||
` projections.idp_templates6_saml.transient_mapping_attribute_name,` +
|
||||
// ldap
|
||||
` projections.idp_templates6_ldap2.idp_id,` +
|
||||
` projections.idp_templates6_ldap2.servers,` +
|
||||
@ -457,6 +463,8 @@ var (
|
||||
"certificate",
|
||||
"binding",
|
||||
"with_signed_request",
|
||||
"name_id_format",
|
||||
"transient_mapping_attribute_name",
|
||||
// ldap config
|
||||
"idp_id",
|
||||
"servers",
|
||||
@ -608,6 +616,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -756,6 +766,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -902,6 +914,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -1047,6 +1061,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -1191,6 +1207,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -1335,6 +1353,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -1480,6 +1500,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -1624,6 +1646,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
"binding",
|
||||
false,
|
||||
domain.SAMLNameIDFormatTransient,
|
||||
"customAttribute",
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -1674,12 +1698,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: domain.AutoLinkingOptionUsername,
|
||||
SAMLIDPTemplate: &SAMLIDPTemplate{
|
||||
IDPID: "idp-id",
|
||||
Metadata: []byte("metadata"),
|
||||
Key: nil,
|
||||
Certificate: nil,
|
||||
Binding: "binding",
|
||||
WithSignedRequest: false,
|
||||
IDPID: "idp-id",
|
||||
Metadata: []byte("metadata"),
|
||||
Key: nil,
|
||||
Certificate: nil,
|
||||
Binding: "binding",
|
||||
WithSignedRequest: false,
|
||||
NameIDFormat: sql.Null[domain.SAMLNameIDFormat]{V: domain.SAMLNameIDFormatTransient, Valid: true},
|
||||
TransientMappingAttributeName: "customAttribute",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1770,6 +1796,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
"idp-id",
|
||||
database.TextArray[string]{"server"},
|
||||
@ -1934,6 +1962,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -2080,6 +2110,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -2254,6 +2286,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
"idp-id",
|
||||
database.TextArray[string]{"server"},
|
||||
@ -2427,6 +2461,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -2574,6 +2610,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
"idp-id-ldap",
|
||||
database.TextArray[string]{"server"},
|
||||
@ -2686,6 +2724,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
"binding",
|
||||
false,
|
||||
domain.SAMLNameIDFormatTransient,
|
||||
"customAttribute",
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -2798,6 +2838,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -2910,6 +2952,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -3022,6 +3066,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -3134,6 +3180,8 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
nil,
|
||||
// ldap config
|
||||
nil,
|
||||
nil,
|
||||
@ -3232,12 +3280,14 @@ func Test_IDPTemplateTemplatesPrepares(t *testing.T) {
|
||||
IsAutoUpdate: true,
|
||||
AutoLinking: domain.AutoLinkingOptionUsername,
|
||||
SAMLIDPTemplate: &SAMLIDPTemplate{
|
||||
IDPID: "idp-id-saml",
|
||||
Metadata: []byte("metadata"),
|
||||
Key: nil,
|
||||
Certificate: nil,
|
||||
Binding: "binding",
|
||||
WithSignedRequest: false,
|
||||
IDPID: "idp-id-saml",
|
||||
Metadata: []byte("metadata"),
|
||||
Key: nil,
|
||||
Certificate: nil,
|
||||
Binding: "binding",
|
||||
WithSignedRequest: false,
|
||||
NameIDFormat: sql.Null[domain.SAMLNameIDFormat]{V: domain.SAMLNameIDFormatTransient, Valid: true},
|
||||
TransientMappingAttributeName: "customAttribute",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -161,13 +161,15 @@ const (
|
||||
ApplePrivateKeyCol = "private_key"
|
||||
AppleScopesCol = "scopes"
|
||||
|
||||
SAMLIDCol = "idp_id"
|
||||
SAMLInstanceIDCol = "instance_id"
|
||||
SAMLMetadataCol = "metadata"
|
||||
SAMLKeyCol = "key"
|
||||
SAMLCertificateCol = "certificate"
|
||||
SAMLBindingCol = "binding"
|
||||
SAMLWithSignedRequestCol = "with_signed_request"
|
||||
SAMLIDCol = "idp_id"
|
||||
SAMLInstanceIDCol = "instance_id"
|
||||
SAMLMetadataCol = "metadata"
|
||||
SAMLKeyCol = "key"
|
||||
SAMLCertificateCol = "certificate"
|
||||
SAMLBindingCol = "binding"
|
||||
SAMLWithSignedRequestCol = "with_signed_request"
|
||||
SAMLNameIDFormatCol = "name_id_format"
|
||||
SAMLTransientMappingAttributeName = "transient_mapping_attribute_name"
|
||||
)
|
||||
|
||||
type idpTemplateProjection struct{}
|
||||
@ -367,6 +369,8 @@ func (*idpTemplateProjection) Init() *old_handler.Check {
|
||||
handler.NewColumn(SAMLCertificateCol, handler.ColumnTypeBytes),
|
||||
handler.NewColumn(SAMLBindingCol, handler.ColumnTypeText, handler.Nullable()),
|
||||
handler.NewColumn(SAMLWithSignedRequestCol, handler.ColumnTypeBool, handler.Nullable()),
|
||||
handler.NewColumn(SAMLNameIDFormatCol, handler.ColumnTypeEnum, handler.Nullable()),
|
||||
handler.NewColumn(SAMLTransientMappingAttributeName, handler.ColumnTypeText, handler.Nullable()),
|
||||
},
|
||||
handler.NewPrimaryKey(SAMLInstanceIDCol, SAMLIDCol),
|
||||
IDPTemplateSAMLSuffix,
|
||||
@ -1967,6 +1971,20 @@ func (p *idpTemplateProjection) reduceSAMLIDPAdded(event eventstore.Event) (*han
|
||||
return nil, zerrors.ThrowInvalidArgumentf(nil, "HANDL-9s02m1", "reduce.wrong.event.type %v", []eventstore.EventType{org.SAMLIDPAddedEventType, instance.SAMLIDPAddedEventType})
|
||||
}
|
||||
|
||||
columns := []handler.Column{
|
||||
handler.NewCol(SAMLIDCol, idpEvent.ID),
|
||||
handler.NewCol(SAMLInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(SAMLMetadataCol, idpEvent.Metadata),
|
||||
handler.NewCol(SAMLKeyCol, idpEvent.Key),
|
||||
handler.NewCol(SAMLCertificateCol, idpEvent.Certificate),
|
||||
handler.NewCol(SAMLBindingCol, idpEvent.Binding),
|
||||
handler.NewCol(SAMLWithSignedRequestCol, idpEvent.WithSignedRequest),
|
||||
handler.NewCol(SAMLTransientMappingAttributeName, idpEvent.TransientMappingAttributeName),
|
||||
}
|
||||
if idpEvent.NameIDFormat != nil {
|
||||
columns = append(columns, handler.NewCol(SAMLNameIDFormatCol, *idpEvent.NameIDFormat))
|
||||
}
|
||||
|
||||
return handler.NewMultiStatement(
|
||||
&idpEvent,
|
||||
handler.AddCreateStatement(
|
||||
@ -1989,15 +2007,7 @@ func (p *idpTemplateProjection) reduceSAMLIDPAdded(event eventstore.Event) (*han
|
||||
},
|
||||
),
|
||||
handler.AddCreateStatement(
|
||||
[]handler.Column{
|
||||
handler.NewCol(SAMLIDCol, idpEvent.ID),
|
||||
handler.NewCol(SAMLInstanceIDCol, idpEvent.Aggregate().InstanceID),
|
||||
handler.NewCol(SAMLMetadataCol, idpEvent.Metadata),
|
||||
handler.NewCol(SAMLKeyCol, idpEvent.Key),
|
||||
handler.NewCol(SAMLCertificateCol, idpEvent.Certificate),
|
||||
handler.NewCol(SAMLBindingCol, idpEvent.Binding),
|
||||
handler.NewCol(SAMLWithSignedRequestCol, idpEvent.WithSignedRequest),
|
||||
},
|
||||
columns,
|
||||
handler.WithTableSuffix(IDPTemplateSAMLSuffix),
|
||||
),
|
||||
), nil
|
||||
@ -2490,5 +2500,11 @@ func reduceSAMLIDPChangedColumns(idpEvent idp.SAMLIDPChangedEvent) []handler.Col
|
||||
if idpEvent.WithSignedRequest != nil {
|
||||
SAMLCols = append(SAMLCols, handler.NewCol(SAMLWithSignedRequestCol, *idpEvent.WithSignedRequest))
|
||||
}
|
||||
if idpEvent.NameIDFormat != nil {
|
||||
SAMLCols = append(SAMLCols, handler.NewCol(SAMLNameIDFormatCol, *idpEvent.NameIDFormat))
|
||||
}
|
||||
if idpEvent.TransientMappingAttributeName != nil {
|
||||
SAMLCols = append(SAMLCols, handler.NewCol(SAMLTransientMappingAttributeName, *idpEvent.TransientMappingAttributeName))
|
||||
}
|
||||
return SAMLCols
|
||||
}
|
||||
|
@ -2774,6 +2774,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) {
|
||||
},
|
||||
"certificate": `+stringToJSONByte("certificate")+`,
|
||||
"binding": "binding",
|
||||
"nameIDFormat": 3,
|
||||
"transientMappingAttributeName": "customAttribute",
|
||||
"withSignedRequest": true,
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
@ -2810,7 +2812,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request, transient_mapping_attribute_name, name_id_format) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
@ -2819,6 +2821,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) {
|
||||
anyArg{},
|
||||
"binding",
|
||||
true,
|
||||
"customAttribute",
|
||||
domain.SAMLNameIDFormatTransient,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2842,6 +2846,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) {
|
||||
},
|
||||
"certificate": `+stringToJSONByte("certificate")+`,
|
||||
"binding": "binding",
|
||||
"nameIDFormat": 3,
|
||||
"transientMappingAttributeName": "customAttribute",
|
||||
"withSignedRequest": true,
|
||||
"isCreationAllowed": true,
|
||||
"isLinkingAllowed": true,
|
||||
@ -2878,7 +2884,7 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
||||
expectedStmt: "INSERT INTO projections.idp_templates6_saml (idp_id, instance_id, metadata, key, certificate, binding, with_signed_request, transient_mapping_attribute_name, name_id_format) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)",
|
||||
expectedArgs: []interface{}{
|
||||
"idp-id",
|
||||
"instance-id",
|
||||
@ -2887,6 +2893,8 @@ func TestIDPTemplateProjection_reducesSAML(t *testing.T) {
|
||||
anyArg{},
|
||||
"binding",
|
||||
true,
|
||||
"customAttribute",
|
||||
domain.SAMLNameIDFormatTransient,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -2,6 +2,7 @@ package idp
|
||||
|
||||
import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@ -9,13 +10,15 @@ import (
|
||||
type SAMLIDPAddedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Metadata []byte `json:"metadata,omitempty"`
|
||||
Key *crypto.CryptoValue `json:"key,omitempty"`
|
||||
Certificate []byte `json:"certificate,omitempty"`
|
||||
Binding string `json:"binding,omitempty"`
|
||||
WithSignedRequest bool `json:"withSignedRequest,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Metadata []byte `json:"metadata,omitempty"`
|
||||
Key *crypto.CryptoValue `json:"key,omitempty"`
|
||||
Certificate []byte `json:"certificate,omitempty"`
|
||||
Binding string `json:"binding,omitempty"`
|
||||
WithSignedRequest bool `json:"withSignedRequest,omitempty"`
|
||||
NameIDFormat *domain.SAMLNameIDFormat `json:"nameIDFormat,omitempty"`
|
||||
TransientMappingAttributeName string `json:"transientMappingAttributeName,omitempty"`
|
||||
Options
|
||||
}
|
||||
|
||||
@ -28,18 +31,22 @@ func NewSAMLIDPAddedEvent(
|
||||
certificate []byte,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
nameIDFormat *domain.SAMLNameIDFormat,
|
||||
transientMappingAttributeName string,
|
||||
options Options,
|
||||
) *SAMLIDPAddedEvent {
|
||||
return &SAMLIDPAddedEvent{
|
||||
BaseEvent: *base,
|
||||
ID: id,
|
||||
Name: name,
|
||||
Metadata: metadata,
|
||||
Key: key,
|
||||
Certificate: certificate,
|
||||
Binding: binding,
|
||||
WithSignedRequest: withSignedRequest,
|
||||
Options: options,
|
||||
BaseEvent: *base,
|
||||
ID: id,
|
||||
Name: name,
|
||||
Metadata: metadata,
|
||||
Key: key,
|
||||
Certificate: certificate,
|
||||
Binding: binding,
|
||||
WithSignedRequest: withSignedRequest,
|
||||
NameIDFormat: nameIDFormat,
|
||||
TransientMappingAttributeName: transientMappingAttributeName,
|
||||
Options: options,
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,13 +74,15 @@ func SAMLIDPAddedEventMapper(event eventstore.Event) (eventstore.Event, error) {
|
||||
type SAMLIDPChangedEvent struct {
|
||||
eventstore.BaseEvent `json:"-"`
|
||||
|
||||
ID string `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Metadata []byte `json:"metadata,omitempty"`
|
||||
Key *crypto.CryptoValue `json:"key,omitempty"`
|
||||
Certificate []byte `json:"certificate,omitempty"`
|
||||
Binding *string `json:"binding,omitempty"`
|
||||
WithSignedRequest *bool `json:"withSignedRequest,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Name *string `json:"name,omitempty"`
|
||||
Metadata []byte `json:"metadata,omitempty"`
|
||||
Key *crypto.CryptoValue `json:"key,omitempty"`
|
||||
Certificate []byte `json:"certificate,omitempty"`
|
||||
Binding *string `json:"binding,omitempty"`
|
||||
WithSignedRequest *bool `json:"withSignedRequest,omitempty"`
|
||||
NameIDFormat *domain.SAMLNameIDFormat `json:"nameIDFormat,omitempty"`
|
||||
TransientMappingAttributeName *string `json:"transientMappingAttributeName,omitempty"`
|
||||
OptionChanges
|
||||
}
|
||||
|
||||
@ -133,6 +142,18 @@ func ChangeSAMLWithSignedRequest(withSignedRequest bool) func(*SAMLIDPChangedEve
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSAMLNameIDFormat(nameIDFormat *domain.SAMLNameIDFormat) func(*SAMLIDPChangedEvent) {
|
||||
return func(e *SAMLIDPChangedEvent) {
|
||||
e.NameIDFormat = nameIDFormat
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSAMLTransientMappingAttributeName(name string) func(*SAMLIDPChangedEvent) {
|
||||
return func(e *SAMLIDPChangedEvent) {
|
||||
e.TransientMappingAttributeName = &name
|
||||
}
|
||||
}
|
||||
|
||||
func ChangeSAMLOptions(options OptionChanges) func(*SAMLIDPChangedEvent) {
|
||||
return func(e *SAMLIDPChangedEvent) {
|
||||
e.OptionChanges = options
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
)
|
||||
@ -1016,6 +1017,8 @@ func NewSAMLIDPAddedEvent(
|
||||
certificate []byte,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
nameIDFormat *domain.SAMLNameIDFormat,
|
||||
transientMappingAttributeName string,
|
||||
options idp.Options,
|
||||
) *SAMLIDPAddedEvent {
|
||||
return &SAMLIDPAddedEvent{
|
||||
@ -1032,6 +1035,8 @@ func NewSAMLIDPAddedEvent(
|
||||
certificate,
|
||||
binding,
|
||||
withSignedRequest,
|
||||
nameIDFormat,
|
||||
transientMappingAttributeName,
|
||||
options,
|
||||
),
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/repository/idp"
|
||||
)
|
||||
@ -1017,6 +1018,8 @@ func NewSAMLIDPAddedEvent(
|
||||
certificate []byte,
|
||||
binding string,
|
||||
withSignedRequest bool,
|
||||
nameIDFormat *domain.SAMLNameIDFormat,
|
||||
transientMappingAttributeName string,
|
||||
options idp.Options,
|
||||
) *SAMLIDPAddedEvent {
|
||||
|
||||
@ -1034,6 +1037,8 @@ func NewSAMLIDPAddedEvent(
|
||||
certificate,
|
||||
binding,
|
||||
withSignedRequest,
|
||||
nameIDFormat,
|
||||
transientMappingAttributeName,
|
||||
options,
|
||||
),
|
||||
}
|
||||
|
@ -534,6 +534,7 @@ Errors:
|
||||
IDPMissing: IDP липсва в заявката
|
||||
IDPInvalid: IDP невалиден за заявката
|
||||
ResponseInvalid: Отговорът на IDP е невалиден
|
||||
MissingSingleMappingAttribute: Не съдържа атрибута за съпоставяне или има повече от една стойност
|
||||
SuccessURLMissing: В заявката липсва URL адрес за успех
|
||||
FailureURLMissing: В заявката липсва URL адрес за грешка
|
||||
StateMissing: В заявката липсва параметър състояние
|
||||
|
@ -514,6 +514,7 @@ Errors:
|
||||
IDPMissing: V požadavku chybí IDP ID
|
||||
IDPInvalid: IDP je pro požadavek neplatné
|
||||
ResponseInvalid: Odpověď IDP je neplatná
|
||||
MissingSingleMappingAttribute: Neobsahuje atribut mapování nebo má více než jednu hodnotu
|
||||
SuccessURLMissing: V požadavku chybí úspěšná URL
|
||||
FailureURLMissing: V požadavku chybí URL selhání
|
||||
StateMissing: V požadavku chybí parametr stavu
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: IDP ID fehlt im Request
|
||||
IDPInvalid: IDP ungültig für die Anfrage
|
||||
ResponseInvalid: IDP-Antwort ist ungültig
|
||||
MissingSingleMappingAttribute: Enthält das Zuordnungsattribut nicht oder hat mehr als einen Wert
|
||||
SuccessURLMissing: Success URL fehlt im Request
|
||||
FailureURLMissing: Failure URL fehlt im Request
|
||||
StateMissing: State parameter fehlt im Request
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: IDP ID is missing in the request
|
||||
IDPInvalid: IDP invalid for the request
|
||||
ResponseInvalid: IDP response is invalid
|
||||
MissingSingleMappingAttribute: IDP response does not contain the mapping attribute or has more than one value
|
||||
SuccessURLMissing: Success URL is missing in the request
|
||||
FailureURLMissing: Failure URL is missing in the request
|
||||
StateMissing: State parameter is missing in the request
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: Falta IDP en la solicitud
|
||||
IDPInvalid: IDP no válido para la solicitud
|
||||
ResponseInvalid: La respuesta del IDP no es válida
|
||||
MissingSingleMappingAttribute: No contiene el atributo de asignación o tiene más de un valor
|
||||
SuccessURLMissing: Falta la URL de éxito en la solicitud
|
||||
FailureURLMissing: Falta la URL de error en la solicitud
|
||||
StateMissing: Falta un parámetro de estado en la solicitud
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: IDP manquant dans la requête
|
||||
IDPInvalid: IDP non valide pour la demande
|
||||
ResponseInvalid: La réponse de l'IDP n'est pas valide
|
||||
MissingSingleMappingAttribute: Ne contient pas l'attribut de mappage ou a plus d'une valeur
|
||||
SuccessURLMissing: Success URL absent de la requête
|
||||
FailureURLMissing: Failure URL absent de la requête
|
||||
StateMissing: Paramètre d'état manquant dans la requête
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: IDP mancante nella richiesta
|
||||
IDPInvalid: IDP non valido per la richiesta
|
||||
ResponseInvalid: La risposta dell'IDP non è valida
|
||||
MissingSingleMappingAttribute: Non contiene l'attributo di mapping o ha più di un valore
|
||||
SuccessURLMissing: URL di successo mancante nella richiesta
|
||||
FailureURLMissing: URL di errore mancante nella richiesta
|
||||
StateMissing: parametro di stato mancante nella richiesta
|
||||
|
@ -505,6 +505,7 @@ Errors:
|
||||
IDPMissing: リクエストにIDP IDが含まれていません
|
||||
IDPInvalid: リクエストのIDPが無効
|
||||
ResponseInvalid: IDPの回答は無効
|
||||
MissingSingleMappingAttribute: マッピング属性が含まれていない、または複数の値がある
|
||||
SuccessURLMissing: リクエストに成功時の URL がありません
|
||||
FailureURLMissing: リクエストに失敗の URL がありません
|
||||
StateMissing: リクエストに State パラメータがありません
|
||||
|
@ -515,6 +515,7 @@ Errors:
|
||||
IDPMissing: ID на IDP недостасува во барањето6bg
|
||||
IDPInvalid: ВРЛ неважечки за барањето
|
||||
ResponseInvalid: Одговорот на ВРЛ е неважечки
|
||||
MissingSingleMappingAttribute: не го содржи атрибутот за мапирање или има повеќе од една вредност
|
||||
SuccessURLMissing: URL за успех недостасува во барањето
|
||||
FailureURLMissing: URL за неуспех недостасува во барањето
|
||||
StateMissing: Параметарот State недостасува во барањето
|
||||
|
@ -515,6 +515,7 @@ Errors:
|
||||
IDPMissing: IDP ID ontbreekt in het verzoek
|
||||
IDPInvalid: IDP ongeldig voor het verzoek
|
||||
ResponseInvalid: IDP respons is ongeldig
|
||||
MissingSingleMappingAttribute: Bevat kenmerk toewijzing niet of heeft meer dan één waarde
|
||||
SuccessURLMissing: Success URL ontbreekt in het verzoek
|
||||
FailureURLMissing: Failure URL ontbreekt in het verzoek
|
||||
StateMissing: Staat parameter ontbreekt in het verzoek
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: Brak identyfikatora IDP w żądaniu
|
||||
IDPInvalid: IDP nieprawidłowe dla żądania
|
||||
ResponseInvalid: Odpowiedź IDP jest nieprawidłowa
|
||||
MissingSingleMappingAttribute: Nie zawiera atrybutu mapowania lub ma więcej niż jedną wartość
|
||||
SuccessURLMissing: Brak adresu URL powodzenia w żądaniu
|
||||
FailureURLMissing: Brak adresu URL niepowodzenia w żądaniu
|
||||
StateMissing: Brak parametru stanu w żądaniu
|
||||
|
@ -515,6 +515,7 @@ Errors:
|
||||
IDPMissing: O ID do IDP está faltando na solicitação
|
||||
IDPInvalid: IDP inválido para o pedido
|
||||
ResponseInvalid: A resposta da PDI é inválida
|
||||
MissingSingleMappingAttribute: Não contém o atributo de mapeamento ou tem mais de um valor
|
||||
SuccessURLMissing: A URL de sucesso está faltando na solicitação
|
||||
FailureURLMissing: A URL de falha está faltando na solicitação
|
||||
StateMissing: O parâmetro de estado está faltando na solicitação
|
||||
|
@ -505,6 +505,7 @@ Errors:
|
||||
NoChallenge: Сеанс без вызова WebAuthN
|
||||
Intent:
|
||||
IDPMissing: В запросе отсутствует идентификатор IDP
|
||||
MissingSingleMappingAttribute: Не содержит атрибут сопоставления или имеет более одного значения
|
||||
SuccessURLMissing: В запросе отсутствует URL-адрес успешного выполнения
|
||||
FailureURLMissing: В запросе отсутствует URL-адрес ошибки
|
||||
StateMissing: В запросе отсутствует параметр State
|
||||
|
@ -516,6 +516,7 @@ Errors:
|
||||
IDPMissing: 请求中缺少IDP ID
|
||||
IDPInvalid: 请求的 IDP 无效
|
||||
ResponseInvalid: IDP 响应无效
|
||||
MissingSingleMappingAttribute: 不包含映射属性或具有多个值
|
||||
SuccessURLMissing: 请求中缺少成功URL
|
||||
FailureURLMissing: 请求中缺少失败的URL
|
||||
StateMissing: 请求中缺少状态参数
|
||||
|
@ -6028,31 +6028,28 @@ message AddSAMLProviderRequest {
|
||||
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
oneof metadata {
|
||||
option (validate.required) = true;
|
||||
// Metadata of the SAML identity provider.
|
||||
bytes metadata_xml = 2 [
|
||||
(validate.rules).bytes.max_len = 500000,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Metadata of the SAML identity provider";
|
||||
}
|
||||
(validate.rules).bytes.max_len = 500000
|
||||
];
|
||||
// Url to the metadata of the SAML identity provider.
|
||||
string metadata_url = 3 [
|
||||
(validate.rules).string.max_len = 200,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://test.com/saml/metadata\""
|
||||
description: "Url to the metadata of the SAML identity provider";
|
||||
}
|
||||
];
|
||||
}
|
||||
zitadel.idp.v1.SAMLBinding binding = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Binding which defines the type of communication with the identity provider";
|
||||
}
|
||||
];
|
||||
bool with_signed_request = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Boolean which defines if the authentication requests are signed";
|
||||
}
|
||||
];
|
||||
// Binding which defines the type of communication with the identity provider.
|
||||
zitadel.idp.v1.SAMLBinding binding = 4;
|
||||
// Boolean which defines if the authentication requests are signed.
|
||||
bool with_signed_request = 5;
|
||||
zitadel.idp.v1.Options provider_options = 6;
|
||||
// Optionally specify the `nameid-format` requested.
|
||||
optional zitadel.idp.v1.SAMLNameIDFormat name_id_format = 7;
|
||||
// Optionally specify the name of the attribute, which will be used to map the user
|
||||
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
|
||||
optional string transient_mapping_attribute_name = 8;
|
||||
}
|
||||
|
||||
message AddSAMLProviderResponse {
|
||||
@ -6063,33 +6060,30 @@ message AddSAMLProviderResponse {
|
||||
message UpdateSAMLProviderRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
// Metadata of the SAML identity provider.
|
||||
oneof metadata {
|
||||
option (validate.required) = true;
|
||||
bytes metadata_xml = 3 [
|
||||
(validate.rules).bytes.max_len = 500000,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Metadata of the SAML identity provider";
|
||||
}
|
||||
(validate.rules).bytes.max_len = 500000
|
||||
];
|
||||
// Url to the metadata of the SAML identity provider
|
||||
string metadata_url = 4 [
|
||||
(validate.rules).string.max_len = 200,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://test.com/saml/metadata\""
|
||||
description: "Url to the metadata of the SAML identity provider";
|
||||
}
|
||||
];
|
||||
}
|
||||
zitadel.idp.v1.SAMLBinding binding = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Binding which defines the type of communication with the identity provider";
|
||||
}
|
||||
];
|
||||
bool with_signed_request = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Boolean which defines if the authentication requests are signed";
|
||||
}
|
||||
];
|
||||
// Binding which defines the type of communication with the identity provider.
|
||||
zitadel.idp.v1.SAMLBinding binding = 5;
|
||||
// Boolean which defines if the authentication requests are signed
|
||||
bool with_signed_request = 6;
|
||||
zitadel.idp.v1.Options provider_options = 7;
|
||||
// Optionally specify the `nameid-format` requested.
|
||||
optional zitadel.idp.v1.SAMLNameIDFormat name_id_format = 8;
|
||||
// Optionally specify the name of the attribute, which will be used to map the user
|
||||
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
|
||||
optional string transient_mapping_attribute_name = 9;
|
||||
}
|
||||
|
||||
message UpdateSAMLProviderResponse {
|
||||
|
@ -276,6 +276,13 @@ enum SAMLBinding {
|
||||
SAML_BINDING_ARTIFACT = 3;
|
||||
}
|
||||
|
||||
enum SAMLNameIDFormat {
|
||||
SAML_NAME_ID_FORMAT_UNSPECIFIED = 0;
|
||||
SAML_NAME_ID_FORMAT_EMAIL_ADDRESS = 1;
|
||||
SAML_NAME_ID_FORMAT_PERSISTENT = 2;
|
||||
SAML_NAME_ID_FORMAT_TRANSIENT = 3;
|
||||
}
|
||||
|
||||
message ProviderConfig {
|
||||
Options options = 1;
|
||||
oneof config {
|
||||
@ -452,21 +459,17 @@ message LDAPConfig {
|
||||
}
|
||||
|
||||
message SAMLConfig {
|
||||
bytes metadata_xml = 1 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Metadata of the SAML identity provider";
|
||||
}
|
||||
];
|
||||
zitadel.idp.v1.SAMLBinding binding = 2 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Binding which defines the type of communication with the identity provider";
|
||||
}
|
||||
];
|
||||
bool with_signed_request = 3 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Boolean which defines if the authentication requests are signed";
|
||||
}
|
||||
];
|
||||
// Metadata of the SAML identity provider.
|
||||
bytes metadata_xml = 1;
|
||||
// Binding which defines the type of communication with the identity provider.
|
||||
zitadel.idp.v1.SAMLBinding binding = 2;
|
||||
// Boolean which defines if the authentication requests are signed.
|
||||
bool with_signed_request = 3;
|
||||
// `nameid-format` for the SAML Request.
|
||||
zitadel.idp.v1.SAMLNameIDFormat name_id_format = 4;
|
||||
// Optional name of the attribute, which will be used to map the user
|
||||
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
|
||||
optional string transient_mapping_attribute_name = 5;
|
||||
}
|
||||
|
||||
message AzureADConfig {
|
||||
|
@ -12767,31 +12767,28 @@ message AddSAMLProviderRequest {
|
||||
string name = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
oneof metadata {
|
||||
option (validate.required) = true;
|
||||
// Metadata of the SAML identity provider.
|
||||
bytes metadata_xml = 2 [
|
||||
(validate.rules).bytes.max_len = 500000,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Metadata of the SAML identity provider";
|
||||
}
|
||||
(validate.rules).bytes.max_len = 500000
|
||||
];
|
||||
// Url to the metadata of the SAML identity provider.
|
||||
string metadata_url = 3 [
|
||||
(validate.rules).string.max_len = 200,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://test.com/saml/metadata\""
|
||||
description: "Url to the metadata of the SAML identity provider";
|
||||
}
|
||||
];
|
||||
}
|
||||
zitadel.idp.v1.SAMLBinding binding = 4 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Binding which defines the type of communication with the identity provider";
|
||||
}
|
||||
];
|
||||
bool with_signed_request = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Boolean which defines if the authentication requests are signed";
|
||||
}
|
||||
];
|
||||
// Binding which defines the type of communication with the identity provider.
|
||||
zitadel.idp.v1.SAMLBinding binding = 4;
|
||||
// Boolean which defines if the authentication requests are signed.
|
||||
bool with_signed_request = 5;
|
||||
zitadel.idp.v1.Options provider_options = 6;
|
||||
// Optionally specify the `nameid-format` requested.
|
||||
optional zitadel.idp.v1.SAMLNameIDFormat name_id_format = 7;
|
||||
// Optionally specify the name of the attribute, which will be used to map the user
|
||||
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
|
||||
optional string transient_mapping_attribute_name = 8;
|
||||
}
|
||||
|
||||
message AddSAMLProviderResponse {
|
||||
@ -12802,33 +12799,30 @@ message AddSAMLProviderResponse {
|
||||
message UpdateSAMLProviderRequest {
|
||||
string id = 1 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
string name = 2 [(validate.rules).string = {min_len: 1, max_len: 200}];
|
||||
// Metadata of the SAML identity provider.
|
||||
oneof metadata {
|
||||
option (validate.required) = true;
|
||||
bytes metadata_xml = 3 [
|
||||
(validate.rules).bytes.max_len = 500000,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Metadata of the SAML identity provider";
|
||||
}
|
||||
(validate.rules).bytes.max_len = 500000
|
||||
];
|
||||
// Url to the metadata of the SAML identity provider.
|
||||
string metadata_url = 4 [
|
||||
(validate.rules).string.max_len = 200,
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
example: "\"https://test.com/saml/metadata\""
|
||||
description: "Url to the metadata of the SAML identity provider";
|
||||
}
|
||||
];
|
||||
}
|
||||
zitadel.idp.v1.SAMLBinding binding = 5 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Binding which defines the type of communication with the identity provider";
|
||||
}
|
||||
];
|
||||
bool with_signed_request = 6 [
|
||||
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
|
||||
description: "Boolean which defines if the authentication requests are signed";
|
||||
}
|
||||
];
|
||||
// Binding which defines the type of communication with the identity provider.
|
||||
zitadel.idp.v1.SAMLBinding binding = 5;
|
||||
// Boolean which defines if the authentication requests are signed.
|
||||
bool with_signed_request = 6;
|
||||
zitadel.idp.v1.Options provider_options = 7;
|
||||
// Optionally specify the `nameid-format` requested.
|
||||
optional zitadel.idp.v1.SAMLNameIDFormat name_id_format = 8;
|
||||
// Optionally specify the name of the attribute, which will be used to map the user
|
||||
// in case the nameid-format returned is `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`.
|
||||
optional string transient_mapping_attribute_name = 9;
|
||||
}
|
||||
|
||||
message UpdateSAMLProviderResponse {
|
||||
|
Loading…
Reference in New Issue
Block a user