Merge branch 'main' into update-connect-dialog

This commit is contained in:
Carlos Alcaide Corvo authored and GitHub committed 2021-06-23 13:49:24 -07:00
commit d5959f4a94
19 files changed
+303 -66

No files matched your search

+4 -9
View File
@@ -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
+19
View File
@@ -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.
+16 -1
View File
@@ -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(),
};
}
},
});
+2 -2
View File
@@ -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() }),
});
+4 -5
View File
@@ -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'
@@ -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,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
+1 -1
View File
@@ -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,
+1 -2
View File
@@ -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 [