Merge pull request #587 from hashicorp/feature-oidc-array-fields-crud
Feature oidc array fields crud
This commit is contained in:
32 files changed
+1151
-146
No files matched your search
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'api/serializers/fragment-account-attributes';
|
||||
@@ -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
|
||||
|
||||
@@ -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(/(?<id>.[^:]*)/);
|
||||
const { command } = payload;
|
||||
const [, id, method] = request.params.id_method.match(
|
||||
/(?<id>.[^:]*):(?<method>(.*))/
|
||||
);
|
||||
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() {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -8,3 +8,6 @@ connection:
|
||||
success:
|
||||
description: "Local proxy address ({protocol}): {address}:{port}"
|
||||
primary: Primary
|
||||
inactive: Inactive
|
||||
active-private: Private
|
||||
active-public: Public
|
||||
@@ -30,5 +30,6 @@
|
||||
cancel=this.handleCancel
|
||||
enableEdit=this.enableEdit
|
||||
)
|
||||
isEditable=this.isEditable
|
||||
)}}
|
||||
</form>
|
||||
@@ -2,6 +2,8 @@
|
||||
@use '../../utilities/type';
|
||||
|
||||
.rose-form-fieldset {
|
||||
margin-bottom: sizing.rems(m);
|
||||
|
||||
.rose-form-fieldset-title {
|
||||
@include type.type(l, bold);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -4,5 +4,8 @@
|
||||
model=@model
|
||||
submit=@submit
|
||||
cancel=@cancel
|
||||
removeItemByIndex=@removeItemByIndex
|
||||
addStringItem=@addStringItem
|
||||
addAccountClaimMapItem=@addAccountClaimMapItem
|
||||
}}
|
||||
{{/if}}
|
||||
@@ -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}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes.issuer as |error|}}
|
||||
{{#each @model.errors.attributes_issuer as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
@@ -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}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes.client_id as |error|}}
|
||||
{{#each @model.errors.attributes_client_id as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
@@ -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}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes.client_secret as |error|}}
|
||||
{{#each @model.errors.attributes_client_secret as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
{{! Signing Algorithms }}
|
||||
<Rose::Form::Fieldset>
|
||||
<:title>{{t 'form.signing_algorithms.label'}}</:title>
|
||||
<:description>{{t 'form.signing_algorithms.help'}}</:description>
|
||||
<:body>
|
||||
{{#if @model.errors.attributes_signing_algorithms}}
|
||||
<Rose::Form::Errors as |errors|>
|
||||
{{#each @model.errors.attributes_signing_algorithms as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</Rose::Form::Errors>
|
||||
{{/if}}
|
||||
<Rose::Table @style='condensed' as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row @hidden={{true}} as |row|>
|
||||
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model.attributes.signing_algorithms as |algorithm i|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.select
|
||||
@value={{algorithm.value}}
|
||||
@error={{@model.errors.attributes_signing_algorithms}}
|
||||
@contextual={{true}}
|
||||
@onChange={{fn (mut algorithm.value)}}
|
||||
as |field|
|
||||
>
|
||||
<field.label>{{t 'form.algorithm.label'}}</field.label>
|
||||
<field.field as |select|>
|
||||
{{#each this.signingAlgorithms as |algorithm|}}
|
||||
<select.option
|
||||
@value={{algorithm}}
|
||||
>{{algorithm}}</select.option>
|
||||
{{/each}}
|
||||
</field.field>
|
||||
</form.select>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.signing_algorithms' i)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.select
|
||||
@value={{this.newSigningAlgorithm}}
|
||||
@label={{t
|
||||
'resources.auth-method.titles.new-signing-algorithm'
|
||||
}}
|
||||
@onChange={{fn (mut this.newSigningAlgorithm)}}
|
||||
as |field|
|
||||
>
|
||||
<field.field as |select|>
|
||||
{{#each this.signingAlgorithms as |algorithm|}}
|
||||
<select.option
|
||||
@value={{algorithm}}
|
||||
>{{algorithm}}</select.option>
|
||||
{{/each}}
|
||||
</field.field>
|
||||
</form.select>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@addStringItem
|
||||
'attributes.signing_algorithms'
|
||||
this.newSigningAlgorithm
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
</:body>
|
||||
</Rose::Form::Fieldset>
|
||||
|
||||
{{! Allowed Audiences }}
|
||||
<Rose::Form::Fieldset>
|
||||
<:title>{{t 'form.allowed_audiences.label'}}</:title>
|
||||
<:description>{{t 'form.allowed_audiences.help'}}</:description>
|
||||
<:body>
|
||||
{{#if @model.errors.attributes_allowed_audiences}}
|
||||
<Rose::Form::Errors as |errors|>
|
||||
{{#each @model.errors.attributes_allowed_audiences as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</Rose::Form::Errors>
|
||||
{{/if}}
|
||||
<Rose::Table @style='condensed' as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row @hidden={{true}} as |row|>
|
||||
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model.attributes.allowed_audiences as |audience i|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.input
|
||||
@type='text'
|
||||
@value={{audience.value}}
|
||||
@error={{@model.errors.attributes_allowed_audiences}}
|
||||
@contextual={{true}}
|
||||
as |field|
|
||||
>
|
||||
<field.label>{{t 'form.claim.label'}}</field.label>
|
||||
<field.field />
|
||||
</form.input>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.allowed_audiences' i)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.input
|
||||
@type='text'
|
||||
@value={{this.newAllowedAudience}}
|
||||
@label={{t 'resources.auth-method.titles.new-allowed-audience'}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@addStringItem
|
||||
'attributes.allowed_audiences'
|
||||
this.newAllowedAudience
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
</:body>
|
||||
</Rose::Form::Fieldset>
|
||||
|
||||
{{! Claims Scopes }}
|
||||
<Rose::Form::Fieldset>
|
||||
<:title>{{t 'form.claims_scopes.label'}}</:title>
|
||||
<:description>{{t 'form.claims_scopes.help'}}</:description>
|
||||
<:body>
|
||||
{{#if @model.errors.attributes_claims_scopes}}
|
||||
<Rose::Form::Errors as |errors|>
|
||||
{{#each @model.errors.attributes_claims_scopes as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</Rose::Form::Errors>
|
||||
{{/if}}
|
||||
<Rose::Table @style='condensed' as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row @hidden={{true}} as |row|>
|
||||
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
|
||||
<row.headerCell />
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model.attributes.claims_scopes as |claimsScope i|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.input
|
||||
@type='text'
|
||||
@value={{claimsScope.value}}
|
||||
@error={{@model.errors.attributes_claims_scopes}}
|
||||
@contextual={{true}}
|
||||
as |field|
|
||||
>
|
||||
<field.label>{{t 'form.claims_scope.label'}}</field.label>
|
||||
<field.field />
|
||||
</form.input>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.claims_scopes' i)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.input
|
||||
@type='text'
|
||||
@value={{this.newClaimsScope}}
|
||||
@label={{t 'resources.auth-method.titles.new-claims-scope'}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@addStringItem
|
||||
'attributes.claims_scopes'
|
||||
this.newClaimsScope
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
</:body>
|
||||
</Rose::Form::Fieldset>
|
||||
|
||||
{{! Account Claim Maps }}
|
||||
<Rose::Form::Fieldset>
|
||||
<:title>{{t 'form.account_claim_maps.label'}}</:title>
|
||||
<:description>{{t 'form.account_claim_maps.help'}}</:description>
|
||||
<:body>
|
||||
{{#if @model.errors.attributes_account_claim_maps}}
|
||||
<Rose::Form::Errors as |errors|>
|
||||
{{#each @model.errors.attributes_account_claim_maps as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</Rose::Form::Errors>
|
||||
{{/if}}
|
||||
<Rose::Table @style='condensed' as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.from_claim.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.to_claim.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model.attributes.account_claim_maps as |claimMap i|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.input
|
||||
@type='text'
|
||||
@value={{claimMap.from}}
|
||||
@error={{@model.errors.attributes_account_claim_maps}}
|
||||
@contextual={{true}}
|
||||
as |field|
|
||||
>
|
||||
<field.label>{{t 'form.from_claim.label'}}</field.label>
|
||||
<field.field />
|
||||
</form.input>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<form.select
|
||||
@value={{claimMap.to}}
|
||||
@error={{@model.errors.attributes_account_claim_maps}}
|
||||
@contextual={{true}}
|
||||
@onChange={{fn (mut claimMap.to)}}
|
||||
as |field|
|
||||
>
|
||||
<field.label>{{t 'form.to_claim.label'}}</field.label>
|
||||
<field.field as |select|>
|
||||
{{#each this.toClaims as |toClaim|}}
|
||||
<select.option
|
||||
@value={{toClaim}}
|
||||
>{{toClaim}}</select.option>
|
||||
{{/each}}
|
||||
</field.field>
|
||||
</form.select>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.account_claim_maps' i)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.input
|
||||
@type='text'
|
||||
@value={{this.newFromClaim}}
|
||||
@label={{t 'resources.auth-method.titles.new-from-claim'}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<form.select
|
||||
@value={{this.newToClaim}}
|
||||
@label={{t 'resources.auth-method.titles.new-to-claim'}}
|
||||
@onChange={{fn (mut this.newToClaim)}}
|
||||
as |field|
|
||||
>
|
||||
<field.field as |select|>
|
||||
{{#each this.toClaims as |toClaim|}}
|
||||
<select.option
|
||||
@value={{toClaim}}
|
||||
>{{toClaim}}</select.option>
|
||||
{{/each}}
|
||||
</field.field>
|
||||
</form.select>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @addAccountClaimMapItem this.newFromClaim this.newToClaim)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
</:body>
|
||||
</Rose::Form::Fieldset>
|
||||
|
||||
{{! Certificates }}
|
||||
<Rose::Form::Fieldset>
|
||||
<:title>{{t 'form.idp_ca_certs.label'}}</:title>
|
||||
<:description>{{t 'form.idp_ca_certs.help'}}</:description>
|
||||
<:body>
|
||||
{{#if @model.errors.attributes_idp_ca_certs}}
|
||||
<Rose::Form::Errors as |errors|>
|
||||
{{#each @model.errors.attributes_idp_ca_certs as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</Rose::Form::Errors>
|
||||
{{/if}}
|
||||
<Rose::Table @style='condensed' as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row @hidden={{true}} as |row|>
|
||||
<row.headerCell>{{t 'titles.value'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model.attributes.idp_ca_certs as |cert i|}}
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.textarea
|
||||
@value={{cert.value}}
|
||||
@error={{@model.errors.attributes_idp_ca_certs}}
|
||||
@contextual={{true}}
|
||||
as |field|
|
||||
>
|
||||
<field.label>{{t 'form.certificate.label'}}</field.label>
|
||||
<field.field />
|
||||
</form.textarea>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='warning'
|
||||
@iconOnly='trash'
|
||||
title={{t 'actions.remove'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn @removeItemByIndex 'attributes.idp_ca_certs' i)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.remove'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
|
||||
<body.row as |row|>
|
||||
<row.cell>
|
||||
<form.textarea
|
||||
@value={{this.newCertificate}}
|
||||
@label={{t 'resources.auth-method.titles.new-certificate'}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell @shrink={{true}}>
|
||||
<Rose::Button
|
||||
@style='secondary'
|
||||
title={{t 'actions.add'}}
|
||||
@disabled={{if form.isEditable false true}}
|
||||
{{on
|
||||
'click'
|
||||
(fn
|
||||
@addStringItem 'attributes.idp_ca_certs' this.newCertificate
|
||||
)
|
||||
}}
|
||||
>
|
||||
{{t 'actions.add'}}
|
||||
</Rose::Button>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
</:body>
|
||||
</Rose::Form::Fieldset>
|
||||
|
||||
<form.input
|
||||
@name='max_age'
|
||||
@type='number'
|
||||
@value={{@model.attributes.max_age}}
|
||||
@label={{t 'form.max_age.label'}}
|
||||
@helperText={{t 'form.max_age.help'}}
|
||||
@error={{@model.errors.attributes.max_age}}
|
||||
@error={{@model.errors.attributes_max_age}}
|
||||
readonly={{false}}
|
||||
step='1'
|
||||
min='0'
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.attributes.max_age}}
|
||||
{{#if @model.errors.attributes_max_age}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes.max_age as |error|}}
|
||||
{{#each @model.errors.attributes_max_age as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
@@ -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}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.attributes.api_url_prefix as |error|}}
|
||||
{{#each @model.errors.attributes_api_url_prefix as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,42 @@
|
||||
{{#if @model.isOIDC}}
|
||||
<Rose::Dropdown
|
||||
@text={{t (concat 'states.' @model.attributes.state)}}
|
||||
@icon={{if
|
||||
@model.isInactive
|
||||
'disabled'
|
||||
(if @model.isPrivate 'lock-closed-outline' 'check-circle-outline')
|
||||
}}
|
||||
@dropdownRight={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
<dropdown.section @title={{t 'actions.change-state'}}>
|
||||
<Rose::Form as |form|>
|
||||
<form.radioGroup
|
||||
@name='theme'
|
||||
@selectedValue={{@model.attributes.state}}
|
||||
@changed={{route-action 'changeState'}}
|
||||
as |radioGroup|
|
||||
>
|
||||
{{#each this.oidcStates as |state|}}
|
||||
<dropdown.item>
|
||||
<radioGroup.radio
|
||||
@label={{t (concat 'states.' state)}}
|
||||
@value={{state}}
|
||||
/>
|
||||
</dropdown.item>
|
||||
{{/each}}
|
||||
</form.radioGroup>
|
||||
</Rose::Form>
|
||||
</dropdown.section>
|
||||
</Rose::Dropdown>
|
||||
{{/if}}
|
||||
|
||||
<Rose::Dropdown
|
||||
@text='{{t 'actions.manage'}}'
|
||||
@dropdownRight={{true}}
|
||||
as |dropdown|
|
||||
>
|
||||
{{#if (feature-flag 'primary-auth-method')}}
|
||||
{{#unless @model.isPrimary}}
|
||||
<dropdown.button
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'makePrimary' @model)}}
|
||||
>
|
||||
{{t 'resources.auth-method.actions.make-primary'}}
|
||||
</dropdown.button>
|
||||
{{/unless}}
|
||||
{{#if @model.isPrimary}}
|
||||
<dropdown.button
|
||||
@disabled={{@model.canSave}}
|
||||
@@ -19,14 +44,32 @@
|
||||
>
|
||||
{{t 'resources.auth-method.actions.remove-as-primary'}}
|
||||
</dropdown.button>
|
||||
{{else}}
|
||||
<dropdown.button
|
||||
@disabled={{@model.canSave}}
|
||||
{{on 'click' (route-action 'makePrimary' @model)}}
|
||||
>
|
||||
{{t 'resources.auth-method.actions.make-primary'}}
|
||||
</dropdown.button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
<dropdown.link
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.new'
|
||||
@model={{@model}}
|
||||
>
|
||||
{{t 'resources.account.actions.create'}}
|
||||
</dropdown.link>
|
||||
{{#if @model.isOIDC}}
|
||||
{{#if (feature-flag 'oidc-account-crud')}}
|
||||
<dropdown.link
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.new'
|
||||
@model={{@model}}
|
||||
>
|
||||
{{t 'resources.account.actions.create'}}
|
||||
</dropdown.link>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
<dropdown.link
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.new'
|
||||
@model={{@model}}
|
||||
>
|
||||
{{t 'resources.account.actions.create'}}
|
||||
</dropdown.link>
|
||||
{{/if}}
|
||||
<dropdown.separator />
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
|
||||
@@ -1,46 +1,91 @@
|
||||
{{#if @model}}
|
||||
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>
|
||||
{{#if @model.firstObject.isPassword}}
|
||||
{{t 'form.login_name.label'}}
|
||||
{{/if}}
|
||||
</row.headerCell>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |account|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
<LinkTo
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.account'
|
||||
@model={{account.id}}
|
||||
>
|
||||
{{! OIDC accounts display more fields }}
|
||||
{{#if @model.firstObject.isOIDC}}
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.issuer.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.subject.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.email.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.full_name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |account|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell scope='row'>
|
||||
{{account.displayName}}
|
||||
</LinkTo>
|
||||
<p>{{account.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
<code>{{account.attributes.issuer}}</code>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<code>{{account.attributes.subject}}</code>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<code>{{account.attributes.email}}</code>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
{{account.attributes.full_name}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{account.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<code>{{account.id}}</code>
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
{{else}}
|
||||
<Rose::Table as |table|>
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>
|
||||
{{#if @model.firstObject.isPassword}}
|
||||
{{t 'form.login_name.label'}}
|
||||
{{/if}}
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{account.username}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{account.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<code>{{account.id}}</code>
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
<table.body as |body|>
|
||||
{{#each @model as |account|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
<LinkTo
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.account'
|
||||
@model={{account.id}}
|
||||
>
|
||||
{{account.displayName}}
|
||||
</LinkTo>
|
||||
<p>{{account.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{account.username}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{account.id}}
|
||||
@buttonText={{t 'actions.copy-to-clipboard'}}
|
||||
@acknowledgeText={{t 'states.copied'}}
|
||||
>
|
||||
<code>{{account.id}}</code>
|
||||
</Copyable>
|
||||
</row.cell>
|
||||
</body.row>
|
||||
{{/each}}
|
||||
</table.body>
|
||||
</Rose::Table>
|
||||
{{/if}}
|
||||
{{else}}
|
||||
|
||||
<Rose::Layout::Centered>
|
||||
|
||||
@@ -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}}
|
||||
/>
|
||||
@@ -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') {
|
||||
|
||||
@@ -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.'
|
||||
// );
|
||||
// });
|
||||
});
|
||||
@@ -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==
|
||||
|
||||
Reference in new issue
Block a user