From 0af81d9bc1354f632b6199ef660232d57d0f19ab Mon Sep 17 00:00:00 2001
From: Lisbet Alvarez <107949262+lisbet-alvarez@users.noreply.github.com>
Date: Thu, 15 Aug 2024 10:46:00 -0700
Subject: [PATCH] =?UTF-8?q?refactor:=20=F0=9F=92=A1=20remove=20route=20act?=
=?UTF-8?q?ion=20usage=20for=20scopes=20(#2385)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
* refactor: 💡 remove route action usage for scopes
✅ Closes: https://hashicorp.atlassian.net/browse/ICU-13755
---
.../app/components/form/onboarding/index.hbs | 1 +
.../app/components/form/onboarding/index.js | 14 ++
ui/admin/app/controllers/onboarding/index.js | 126 ++++++++++++++++++
.../app/controllers/onboarding/success.js | 29 ++++
.../scopes/scope/authenticate/method/index.js | 74 ++++++++++
.../scopes/scope/authenticate/method/oidc.js | 4 +-
ui/admin/app/controllers/scopes/scope/edit.js | 10 ++
.../app/controllers/scopes/scope/index.js | 77 +++++++++++
.../controllers/scopes/scope/scopes/new.js | 10 ++
ui/admin/app/routes/onboarding.js | 97 +-------------
ui/admin/app/routes/onboarding/success.js | 14 +-
ui/admin/app/routes/scopes.js | 19 ---
ui/admin/app/routes/scopes/scope.js | 56 --------
.../scopes/scope/authenticate/method.js | 64 ---------
ui/admin/app/templates/onboarding/index.hbs | 4 +-
ui/admin/app/templates/onboarding/success.hbs | 2 +-
.../scope/authenticate/method/index.hbs | 2 +-
.../scopes/scope/authenticate/method/oidc.hbs | 2 +-
ui/admin/app/templates/scopes/scope/edit.hbs | 8 +-
.../app/templates/scopes/scope/scopes/new.hbs | 4 +-
.../unit/controllers/onboarding/index-test.js | 95 +++++++++++++
.../controllers/onboarding/success-test.js | 78 +++++++++++
.../scope/authenticate/method/index-test.js | 91 +++++++++++++
.../scope/authenticate/method/oidc-test.js | 2 +-
.../controllers/scopes/scope/edit-test.js | 17 +++
.../controllers/scopes/scope/index-test.js | 98 ++++++++++++++
.../scopes/scope/scopes/new-test.js | 17 +++
27 files changed, 755 insertions(+), 260 deletions(-)
create mode 100644 ui/admin/app/controllers/onboarding/index.js
create mode 100644 ui/admin/app/controllers/onboarding/success.js
create mode 100644 ui/admin/app/controllers/scopes/scope/authenticate/method/index.js
create mode 100644 ui/admin/app/controllers/scopes/scope/edit.js
create mode 100644 ui/admin/app/controllers/scopes/scope/index.js
create mode 100644 ui/admin/app/controllers/scopes/scope/scopes/new.js
create mode 100644 ui/admin/tests/unit/controllers/onboarding/index-test.js
create mode 100644 ui/admin/tests/unit/controllers/onboarding/success-test.js
create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/authenticate/method/index-test.js
create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/edit-test.js
create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/index-test.js
create mode 100644 ui/admin/tests/unit/controllers/scopes/scope/scopes/new-test.js
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);
+ });
+});