diff --git a/ui/core/app/router.js b/ui/core/app/router.js index ef3536c60..3c95ac355 100644 --- a/ui/core/app/router.js +++ b/ui/core/app/router.js @@ -18,6 +18,7 @@ Router.map(function () { this.route('new'); }); }); + this.route('cope'); }); this.route('orgs', function () { diff --git a/ui/core/app/routes/application.js b/ui/core/app/routes/application.js index be17ca092..024a0b887 100644 --- a/ui/core/app/routes/application.js +++ b/ui/core/app/routes/application.js @@ -36,16 +36,11 @@ export default class ApplicationRoute extends Route.extend( /** * When the session ends, redirect to authenticate and reload the page to * purge any in-memory state. - * - * TODO This application route is redirecting to a subroute, assuming - * that the user is logged into a specific organization. This won't always - * necessarily be the case. When we support MSP, users may authenticate - * without an org scope. So this will need to be rethought. */ async sessionInvalidated() { // Catch error in this transition, since it will be aborted by the - // org auth route when it redirects to the first auth method. - await this.transitionTo(this.routeIfUnauthenticated); + // scope auth route when it redirects to the first auth method. + await this.transitionTo(this.routeIfUnauthenticated).catch(() => {}); // The Ember way of accessing globals... const document = getOwner(this).lookup('service:-document').documentElement; // defaultView === window, but without using globals directly diff --git a/ui/core/app/routes/index.js b/ui/core/app/routes/index.js new file mode 100644 index 000000000..4ec1a62b0 --- /dev/null +++ b/ui/core/app/routes/index.js @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; + +export default class IndexRoute extends Route { + + // =methods + + /** + * Redirects to scopes route for further processing. + */ + redirect() { + this.transitionTo('scopes'); + } + +} diff --git a/ui/core/app/routes/scopes/index.js b/ui/core/app/routes/scopes/index.js new file mode 100644 index 000000000..06a263171 --- /dev/null +++ b/ui/core/app/routes/scopes/index.js @@ -0,0 +1,29 @@ +import Route from '@ember/routing/route'; +import { inject as service } from '@ember/service'; +import { get } from '@ember/object'; + +export default class ScopesIndexRoute extends Route { + + // =services + + @service session; + + // =methods + + /** + * If authenticated, redirects to the scope route of the authenticted scope. + * If unauthenticated, redirects to the first scope that was loaded (if any). + * @param {[ScopeModel]} model + * @param {?ScopeModel} model.firstObject + */ + redirect({ firstObject }) { + const authenticatedScopeID = + get(this.session, 'data.authenticated.scope.id'); + if (authenticatedScopeID) { + this.transitionTo('scopes.scope', authenticatedScopeID); + } else if (firstObject) { + this.transitionTo('scopes.scope', firstObject.id); + } + } + +} diff --git a/ui/core/app/routes/scopes/scope/index.js b/ui/core/app/routes/scopes/scope/index.js new file mode 100644 index 000000000..0215ea9da --- /dev/null +++ b/ui/core/app/routes/scopes/scope/index.js @@ -0,0 +1,14 @@ +import Route from '@ember/routing/route'; + +export default class ScopesScopeIndexRoute extends Route { + + // =methods + + /** + * Redirects to scopes/scope/authenticate for further processing. + */ + redirect() { + this.transitionTo('scopes.scope.authenticate'); + } + +} diff --git a/ui/core/app/routes/scopes/scope/projects.js b/ui/core/app/routes/scopes/scope/projects.js index ebc5e97e5..a7ab28af6 100644 --- a/ui/core/app/routes/scopes/scope/projects.js +++ b/ui/core/app/routes/scopes/scope/projects.js @@ -7,9 +7,17 @@ export default class ScopesScopeProjectsRoute extends Route { // =services @service notify; + @service session; // =methods + /** + * If arriving here unauthenticated, redirect to index for further processing. + */ + beforeModel() { + if (!this.session.isAuthenticated) this.transitionTo('index'); + } + /** * Loads all scopes under the current scope. * @return {Promise{[ScopeModel]}} diff --git a/ui/core/app/templates/scopes/index.hbs b/ui/core/app/templates/scopes/index.hbs new file mode 100644 index 000000000..e2147cab0 --- /dev/null +++ b/ui/core/app/templates/scopes/index.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui/core/app/templates/scopes/scope/index.hbs b/ui/core/app/templates/scopes/scope/index.hbs new file mode 100644 index 000000000..e2147cab0 --- /dev/null +++ b/ui/core/app/templates/scopes/scope/index.hbs @@ -0,0 +1 @@ +{{outlet}} \ No newline at end of file diff --git a/ui/core/mirage/config.js b/ui/core/mirage/config.js index 2ce197f7b..fec96c052 100644 --- a/ui/core/mirage/config.js +++ b/ui/core/mirage/config.js @@ -1,6 +1,6 @@ import config from '../config/environment'; import { authHandler, deauthHandler } from './route-handlers/auth'; -import { Response } from 'miragejs'; +//import { Response } from 'miragejs'; export default function() { @@ -21,9 +21,10 @@ export default function() { this.get('/scopes', ({ scopes }, { queryParams: { scope_id } }) => { // Default parent scope is global if (!scope_id) scope_id = 'global'; - return scopes.where(scope => { + const results = scopes.where(scope => { return scope.scope ? scope.scope.id === scope_id : false }); + return results; }); this.post('/scopes', function ({ scopes }, request) { // Parent scope comes through the payload via `scope_id`, but this needs @@ -34,17 +35,7 @@ export default function() { attrs.scope = parentScopeAttrs; return scopes.create(attrs); }); - // To simulate a possible real-world case, org scopes are not returned, - // but project scopes are. - // TODO this should be expanded to support other scenarios. - this.get('/scopes/:id', ({ scopes }, { params: { id: scopeID } }) => { - const scope = scopes.find(scopeID); - if (scope.type !== 'project') { - return new Response(404, {}, {}); - } else { - return scope; - } - }); + this.get('/scopes/:id'); this.patch('/scopes/:id'); this.del('/scopes/:id'); diff --git a/ui/core/tests/acceptance/authentication-test.js b/ui/core/tests/acceptance/authentication-test.js index e71c1a3b0..f6fa1c8ab 100644 --- a/ui/core/tests/acceptance/authentication-test.js +++ b/ui/core/tests/acceptance/authentication-test.js @@ -6,15 +6,19 @@ import a11yAudit from 'ember-a11y-testing/test-support/audit'; import { currentSession, authenticateSession, - //invalidateSession, + invalidateSession, } from 'ember-simple-auth/test-support'; module('Acceptance | authentication', function(hooks) { setupApplicationTest(hooks); setupMirage(hooks); + let indexURL; + let globalScope; let orgScope; let orgScopeID; + let scopesURL; + let orgScopeURL; let scope; let authMethod; let authMethodID; @@ -23,11 +27,19 @@ module('Acceptance | authentication', function(hooks) { let projectsURL; hooks.beforeEach(function () { - orgScope = this.server.create('scope', {type: 'org'}, 'withChildren'); + invalidateSession(); + indexURL = '/'; + globalScope = this.server.create('scope', { id: 'global' }); + orgScope = this.server.create('scope', { + type: 'org', + scope: { id: globalScope.id, type: globalScope.type } + }, 'withChildren'); scope = { id: orgScope.id, type: orgScope.type }; authMethod = this.server.create('auth-method', { scope }); orgScopeID = orgScope.id; authMethodID = authMethod.id; + scopesURL = `/scopes`; + orgScopeURL = `/scopes/${orgScopeID}`; authenticateURL = `/scopes/${orgScopeID}/authenticate`; authMethodAuthenticateURL = `/scopes/${orgScopeID}/authenticate/${authMethodID}`; projectsURL = `/scopes/${orgScopeID}/projects`; @@ -40,6 +52,22 @@ module('Acceptance | authentication', function(hooks) { assert.equal(currentURL(), authMethodAuthenticateURL); }); + test('visiting any authentication parent route while unauthenticated redirects to first authenticate method', async function(assert) { + assert.expect(3); + await visit(indexURL); + assert.equal(currentURL(), authMethodAuthenticateURL); + await visit(scopesURL); + assert.equal(currentURL(), authMethodAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + + test('visiting projects while unauthenticated redirects to first authenticate method', async function(assert) { + assert.expect(2); + await visit(projectsURL); + assert.equal(currentURL(), authMethodAuthenticateURL); + assert.notOk(currentSession().isAuthenticated); + }); + test('visiting auth method authenticate route', async function(assert) { assert.expect(1); await visit(authMethodAuthenticateURL); @@ -47,13 +75,20 @@ module('Acceptance | authentication', function(hooks) { assert.equal(currentURL(), authMethodAuthenticateURL); }); - test('visiting any authenticate route while already authenticated redirects to projects', async function(assert) { - assert.expect(2); - authenticateSession(); + test('visiting any authentication parent route while already authenticated redirects to projects', async function(assert) { + assert.expect(6); + authenticateSession({ scope }); + await visit(indexURL); + assert.equal(currentURL(), projectsURL); + await visit(scopesURL); + assert.equal(currentURL(), projectsURL); + await visit(orgScopeURL); + assert.equal(currentURL(), projectsURL); await visit(authenticateURL); assert.equal(currentURL(), projectsURL); await visit(authMethodAuthenticateURL); assert.equal(currentURL(), projectsURL); + assert.ok(currentSession().isAuthenticated); }); test('failed authentication shows a notification message', async function(assert) { @@ -78,7 +113,7 @@ module('Acceptance | authentication', function(hooks) { assert.ok(currentSession().isAuthenticated); }); - test('deauthentication redirects to index', async function(assert) { + test('deauthentication redirects to first authenticate method', async function(assert) { assert.expect(3); await visit(authMethodAuthenticateURL); await fillIn('[name="identification"]', 'test'); @@ -87,7 +122,7 @@ module('Acceptance | authentication', function(hooks) { assert.ok(currentSession().isAuthenticated); await click('.rose-button-header-dropdown'); assert.notOk(currentSession().isAuthenticated); - assert.equal(currentURL(), '/'); + assert.equal(currentURL(), authMethodAuthenticateURL); }); }); diff --git a/ui/core/tests/unit/routes/index-test.js b/ui/core/tests/unit/routes/index-test.js new file mode 100644 index 000000000..eaaac17bc --- /dev/null +++ b/ui/core/tests/unit/routes/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | index', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/index-test.js b/ui/core/tests/unit/routes/scopes/index-test.js new file mode 100644 index 000000000..c2fb98357 --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/index', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/index'); + assert.ok(route); + }); +}); diff --git a/ui/core/tests/unit/routes/scopes/scope/index-test.js b/ui/core/tests/unit/routes/scopes/scope/index-test.js new file mode 100644 index 000000000..ba0a5bdba --- /dev/null +++ b/ui/core/tests/unit/routes/scopes/scope/index-test.js @@ -0,0 +1,11 @@ +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; + +module('Unit | Route | scopes/scope/index', function(hooks) { + setupTest(hooks); + + test('it exists', function(assert) { + let route = this.owner.lookup('route:scopes/scope/index'); + assert.ok(route); + }); +});