diff --git a/ui/admin/app/components/form/onboarding/index.hbs b/ui/admin/app/components/form/onboarding/index.hbs index 7588c9192..e59945d69 100644 --- a/ui/admin/app/components/form/onboarding/index.hbs +++ b/ui/admin/app/components/form/onboarding/index.hbs @@ -7,6 +7,7 @@ class='full-width' @cancel={{@cancel}} @onSubmit={{this.submit}} + @disabled={{this.isSaving}} as |form| >
diff --git a/ui/admin/app/components/form/onboarding/index.js b/ui/admin/app/components/form/onboarding/index.js index ce181c534..2f513860b 100644 --- a/ui/admin/app/components/form/onboarding/index.js +++ b/ui/admin/app/components/form/onboarding/index.js @@ -9,12 +9,26 @@ import { tracked } from '@glimmer/tracking'; export default class FormOnboardingComponent extends Component { // =properties + @tracked targetAddress; @tracked targetAddressInvalid = false; @tracked targetPort; @tracked targetPortInvalid = false; + /** + * Returns true if any of the resources that are being created are still saving. + * @type {boolean} + */ + get isSaving() { + const { org, project, target, role } = this.args.model; + return org.isSaving || project.isSaving || target.isSaving || role.isSaving; + } + // =actions + + /** + * Passes in target address and port to submit function for further proccessing. + */ @action submit() { // Check targetAddress and targetPort are valid before submit diff --git a/ui/admin/app/controllers/onboarding/index.js b/ui/admin/app/controllers/onboarding/index.js new file mode 100644 index 000000000..a21b5cb7b --- /dev/null +++ b/ui/admin/app/controllers/onboarding/index.js @@ -0,0 +1,126 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { loading } from 'ember-loading'; +import { notifyError } from 'core/decorators/notify'; + +export default class OnboardingIndexController extends Controller { + // =services + + @service intl; + @service router; + + // =actions + + /** + * Creates resources then redirects to success page. + * @param {object} model + * @param {string} targetAddress + * @param {string} targetPort + */ + @action + @loading + @notifyError(function () { + return this.intl.t('errors.onboarding-failed.description'); + }) + async createResources(model, targetAddress, targetPort) { + const { org, project, target, role } = model; + + await this.createOrg(org); + await this.createProject(org, project); + await this.createTarget(project, target, targetAddress, targetPort); + await this.createGrants(org, project, target, role); + + // Redirect to success + this.router.transitionTo('onboarding.success'); + } + + /** + * Creates Org, if fails, redirects to Orgs empty list + * @param {ScopeModel} org + */ + async createOrg(org) { + try { + await org.save(); + } catch (error) { + console.log('error ', error); + // Redirects to Orgs screen + this.router.transitionTo('scopes.scope', 'global'); + throw new Error(error); + } + } + + /** + * Creates Project, if fails redirects to Projects from Org empty list. + * @param {ScopeModel} org + * @param {ScopeModel} project + */ + async createProject(org, project) { + project.scopeID = org.id; + + try { + await project.save(); + } catch (error) { + // Redirects to Project screen + this.router.transitionTo('scopes.scope.scopes', project.scopeID); + throw new Error(error); + } + } + + /** + * Creates a Target, if fails redirects to Targets empty list. + * @param {ScopeModel} project + * @param {TargetModel} target + * @param {string} targetAddress + * @param {string} targetPort + */ + async createTarget(project, target, targetAddress, targetPort) { + target.scopeID = project.id; + target.address = targetAddress; + target.default_port = targetPort; + + try { + await target.save(); + } catch (error) { + // Redirect to targets global + this.router.replaceWith('scopes.scope.targets', project.scopeID); + throw new Error(error); + } + } + + /** + * Assigns default grants and scope to a role. + * @param {ScopeModel} org + * @param {ScopeModel} project + * @param {TargetModel} target + * @param {RoleModel} role + */ + async createGrants(org, project, target, role) { + role.scopeID = org.id; + role.grant_scope_id = project.id; + try { + await role.save(); + await role.saveGrantStrings([ + `type=target;actions=list`, + `ids=${target.id};actions=authorize-session`, + ]); + } catch (error) { + // Redirect to targets that belong to scope + this.router.replaceWith('scopes.scope.targets', project.scopeID); + throw new Error(error); + } + } + + /** + * Redirects user to index route for further processing. + */ + @action + doLater() { + this.router.transitionTo('index'); + } +} diff --git a/ui/admin/app/controllers/onboarding/success.js b/ui/admin/app/controllers/onboarding/success.js new file mode 100644 index 000000000..41518cb39 --- /dev/null +++ b/ui/admin/app/controllers/onboarding/success.js @@ -0,0 +1,29 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; + +export default class OnboardingSuccessController extends Controller { + // =services + + @service router; + + // =actions + + /** + * Redirect user to target created during onboarding. + * @param {object} model + */ + @action + showTargetList(model) { + const { + project: { id: scopeID }, + target: { id: targetID }, + } = model; + this.router.transitionTo('scopes.scope.targets.target', scopeID, targetID); + } +} diff --git a/ui/admin/app/controllers/scopes/scope/authenticate/method/index.js b/ui/admin/app/controllers/scopes/scope/authenticate/method/index.js new file mode 100644 index 000000000..9c739ffca --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/authenticate/method/index.js @@ -0,0 +1,74 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; +import { getOwner } from '@ember/application'; +import { action } from '@ember/object'; +import { loading } from 'ember-loading'; +import { notifyError } from 'core/decorators/notify'; +import { + TYPE_AUTH_METHOD_PASSWORD, + TYPE_AUTH_METHOD_OIDC, + TYPE_AUTH_METHOD_LDAP, +} from 'api/models/auth-method'; + +export default class ScopesScopeAuthenticateMethodIndexController extends Controller { + // =services + + @service session; + @service windowManager; + @service router; + + // =actions + + /** + * Delegates authentication to an authenticator of type matching the current + * auth method. If authentication fails, notifies with an alert. + */ + @action + @loading + @notifyError(() => 'errors.authentication-failed.title', { + sticky: false, + catch: true, + }) + async authenticate(authMethod, creds) { + const scope = authMethod.scope; + const authenticatorName = `authenticator:${authMethod.type}`; + const requestCookies = false; + switch (authMethod.type) { + case TYPE_AUTH_METHOD_PASSWORD: + case TYPE_AUTH_METHOD_LDAP: + await this.session.authenticate( + authenticatorName, + creds, + requestCookies, + { scope, authMethod }, + ); + this.router.transitionTo('index'); + break; + case TYPE_AUTH_METHOD_OIDC: + await this.startOIDCAuthentication(authenticatorName, { + scope, + authMethod, + }); + this.router.transitionTo('scopes.scope.authenticate.method.oidc'); + break; + } + } + + /** + * Kicks off the OIDC auth flow by opening a new window to the provider. + * @param {string} authenticatorName + * @param {object} options + */ + async startOIDCAuthentication(authenticatorName, options) { + const oidc = getOwner(this).lookup(authenticatorName); + // TODO: delegate this call from the session service so that we don't have + // to look up the authenticator directly + const json = await oidc.startAuthentication(options); + await this.windowManager.open(json.attributes.auth_url); + } +} diff --git a/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js b/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js index c64b230c3..39f22f9ba 100644 --- a/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js +++ b/ui/admin/app/controllers/scopes/scope/authenticate/method/oidc.js @@ -3,10 +3,12 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import Controller from '@ember/controller'; +import Controller, { inject as controller } from '@ember/controller'; import { inject as service } from '@ember/service'; export default class ScopesScopeAuthenticateMethodOidcController extends Controller { + @controller('scopes/scope/authenticate/method/index') authMethod; + // =services @service session; diff --git a/ui/admin/app/controllers/scopes/scope/edit.js b/ui/admin/app/controllers/scopes/scope/edit.js new file mode 100644 index 000000000..ebe02ed1f --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/edit.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller, { inject as controller } from '@ember/controller'; + +export default class ScopesScopeEditController extends Controller { + @controller('scopes/scope/index') scopes; +} diff --git a/ui/admin/app/controllers/scopes/scope/index.js b/ui/admin/app/controllers/scopes/scope/index.js new file mode 100644 index 000000000..06151bf10 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/index.js @@ -0,0 +1,77 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller from '@ember/controller'; +import { inject as service } from '@ember/service'; +import { action } from '@ember/object'; +import { loading } from 'ember-loading'; +import { confirm } from 'core/decorators/confirm'; +import { notifySuccess, notifyError } from 'core/decorators/notify'; + +export default class ScopesScopeIndexController extends Controller { + // =services + + @service router; + + //= actions + + /** + * Rollback changes on scope. + * @param {ScopeModel} scope + */ + @action + cancel(scope) { + const { isNew } = scope; + scope.rollbackAttributes(); + if (isNew) this.router.transitionTo('scopes.scope'); + } + + /** + * Handle save scope. + * @param {ScopeModel} scope + */ + @action + @loading + @notifyError(({ message }) => message) + @notifySuccess(({ isNew }) => + isNew ? 'notifications.create-success' : 'notifications.save-success', + ) + async save(scope) { + const { isNew } = scope; + await scope.save(); + await this.router.transitionTo('scopes.scope.edit', scope); + if (isNew) this.router.refresh(); + } + + /** + * Deletes the scope and redirects to index. + * @param {ScopeModel} scope + */ + @action + @loading + @confirm('questions.delete-confirm') + @notifyError(({ message }) => message, { catch: true }) + @notifySuccess('notifications.delete-success') + async delete(scope) { + const { scopeID } = scope; + await scope.destroyRecord(); + this.router.replaceWith('scopes.scope.scopes', scopeID); + } + + /** + * Deletes the scope and redirects to index. + * @param {ScopeModel} scope + */ + @action + @loading + @confirm('resources.policy.questions.detach') + @notifyError(({ message }) => message) + @notifySuccess('resources.policy.messages.detach') + async detachStoragePolicy(scope) { + const { storage_policy_id } = scope; + scope.storage_policy_id = ''; + await scope.detachStoragePolicy(storage_policy_id); + } +} diff --git a/ui/admin/app/controllers/scopes/scope/scopes/new.js b/ui/admin/app/controllers/scopes/scope/scopes/new.js new file mode 100644 index 000000000..7792ce152 --- /dev/null +++ b/ui/admin/app/controllers/scopes/scope/scopes/new.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import Controller, { inject as controller } from '@ember/controller'; + +export default class ScopesScopeScopesNewController extends Controller { + @controller('scopes/scope/index') scopes; +} diff --git a/ui/admin/app/routes/onboarding.js b/ui/admin/app/routes/onboarding.js index 1d6eb39fd..c5c0f6b84 100644 --- a/ui/admin/app/routes/onboarding.js +++ b/ui/admin/app/routes/onboarding.js @@ -5,9 +5,6 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; -import { loading } from 'ember-loading'; -import { notifyError } from 'core/decorators/notify'; import { TYPE_TARGET_TCP } from 'api/models/target'; export default class OnboardingRoute extends Route { @@ -16,7 +13,6 @@ export default class OnboardingRoute extends Route { @service store; @service session; @service router; - @service intl; // =methods @@ -34,7 +30,7 @@ export default class OnboardingRoute extends Route { /** * List all scopes (orgs) under global. - * Creare org, project, target and roles + * Create org, project, target and roles */ async model() { return { @@ -59,95 +55,4 @@ export default class OnboardingRoute extends Route { }), }; } - - // =actions - @action - @loading - @notifyError(function () { - return this.intl.t('errors.onboarding-failed.description'); - }) - async createResources(targetAddress, targetPort) { - const { org, project, target, role } = this.currentModel; - - await this.createOrg(org); - await this.createProject(org, project); - await this.createTarget(project, target, targetAddress, targetPort); - await this.createGrants(org, project, target, role); - - // Redirect to success - this.router.transitionTo('onboarding.success'); - } - - /** - * Creates Org, if fails, redirects to Orgs empty list - * @param {Object} org - */ - async createOrg(org) { - try { - await org.save(); - } catch (error) { - // Redirects to Orgs screen - this.router.transitionTo('scopes.scope', 'global'); - throw new Error(error); - } - } - - /** - * Creates Project, if fails redirects to Projects from Org empty list. - * @param {Object} org - * @param {Object} project - */ - async createProject(org, project) { - project.scopeID = org.id; - - try { - await project.save(); - } catch (error) { - // Redirects to Project screen - this.router.transitionTo('scopes.scope.scopes', project.scopeID); - throw new Error(error); - } - } - - /** - * Creates a Target, if fails redirects to Targets empyt list. - * @param {Object} project - * @param {Object} target - * @param {Object} targetAddress - * @param {Object} targetPort - */ - async createTarget(project, target, targetAddress, targetPort) { - target.scopeID = project.id; - target.address = targetAddress; - target.default_port = targetPort; - - try { - await target.save(); - } catch (error) { - // Redirect to targets global - this.router.replaceWith('scopes.scope.targets', project.scopeID); - throw new Error(error); - } - } - - async createGrants(org, project, target, role) { - role.scopeID = org.id; - role.grant_scope_ids = [project.id]; - try { - await role.save(); - await role.saveGrantStrings([ - `type=target;actions=list`, - `ids=${target.id};actions=authorize-session`, - ]); - } catch (error) { - // Redirect to targets that belong to scope - this.router.replaceWith('scopes.scope.targets', project.scopeID); - throw new Error(error); - } - } - - @action - async doLater() { - this.router.transitionTo('index'); - } } diff --git a/ui/admin/app/routes/onboarding/success.js b/ui/admin/app/routes/onboarding/success.js index e62986550..b0db38bfd 100644 --- a/ui/admin/app/routes/onboarding/success.js +++ b/ui/admin/app/routes/onboarding/success.js @@ -5,15 +5,14 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; export default class OnboardingSuccessRoute extends Route { // =services - @service router; - @service session; + @service('browser/window') window; // =methods + model() { const model = this.modelFor('onboarding'); return { @@ -21,13 +20,4 @@ export default class OnboardingSuccessRoute extends Route { clusterUrl: this.window.origin, }; } - - @action - showTargetList(scope) { - const { - project: { id: scopeID }, - target: { id: targetID }, - } = scope; - this.router.transitionTo('scopes.scope.targets.target', scopeID, targetID); - } } diff --git a/ui/admin/app/routes/scopes.js b/ui/admin/app/routes/scopes.js index fd22145bd..796ea10f7 100644 --- a/ui/admin/app/routes/scopes.js +++ b/ui/admin/app/routes/scopes.js @@ -6,10 +6,6 @@ import Route from '@ember/routing/route'; import { A } from '@ember/array'; import { inject as service } from '@ember/service'; -import { action } from '@ember/object'; -import { loading } from 'ember-loading'; -import { confirm } from 'core/decorators/confirm'; -import { notifySuccess, notifyError } from 'core/decorators/notify'; export default class ScopesRoute extends Route { // =services @@ -41,19 +37,4 @@ export default class ScopesRoute extends Route { }) .catch(() => A([])); } - - /** - * Deletes the scope and redirects to index. - * @param {Model} scope - */ - @action - @loading - @confirm('resources.policy.questions.detach') - @notifyError(({ message }) => message) - @notifySuccess('resources.policy.messages.detach') - async detachStoragePolicy(scope) { - const { storage_policy_id } = scope; - scope.storage_policy_id = ''; - await scope.detachStoragePolicy(storage_policy_id); - } } diff --git a/ui/admin/app/routes/scopes/scope.js b/ui/admin/app/routes/scopes/scope.js index f567cae2a..f7578b373 100644 --- a/ui/admin/app/routes/scopes/scope.js +++ b/ui/admin/app/routes/scopes/scope.js @@ -6,21 +6,13 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; import { A } from '@ember/array'; -import { action } from '@ember/object'; -import { next } from '@ember/runloop'; -import { loading } from 'ember-loading'; -import { confirm } from 'core/decorators/confirm'; -import { notifySuccess, notifyError } from 'core/decorators/notify'; import { TYPE_SCOPE_GLOBAL, TYPE_SCOPE_ORG } from 'api/models/scope'; export default class ScopesScopeRoute extends Route { // =services @service store; - @service intl; - @service session; @service scope; - @service router; // =methods @@ -105,52 +97,4 @@ export default class ScopesScopeRoute extends Route { /* eslint-disable-next-line ember/no-controller-access-in-routes */ this.controller.setProperties({ scopes }); } - - // =actions - - /** - * Rollback changes on scope. - * @param {Model} scope - */ - @action - cancel(scope) { - const { isNew } = scope; - next(() => { - scope.rollbackAttributes(); - if (isNew) this.router.transitionTo('scopes.scope'); - }); - } - - /** - * Handle save scope. - * @param {Model} scope - * @param {Event} e - */ - @action - @loading - @notifyError(({ message }) => message) - @notifySuccess(({ isNew }) => - isNew ? 'notifications.create-success' : 'notifications.save-success', - ) - async save(scope) { - const { isNew } = scope; - await scope.save(); - await this.router.transitionTo('scopes.scope.edit', scope); - if (isNew) this.refresh(); - } - - /** - * Deletes the scope and redirects to index. - * @param {Model} scope - */ - @action - @loading - @confirm('questions.delete-confirm') - @notifyError(({ message }) => message, { catch: true }) - @notifySuccess('notifications.delete-success') - async delete(scope) { - const { scopeID } = scope; - await scope.destroyRecord(); - this.router.replaceWith('scopes.scope.scopes', scopeID); - } } diff --git a/ui/admin/app/routes/scopes/scope/authenticate/method.js b/ui/admin/app/routes/scopes/scope/authenticate/method.js index f531eef00..20070347c 100644 --- a/ui/admin/app/routes/scopes/scope/authenticate/method.js +++ b/ui/admin/app/routes/scopes/scope/authenticate/method.js @@ -5,24 +5,11 @@ import Route from '@ember/routing/route'; import { inject as service } from '@ember/service'; -import { getOwner } from '@ember/application'; -import { action } from '@ember/object'; -import { loading } from 'ember-loading'; -import { notifyError } from 'core/decorators/notify'; -import { - TYPE_AUTH_METHOD_PASSWORD, - TYPE_AUTH_METHOD_OIDC, - TYPE_AUTH_METHOD_LDAP, -} from 'api/models/auth-method'; export default class ScopesScopeAuthenticateMethodRoute extends Route { // =services @service store; - @service session; - @service intl; - @service windowManager; - @service router; // =methods @@ -35,55 +22,4 @@ export default class ScopesScopeAuthenticateMethodRoute extends Route { const adapterOptions = { scopeID: this.modelFor('scopes.scope').id }; return this.store.peekRecord('auth-method', id, adapterOptions); } - - /** - * Kicks off the OIDC auth flow by opening a new window to the provider. - * @param {string} authenticatorName - * @param {object} options - */ - async startOIDCAuthentication(authenticatorName, options) { - const oidc = getOwner(this).lookup(authenticatorName); - // TODO: delegate this call from the session service so that we don't have - // to look up the authenticator directly - const json = await oidc.startAuthentication(options); - await this.windowManager.open(json.attributes.auth_url); - } - - // =actions - - /** - * Delegates authentication to an authenticator of type matching the current - * auth method. If authentication fails, notifies with an alert. - */ - @action - @loading - @notifyError(() => 'errors.authentication-failed.title', { - sticky: false, - catch: true, - }) - async authenticate(creds) { - const scope = this.modelFor('scopes.scope'); - const authMethod = this.modelFor('scopes.scope.authenticate.method'); - const authenticatorName = `authenticator:${authMethod.type}`; - const requestCookies = false; - switch (authMethod.type) { - case TYPE_AUTH_METHOD_PASSWORD: - case TYPE_AUTH_METHOD_LDAP: - await this.session.authenticate( - authenticatorName, - creds, - requestCookies, - { scope, authMethod }, - ); - this.router.transitionTo('index'); - break; - case TYPE_AUTH_METHOD_OIDC: - await this.startOIDCAuthentication(authenticatorName, { - scope, - authMethod, - }); - this.router.transitionTo('scopes.scope.authenticate.method.oidc'); - break; - } - } } diff --git a/ui/admin/app/templates/onboarding/index.hbs b/ui/admin/app/templates/onboarding/index.hbs index 406533b49..5a5bb223e 100644 --- a/ui/admin/app/templates/onboarding/index.hbs +++ b/ui/admin/app/templates/onboarding/index.hbs @@ -12,8 +12,8 @@

{{t 'onboarding.add-target.descriptions.setup-first-target'}}

diff --git a/ui/admin/app/templates/onboarding/success.hbs b/ui/admin/app/templates/onboarding/success.hbs index e3e399658..3742b6718 100644 --- a/ui/admin/app/templates/onboarding/success.hbs +++ b/ui/admin/app/templates/onboarding/success.hbs @@ -86,7 +86,7 @@ diff --git a/ui/admin/app/templates/scopes/scope/authenticate/method/index.hbs b/ui/admin/app/templates/scopes/scope/authenticate/method/index.hbs index 59c42f585..4b6dd00c0 100644 --- a/ui/admin/app/templates/scopes/scope/authenticate/method/index.hbs +++ b/ui/admin/app/templates/scopes/scope/authenticate/method/index.hbs @@ -5,6 +5,6 @@ \ No newline at end of file diff --git a/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs b/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs index 94f71defc..8b0334f81 100644 --- a/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs +++ b/ui/admin/app/templates/scopes/scope/authenticate/method/oidc.hbs @@ -12,7 +12,7 @@

{{t 'resources.auth-method.messages.pending.description'}}

{{t 'resources.auth-method.questions.no-see-window'}} - + {{t 'actions.retry'}}

diff --git a/ui/admin/app/templates/scopes/scope/edit.hbs b/ui/admin/app/templates/scopes/scope/edit.hbs index 180116e6a..b3054456f 100644 --- a/ui/admin/app/templates/scopes/scope/edit.hbs +++ b/ui/admin/app/templates/scopes/scope/edit.hbs @@ -64,7 +64,7 @@ {{#if @model.isOrg}} {{t 'resources.org.actions.delete'}} @@ -82,8 +82,8 @@ {{#if (can 'read model' @model)}} {{else}} @@ -176,7 +176,7 @@ @color='critical' {{on 'click' - (route-action 'detachStoragePolicy' @model) + (fn this.scopes.detachStoragePolicy @model) }} /> diff --git a/ui/admin/app/templates/scopes/scope/scopes/new.hbs b/ui/admin/app/templates/scopes/scope/scopes/new.hbs index ce67c5115..b997296a2 100644 --- a/ui/admin/app/templates/scopes/scope/scopes/new.hbs +++ b/ui/admin/app/templates/scopes/scope/scopes/new.hbs @@ -41,8 +41,8 @@ diff --git a/ui/admin/tests/unit/controllers/onboarding/index-test.js b/ui/admin/tests/unit/controllers/onboarding/index-test.js new file mode 100644 index 000000000..39017aaed --- /dev/null +++ b/ui/admin/tests/unit/controllers/onboarding/index-test.js @@ -0,0 +1,95 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { currentURL, waitUntil, visit } from '@ember/test-helpers'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import { TYPE_TARGET_TCP } from 'api/models/target'; + +module('Unit | Controller | onboarding/index', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + setupIndexedDb(hooks); + + let store; + let controller; + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + target: null, + role: null, + }; + + const urls = { + onboarding: null, + success: null, + globalScope: null, + }; + + hooks.beforeEach(async function () { + authenticateSession({}); + store = this.owner.lookup('service:store'); + controller = this.owner.lookup('controller:onboarding/index'); + + instances.scopes.global = this.server.create('scope', { + id: 'global', + type: 'global', + }); + + urls.onboarding = '/onboarding'; + urls.success = '/onboarding/success'; + urls.globalScope = '/scopes/global/scopes'; + }); + + test('it exists', function (assert) { + assert.ok(controller); + }); + + test('createResources action creates and saves resources', async function (assert) { + await visit(urls.onboarding); + const global = await store.findRecord('scope', instances.scopes.global.id); + const org = store.createRecord('scope', { type: 'org' }); + org.scopeModel = global; + const project = store.createRecord('scope', { type: 'project' }); + const target = store.createRecord('target', { + type: TYPE_TARGET_TCP, + scope: project, + }); + const role = store.createRecord('role', { scope: org }); + + await controller.createResources( + { org, project, target, role }, + 'localhost', + '22', + ); + + assert.strictEqual(project.scopeID, org.id); + assert.strictEqual(target.address, 'localhost'); + assert.strictEqual(target.default_port, 22); + assert.strictEqual(target.scopeID, project.id); + assert.strictEqual(role.scopeID, org.id); + assert.deepEqual(role.grant_strings, [ + 'type=target;actions=list', + 'ids=1;actions=authorize-session', + ]); + assert.strictEqual(currentURL(), urls.success); + }); + + test('doLater action redirects user to correct page', async function (assert) { + await visit(urls.onboarding); + + controller.doLater(); + await waitUntil(() => currentURL() === urls.globalScope); + + assert.strictEqual(currentURL(), urls.globalScope); + }); +}); diff --git a/ui/admin/tests/unit/controllers/onboarding/success-test.js b/ui/admin/tests/unit/controllers/onboarding/success-test.js new file mode 100644 index 000000000..31df549e4 --- /dev/null +++ b/ui/admin/tests/unit/controllers/onboarding/success-test.js @@ -0,0 +1,78 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { currentURL, waitUntil, visit } from '@ember/test-helpers'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { authenticateSession } from 'ember-simple-auth/test-support'; + +module('Unit | Controller | onboarding/success', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + setupIndexedDb(hooks); + + let controller; + let store; + + const instances = { + scopes: { + global: null, + org: null, + project: null, + }, + target: null, + }; + + const urls = { + success: null, + target: null, + }; + + hooks.beforeEach(async function () { + authenticateSession({}); + controller = this.owner.lookup('controller:onboarding/success'); + store = this.owner.lookup('service:store'); + + instances.scopes.global = this.server.create('scope', { + id: 'global', + type: 'global', + }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.scopes.project = this.server.create('scope', { + type: 'project', + scope: { id: instances.scopes.org.id, type: 'org' }, + }); + instances.target = this.server.create('target', { + scope: instances.scopes.project, + }); + + urls.success = '/onboarding/success'; + urls.target = `/scopes/${instances.scopes.project.id}/targets/${instances.target.id}`; + }); + + test('it exists', function (assert) { + assert.ok(controller); + }); + + test('showTargetList action redirects user a target', async function (assert) { + await visit(urls.success); + const target = await store.findRecord('target', instances.target.id); + const project = await store.findRecord( + 'scope', + instances.scopes.project.id, + ); + const model = { target, project }; + + controller.showTargetList(model); + await waitUntil(() => currentURL() === urls.target); + + assert.strictEqual(currentURL(), urls.target); + }); +}); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js new file mode 100644 index 000000000..6a9227f4a --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js @@ -0,0 +1,91 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { currentURL, visit, waitUntil } from '@ember/test-helpers'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { authenticateSession } from 'ember-simple-auth/test-support'; +import { TYPE_AUTH_METHOD_PASSWORD } from 'api/models/auth-method'; + +module( + 'Unit | Controller | scopes/scope/authenticate/method/index', + function (hooks) { + setupTest(hooks); + setupMirage(hooks); + setupIndexedDb(hooks); + + let controller; + let store; + let session; + + const instances = { + scopes: { + global: null, + }, + authMethod: null, + }; + + const urls = { + globalScope: null, + authenticate: null, + }; + + hooks.beforeEach(async function () { + authenticateSession({}); + controller = this.owner.lookup( + 'controller:scopes/scope/authenticate/method/index', + ); + store = this.owner.lookup('service:store'); + session = this.owner.lookup('service:session'); + + instances.scopes.global = this.server.create('scope', { + id: 'global', + type: 'global', + }); + instances.authMethod = this.server.create('auth-method', { + scope: instances.scopes.global, + type: TYPE_AUTH_METHOD_PASSWORD, + }); + + urls.globalScope = '/scopes/global/scopes'; + urls.authenticate = `scopes/global/authenticate/${instances.authMethod}`; + }); + + test('it exists', function (assert) { + assert.ok(controller); + }); + + test('authenticate action saves login information and redirects to correct page', async function (assert) { + await visit(urls.authenticate); + const identification = 'admin123'; + const authMethod = await store.findRecord( + 'auth-method', + instances.authMethod.id, + ); + const { authenticator: authBefore, username: usernameBefore } = + session.data.authenticated; + + assert.notOk(usernameBefore); + assert.strictEqual(authBefore, 'authenticator:test'); + + await controller.authenticate(authMethod, { + identification, + password: 'password', + }); + await waitUntil(() => currentURL() === urls.globalScope); + const { authenticator: authAfter, username: usernameAfter } = + session.data.authenticated; + + assert.strictEqual(currentURL(), urls.globalScope); + assert.strictEqual( + authAfter, + `authenticator:${instances.authMethod.type}`, + ); + assert.strictEqual(usernameAfter, identification); + }); + }, +); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/oidc-test.js b/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/oidc-test.js index 1b63d123c..547d001af 100644 --- a/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/oidc-test.js +++ b/ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/oidc-test.js @@ -11,12 +11,12 @@ module( function (hooks) { setupTest(hooks); - // TODO: Replace this with your real tests. test('it exists', function (assert) { let controller = this.owner.lookup( 'controller:scopes/scope/authenticate/method/oidc', ); assert.ok(controller); + assert.ok(controller.authMethod); }); }, ); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/edit-test.js b/ui/admin/tests/unit/controllers/scopes/scope/edit-test.js new file mode 100644 index 000000000..cd9e67e30 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/edit-test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/edit', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/edit'); + assert.ok(controller); + assert.ok(controller.scopes); + }); +}); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/index-test.js b/ui/admin/tests/unit/controllers/scopes/scope/index-test.js new file mode 100644 index 000000000..3bc6e498e --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/index-test.js @@ -0,0 +1,98 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { visit } from '@ember/test-helpers'; +import setupMirage from 'ember-cli-mirage/test-support/setup-mirage'; +import { setupIndexedDb } from 'api/test-support/helpers/indexed-db'; +import { authenticateSession } from 'ember-simple-auth/test-support'; + +module('Unit | Controller | scopes/scope/index', function (hooks) { + setupTest(hooks); + setupMirage(hooks); + setupIndexedDb(hooks); + + let store; + let controller; + let getScopeCount; + + const instances = { + scopes: { + global: null, + org: null, + }, + policy: null, + }; + + const urls = { + globalScope: null, + }; + + hooks.beforeEach(function () { + authenticateSession({}); + store = this.owner.lookup('service:store'); + controller = this.owner.lookup('controller:scopes/scope/index'); + + instances.scopes.global = this.server.create('scope', { id: 'global' }); + instances.scopes.org = this.server.create('scope', { + type: 'org', + scope: { id: 'global', type: 'global' }, + }); + instances.policy = this.server.create('policy', { + scope: instances.scopes.org, + }); + + urls.globalScope = `/scopes/global/scopes`; + + getScopeCount = () => this.server.schema.scopes.all().models.length; + }); + + test('it exists', function (assert) { + assert.ok(controller); + }); + + test('cancel action rolls-back changes on the specified model', async function (assert) { + await visit(urls.globalScope); + const org = await store.findRecord('scope', instances.scopes.org.id); + org.name = 'test'; + + assert.strictEqual(org.name, 'test'); + + await controller.cancel(org); + + assert.notEqual(org.name, 'test'); + }); + + test('save action saves changes on the specified model', async function (assert) { + await visit(urls.globalScope); + const org = await store.findRecord('scope', instances.scopes.org.id); + org.name = 'test'; + + await controller.save(org); + + assert.strictEqual(org.name, 'test'); + }); + + test('delete action destroys specified model', async function (assert) { + const org = await store.findRecord('scope', instances.scopes.org.id); + const scopeCount = getScopeCount(); + + await controller.delete(org); + + assert.strictEqual(getScopeCount(), scopeCount - 1); + }); + + test('detachStoragePolicy action removes policy from model', async function (assert) { + instances.scopes.org.update({ storage_policy_id: instances.policy.id }); + const org = await store.findRecord('scope', instances.scopes.org.id); + + assert.strictEqual(org.storage_policy_id, instances.policy.id); + + await controller.detachStoragePolicy(org); + + assert.notOk(org.storage_policy_id); + }); +}); diff --git a/ui/admin/tests/unit/controllers/scopes/scope/scopes/new-test.js b/ui/admin/tests/unit/controllers/scopes/scope/scopes/new-test.js new file mode 100644 index 000000000..4ebf3f6e9 --- /dev/null +++ b/ui/admin/tests/unit/controllers/scopes/scope/scopes/new-test.js @@ -0,0 +1,17 @@ +/** + * Copyright (c) HashiCorp, Inc. + * SPDX-License-Identifier: BUSL-1.1 + */ + +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Controller | scopes/scope/scopes/new', function (hooks) { + setupTest(hooks); + + test('it exists', function (assert) { + let controller = this.owner.lookup('controller:scopes/scope/scopes/new'); + assert.ok(controller); + assert.ok(controller.scopes); + }); +});