diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 279e9b73f..12112ed67 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -248,7 +248,11 @@ export default class ApplicationAdapter extends RESTAdapter.extend( // Normalize field-specific errors, if any. const errors = fieldErrors.map((error) => ({ detail: error.description, - source: { pointer: `/data/attributes/${error.name}` }, + // Ember Data cannot handle errors on nested attributes, so these are + // flattened (e.g. `attributes.foobar` becomes `attributes_foobar`). + // To capture these errors, add read-only fields on the model + // corresponding to the underscored field name. + source: { pointer: `/data/attributes/${error.name.replace('.', '_')}` }, })); // Return a list of JSON API errors rooted under the `errors` key. return { diff --git a/addons/api/addon/models/account.js b/addons/api/addon/models/account.js index 90ad55c42..e5f702541 100644 --- a/addons/api/addon/models/account.js +++ b/addons/api/addon/models/account.js @@ -17,6 +17,11 @@ export default class AccountModel extends GeneratedAccountModel { */ @equal('type', 'password') isPassword; + /** + * @type {boolean} + */ + @equal('type', 'oidc') isOIDC; + /** * Convenience for getting username in account. * @type {string} diff --git a/addons/api/addon/models/auth-method.js b/addons/api/addon/models/auth-method.js index dc03abc0d..8e6c4f198 100644 --- a/addons/api/addon/models/auth-method.js +++ b/addons/api/addon/models/auth-method.js @@ -1,9 +1,10 @@ import GeneratedAuthMethodModel from '../generated/models/auth-method'; +import { attr } from '@ember-data/model'; import { fragment } from 'ember-data-model-fragments/attributes'; import { equal } from '@ember/object/computed'; /** - * Enum ptions per auth method type and field. + * Enum options per auth method type and field. */ export const options = { oidc: { @@ -27,6 +28,26 @@ export const options = { }; export default class AuthMethodModel extends GeneratedAuthMethodModel { + // =error attributes + // These attributes exist solely to capture errors on nested fields. + // See the application adapter's error normalization method for + // more information. + + @attr('string', { readOnly: true }) attributes_state; + @attr('string', { readOnly: true }) attributes_issuer; + @attr('string', { readOnly: true }) attributes_client_id; + @attr('string', { readOnly: true }) attributes_client_secret; + @attr('string', { readOnly: true }) attributes_max_age; + @attr('string', { readOnly: true }) attributes_api_url_prefix; + @attr('string', { readOnly: true }) + attributes_disable_discovered_config_validation; + @attr('string', { readOnly: true }) attributes_dry_run; + @attr('string', { readOnly: true }) attributes_account_claim_maps; + @attr('string', { readOnly: true }) attributes_claims_scopes; + @attr('string', { readOnly: true }) attributes_signing_algorithms; + @attr('string', { readOnly: true }) attributes_allowed_audiences; + @attr('string', { readOnly: true }) attributes_idp_ca_certs; + // =attributes /** @@ -54,6 +75,27 @@ export default class AuthMethodModel extends GeneratedAuthMethodModel { return this.type === 'oidc'; } + /** + * @type {boolean} + */ + get isInactive() { + return this.attributes.state === 'inactive'; + } + + /** + * @type {boolean} + */ + get isPrivate() { + return this.attributes.state === 'active-private'; + } + + /** + * @type {boolean} + */ + get isPublic() { + return this.attributes.state === 'active-public'; + } + // =methods /** diff --git a/addons/api/addon/models/fragment-account-attributes.js b/addons/api/addon/models/fragment-account-attributes.js index bb0371398..0297f4399 100644 --- a/addons/api/addon/models/fragment-account-attributes.js +++ b/addons/api/addon/models/fragment-account-attributes.js @@ -2,8 +2,16 @@ import Fragment from 'ember-data-model-fragments/fragment'; import { attr } from '@ember-data/model'; export default class FragmentAccountAttributesModel extends Fragment { + // =attributes (password) @attr('string', { + for: 'password', description: 'The account login name', }) login_name; + + // =attributes (OIDC) + @attr('string', { for: 'oidc' }) subject; + @attr('string', { for: 'oidc' }) issuer; + @attr('string', { for: 'oidc', readOnly: true }) email; + @attr('string', { for: 'oidc', readOnly: true }) full_name; } diff --git a/addons/api/addon/serializers/account.js b/addons/api/addon/serializers/account.js index dfbb7e580..939aceb70 100644 --- a/addons/api/addon/serializers/account.js +++ b/addons/api/addon/serializers/account.js @@ -20,13 +20,36 @@ export default class AccountSerializer extends ApplicationSerializer { * @return {object} */ serialize(snapshot) { + switch (snapshot.record.type) { + case 'password': + return this.serializePassword(...arguments); + } + } + + /** + * Password serialization omits `attributes` and handles password-related + * custom methods. + * @param {Snapshot} snapshot + * @return {object} + */ + serializePassword(snapshot) { const password = snapshot?.adapterOptions?.password; let serialized = super.serialize(...arguments); + // Only login_name is serialized into password attributes. + const { login_name } = serialized.attributes; + if (login_name) { + serialized.attributes = { login_name }; + } else { + serialized.attributes = {}; + } + // New record case if (password && snapshot?.record?.isNew) serialized.attributes.password = password; + // Set password custom method if (snapshot?.adapterOptions?.method === 'set-password') { serialized = this.serializeForSetPassword(snapshot, password); } + // Change password custom method if (snapshot?.adapterOptions?.method === 'change-password') { const { currentPassword, newPassword } = snapshot?.adapterOptions; serialized = this.serializeForChangePassword( diff --git a/addons/api/addon/serializers/fragment-account-attributes.js b/addons/api/addon/serializers/fragment-account-attributes.js new file mode 100644 index 000000000..a9dcb590c --- /dev/null +++ b/addons/api/addon/serializers/fragment-account-attributes.js @@ -0,0 +1,26 @@ +import JSONSerializer from '@ember-data/serializer/json'; + +export default class FragmentAccountAttributesSerializer extends JSONSerializer { + /** + * If an attribute is annotated as readOnly in the model, don't serialize it. + * Otherwise delegate to default attribute serializer. + * + * @override + * @method serializeAttribute + * @param {Snapshot} snapshot + * @param {Object} json + * @param {String} key + * @param {Object} attribute + */ + serializeAttribute(snapshot, json, key, attribute) { + const { type, options } = attribute; + let value = super.serializeAttribute(...arguments); + // Convert empty strings to null. + if (type === 'string' && json[key] === '') json[key] = null; + // Do not serialize read-only attributes. + if (options.readOnly) delete json[key]; + // Client secret cannot be unset. If it's falsy, it must be removed. + if (key === 'client_secret' && !json[key]) delete json[key]; + return value; + } +} diff --git a/addons/api/app/serializers/fragment-account-attributes.js b/addons/api/app/serializers/fragment-account-attributes.js new file mode 100644 index 000000000..b37aa8148 --- /dev/null +++ b/addons/api/app/serializers/fragment-account-attributes.js @@ -0,0 +1 @@ +export { default } from 'api/serializers/fragment-account-attributes'; diff --git a/addons/api/mirage/config.js b/addons/api/mirage/config.js index 769e7e810..007950f69 100644 --- a/addons/api/mirage/config.js +++ b/addons/api/mirage/config.js @@ -106,8 +106,9 @@ export default function () { } ); - // Authenticate/deauthenticate routes + // Authenticate + auth methods custom routes this.post('/auth-methods/:id_method', authHandler); + // Deauthenticate route this.post('/scopes/:id_method', deauthHandler); // IAM : Users diff --git a/addons/api/mirage/route-handlers/auth.js b/addons/api/mirage/route-handlers/auth.js index bd4fca7ac..6f9a51634 100644 --- a/addons/api/mirage/route-handlers/auth.js +++ b/addons/api/mirage/route-handlers/auth.js @@ -107,14 +107,34 @@ const commandHandlers = { }, }; +// Handles all custom methods on /auth-methods/:id route export function authHandler({ scopes, authMethods }, request) { - const payload = JSON.parse(request.requestBody); - const [, id] = request.params.id_method.match(/(?.[^:]*)/); - const { command } = payload; + const [, id, method] = request.params.id_method.match( + /(?.[^:]*):(?(.*))/ + ); const authMethod = authMethods.find(id); - const scope = scopes.find(authMethod.scopeId); - const scopeAttrs = this.serialize(scopes.find(scope.id)); - return commandHandlers[authMethod.type][command](payload, scopeAttrs); + + if (method === 'authenticate') { + const payload = JSON.parse(request.requestBody); + const { command } = payload; + const scope = scopes.find(authMethod.scopeId); + const scopeAttrs = this.serialize(scopes.find(scope.id)); + return commandHandlers[authMethod.type][command](payload, scopeAttrs); + } + + // TODO: this handler doesn't really belong here, but we already route + // POST requests on existing auth methods to this route handler file under the + // assumption it would be for authentication. While authentication should + // still occur here, we should handle other custom methods in the + // mirage config and route here only for auth. + if (method === 'change-state') { + const attrs = this.normalizedRequestAttrs(); + return authMethod.update({ + attributes: { + state: attrs.attributes.state, + }, + }); + } } export function deauthHandler() { diff --git a/addons/api/tests/unit/serializers/fragment-account-attributes-test.js b/addons/api/tests/unit/serializers/fragment-account-attributes-test.js new file mode 100644 index 000000000..3245f8b80 --- /dev/null +++ b/addons/api/tests/unit/serializers/fragment-account-attributes-test.js @@ -0,0 +1,23 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Serializer | fragment account attributes', function (hooks) { + setupTest(hooks); + + // Replace this with your real tests. + test('it exists', function (assert) { + let store = this.owner.lookup('service:store'); + let serializer = store.serializerFor('fragment-account-attributes'); + + assert.ok(serializer); + }); + + test('it serializes records', function (assert) { + let store = this.owner.lookup('service:store'); + let record = store.createRecord('fragment-account-attributes', {}); + + let serializedRecord = record.serialize(); + + assert.ok(serializedRecord); + }); +}); diff --git a/addons/core/translations/actions/en-us.yaml b/addons/core/translations/actions/en-us.yaml index eccbad6eb..2c501df04 100644 --- a/addons/core/translations/actions/en-us.yaml +++ b/addons/core/translations/actions/en-us.yaml @@ -31,3 +31,4 @@ set-password: Set Password change-password: Change Password edit-form: Edit Form change-origin: Change Origin URL +change-state: Change State diff --git a/addons/core/translations/en-us.yaml b/addons/core/translations/en-us.yaml index 56621bf33..5370c7e75 100644 --- a/addons/core/translations/en-us.yaml +++ b/addons/core/translations/en-us.yaml @@ -27,6 +27,8 @@ titles: abandon-confirm: Discard unsaved changes? toggle-color-theme: Toggle Color Theme welcome-to-boundary: Welcome to Boundary + label: Label + value: Value descriptions: empty-set: There are no items to display yet. You may be able to add items or try back later. origin-initialization: To get started, please enter your origin URL diff --git a/addons/core/translations/form/en-us.yaml b/addons/core/translations/form/en-us.yaml index 974b2d313..56af46588 100644 --- a/addons/core/translations/form/en-us.yaml +++ b/addons/core/translations/form/en-us.yaml @@ -59,7 +59,7 @@ client_id: help: An OAuth 2.0 Client Identifier valid at the Authorization Server. client_secret: label: Client Secret - description: The client secret is set-only, may not be unset, and will not appear after a successful save. + help: The client secret is set-only and may not be unset. max_age: label: Maximum Age help: The OIDC "max_age" parameter sent to the Authorization Server indicating a maximum acceptable time in seconds since the user's last authentication before requiring the user to reauthenticate. 0 indicates an immediate need to reauthenticate. @@ -70,3 +70,39 @@ disable_discovered_config_validation: label: Disable discovered config validation help: > If the Authorization Server's discovered configuration contains values that do not match the configuration set on this auth method, this can be set to force the local configuration to override the discovered values. This is useful in the case of slow-changing or incorrect values announced by the Authorization Server. NOTE: For safety, not providing this value will be treated the same as "false"; that is, every time the method is updated or the state is changed, this value must be set to "true" or it will be disabled. +idp_ca_certs: + label: Certificates + help: Optional PEM-encoded X.509 CA certificates that can be used as trust anchors when connecting to an OIDC provider. +certificate: + label: Certificate +allowed_audiences: + label: Allowed Audiences + help: Allowed audience claims for this auth method. +claim: + label: Claim +claims_scopes: + label: Claims Scopes + help: Optional claims scopes that will be requested during authentication. +claims_scope: + label: Claims Scope +account_claim_maps: + label: Account Claim Maps + help: > + Optional claim maps from custom claims to the standard claims of sub, name and email. These maps are represented as key=value where the key equals the from_claim and the value equals the to_claim. For example "oid=sub". +account_claim_map: + label: Account Claim Map +from_claim: + label: From Claim +to_claim: + label: To Claim +signing_algorithms: + label: Signing Algorithms + help: The signing algorithms allowed for the auth method. +algorithm: + label: Algorithm +subject: + label: Subject +email: + label: Email +full_name: + label: Full Name diff --git a/addons/core/translations/resources/en-us.yaml b/addons/core/translations/resources/en-us.yaml index 7432ac466..8ab5152ac 100644 --- a/addons/core/translations/resources/en-us.yaml +++ b/addons/core/translations/resources/en-us.yaml @@ -30,6 +30,13 @@ auth-method: pending: title: Authentication Pending description: Complete authentication in the newly-opened window. + titles: + new-signing-algorithm: New Signing Algorithm + new-allowed-audience: New Allowed Audience + new-claims-scope: New Claims Scope + new-from-claim: New From Claim + new-to-claim: New To Claim + new-certificate: New Certificate types: password: Password oidc: OIDC diff --git a/addons/core/translations/states/en-us.yaml b/addons/core/translations/states/en-us.yaml index aa8e846bd..aca77b801 100644 --- a/addons/core/translations/states/en-us.yaml +++ b/addons/core/translations/states/en-us.yaml @@ -8,3 +8,6 @@ connection: success: description: "Local proxy address ({protocol}): {address}:{port}" primary: Primary +inactive: Inactive +active-private: Private +active-public: Public diff --git a/addons/rose/addon/components/rose/form/index.hbs b/addons/rose/addon/components/rose/form/index.hbs index 80b027371..8c545ec22 100644 --- a/addons/rose/addon/components/rose/form/index.hbs +++ b/addons/rose/addon/components/rose/form/index.hbs @@ -30,5 +30,6 @@ cancel=this.handleCancel enableEdit=this.enableEdit ) + isEditable=this.isEditable )}} diff --git a/addons/rose/addon/styles/rose/components/form/_fieldset.scss b/addons/rose/addon/styles/rose/components/form/_fieldset.scss index 8696ad562..d0b35d2ea 100644 --- a/addons/rose/addon/styles/rose/components/form/_fieldset.scss +++ b/addons/rose/addon/styles/rose/components/form/_fieldset.scss @@ -2,6 +2,8 @@ @use '../../utilities/type'; .rose-form-fieldset { + margin-bottom: sizing.rems(m); + .rose-form-fieldset-title { @include type.type(l, bold); } diff --git a/addons/rose/addon/styles/rose/components/table/_table.scss b/addons/rose/addon/styles/rose/components/table/_table.scss index 5429a1cd9..40520adee 100644 --- a/addons/rose/addon/styles/rose/components/table/_table.scss +++ b/addons/rose/addon/styles/rose/components/table/_table.scss @@ -12,13 +12,19 @@ } // This styles are add to stop the input to apply extra margin (default on forms) + .rose-form-input, .rose-form-input-field, .rose-form-select, - .rose-form-input { + .rose-form-select-field { margin: 0; width: 100%; } + // Fix margin issue with textarea elements inside tables + textarea { + display: block; + } + .rose-table-cell, .rose-table-header-cell { vertical-align: middle; diff --git a/addons/rose/package.json b/addons/rose/package.json index 27fdcceec..f4a0d52d6 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -43,7 +43,7 @@ "@ember/test-helpers": "^2.1.4", "@glimmer/component": "^1.0.3", "@glimmer/tracking": "^1.0.3", - "@hashicorp/structure-icons": "^1.8.1", + "@hashicorp/structure-icons": "^1.9.0-0", "@storybook/addon-actions": "^6.0.26", "@storybook/addon-docs": "^6.0.26", "@storybook/addon-knobs": "^6.0.26", diff --git a/ui/admin/app/components/form/auth-method/index.hbs b/ui/admin/app/components/form/auth-method/index.hbs index 478c2ea91..77cf37d4d 100644 --- a/ui/admin/app/components/form/auth-method/index.hbs +++ b/ui/admin/app/components/form/auth-method/index.hbs @@ -4,5 +4,8 @@ model=@model submit=@submit cancel=@cancel + removeItemByIndex=@removeItemByIndex + addStringItem=@addStringItem + addAccountClaimMapItem=@addAccountClaimMapItem }} {{/if}} \ No newline at end of file diff --git a/ui/admin/app/components/form/auth-method/oidc/index.hbs b/ui/admin/app/components/form/auth-method/oidc/index.hbs index f157e5090..e1a5592c0 100644 --- a/ui/admin/app/components/form/auth-method/oidc/index.hbs +++ b/ui/admin/app/components/form/auth-method/oidc/index.hbs @@ -39,7 +39,6 @@ @label={{t 'form.description.label'}} @helperText={{t 'form.description.help'}} @error={{@model.errors.description}} - disabled={{@model.isSaving}} readonly={{false}} as |field| > @@ -58,13 +57,13 @@ @value={{@model.attributes.issuer}} @label={{t 'form.issuer.label'}} @helperText={{t 'form.issuer.help'}} - @error={{@model.errors.attributes.issuer}} + @error={{@model.errors.attributes_issuer}} readonly={{false}} as |field| > - {{#if @model.errors.attributes.issuer}} + {{#if @model.errors.attributes_issuer}} - {{#each @model.errors.attributes.issuer as |error|}} + {{#each @model.errors.attributes_issuer as |error|}} {{error.message}} {{/each}} @@ -87,13 +86,13 @@ @value={{@model.attributes.client_id}} @label={{t 'form.client_id.label'}} @helperText={{t 'form.client_id.help'}} - @error={{@model.errors.attributes.client_id}} + @error={{@model.errors.attributes_client_id}} readonly={{false}} as |field| > - {{#if @model.errors.attributes.client_id}} + {{#if @model.errors.attributes_client_id}} - {{#each @model.errors.attributes.client_id as |error|}} + {{#each @model.errors.attributes_client_id as |error|}} {{error.message}} {{/each}} @@ -106,34 +105,488 @@ @value={{@model.attributes.client_secret}} @label={{t 'form.client_secret.label'}} @helperText={{t 'form.client_secret.help'}} - @error={{@model.errors.attributes.client_secret}} + @error={{@model.errors.attributes_client_secret}} readonly={{false}} as |field| > - {{#if @model.errors.attributes.client_secret}} + {{#if @model.errors.attributes_client_secret}} - {{#each @model.errors.attributes.client_secret as |error|}} + {{#each @model.errors.attributes_client_secret as |error|}} {{error.message}} {{/each}} {{/if}} + {{! Signing Algorithms }} + + <:title>{{t 'form.signing_algorithms.label'}} + <:description>{{t 'form.signing_algorithms.help'}} + <:body> + {{#if @model.errors.attributes_signing_algorithms}} + + {{#each @model.errors.attributes_signing_algorithms as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'titles.value'}} + {{t 'titles.actions'}} + + + + {{#each @model.attributes.signing_algorithms as |algorithm i|}} + + + + {{t 'form.algorithm.label'}} + + {{#each this.signingAlgorithms as |algorithm|}} + {{algorithm}} + {{/each}} + + + + + + {{t 'actions.remove'}} + + + + {{/each}} + + + + + + {{#each this.signingAlgorithms as |algorithm|}} + {{algorithm}} + {{/each}} + + + + + + {{t 'actions.add'}} + + + + + + + + + {{! Allowed Audiences }} + + <:title>{{t 'form.allowed_audiences.label'}} + <:description>{{t 'form.allowed_audiences.help'}} + <:body> + {{#if @model.errors.attributes_allowed_audiences}} + + {{#each @model.errors.attributes_allowed_audiences as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'titles.value'}} + {{t 'titles.actions'}} + + + + {{#each @model.attributes.allowed_audiences as |audience i|}} + + + + {{t 'form.claim.label'}} + + + + + + {{t 'actions.remove'}} + + + + {{/each}} + + + + + + + + {{t 'actions.add'}} + + + + + + + + + {{! Claims Scopes }} + + <:title>{{t 'form.claims_scopes.label'}} + <:description>{{t 'form.claims_scopes.help'}} + <:body> + {{#if @model.errors.attributes_claims_scopes}} + + {{#each @model.errors.attributes_claims_scopes as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'titles.value'}} + + + + + {{#each @model.attributes.claims_scopes as |claimsScope i|}} + + + + {{t 'form.claims_scope.label'}} + + + + + + {{t 'actions.remove'}} + + + + {{/each}} + + + + + + + + {{t 'actions.add'}} + + + + + + + + + {{! Account Claim Maps }} + + <:title>{{t 'form.account_claim_maps.label'}} + <:description>{{t 'form.account_claim_maps.help'}} + <:body> + {{#if @model.errors.attributes_account_claim_maps}} + + {{#each @model.errors.attributes_account_claim_maps as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'form.from_claim.label'}} + {{t 'form.to_claim.label'}} + {{t 'titles.actions'}} + + + + {{#each @model.attributes.account_claim_maps as |claimMap i|}} + + + + {{t 'form.from_claim.label'}} + + + + + + {{t 'form.to_claim.label'}} + + {{#each this.toClaims as |toClaim|}} + {{toClaim}} + {{/each}} + + + + + + {{t 'actions.remove'}} + + + + {{/each}} + + + + + + + + + {{#each this.toClaims as |toClaim|}} + {{toClaim}} + {{/each}} + + + + + + {{t 'actions.add'}} + + + + + + + + + {{! Certificates }} + + <:title>{{t 'form.idp_ca_certs.label'}} + <:description>{{t 'form.idp_ca_certs.help'}} + <:body> + {{#if @model.errors.attributes_idp_ca_certs}} + + {{#each @model.errors.attributes_idp_ca_certs as |error|}} + {{error.message}} + {{/each}} + + {{/if}} + + + + {{t 'titles.value'}} + {{t 'titles.actions'}} + + + + {{#each @model.attributes.idp_ca_certs as |cert i|}} + + + + {{t 'form.certificate.label'}} + + + + + + {{t 'actions.remove'}} + + + + {{/each}} + + + + + + + + {{t 'actions.add'}} + + + + + + + + - {{#if @model.errors.attributes.max_age}} + {{#if @model.errors.attributes_max_age}} - {{#each @model.errors.attributes.max_age as |error|}} + {{#each @model.errors.attributes_max_age as |error|}} {{error.message}} {{/each}} @@ -146,13 +599,13 @@ @value={{@model.attributes.api_url_prefix}} @label={{t 'form.api_url_prefix.label'}} @helperText={{t 'form.api_url_prefix.help'}} - @error={{@model.errors.attributes.api_url_prefix}} + @error={{@model.errors.attributes_api_url_prefix}} readonly={{false}} as |field| > - {{#if @model.errors.attributes.api_url_prefix}} + {{#if @model.errors.attributes_api_url_prefix}} - {{#each @model.errors.attributes.api_url_prefix as |error|}} + {{#each @model.errors.attributes_api_url_prefix as |error|}} {{error.message}} {{/each}} diff --git a/ui/admin/app/components/form/auth-method/oidc/index.js b/ui/admin/app/components/form/auth-method/oidc/index.js new file mode 100644 index 000000000..166e33561 --- /dev/null +++ b/ui/admin/app/components/form/auth-method/oidc/index.js @@ -0,0 +1,20 @@ +import Component from '@glimmer/component'; +import { options } from 'api/models/auth-method'; + +export default class FormAuthMethodOidcComponent extends Component { + // =attributes + /** + * @type {object} + */ + signingAlgorithms = options.oidc.signing_algorithms; + + /** + * @type {object} + */ + toClaims = options.oidc.account_claim_maps.to; + + /** + * @type {string} + */ + newSigningAlgorithm = options.oidc.signing_algorithms[0]; +} diff --git a/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method.js b/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method.js index 1005f0216..1aa269744 100644 --- a/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method.js +++ b/ui/admin/app/controllers/scopes/scope/auth-methods/auth-method.js @@ -1,4 +1,5 @@ import Controller from '@ember/controller'; +import { options } from 'api/models/auth-method'; export default class ScopesScopeAuthMethodsAuthMethodController extends Controller { // =attributes @@ -10,4 +11,12 @@ export default class ScopesScopeAuthMethodsAuthMethodController extends Controll get breadCrumb() { return this.model.displayName; } + + /** + * OIDC auth-method states + * @type {[string]} + */ + get oidcStates() { + return options.oidc.state; + } } diff --git a/ui/admin/app/routes/scopes/scope/auth-methods.js b/ui/admin/app/routes/scopes/scope/auth-methods.js index 291a9573d..31c0778a5 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods.js @@ -134,4 +134,45 @@ export default class ScopesScopeAuthMethodsRoute extends Route { } await authMethod.reload(); } + + /** + * Removes an item from array `property` at `index` on the + * passed `authMethod`. This is used to manage entries in fragment array + * fields such as `signing_algorithms`. + * @param {AuthMethodModel} authMethod + * @param {string} property + * @param {number} index + */ + @action + async removeItemByIndex(authMethod, property, index) { + const array = authMethod.get(property); + array.removeAt(index); + } + + /** + * Adds a string item to array `property` on the passed `authMethod`. + * This is used to manage entries in fragment OIDC string array fields such + * as `signing_algorithms`. + * @param {AuthMethodModel} authMethod + * @param {string} property + * @param {string} value + */ + @action + async addStringItem(authMethod, property, value) { + const array = authMethod.get(property); + array.addObject({ value }); + } + + /** + * Adds an account claim map fragment to the passed OIDC `authMethod`. + * @param {AuthMethodModel} authMethod + * @param {string} property + * @param {string} value + */ + @action + async addAccountClaimMapItem(authMethod, from, to) { + const array = authMethod.attributes.account_claim_maps; + const value = { from, to }; + array.addObject(value); + } } diff --git a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js index 3c608f660..c0678ed0a 100644 --- a/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js +++ b/ui/admin/app/routes/scopes/scope/auth-methods/auth-method.js @@ -1,4 +1,6 @@ import Route from '@ember/routing/route'; +import { action } from '@ember/object'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route { // =methods @@ -36,4 +38,18 @@ export default class ScopesScopeAuthMethodsAuthMethodRoute extends Route { outlet: 'actions', }); } + + // =actions + + /** + * Update state of OIDC auth method + * @param {string} state + */ + @action + @notifyError(({ message }) => message) + @notifySuccess('notifications.save-success') + async changeState(state) { + const model = this.modelFor('scopes.scope.auth-methods.auth-method'); + await model.changeState(state); + } } diff --git a/ui/admin/app/styles/app.scss b/ui/admin/app/styles/app.scss index 420258319..8ee952891 100644 --- a/ui/admin/app/styles/app.scss +++ b/ui/admin/app/styles/app.scss @@ -80,11 +80,13 @@ } } - fieldset { - margin-bottom: sizing.rems(m); - - legend { - @extend .rose-form-label; + .rose-form-fieldset { + .rose-table { + .rose-form-label { + left: -9999px; + position: absolute; + top: -9999px; + } } } } diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs index c46e656f8..1ae2b32b3 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/-actions.hbs @@ -1,17 +1,42 @@ +{{#if @model.isOIDC}} + + + + + {{#each this.oidcStates as |state|}} + + + + {{/each}} + + + + +{{/if}} + {{#if (feature-flag 'primary-auth-method')}} - {{#unless @model.isPrimary}} - - {{t 'resources.auth-method.actions.make-primary'}} - - {{/unless}} {{#if @model.isPrimary}} {{t 'resources.auth-method.actions.remove-as-primary'}} + {{else}} + + {{t 'resources.auth-method.actions.make-primary'}} + {{/if}} {{/if}} - - {{t 'resources.account.actions.create'}} - + {{#if @model.isOIDC}} + {{#if (feature-flag 'oidc-account-crud')}} + + {{t 'resources.account.actions.create'}} + + {{/if}} + {{else}} + + {{t 'resources.account.actions.create'}} + + {{/if}} - - - {{t 'form.name.label'}} - - {{#if @model.firstObject.isPassword}} - {{t 'form.login_name.label'}} - {{/if}} - - {{t 'form.id.label'}} - - - - {{#each @model as |account|}} - - - + {{! OIDC accounts display more fields }} + {{#if @model.firstObject.isOIDC}} + + + + {{t 'form.name.label'}} + {{t 'form.issuer.label'}} + {{t 'form.subject.label'}} + {{t 'form.email.label'}} + {{t 'form.full_name.label'}} + {{t 'form.id.label'}} + + + + {{#each @model as |account|}} + + {{account.displayName}} - -

{{account.description}}

+
+ + {{account.attributes.issuer}} + + + {{account.attributes.subject}} + + + {{account.attributes.email}} + + + {{account.attributes.full_name}} + + + + {{account.id}} + + +
+ {{/each}} +
+ + {{else}} + + + + {{t 'form.name.label'}} + + {{#if @model.firstObject.isPassword}} + {{t 'form.login_name.label'}} + {{/if}} - - {{account.username}} - - - - {{account.id}} - - - - {{/each}} - - - + {{t 'form.id.label'}} + + + + {{#each @model as |account|}} + + + + {{account.displayName}} + +

{{account.description}}

+
+ + {{account.username}} + + + + {{account.id}} + + +
+ {{/each}} +
+ + {{/if}} {{else}} diff --git a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs index b82869296..e3302df0b 100644 --- a/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs +++ b/ui/admin/app/templates/scopes/scope/auth-methods/auth-method/index.hbs @@ -2,4 +2,7 @@ @model={{@model}} @submit={{route-action 'save' @model}} @cancel={{route-action 'cancel' @model}} + @removeItemByIndex={{route-action 'removeItemByIndex' @model}} + @addStringItem={{route-action 'addStringItem' @model}} + @addAccountClaimMapItem={{route-action 'addAccountClaimMapItem' @model}} /> \ No newline at end of file diff --git a/ui/admin/config/environment.js b/ui/admin/config/environment.js index 21d1e3da6..1e9db7322 100644 --- a/ui/admin/config/environment.js +++ b/ui/admin/config/environment.js @@ -88,7 +88,8 @@ module.exports = function (environment) { featureFlags: { 'primary-auth-method': true, oidc: true, - 'oidc-crud': false, + 'oidc-crud': true, + 'oidc-account-crud': false, search: false, filter: false, capabilities: false, @@ -131,6 +132,7 @@ module.exports = function (environment) { ENV.featureFlags['primary-auth-method'] = true; ENV.featureFlags['oidc'] = true; ENV.featureFlags['oidc-crud'] = true; + //ENV.featureFlags['oidc-account-crud'] = true; } if (environment === 'test') { diff --git a/ui/admin/tests/acceptance/auth-methods/oidc-test.js b/ui/admin/tests/acceptance/auth-methods/oidc-test.js new file mode 100644 index 000000000..3bc1f226b --- /dev/null +++ b/ui/admin/tests/acceptance/auth-methods/oidc-test.js @@ -0,0 +1,139 @@ +import { module, test } from 'qunit'; +import { + visit, + currentURL, + click, + // fillIn, + find, + // findAll, +} from '@ember/test-helpers'; +import { setupApplicationTest } from 'ember-qunit'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import a11yAudit from 'ember-a11y-testing/test-support/audit'; +//import { Response } from 'miragejs'; +import { + authenticateSession, + // These are left here intentionally for future reference. + //currentSession, + //invalidateSession, +} from 'ember-simple-auth/test-support'; + +module('Acceptance | auth methods | oidc', function (hooks) { + setupApplicationTest(hooks); + setupMirage(hooks); + + const instances = { + scopes: { + global: null, + org: null, + }, + authMethod: null, + }; + const urls = { + orgScope: null, + authMethods: null, + authMethod: null, + }; + + hooks.beforeEach(function () { + authenticateSession({}); + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.authMethod = this.server.create('auth-method', { + scope: instances.scopes.org, + type: 'oidc', + }); + + urls.orgScope = `/scopes/${instances.scopes.org.id}`; + urls.authMethods = `${urls.orgScope}/auth-methods`; + urls.authMethod = `${urls.authMethods}/${instances.authMethod.id}`; + }); + + test('visiting oidc auth method', async function (assert) { + assert.expect(1); + await visit(urls.authMethod); + await a11yAudit(); + assert.equal(currentURL(), urls.authMethod); + }); + + test('can view oidc state', async function (assert) { + assert.expect(1); + await visit(urls.authMethod); + await click('.rose-layout-page-actions .rose-dropdown-trigger'); + assert.equal( + find('.rose-dropdown[open] input[aria-checked=true]').value, + instances.authMethod.attributes.state + ); + }); + + test('can update oidc state', async function (assert) { + assert.expect(1); + const updateValue = 'inactive'; + await visit(urls.authMethod); + await click('.rose-layout-page-actions .rose-dropdown-trigger'); + await click(`.rose-dropdown[open] input[value="${updateValue}"]`); + const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + assert.equal(authMethod.attributes.state, updateValue); + }); + + test('can update oidc state to active-private', async function (assert) { + assert.expect(1); + const updateValue = 'active-private'; + await visit(urls.authMethod); + await click('.rose-layout-page-actions .rose-dropdown-trigger'); + await click(`.rose-dropdown[open] input[value="${updateValue}"]`); + const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + assert.equal(authMethod.attributes.state, updateValue); + }); + + test('can update oidc state to active-public', async function (assert) { + assert.expect(1); + // Update default 'active-public' state to inactive + instances.authMethod.attributes.state = 'inactive'; + const updateValue = 'active-public'; + await visit(urls.authMethod); + await click('.rose-layout-page-actions .rose-dropdown-trigger'); + await click(`.rose-dropdown[open] input[value="${updateValue}"]`); + const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + assert.equal(authMethod.attributes.state, updateValue); + }); + + // FIXME: How to mock just one request routed to /auth-methods and let everything else passthrough? + // test('errors are displayed when state update fails', async function (assert) { + // assert.expect(2); + // this.server.post('/auth-methods/:idMethod', (_, request) => { + // // Only respond to state update with error + // if (request.params.idMethod.match(/(change-state)/i)) { + // return new Response( + // 400, + // {}, + // { + // status: 400, + // code: 'error', + // message: 'Sorry!', + // } + // ); + // } + // return request; + // }); + // const newState = 'inactive'; + // await visit(urls.authMethod); + // await click('.rose-layout-page-actions .rose-dropdown-trigger'); + // await click(`.rose-dropdown[open] input[value="${newState}"]`); + // // debugger; + // const authMethod = this.server.db.authMethods.find(instances.authMethod.id); + // assert.notEqual( + // newState, + // authMethod.attributes.state, + // 'Auth method state is not be updated.' + // ); + // assert.ok( + // find('[role="alert"]').textContent.trim(), + // 'Sorry!', + // 'Displays error message.' + // ); + // }); +}); diff --git a/yarn.lock b/yarn.lock index 9e3fcdc2a..d337a2651 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2068,10 +2068,10 @@ resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-1.1.0.tgz#d6dbc7574774b238114582410e8fee0dc3532bdf" integrity sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A== -"@hashicorp/structure-icons@^1.8.1": - version "1.8.1" - resolved "https://registry.yarnpkg.com/@hashicorp/structure-icons/-/structure-icons-1.8.1.tgz#d29945df2b41dcb317b141e51a26bd4be796a164" - integrity sha512-XFYdCIshmaR3Igc8eWpOZ2Gr3IR/0TogXZ4PQ9bz1E9cLzF3njBcs3tCpJUOwRwe/wMI5YTlL/sOGvcZ77AB/Q== +"@hashicorp/structure-icons@^1.9.0-0": + version "1.9.1" + resolved "https://registry.yarnpkg.com/@hashicorp/structure-icons/-/structure-icons-1.9.1.tgz#eb294ef28e8192062f6c7c08459382ba31a8b4af" + integrity sha512-uuncAHmehXk/WeNzE3NqS8z0YX/AU3Go8rDEUK2rNFdp1c0T1HiHO6RaBW5byB8KEBAPRdSsFSNqB4RaETarNA== "@iarna/cli@^1.2.0": version "1.2.0" @@ -2209,18 +2209,18 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" -"@nodelib/fs.scandir@2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz#d4b3549a5db5de2683e0c1071ab4f140904bbf69" - integrity sha512-33g3pMJk3bg5nXbL/+CY6I2eJDzZAni49PfJnL5fghPTggPvBd/pFNSgJsdAgWptuFu7qq/ERvOYFlhvsLTCKA== +"@nodelib/fs.scandir@2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" + integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== dependencies: - "@nodelib/fs.stat" "2.0.4" + "@nodelib/fs.stat" "2.0.5" run-parallel "^1.1.9" -"@nodelib/fs.stat@2.0.4", "@nodelib/fs.stat@^2.0.2": - version "2.0.4" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.4.tgz#a3f2dd61bab43b8db8fa108a121cfffe4c676655" - integrity sha512-IYlHJA0clt2+Vg7bccq+TzRdJvv19c2INqBSsoOLp1je7xjtr7J26+WXR72MCdvU9q1qTzIWDfhMf+DRvQJK4Q== +"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": + version "2.0.5" + resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" + integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== "@nodelib/fs.stat@^1.1.2": version "1.1.3" @@ -2228,11 +2228,11 @@ integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== "@nodelib/fs.walk@^1.2.3": - version "1.2.6" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.6.tgz#cce9396b30aa5afe9e3756608f5831adcb53d063" - integrity sha512-8Broas6vTtW4GIXTAHDoE32hnN2M5ykgCpWGbuXHQ15vEMqr23pB76e/GZcYsZCHALv50ktd24qhEyKr6wBtow== + version "1.2.7" + resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz#94c23db18ee4653e129abd26fb06f870ac9e1ee2" + integrity sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA== dependencies: - "@nodelib/fs.scandir" "2.1.4" + "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" "@npmcli/ci-detect@^1.0.0": @@ -3043,9 +3043,9 @@ integrity sha512-LfZwXoGUDo0C3me81HXgkBg5CTQYb6xzEl+fNmbO4JdRiSKQ8A0GD1OBBvKAIsbCUgoyAty7m99GqqMQe784ew== "@types/express-serve-static-core@^4.17.18": - version "4.17.20" - resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.20.tgz#44caee029f2c26c46711da5e845cdc12167ad72d" - integrity sha512-8qqFN4W53IEWa9bdmuVrUcVkFemQWnt5DKPQ/oa8xKDYgtjCr2OO6NX5TIK49NLFr3mPYU2cLh92DQquC3oWWQ== + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.21.tgz#a427278e106bca77b83ad85221eae709a3414d42" + integrity sha512-gwCiEZqW6f7EoR8TTEfalyEhb1zA5jQJnRngr97+3pzMaO1RKoI1w2bw07TK72renMUVWcWS5mLI6rk1NqN0nA== dependencies: "@types/node" "*" "@types/qs" "*" @@ -3193,9 +3193,9 @@ form-data "^3.0.0" "@types/node@*", "@types/node@>=10.0.0": - version "15.6.2" - resolved "https://registry.yarnpkg.com/@types/node/-/node-15.6.2.tgz#c61d49f38af70da32424b5322eee21f97e627175" - integrity sha512-dxcOx8801kMo3KlU+C+/ctWrzREAH7YvoF3aoVpRdqgs+Kf7flp+PJDN/EX5bME3suDUZHsxes9hpvBmzYlWbA== + version "15.12.1" + resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.1.tgz#9b60797dee1895383a725f828a869c86c6caa5c2" + integrity sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw== "@types/node@^14.0.10": version "14.17.2" @@ -3589,7 +3589,7 @@ abbrev@1, abbrev@~1.1.1: resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -abortcontroller-polyfill@^1.4.0: +abortcontroller-polyfill@^1.7.1: version "1.7.3" resolved "https://registry.yarnpkg.com/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.3.tgz#1b5b487bd6436b5b764fd52a612509702c3144b5" integrity sha512-zetDJxd89y3X99Kvo4qFx8GKlt6GsvN3UcRZHwU6iFA/0KiOmhkTVhe8oRoTBiTVPZu09x3vCra47+w8Yz1+2Q== @@ -4264,9 +4264,9 @@ axe-core@3.5.5: integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== axe-core@^4.1.4: - version "4.2.1" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.1.tgz#2e50bcf10ee5b819014f6e342e41e45096239e34" - integrity sha512-evY7DN8qSIbsW2H/TWQ1bX3sXN1d4MNb5Vb4n7BzPuCwRHdkZ1H2eNLuSh73EoQqkGKUtju2G2HCcjCfhvZIAA== + version "4.2.2" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.2.2.tgz#0c987d82c8b82b4b9b7a945f1b5ef0d8fed586ed" + integrity sha512-OKRkKM4ojMEZRJ5UNJHmq9tht7cEnRnqKG6KyB/trYws00Xtkv12mHtlJ0SK7cmuNbrU8dPUova3ELTuilfBbw== babel-code-frame@^6.26.0: version "6.26.0" @@ -5431,7 +5431,7 @@ broccoli-clean-css@^1.1.0: inline-source-map-comment "^1.0.5" json-stable-stringify "^1.0.0" -broccoli-concat@^4.2.2, broccoli-concat@^4.2.4: +broccoli-concat@^4.2.2, broccoli-concat@^4.2.4, broccoli-concat@^4.2.5: version "4.2.5" resolved "https://registry.yarnpkg.com/broccoli-concat/-/broccoli-concat-4.2.5.tgz#d578f00094048b5fc87195e82fbdbde20d838d29" integrity sha512-dFB5ATPwOyV8S2I7a07HxCoutoq23oY//LhM6Mou86cWUTB174rND5aQLR7Fu8FjFFLxoTbkk7y0VPITJ1IQrw== @@ -6381,9 +6381,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001219: - version "1.0.30001233" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001233.tgz#b7cb4a377a4b12ed240d2fa5c792951a06e5f2c4" - integrity sha512-BmkbxLfStqiPA7IEzQpIk0UFZFf3A4E6fzjPJ6OR+bFC2L8ES9J8zGA/asoi47p8XDVkev+WJo2I2Nc8c/34Yg== + version "1.0.30001234" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001234.tgz#8fc2e709e3b0679d7af7f073a1c661155c39b975" + integrity sha512-a3gjUVKkmwLdNysa1xkUAwN2VfJUJyVW47rsi3aCbkRCtbHAfo+rOsCqVw29G6coQ8gzAPb5XBXwiGHwme3isA== capture-exit@^2.0.0: version "2.0.0" @@ -7038,9 +7038,9 @@ concat-stream@^1.4.6, concat-stream@^1.5.0: typedarray "^0.0.6" config-chain@^1.1.11, config-chain@^1.1.12: - version "1.1.12" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" - integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== + version "1.1.13" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4" + integrity sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ== dependencies: ini "^1.3.4" proto-list "~1.2.1" @@ -7643,7 +7643,7 @@ debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.6: dependencies: ms "^2.1.1" -debuglog@^1.0.1: +debuglog@*, debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= @@ -8211,9 +8211,9 @@ electron-rebuild@^2.0.3: yargs "^16.0.0" electron-to-chromium@^1.3.723: - version "1.3.743" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.743.tgz#fcec24d6d647cb84fd796b42caa1b4039a180894" - integrity sha512-K2wXfo9iZQzNJNx67+Pld0DRF+9bYinj62gXCdgPhcu1vidwVuLPHQPPFnCdO55njWigXXpfBiT90jGUPbw8Zg== + version "1.3.748" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.748.tgz#16638a8130f407ae5bf2fc168f2173574deb36c5" + integrity sha512-fmIKfYALVeEybk/L2ucdgt7jN3JsbGtg3K9pmF/MRWgkeADBI1VSAa5IzdG2gZwTxsnsrFtdMpOTSM5mrBRKVQ== element-resize-detector@^1.2.2: version "1.2.2" @@ -9050,12 +9050,12 @@ ember-feature-flags@^6.0.0: ember-cli-babel "^7.11.1" ember-fetch@^8.0.1: - version "8.0.4" - resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-8.0.4.tgz#b9a3239e9d188ada18a4448ccb4fea2a091e3952" - integrity sha512-vx/v6+OTZMDbm6BT5TI6Q/NSuaWPbQxb2KRDD4yR/iJoRl2DtfgXiCA491pYT5IwAAtp6NbMa1CitIQDoaII9Q== + version "8.0.5" + resolved "https://registry.yarnpkg.com/ember-fetch/-/ember-fetch-8.0.5.tgz#0ed6257b3158473f662e72831cab223010c5ec50" + integrity sha512-tKwSWZ2l4jKynqUuF8qgwiCfzmnRWDDO2j5l0Lrn4TgZo8YXbdM3DfgheLSZd+aybBFEAtXBgKH0qaTox28LFg== dependencies: - abortcontroller-polyfill "^1.4.0" - broccoli-concat "^4.2.4" + abortcontroller-polyfill "^1.7.1" + broccoli-concat "^4.2.5" broccoli-debug "^0.6.5" broccoli-merge-trees "^4.2.0" broccoli-rollup "^2.1.1" @@ -9063,10 +9063,11 @@ ember-fetch@^8.0.1: broccoli-templater "^2.0.1" calculate-cache-key-for-tree "^2.0.0" caniuse-api "^3.0.0" - ember-cli-babel "^7.23.0" + ember-cli-babel "^7.23.1" ember-cli-typescript "^3.1.3" + ember-cli-version-checker "^5.1.2" node-fetch "^2.6.1" - whatwg-fetch "^3.4.0" + whatwg-fetch "^3.6.2" ember-focus-trap@^0.4.0: version "0.4.0" @@ -10158,9 +10159,9 @@ execa@^4.0.0, execa@^4.0.3, execa@^4.1.0: strip-final-newline "^2.0.0" execa@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.0.tgz#3ea50ee863d226bfa323528cce1684e7481dfe46" - integrity sha512-CkdUB7s2y6S+d4y+OM/+ZtQcJCiKUCth4cNImGMqrt2zEVtW2rfHGspQBE1GDo6LjeNIQmTPKXqTCKjqFKyu3A== + version "5.1.1" + resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd" + integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg== dependencies: cross-spawn "^7.0.3" get-stream "^6.0.0" @@ -12051,9 +12052,9 @@ heimdalljs@^0.3.0: rsvp "~3.2.1" highlight.js@^10.1.1, highlight.js@^10.4.1, highlight.js@~10.7.0: - version "10.7.2" - resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360" - integrity sha512-oFLl873u4usRM9K63j4ME9u3etNF0PLiJhSQ8rdfuL51Wn3zkD6drf9ZW0dOzjnZI22YYG24z30JcmfCZjMgYg== + version "10.7.3" + resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531" + integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A== hmac-drbg@^1.0.1: version "1.0.1" @@ -12409,7 +12410,7 @@ import-lazy@^4.0.0: resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw== -imurmurhash@^0.1.4: +imurmurhash@*, imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= @@ -14098,6 +14099,11 @@ lodash._baseflatten@^3.0.0: lodash.isarguments "^3.0.0" lodash.isarray "^3.0.0" +lodash._baseindexof@*: + version "3.1.0" + resolved "https://registry.yarnpkg.com/lodash._baseindexof/-/lodash._baseindexof-3.1.0.tgz#fe52b53a1c6761e42618d654e4a25789ed61822c" + integrity sha1-/lK1OhxnYeQmGNZU5KJXie1hgiw= + lodash._baseuniq@~4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" @@ -14106,11 +14112,16 @@ lodash._baseuniq@~4.6.0: lodash._createset "~4.0.0" lodash._root "~3.0.0" -lodash._bindcallback@^3.0.0: +lodash._bindcallback@*, lodash._bindcallback@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/lodash._bindcallback/-/lodash._bindcallback-3.0.1.tgz#e531c27644cf8b57a99e17ed95b35c748789392e" integrity sha1-5THCdkTPi1epnhftlbNcdIeJOS4= +lodash._cacheindexof@*: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash._cacheindexof/-/lodash._cacheindexof-3.0.2.tgz#3dc69ac82498d2ee5e3ce56091bafd2adc7bde92" + integrity sha1-PcaayCSY0u5ePOVgkbr9Ktx73pI= + lodash._createassigner@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/lodash._createassigner/-/lodash._createassigner-3.1.1.tgz#838a5bae2fdaca63ac22dee8e19fa4e6d6970b11" @@ -14120,12 +14131,19 @@ lodash._createassigner@^3.0.0: lodash._isiterateecall "^3.0.0" lodash.restparam "^3.0.0" +lodash._createcache@*: + version "3.1.2" + resolved "https://registry.yarnpkg.com/lodash._createcache/-/lodash._createcache-3.1.2.tgz#56d6a064017625e79ebca6b8018e17440bdcf093" + integrity sha1-VtagZAF2JeeevKa4AY4XRAvc8JM= + dependencies: + lodash._getnative "^3.0.0" + lodash._createset@~4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" integrity sha1-D0ZZ+7CddRlPqeK4imZE02PJ/iY= -lodash._getnative@^3.0.0: +lodash._getnative@*, lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= @@ -14343,7 +14361,7 @@ lodash.pick@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= -lodash.restparam@^3.0.0: +lodash.restparam@*, lodash.restparam@^3.0.0: version "3.6.1" resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= @@ -18766,9 +18784,9 @@ sass-lint@^1.13.1: util "^0.10.3" sass@^1.26.5, sass@^1.29.0: - version "1.34.0" - resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.0.tgz#e46d5932d8b0ecc4feb846d861f26a578f7f7172" - integrity sha512-rHEN0BscqjUYuomUEaqq3BMgsXqQfkcMVR7UhscsAVub0/spUrZGBMxQXFS2kfiDsPLZw5yuU9iJEFNC2x38Qw== + version "1.34.1" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.34.1.tgz#30f45c606c483d47b634f1e7371e13ff773c96ef" + integrity sha512-scLA7EIZM+MmYlej6sdVr0HRbZX5caX5ofDT9asWnUJj21oqgsC+1LuNfm0eg+vM0fCTZHhwImTiCU0sx9h9CQ== dependencies: chokidar ">=3.0.0 <4.0.0" @@ -21530,7 +21548,7 @@ whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@^3.4.0: +whatwg-fetch@^3.6.2: version "3.6.2" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz#dced24f37f2624ed0281725d51d0e2e3fe677f8c" integrity sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==