Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/src/i18n/translations/en-US.json
Original file line number Diff line number Diff line change
Expand Up @@ -584,6 +584,10 @@
"sp_metadata_url": {
"label": "SP Metadata URL",
"helper_text": "Some SAML identity providers can accept importing metadata directly using this URL."
},
"sp_issuer_urn": {
"label": "SP Issuer URN",
"helper_text": "Use this as the SP Entity ID (Issuer) in your SAML identity provider."
}
},
"adfs": {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/i18n/translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,10 @@
"sp_metadata_url": {
"label": "URL des métadonnées SP",
"helper_text": "Certains fournisseurs d'identité SAML peuvent importer les métadonnées directement à l'aide de cette URL."
},
"sp_issuer_urn": {
"label": "URN d'émetteur SP",
"helper_text": "Utilisez cette valeur comme ID d'entité SP (émetteur) dans votre fournisseur d'identité SAML."
}
},
"adfs": {
Expand Down
4 changes: 4 additions & 0 deletions packages/core/src/i18n/translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,10 @@
"sp_metadata_url": {
"label": "SP メタデータ URL",
"helper_text": "一部の SAML ID プロバイダーは、この URL を使用してメタデータを直接インポートできます。"
},
"sp_issuer_urn": {
"label": "SP 発行者 URN",
"helper_text": "この値を SAML ID プロバイダーの SP エンティティ ID(発行者)として使用してください。"
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ describe('SamlpProviderForm', () => {
<SamlpProviderForm idpConfig={null} connectionName="my-saml-connection" />,
);

// One copy button per field (CopyableTextField's aria-label resolves to the "copy" key).
expect(screen.getAllByLabelText('copy')).toHaveLength(3);
// One copy button per field — now 4 fields including SP Issuer URN.
expect(screen.getAllByLabelText('copy')).toHaveLength(4);

const callbackInput = screen.getByDisplayValue(
'https://test-domain.auth0.com/login/callback',
Expand Down Expand Up @@ -348,5 +348,56 @@ describe('SamlpProviderForm', () => {
);
});
});

it('should render SP Issuer URN label and helper text', () => {
renderWithProviders(<SamlpProviderForm idpConfig={null} connectionName="my-conn" />);

expect(screen.getByText('fields.samlp.sp_issuer_urn.label')).toBeInTheDocument();
expect(screen.getByText('fields.samlp.sp_issuer_urn.helper_text')).toBeInTheDocument();
});

it('should compute URN fallback from domain when resolveSamlMetadata is not provided', () => {
renderWithProviders(<SamlpProviderForm idpConfig={null} connectionName="my-conn" />, {
authDetails: { domain: 'staff0.auth0.com' },
});

expect(screen.getByDisplayValue('urn:auth0:staff0:my-conn')).toBeInTheDocument();
});

it('should use resolveSamlMetadata when provided', () => {
const resolveSamlMetadata = vi.fn().mockReturnValue({ entityId: 'urn:auth0:acme:my-conn' });

renderWithProviders(
<SamlpProviderForm
idpConfig={null}
connectionName="my-conn"
resolveSamlMetadata={resolveSamlMetadata}
/>,
);

expect(screen.getByDisplayValue('urn:auth0:acme:my-conn')).toBeInTheDocument();
expect(resolveSamlMetadata).toHaveBeenCalledWith({ connectionName: 'my-conn' });
});

it('should display an empty SP Issuer URN when connectionName is absent', () => {
renderWithProviders(<SamlpProviderForm idpConfig={null} />);

// URN field renders but its value is empty without a connectionName.
expect(screen.queryByDisplayValue(/^urn:auth0:/)).not.toBeInTheDocument();
});

it('should not include sp_issuer_urn in formRef.getData() payload', async () => {
const formRef = React.createRef<SamlpConfigureFormHandle>();

renderWithProviders(
<SamlpProviderForm ref={formRef} idpConfig={null} connectionName="my-conn" />,
);

await waitFor(() => {
const data = formRef.current?.getData() as Record<string, unknown>;
expect(data).toBeDefined();
expect(data.sp_issuer_urn).toBeUndefined();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export const SamlpProviderForm = React.forwardRef<
onFormDirty,
idpConfig,
connectionName,
resolveSamlMetadata,
showThirdPartyAccess = false,
isThirdPartyAccessReadOnly = false,
showCrossAppAccess = false,
Expand Down Expand Up @@ -123,6 +124,14 @@ export const SamlpProviderForm = React.forwardRef<
};
}, [coreClient, connectionName]);

const spIssuerUrn = React.useMemo(() => {
if (!connectionName) return '';
if (resolveSamlMetadata) return resolveSamlMetadata({ connectionName }).entityId;
const domain = coreClient?.getDomain();
const tenant = domain?.split('.')[0] ?? '';
return `urn:auth0:${tenant}:${connectionName}`;
Comment thread
chakrihacker marked this conversation as resolved.
}, [resolveSamlMetadata, connectionName, coreClient]);

const [uploadedFiles, setUploadedFiles] = useState<File[]>([]);

const samlpData = initialData as SamlpConfigureFormInput | undefined;
Expand Down Expand Up @@ -553,6 +562,18 @@ export const SamlpProviderForm = React.forwardRef<
)}
/>

<div className="grid gap-2">
<Label htmlFor="sp_issuer_urn" className="text-label font-medium">
{t('fields.samlp.sp_issuer_urn.label')}
</Label>
<CopyableTextField id="sp_issuer_urn" type="text" readOnly={true} value={spIssuerUrn} />
<p
className={cn('text-muted-foreground text-sm', 'text-paragraph font-normal text-left')}
>
{t('fields.samlp.sp_issuer_urn.helper_text')}
</p>
</div>

