diff --git a/addons/api/addon/models/auth-method.js b/addons/api/addon/models/auth-method.js index 2874b0a67..d213e5c51 100644 --- a/addons/api/addon/models/auth-method.js +++ b/addons/api/addon/models/auth-method.js @@ -44,4 +44,29 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel { get isOIDC() { return this.type === 'oidc'; } + + // =methods + + /** + * Change the active and visibility state of an OIDC auth method + * given its ID. + * For OIDC auth methods only. + * @param {[string]} state + * @param {object} options + * @param {object} options.adapterOptions + * @return {Promise} + */ + changeState(state, options = { adapterOptions: {} }) { + const defaultAdapterOptions = { + method: 'change-state', + state, + }; + return this.save({ + ...options, + adapterOptions: { + ...defaultAdapterOptions, + ...options.adapterOptions, + }, + }); + } } diff --git a/addons/api/addon/serializers/auth-method.js b/addons/api/addon/serializers/auth-method.js index a8569c14d..e40285ce9 100644 --- a/addons/api/addon/serializers/auth-method.js +++ b/addons/api/addon/serializers/auth-method.js @@ -1,6 +1,63 @@ import ApplicationSerializer from './application'; export default class AuthMethodSerializer extends ApplicationSerializer { + // =methods + + /** + * Delegates to a type-specific serialization, or default. + * @override + * @param {Snapshot} snapshot + * @return {object} + */ + serialize(snapshot) { + switch (snapshot.record.type) { + case 'oidc': + return this.serializeOIDC(...arguments); + default: + return this.serializeDefault(...arguments); + } + } + + /** + * Default serialization omits `attributes`. + * @return {object} + */ + serializeDefault() { + let serialized = super.serialize(...arguments); + delete serialized.attributes; + return serialized; + } + + /** + * If `adapterOptions.state` is set, the serialization should + * include **only state** and version. Normally, this is not serialized. + * @param {Snapshot} snapshot + * @return {object} + */ + serializeOIDC(snapshot) { + let serialized = super.serialize(...arguments); + const state = snapshot?.adapterOptions?.state; + if (state) { + serialized = this.serializeOIDCWithState(snapshot, state); + } else { + delete serialized.attributes.state; + } + return serialized; + } + + /** + * Returns a payload containing only state and version. + * @param {Snapshot} snapshot + * @param {string} state + * @return {object} + */ + serializeOIDCWithState(snapshot, state) { + return { + version: snapshot.attr('version'), + attributes: { state }, + }; + } + /** * Sorts the array in order of "is_primary" such that the primary method * appears first. The "natural" order of auth methods places primary first. diff --git a/addons/api/tests/unit/models/auth-method-test.js b/addons/api/tests/unit/models/auth-method-test.js index 31ff3fca1..ff746e777 100644 --- a/addons/api/tests/unit/models/auth-method-test.js +++ b/addons/api/tests/unit/models/auth-method-test.js @@ -1,8 +1,10 @@ import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; module('Unit | Model | auth method', function (hooks) { setupTest(hooks); + setupMirage(hooks); // Replace this with your real tests. test('it exists', function (assert) { @@ -10,4 +12,39 @@ module('Unit | Model | auth method', function (hooks) { let model = store.createRecord('auth-method', {}); assert.ok(model); }); + + test('it has a `changeState` method that targets a specific POST API endpoint and serialization', async function (assert) { + assert.expect(1); + this.server.post( + '/v1/auth-methods/123abc:change-state', + (schema, request) => { + const body = JSON.parse(request.requestBody); + assert.deepEqual(body, { + attributes: { + state: 'foobar', + }, + version: 1, + }); + return { id: '123abc' }; + } + ); + const store = this.owner.lookup('service:store'); + store.push({ + data: { + id: '123abc', + type: 'auth-method', + attributes: { + type: 'oidc', + state: 'foobar', + version: 1, + scope: { + scope_id: 'o_1', + type: 'scope', + }, + }, + }, + }); + const model = store.peekRecord('auth-method', '123abc'); + await model.changeState('foobar'); + }); }); diff --git a/addons/api/tests/unit/serializers/auth-method-test.js b/addons/api/tests/unit/serializers/auth-method-test.js index cd7fcd396..54d10dede 100644 --- a/addons/api/tests/unit/serializers/auth-method-test.js +++ b/addons/api/tests/unit/serializers/auth-method-test.js @@ -12,13 +12,59 @@ module('Unit | Serializer | auth method', function (hooks) { assert.ok(serializer); }); - test('it serializes records', function (assert) { + test('it serializes password records', function (assert) { + assert.expect(2); let store = this.owner.lookup('service:store'); - let record = store.createRecord('auth-method', {}); + let record = store.createRecord('auth-method', { type: 'password' }); let serializedRecord = record.serialize(); assert.ok(serializedRecord); + assert.notOk( + serializedRecord.attributes, + 'Password should not have attributes' + ); + }); + + test('it serializes OIDC records without state', function (assert) { + assert.expect(3); + let store = this.owner.lookup('service:store'); + let record = store.createRecord('auth-method', { + type: 'oidc', + attributes: { + state: 'foo', + }, + }); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + assert.ok(serializedRecord.attributes, 'OIDC should have attributes'); + assert.notOk(serializedRecord.attributes.state); + }); + + test('it serializes OIDC records with only state and version when `adapterOptions.state` is passed', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('auth-method'); + const record = store.createRecord('auth-method', { + type: 'oidc', + attributes: { + state: 'foo', + }, + version: 1, + }); + const snapshot = record._createSnapshot(); + snapshot.adapterOptions = { + state: 'bar', + }; + const serializedRecord = serializer.serialize(snapshot); + assert.deepEqual(serializedRecord, { + attributes: { + state: 'bar', + }, + version: 1, + }); }); test('it sorts primary first in normalizeArrayResponse', function (assert) {