Merge branch 'main' into update-connect-dialog
This commit is contained in:
19 files changed
+303
-66
No files matched your search
@@ -26,15 +26,10 @@ export default class AccountModel extends GeneratedAccountModel {
|
||||
* Convenience for getting username in account.
|
||||
* @type {string}
|
||||
*/
|
||||
@computed('type', 'attributes.login_name')
|
||||
get username() {
|
||||
let username = '';
|
||||
switch (this.type) {
|
||||
case 'password':
|
||||
username = this.attributes.login_name;
|
||||
break;
|
||||
}
|
||||
return username;
|
||||
@computed('attributes.{email,full_name,login_name}')
|
||||
get accountName() {
|
||||
const { email, full_name, login_name } = this.attributes;
|
||||
return email || full_name || login_name;
|
||||
}
|
||||
|
||||
// =methods
|
||||
|
||||
@@ -23,9 +23,28 @@ export default class AccountSerializer extends ApplicationSerializer {
|
||||
switch (snapshot.record.type) {
|
||||
case 'password':
|
||||
return this.serializePassword(...arguments);
|
||||
case 'oidc':
|
||||
return this.serializeOIDC(...arguments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OIDC serialization limits the `attributes` posted on create and omits
|
||||
* `attributes` entirely on update.
|
||||
*/
|
||||
serializeOIDC(snapshot) {
|
||||
const { isNew } = snapshot?.record;
|
||||
let serialized = super.serialize(...arguments);
|
||||
delete serialized.attributes;
|
||||
if (isNew) {
|
||||
serialized.attributes = {};
|
||||
const { issuer, subject } = snapshot.attr('attributes').record;
|
||||
if (issuer) serialized.attributes.issuer = issuer;
|
||||
if (subject) serialized.attributes.subject = subject;
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Password serialization omits `attributes` and handles password-related
|
||||
* custom methods.
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
import factory from '../generated/factories/account';
|
||||
import { random, internet } from 'faker';
|
||||
|
||||
export default factory.extend();
|
||||
export default factory.extend({
|
||||
attributes() {
|
||||
switch (this.type) {
|
||||
case 'password':
|
||||
return { login_name: random.words() };
|
||||
case 'oidc':
|
||||
return {
|
||||
issuer: internet.ip(),
|
||||
subject: 'sub',
|
||||
email: internet.email(),
|
||||
full_name: random.words(),
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -14,9 +14,9 @@ export default factory.extend({
|
||||
*/
|
||||
withAccountsAndUsers: trait({
|
||||
afterCreate(authMethod, server) {
|
||||
const { scope } = authMethod;
|
||||
const { scope, type } = authMethod;
|
||||
server.createList('user', 5, { scope }).map((user) => {
|
||||
const { id } = server.create('account', { scope, authMethod });
|
||||
const { id } = server.create('account', { scope, type, authMethod });
|
||||
user.update({ accountIds: [id] });
|
||||
});
|
||||
},
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
import { Factory } from 'ember-cli-mirage';
|
||||
import { random, date, datatype } from 'faker';
|
||||
import { date, datatype } from 'faker';
|
||||
|
||||
/**
|
||||
* GeneratedAccountFactory
|
||||
*/
|
||||
export default Factory.extend({
|
||||
type: 'password',
|
||||
name: () => random.words(),
|
||||
description: () => random.words(),
|
||||
// name: () => random.words(),
|
||||
// description: () => random.words(),
|
||||
created_time: () => date.recent(),
|
||||
updated_time: () => date.recent(),
|
||||
version: () => datatype.number(),
|
||||
attributes: () => ({ login_name: random.words() }),
|
||||
});
|
||||
@@ -10,15 +10,14 @@ module('Unit | Model | account', function (hooks) {
|
||||
assert.ok(model);
|
||||
});
|
||||
|
||||
test('it has an username attribute for password account', function (assert) {
|
||||
test('it has an accountName attribute for password account', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
let model = store.createRecord('account', {
|
||||
name: 'name',
|
||||
type: 'password',
|
||||
});
|
||||
assert.notOk(model.username);
|
||||
model.attributes.login_name = 'username';
|
||||
assert.equal(model.username, 'username');
|
||||
assert.notOk(model.accountName);
|
||||
model.attributes.login_name = 'foobar';
|
||||
assert.equal(model.accountName, 'foobar');
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,75 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Serializer | account', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it serializes normally', function (assert) {
|
||||
test('it serializes OIDC-type with only issuer and subject in `attributes` on create', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('account');
|
||||
const record = store.createRecord('account', {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
issuer: 'issuer',
|
||||
subject: 'sub',
|
||||
full_name: 'full_name',
|
||||
email: 'email',
|
||||
},
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
version: 1,
|
||||
attributes: {
|
||||
issuer: 'issuer',
|
||||
subject: 'sub',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes OIDC-type without `attributes` on update', function (assert) {
|
||||
assert.expect(2);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('account');
|
||||
store.push({
|
||||
data: {
|
||||
id: '1',
|
||||
type: 'account',
|
||||
attributes: {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
attributes: {
|
||||
issuer: 'issuer',
|
||||
subject: 'sub',
|
||||
full_name: 'full_name',
|
||||
email: 'email',
|
||||
},
|
||||
version: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
const record = store.peekRecord('account', '1');
|
||||
const snapshot = record._createSnapshot();
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.notOk(serializedRecord.attributes);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'oidc',
|
||||
name: 'Name',
|
||||
auth_method_id: '1',
|
||||
description: 'Description',
|
||||
version: 1,
|
||||
});
|
||||
});
|
||||
|
||||
test('it serializes password-type normally', function (assert) {
|
||||
assert.expect(1);
|
||||
const store = this.owner.lookup('service:store');
|
||||
const serializer = store.serializerFor('account');
|
||||
@@ -19,7 +87,6 @@ module('Unit | Serializer | account', function (hooks) {
|
||||
version: 1,
|
||||
});
|
||||
const snapshot = record._createSnapshot();
|
||||
snapshot.adapterOptions = {};
|
||||
const serializedRecord = serializer.serialize(snapshot);
|
||||
assert.deepEqual(serializedRecord, {
|
||||
type: 'password',
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{{#if @model.type}}
|
||||
{{component
|
||||
(concat 'form/account/' @model.type)
|
||||
model=@model
|
||||
submit=@submit
|
||||
cancel=@cancel
|
||||
}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,127 @@
|
||||
<Rose::Form
|
||||
@onSubmit={{@submit}}
|
||||
@cancel={{@cancel}}
|
||||
@disabled={{@model.isSaving}}
|
||||
@error={{@model.errors.base}}
|
||||
@showEditToggle={{if @model.isNew false true}}
|
||||
as |form|
|
||||
>
|
||||
<form.input
|
||||
@value={{@model.type}}
|
||||
@label={{t 'form.type.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
|
||||
{{#unless @model.isNew}}
|
||||
<form.input
|
||||
@value={{@model.attributes.issuer}}
|
||||
@label={{t 'form.issuer.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.attributes.subject}}
|
||||
@label={{t 'form.subject.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.attributes.email}}
|
||||
@label={{t 'form.email.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
<form.input
|
||||
@value={{@model.attributes.full_name}}
|
||||
@label={{t 'form.full_name.label'}}
|
||||
readonly={{true}}
|
||||
@disabled={{true}}
|
||||
/>
|
||||
{{/unless}}
|
||||
|
||||
<form.input
|
||||
@name='name'
|
||||
@type='name'
|
||||
@value={{@model.name}}
|
||||
@label={{t 'form.name.label'}}
|
||||
@helperText={{t 'form.name.help'}}
|
||||
@error={{@model.errors.name}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.name}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.name as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.textarea
|
||||
@name='description'
|
||||
@type='description'
|
||||
@value={{@model.description}}
|
||||
@label={{t 'form.description.label'}}
|
||||
@helperText={{t 'form.description.help'}}
|
||||
@error={{@model.errors.description}}
|
||||
disabled={{@model.isSaving}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.description}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.description as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.textarea>
|
||||
|
||||
{{#if @model.isNew}}
|
||||
<form.input
|
||||
@name='issuer'
|
||||
@type='text'
|
||||
@value={{@model.attributes.issuer}}
|
||||
@label={{t 'form.issuer.label'}}
|
||||
@error={{@model.errors.issuer}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.issuer}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.issuer as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
|
||||
<form.input
|
||||
@name='subject'
|
||||
@type='text'
|
||||
@value={{@model.attributes.subject}}
|
||||
@label={{t 'form.subject.label'}}
|
||||
@error={{@model.errors.subject}}
|
||||
readonly={{false}}
|
||||
as |field|
|
||||
>
|
||||
{{#if @model.errors.subject}}
|
||||
<field.errors as |errors|>
|
||||
{{#each @model.errors.subject as |error|}}
|
||||
<errors.message>{{error.message}}</errors.message>
|
||||
{{/each}}
|
||||
</field.errors>
|
||||
{{/if}}
|
||||
</form.input>
|
||||
{{/if}}
|
||||
|
||||
<form.actions
|
||||
@disabled={{if @model.cannotSave @model.cannotSave}}
|
||||
@enableEditText={{t 'actions.edit-form'}}
|
||||
@submitText={{t 'actions.save'}}
|
||||
@cancelText={{t 'actions.cancel'}}
|
||||
/>
|
||||
|
||||
</Rose::Form>
|
||||
@@ -17,7 +17,8 @@
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.username.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'resources.auth-method.title'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
@@ -31,7 +32,12 @@
|
||||
onChange={{fn this.toggleAccount account}}
|
||||
/>
|
||||
</row.cell>
|
||||
<row.cell>{{account.username}}</row.cell>
|
||||
<row.cell>{{account.accountName}}</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Badge>
|
||||
{{t (concat 'resources.auth-method.types.' account.type)}}
|
||||
</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<code>{{account.auth_method_id}}</code>
|
||||
</row.cell>
|
||||
|
||||
@@ -8,11 +8,11 @@ export default class ScopesScopeAuthMethodsAuthMethodAccountsNewRoute extends Ro
|
||||
* @return {AccountModel}
|
||||
*/
|
||||
model() {
|
||||
const { id: auth_method_id } = this.modelFor(
|
||||
const { id: auth_method_id, type } = this.modelFor(
|
||||
'scopes.scope.auth-methods.auth-method'
|
||||
);
|
||||
return this.store.createRecord('account', {
|
||||
type: 'password',
|
||||
type,
|
||||
auth_method_id,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,23 +53,12 @@
|
||||
</dropdown.button>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#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.link
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.new'
|
||||
@model={{@model}}
|
||||
>
|
||||
{{t 'resources.account.actions.create'}}
|
||||
</dropdown.link>
|
||||
<dropdown.separator />
|
||||
<dropdown.button
|
||||
@style='danger'
|
||||
|
||||
+7
-5
@@ -4,9 +4,11 @@
|
||||
>
|
||||
{{t 'titles.details'}}
|
||||
</nav.link>
|
||||
<nav.link
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.account.set-password'
|
||||
>
|
||||
{{t 'actions.set-password'}}
|
||||
</nav.link>
|
||||
{{#if @model.isPassword}}
|
||||
<nav.link
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.account.set-password'
|
||||
>
|
||||
{{t 'actions.set-password'}}
|
||||
</nav.link>
|
||||
{{/if}}
|
||||
</Rose::Nav::Tabs>
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
<Form::Account::Password
|
||||
<Form::Account
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<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.type.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
@@ -17,7 +18,13 @@
|
||||
{{#each @model as |account|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell scope='row'>
|
||||
{{account.displayName}}
|
||||
<LinkTo
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.account'
|
||||
@model={{account.id}}
|
||||
>
|
||||
{{account.accountName}}
|
||||
<p>{{account.description}}</p>
|
||||
</LinkTo>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
<code>{{account.attributes.issuer}}</code>
|
||||
@@ -31,6 +38,9 @@
|
||||
<row.cell>
|
||||
{{account.attributes.full_name}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Badge>{{account.type}}</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
@text={{account.id}}
|
||||
@@ -49,11 +59,8 @@
|
||||
<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.login_name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
</header.row>
|
||||
</table.header>
|
||||
@@ -65,12 +72,15 @@
|
||||
@route='scopes.scope.auth-methods.auth-method.accounts.account'
|
||||
@model={{account.id}}
|
||||
>
|
||||
{{account.displayName}}
|
||||
{{account.accountName}}
|
||||
<p>{{account.description}}</p>
|
||||
</LinkTo>
|
||||
<p>{{account.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{account.username}}
|
||||
{{account.attributes.login_name}}
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Rose::Badge>{{account.type}}</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Form::Account::Password
|
||||
<Form::Account
|
||||
@model={{@model}}
|
||||
@submit={{route-action 'save' @model}}
|
||||
@cancel={{route-action 'cancel' @model}}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<table.header as |header|>
|
||||
<header.row as |row|>
|
||||
<row.headerCell>{{t 'form.name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.login_name.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.type.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'form.id.label'}}</row.headerCell>
|
||||
<row.headerCell>{{t 'titles.actions'}}</row.headerCell>
|
||||
</header.row>
|
||||
@@ -15,11 +15,13 @@
|
||||
{{#each @model.accounts as |account|}}
|
||||
<body.row as |row|>
|
||||
<row.headerCell>
|
||||
{{account.displayName}}
|
||||
{{account.accountName}}
|
||||
<p>{{account.description}}</p>
|
||||
</row.headerCell>
|
||||
<row.cell>
|
||||
{{account.username}}
|
||||
<Rose::Badge>
|
||||
{{t (concat 'resources.auth-method.types.' account.type)}}
|
||||
</Rose::Badge>
|
||||
</row.cell>
|
||||
<row.cell>
|
||||
<Copyable
|
||||
|
||||
@@ -89,7 +89,7 @@ module.exports = function (environment) {
|
||||
'primary-auth-method': true,
|
||||
oidc: true,
|
||||
'oidc-crud': true,
|
||||
'oidc-account-crud': false,
|
||||
'oidc-account-crud': true,
|
||||
search: false,
|
||||
filter: false,
|
||||
capabilities: false,
|
||||
|
||||
@@ -15,8 +15,7 @@ const generateMenuTemplate = () => {
|
||||
type: 'none',
|
||||
message: config.productName,
|
||||
detail: `${appVersion}\n${appCommit}\n\n${cliVersion}\n\n${copyright}`,
|
||||
};
|
||||
dialog.showMessageBox(dialogOpts);
|
||||
});
|
||||
};
|
||||
|
||||
return [
|
||||
|
||||
Reference in new issue
Block a user