<CommonConfigureFields
idpConfig={idpConfig}
readOnly={readOnly}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ export function SsoProviderDetails({
showCrossAppAccess = false,
isCrossAppAccessReadOnly = false,
isOrganizationBlocked = false,
resolveSamlMetadata,
}: SsoProviderDetailsProps) {
const { t } = useTranslator('idp_management.sso_provider_details', customMessages);
const { isDarkMode } = useTheme();
Expand Down Expand Up @@ -143,6 +144,7 @@ export function SsoProviderDetails({
showCrossAppAccess={showCrossAppAccess}
isCrossAppAccessReadOnly={isCrossAppAccessReadOnly}
isOrganizationBlocked={isOrganizationBlocked}
resolveSamlMetadata={resolveSamlMetadata}
styling={styling}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ function SsoProviderCreate(props: SsoProviderCreateProps) {
},
onNext,
onPrevious,
resolveSamlMetadata,
} = props;

const ssoProviderCreate = useSsoProviderCreate({
Expand All @@ -69,6 +70,7 @@ function SsoProviderCreate(props: SsoProviderCreateProps) {
backButton={backButton}
onNext={onNext}
onPrevious={onPrevious}
resolveSamlMetadata={resolveSamlMetadata}
/>
</GateKeeper>
);
Expand Down Expand Up @@ -99,6 +101,7 @@ function SsoProviderCreateView(props: SsoProviderCreateViewProps) {
isCrossAppAccessReadOnly,
crossAppAccessDefaultValue,
isOrganizationBlocked,
resolveSamlMetadata,
detailsRef,
configureRef,
onNext,
Expand Down Expand Up @@ -176,6 +179,7 @@ function SsoProviderCreateView(props: SsoProviderCreateViewProps) {
isCrossAppAccessReadOnly={isCrossAppAccessReadOnly}
crossAppAccessDefaultValue={crossAppAccessDefaultValue}
isOrganizationBlocked={isOrganizationBlocked}
resolveSamlMetadata={resolveSamlMetadata}
styling={styling}
/>
) : null,
Expand All @@ -201,6 +205,7 @@ function SsoProviderCreateView(props: SsoProviderCreateViewProps) {
isCrossAppAccessReadOnly,
crossAppAccessDefaultValue,
isOrganizationBlocked,
resolveSamlMetadata,
],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@ export interface ProviderConfigureProps
isCrossAppAccessReadOnly?: boolean;
crossAppAccessDefaultValue?: 'enabled' | 'disabled';
isOrganizationBlocked?: boolean;
/**
* Optional resolver for SAML Service Provider metadata.
*
* @param params - Resolver parameters.
* @param params.connectionName - The SAML connection name.
* @returns An object containing `entityId` for the SP Issuer URN.
*/
resolveSamlMetadata?: (params: { connectionName: string }) => { entityId: string };
}

export interface ProviderConfigureFieldsProps
Expand All @@ -106,6 +114,14 @@ export interface ProviderConfigureFieldsProps
isCrossAppAccessReadOnly?: boolean;
crossAppAccessDefaultValue?: 'enabled' | 'disabled';
isOrganizationBlocked?: boolean;
/**
* Optional resolver for SAML Service Provider metadata.
*
* @param params - Resolver parameters.
* @param params.connectionName - The SAML connection name.
* @returns An object containing `entityId` for the SP Issuer URN.
*/
resolveSamlMetadata?: (params: { connectionName: string }) => { entityId: string };
}

export interface SsoProviderCreateBackButton extends Omit<BackButton, 'onClick'> {
Expand All @@ -122,6 +138,14 @@ export interface SsoProviderCreateProps
backButton?: SsoProviderCreateBackButton;
onPrevious?: (stepId: string, values: Partial<SsoProviderFormValues>) => boolean;
onNext?: (stepId: string, values: Partial<SsoProviderFormValues>) => boolean;
/**
* Optional resolver for SAML Service Provider metadata.
*
* @param params - Resolver parameters.
* @param params.connectionName - The SAML connection name.
* @returns An object containing `entityId` for the SP Issuer URN.
*/
resolveSamlMetadata?: (params: { connectionName: string }) => { entityId: string };
}

export interface UseSsoProviderCreateOptions {
Expand Down Expand Up @@ -180,7 +204,7 @@ export interface SsoProviderCreateViewProps
extends UseSsoProviderCreateResult,
Pick<
SsoProviderCreateProps,
'styling' | 'customMessages' | 'backButton' | 'onNext' | 'onPrevious'
'styling' | 'customMessages' | 'backButton' | 'onNext' | 'onPrevious' | 'resolveSamlMetadata'
> {
strategy?: IdpStrategy;
details?: ProviderDetailsFormValues | null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,14 @@ export interface SsoProviderDetailsProps
showCrossAppAccess?: boolean;
isCrossAppAccessReadOnly?: boolean;
isOrganizationBlocked?: boolean;
/**
* Optional resolver for SAML Service Provider metadata.
*
* @param params - Resolver parameters.
* @param params.connectionName - The SAML connection name.
* @returns An object containing `entityId` for the SP Issuer URN.
*/
resolveSamlMetadata?: (params: { connectionName: string }) => { entityId: string };
}

export interface ThirdPartyAccessSectionProps {
Expand Down
Loading