chore: make sub claim mapping readonly after creation (#3306)
This commit is contained in:
1 parent
f80d7ebeb3
commit
fa51973e6d
7 files changed
+201
-12
No files matched your search
@@ -48,6 +48,18 @@ export default class AuthMethodSerializer extends ApplicationSerializer {
|
||||
} else {
|
||||
delete serialized.attributes.state;
|
||||
}
|
||||
|
||||
// Don't send account_claim_maps on update if there's a saved 'sub' mapping
|
||||
// The API doesn't allow updating when 'sub' claim mappings exist
|
||||
if (!snapshot.record.isNew && snapshot.record.account_claim_maps) {
|
||||
const hasSubClaimMap = snapshot.record.account_claim_maps.some(
|
||||
(item) => item?.value === 'sub',
|
||||
);
|
||||
|
||||
if (hasSubClaimMap) {
|
||||
delete serialized.attributes.account_claim_maps;
|
||||
}
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
|
||||
@@ -506,4 +506,90 @@ module('Unit | Serializer | auth method', function (hooks) {
|
||||
assert.strictEqual(record.client_certificate_key, '');
|
||||
assert.strictEqual(record.bind_password, '');
|
||||
});
|
||||
|
||||
test('it includes account_claim_maps when serializing new OIDC records', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
const record = store.createRecord('auth-method', {
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
name: 'Test OIDC',
|
||||
account_claim_maps: [
|
||||
{ key: 'email', value: 'email' },
|
||||
{ key: 'name', value: 'sub' },
|
||||
],
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.ok(
|
||||
serializedRecord.attributes.account_claim_maps,
|
||||
'account_claim_maps is included for new records',
|
||||
);
|
||||
|
||||
assert.deepEqual(serializedRecord.attributes.account_claim_maps, [
|
||||
'email=email',
|
||||
'name=sub',
|
||||
]);
|
||||
});
|
||||
|
||||
test('it excludes account_claim_maps when serializing existing OIDC records with "sub" mapping', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
|
||||
const record = store.push({
|
||||
data: {
|
||||
id: 'oidc123',
|
||||
type: 'auth-method',
|
||||
attributes: {
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
name: 'Test OIDC',
|
||||
account_claim_maps: [
|
||||
{ key: 'email', value: 'email' },
|
||||
{ key: 'name', value: 'sub' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.notOk(
|
||||
serializedRecord.attributes.account_claim_maps,
|
||||
'account_claim_maps is excluded when updating with "sub" mapping',
|
||||
);
|
||||
});
|
||||
|
||||
test('it includes account_claim_maps when serializing existing OIDC records without "sub" mapping', function (assert) {
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('auth-method');
|
||||
|
||||
const record = store.push({
|
||||
data: {
|
||||
id: 'oidc456',
|
||||
type: 'auth-method',
|
||||
attributes: {
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
name: 'Test OIDC',
|
||||
account_claim_maps: [
|
||||
{ key: 'email', value: 'email' },
|
||||
{ key: 'name', value: 'name' },
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
|
||||
assert.ok(
|
||||
serializedRecord.attributes.account_claim_maps,
|
||||
'account_claim_maps is included when updating without "sub" mapping',
|
||||
);
|
||||
|
||||
assert.deepEqual(serializedRecord.attributes.account_claim_maps, [
|
||||
'email=email',
|
||||
'name=name',
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -242,7 +242,7 @@
|
||||
@data={{@model.account_claim_maps}}
|
||||
@legend={{t 'form.account_claim_maps.label'}}
|
||||
@helperText={{t 'form.account_claim_maps.help'}}
|
||||
@disabled={{form.disabled}}
|
||||
@disabled={{or form.disabled this.hasSavedSubClaimMap}}
|
||||
@isOptional={{true}}
|
||||
>
|
||||
<:row as |R|>
|
||||
@@ -264,7 +264,7 @@
|
||||
<option disabled hidden selected value=''>
|
||||
{{t 'titles.choose-an-option'}}
|
||||
</option>
|
||||
{{#each this.toClaims as |claim|}}
|
||||
{{#each (this.toClaimOptions R.rowData) as |claim|}}
|
||||
<option value={{claim}} selected={{eq R.rowData.value claim}}>
|
||||
{{claim}}
|
||||
</option>
|
||||
|
||||
@@ -39,6 +39,35 @@ export default class FormAuthMethodOidcComponent extends Component {
|
||||
@tracked selectedPrompts = this.parsePromptsArray();
|
||||
@tracked skipPromptsList = this.isToggleChecked();
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get hasSavedSubClaimMap() {
|
||||
return (
|
||||
!this.args.model?.isNew &&
|
||||
this.args.model.account_claim_maps?.some((item) => item?.value === 'sub')
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter 'sub' from dropdown options when updating
|
||||
* On creation, show all options including sub.
|
||||
* On update, hide sub unless it's the current value of the row (to display it when disabled).
|
||||
* @param {Object} rowData - The current row data
|
||||
* @returns {string[]}
|
||||
*/
|
||||
toClaimOptions = (rowData) => {
|
||||
// On creation, show all options
|
||||
if (this.args.model?.isNew) {
|
||||
return this.toClaims;
|
||||
}
|
||||
|
||||
// On update, filter "sub" unless it's the current value
|
||||
return this.toClaims.filter(
|
||||
(claim) => claim !== 'sub' || rowData?.value === 'sub',
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {string}
|
||||
*/
|
||||
|
||||
@@ -120,6 +120,9 @@ module('Acceptance | auth-methods | create', function (hooks) {
|
||||
);
|
||||
await click(selectors.FIELD_CLAIMS_SCOPES_ADD_BTN);
|
||||
|
||||
// Verify Account Claim Maps field is editable during creation
|
||||
assert.dom(selectors.FIELD_ACCOUNT_CLAIM_MAPS).isNotDisabled();
|
||||
|
||||
await fillIn(
|
||||
selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM,
|
||||
selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE,
|
||||
|
||||
@@ -30,12 +30,13 @@ export const FIELD_CLAIMS_SCOPES_ADD_BTN =
|
||||
'[name=claims_scopes] [data-test-add-button]';
|
||||
export const FIELD_CLAIMS_SCOPES_DELETE_BTN =
|
||||
'[name=claims_scopes] [data-test-delete-button]';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS = '[name=account_claim_maps]';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM =
|
||||
'[name=account_claim_maps] [data-test-key-input]';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE = 'from_claim';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM =
|
||||
'[name=account_claim_maps] [data-test-value-select]';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE = 'email';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE = 'sub';
|
||||
export const FIELD_ACCOUNT_CLAIM_MAPS_DELETE_BTN =
|
||||
'[name=account_claim_maps] [data-test-delete-button]';
|
||||
export const FIELD_IDP_CERTS = '[name=idp_ca_certs] [data-test-value-input]';
|
||||
|
||||
@@ -169,14 +169,16 @@ module('Acceptance | auth-methods | update', function (hooks) {
|
||||
await click(element);
|
||||
}
|
||||
|
||||
await fillIn(
|
||||
selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM,
|
||||
selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM_VALUE,
|
||||
);
|
||||
await select(
|
||||
selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM,
|
||||
selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM_VALUE,
|
||||
);
|
||||
await fillIn(selectors.FIELD_ACCOUNT_CLAIM_MAPS_FROM_CLAIM, 'test_claim');
|
||||
|
||||
// Verify "sub" option is not available during update
|
||||
await click(selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM);
|
||||
assert
|
||||
.dom(`${selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM} option[value="sub"]`)
|
||||
.doesNotExist('"sub" option is not available during update');
|
||||
|
||||
// Select 'email' instead since 'sub' is not available
|
||||
await select(selectors.FIELD_ACCOUNT_CLAIM_MAPS_TO_CLAIM, 'email');
|
||||
|
||||
// Remove all certificates
|
||||
const certificatesList = findAll(selectors.FIELD_IDP_CERTS_DELETE_BTN);
|
||||
@@ -223,7 +225,7 @@ module('Acceptance | auth-methods | update', function (hooks) {
|
||||
selectors.FIELD_CLAIMS_SCOPES_VALUE,
|
||||
]);
|
||||
assert.deepEqual(authMethod.attributes.account_claim_maps, [
|
||||
'from_claim=email',
|
||||
'test_claim=email',
|
||||
]);
|
||||
assert.deepEqual(authMethod.attributes.idp_ca_certs, [
|
||||
selectors.FIELD_IDP_CERTS_VALUE,
|
||||
@@ -239,6 +241,62 @@ module('Acceptance | auth-methods | update', function (hooks) {
|
||||
assert.deepEqual(authMethod.attributes.prompts, ['none']);
|
||||
});
|
||||
|
||||
test('account claim maps field is disabled when sub mapping exists', async function (assert) {
|
||||
setRunOptions({
|
||||
rules: {
|
||||
'color-contrast': {
|
||||
// [ember-a11y-ignore]: axe rule "color-contrast" automatically ignored on 2026-06-02
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create OIDC auth method with a 'sub' mapping
|
||||
instances.authMethod = this.server.create('auth-method', {
|
||||
scope: instances.scopes.org,
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
});
|
||||
instances.authMethod.attributes.account_claim_maps = ['username=sub'];
|
||||
|
||||
await visit(urls.authMethods);
|
||||
|
||||
await click(
|
||||
commonSelectors.HREF(`${urls.authMethods}/${instances.authMethod.id}`),
|
||||
);
|
||||
await click(commonSelectors.EDIT_BTN);
|
||||
|
||||
assert.dom(selectors.FIELD_ACCOUNT_CLAIM_MAPS).isDisabled();
|
||||
});
|
||||
|
||||
test('account claim maps field is editable when no sub mapping exists', async function (assert) {
|
||||
setRunOptions({
|
||||
rules: {
|
||||
'color-contrast': {
|
||||
// [ember-a11y-ignore]: axe rule "color-contrast" automatically ignored on 2026-06-02
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Create OIDC auth method with email/name mappings (no 'sub')
|
||||
instances.authMethod = this.server.create('auth-method', {
|
||||
scope: instances.scopes.org,
|
||||
type: TYPE_AUTH_METHOD_OIDC,
|
||||
});
|
||||
instances.authMethod.attributes.account_claim_maps = [
|
||||
'username=email',
|
||||
'fullname=name',
|
||||
];
|
||||
|
||||
await visit(urls.authMethods);
|
||||
await click(
|
||||
commonSelectors.HREF(`${urls.authMethods}/${instances.authMethod.id}`),
|
||||
);
|
||||
await click(commonSelectors.EDIT_BTN);
|
||||
|
||||
assert.dom(selectors.FIELD_ACCOUNT_CLAIM_MAPS).isNotDisabled();
|
||||
});
|
||||
|
||||
test('can update an ldap auth method and save changes', async function (assert) {
|
||||
setRunOptions({
|
||||
rules: {
|
||||
|
||||
Reference in new issue
Block a user