From 77ee67c34e70e91876a5cc6b295e0ebd2c941209 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Fri, 28 Sep 2018 21:28:45 -0500 Subject: [PATCH 1/5] fix token expiration calculation --- ui/app/services/auth.js | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/ui/app/services/auth.js b/ui/app/services/auth.js index b4bd43ff2ed6..2b236949804f 100644 --- a/ui/app/services/auth.js +++ b/ui/app/services/auth.js @@ -95,18 +95,15 @@ export default Service.extend({ return this.ajax(url, 'POST', { namespace }); }, - calculateExpiration(resp, creationTime) { - const creationTTL = resp.creation_ttl || resp.lease_duration; - const leaseMilli = creationTTL ? creationTTL * 1e3 : null; - const tokenIssueEpoch = resp.creation_time ? resp.creation_time * 1e3 : creationTime || Date.now(); - const tokenExpirationEpoch = tokenIssueEpoch + leaseMilli; - const expirationData = { - tokenIssueEpoch, + calculateExpiration(resp) { + let now = Date.now(); + const ttl = resp.ttl || resp.lease_duration; + const tokenExpirationEpoch = now + ttl * 1e3; + this.set('expirationCalcTS', now); + return { + ttl, tokenExpirationEpoch, - leaseMilli, }; - this.set('expirationCalcTS', Date.now()); - return expirationData; }, persistAuthData() { @@ -215,12 +212,14 @@ export default Service.extend({ renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function() { const tokenName = this.get('currentTokenName'); + let { expirationCalcTS } = this; const data = this.getTokenData(tokenName); if (!tokenName || !data) { return null; } - const { leaseMilli, tokenIssueEpoch, renewable } = data; - return data && renewable ? Math.floor(leaseMilli / 2) + tokenIssueEpoch : null; + const { ttl, renewable } = data; + // renew after last expirationCalc time + half of the ttl (in ms) + return renewable ? Math.floor((ttl * 1e3) / 2) + expirationCalcTS : null; }), renew() { @@ -264,9 +263,11 @@ export default Service.extend({ }, getTokensFromStorage(filterFn) { - return this.storage().keys().reject(key => { - return key.indexOf(TOKEN_PREFIX) !== 0 || (filterFn && filterFn(key)); - }); + return this.storage() + .keys() + .reject(key => { + return key.indexOf(TOKEN_PREFIX) !== 0 || (filterFn && filterFn(key)); + }); }, checkForRootToken() { From 45bce6facde81a62d99995bbc6dfe5308fd906be Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Fri, 28 Sep 2018 22:12:40 -0500 Subject: [PATCH 2/5] move authenticate to an ember concurrency task --- ui/app/components/auth-form.js | 44 ++++++++++++----------- ui/app/templates/components/auth-form.hbs | 2 +- 2 files changed, 25 insertions(+), 21 deletions(-) diff --git a/ui/app/components/auth-form.js b/ui/app/components/auth-form.js index ff21640300e6..1bbe33c29d44 100644 --- a/ui/app/components/auth-form.js +++ b/ui/app/components/auth-form.js @@ -152,7 +152,7 @@ export default Component.extend(DEFAULTS, { } }), - showLoading: or('fetchMethods.isRunning', 'unwrapToken.isRunning'), + showLoading: or('authenticate.isRunning', 'fetchMethods.isRunning', 'unwrapToken.isRunning'), handleError(e) { this.set('loading', false); @@ -165,14 +165,34 @@ export default Component.extend(DEFAULTS, { this.set('error', `Authentication failed: ${errors.join('.')}`); }, + authenticate: task(function*(backendType, data) { + let clusterId = this.cluster.id; + let targetRoute = this.redirectTo || 'vault.cluster'; + try { + let authResponse = yield this.auth.authenticate({ clusterId, backend: backendType, data }); + + let { isRoot, namespace } = authResponse; + let transition = this.router.transitionTo(targetRoute, { queryParams: { namespace } }); + // returning this w/then because if we keep it + // in the task, it will get cancelled when the component in un-rendered + return transition.followRedirects().then(() => { + if (isRoot) { + this.flashMessages.warning( + 'You have logged in with a root token. As a security precaution, this root token will not be stored by your browser and you will need to re-authenticate after the window is closed or refreshed.' + ); + } + }); + } catch (e) { + this.handleError(e); + } + }), + actions: { doSubmit() { let data = {}; this.setProperties({ - loading: true, error: null, }); - let targetRoute = this.get('redirectTo') || 'vault.cluster'; let backend = this.get('selectedAuthBackend') || {}; let backendMeta = BACKENDS.find( b => (get(b, 'type') || '').toLowerCase() === (get(backend, 'type') || '').toLowerCase() @@ -183,23 +203,7 @@ export default Component.extend(DEFAULTS, { if (this.get('customPath') || get(backend, 'id')) { data.path = this.get('customPath') || get(backend, 'id'); } - const clusterId = this.get('cluster.id'); - this.get('auth') - .authenticate({ clusterId, backend: get(backend, 'type'), data }) - .then( - ({ isRoot, namespace }) => { - this.set('loading', false); - const transition = this.get('router').transitionTo(targetRoute, { queryParams: { namespace } }); - if (isRoot) { - transition.followRedirects().then(() => { - this.get('flashMessages').warning( - 'You have logged in with a root token. As a security precaution, this root token will not be stored by your browser and you will need to re-authenticate after the window is closed or refreshed.' - ); - }); - } - }, - (...errArgs) => this.handleError(...errArgs) - ); + this.authenticate.perform(backend.type, data); }, }, }); diff --git a/ui/app/templates/components/auth-form.hbs b/ui/app/templates/components/auth-form.hbs index 90e25091496a..50ae2340d37c 100644 --- a/ui/app/templates/components/auth-form.hbs +++ b/ui/app/templates/components/auth-form.hbs @@ -93,7 +93,7 @@ {{/unless}}
-
From 22569558bfe98a85a3cd17eac9e0b410f74401a2 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Fri, 28 Sep 2018 22:20:49 -0500 Subject: [PATCH 3/5] don't show logged in nav while still on the auth route --- ui/app/controllers/vault/cluster.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ui/app/controllers/vault/cluster.js b/ui/app/controllers/vault/cluster.js index a928fbe90d89..ed9327fa3fda 100644 --- a/ui/app/controllers/vault/cluster.js +++ b/ui/app/controllers/vault/cluster.js @@ -6,6 +6,7 @@ export default Controller.extend({ auth: service(), store: service(), media: service(), + router: service(), namespaceService: service('namespace'), vaultVersion: service('version'), @@ -38,6 +39,7 @@ export default Controller.extend({ }), showNav: computed( + 'router.currentRouteName', 'activeClusterName', 'auth.currentToken', 'activeCluster.{dr.isSecondary,needsInit,sealed}', @@ -49,7 +51,11 @@ export default Controller.extend({ ) { return false; } - if (this.get('activeClusterName') && this.get('auth.currentToken')) { + if ( + this.activeClusterName && + this.auth.currentToken && + this.router.currentRouteName !== 'vault.cluster.auth' + ) { return true; } } From b2979e3d343db879697b5d51b3d85a9b947e1d30 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Fri, 28 Sep 2018 22:52:28 -0500 Subject: [PATCH 4/5] move current tests to integration folder, add unit test for expiration calculation --- ui/app/services/auth.js | 10 +- ui/tests/integration/services/auth-test.js | 343 ++++++++++++++++++++ ui/tests/unit/services/auth-test.js | 354 ++------------------- 3 files changed, 370 insertions(+), 337 deletions(-) create mode 100644 ui/tests/integration/services/auth-test.js diff --git a/ui/app/services/auth.js b/ui/app/services/auth.js index 2b236949804f..5367abbeb4c3 100644 --- a/ui/app/services/auth.js +++ b/ui/app/services/auth.js @@ -61,6 +61,10 @@ export default Service.extend({ return ENV.environment; }, + now() { + return Date.now(); + }, + setCluster(clusterId) { this.set('activeCluster', clusterId); }, @@ -96,7 +100,7 @@ export default Service.extend({ }, calculateExpiration(resp) { - let now = Date.now(); + let now = this.now(); const ttl = resp.ttl || resp.lease_duration; const tokenExpirationEpoch = now + ttl * 1e3; this.set('expirationCalcTS', now); @@ -207,7 +211,7 @@ export default Service.extend({ tokenExpired: computed(function() { const expiration = this.get('tokenExpirationDate'); - return expiration ? Date.now() >= expiration : null; + return expiration ? this.now() >= expiration : null; }).volatile(), renewAfterEpoch: computed('currentTokenName', 'expirationCalcTS', function() { @@ -242,7 +246,7 @@ export default Service.extend({ }, shouldRenew: computed(function() { - const now = Date.now(); + const now = this.now(); const lastFetch = this.get('lastFetch'); const renewTime = this.get('renewAfterEpoch'); if (this.get('tokenExpired') || this.get('allowExpiration') || !renewTime) { diff --git a/ui/tests/integration/services/auth-test.js b/ui/tests/integration/services/auth-test.js new file mode 100644 index 000000000000..2e40a9fa18bb --- /dev/null +++ b/ui/tests/integration/services/auth-test.js @@ -0,0 +1,343 @@ +import { run } from '@ember/runloop'; +import { copy } from '@ember/object/internals'; +import { module, test } from 'qunit'; +import { setupTest } from 'ember-qunit'; +import { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX } from 'vault/services/auth'; +import Pretender from 'pretender'; + +function storage() { + return { + items: {}, + getItem(key) { + var item = this.items[key]; + return item && JSON.parse(item); + }, + + setItem(key, val) { + return (this.items[key] = JSON.stringify(val)); + }, + + removeItem(key) { + delete this.items[key]; + }, + + keys() { + return Object.keys(this.items); + }, + }; +} + +let ROOT_TOKEN_RESPONSE = { + request_id: 'e6674d7f-c96f-d51f-4463-cc95f0ad307e', + lease_id: '', + renewable: false, + lease_duration: 0, + data: { + accessor: '1dd25306-fdb9-0f43-8169-48ad702041b0', + creation_time: 1477671134, + creation_ttl: 0, + display_name: 'root', + explicit_max_ttl: 0, + id: '', + meta: null, + num_uses: 0, + orphan: true, + path: 'auth/token/root', + policies: ['root'], + ttl: 0, + }, + wrap_info: null, + warnings: null, + auth: null, +}; + +let TOKEN_NON_ROOT_RESPONSE = function() { + return { + request_id: '3ca32cd9-fd40-891d-02d5-ea23138e8642', + lease_id: '', + renewable: false, + lease_duration: 0, + data: { + accessor: '4ef32471-a94c-79ee-c290-aeba4d63bdc9', + creation_time: Math.floor(Date.now() / 1000), + creation_ttl: 2764800, + display_name: 'token', + explicit_max_ttl: 0, + id: '6d83e912-1b21-9df9-b51a-d201b709f3d5', + meta: null, + num_uses: 0, + orphan: false, + path: 'auth/token/create', + policies: ['default', 'userpass'], + renewable: true, + ttl: 2763327, + }, + wrap_info: null, + warnings: null, + auth: null, + }; +}; + +let USERPASS_RESPONSE = { + request_id: '7e5e8d3d-599e-6ef7-7570-f7057fc7c53d', + lease_id: '', + renewable: false, + lease_duration: 0, + data: null, + wrap_info: null, + warnings: null, + auth: { + client_token: '5313ff81-05cb-699f-29d1-b82b4e2906dc', + accessor: '5c5303e7-56d6-ea13-72df-d85411bd9a7d', + policies: ['default'], + metadata: { + username: 'matthew', + }, + lease_duration: 2764800, + renewable: true, + }, +}; + +let GITHUB_RESPONSE = { + request_id: '4913f9cd-a95f-d1f9-5746-4c3af4e15660', + lease_id: '', + renewable: false, + lease_duration: 0, + data: null, + wrap_info: null, + warnings: null, + auth: { + client_token: '0d39b535-598e-54d9-96e3-97493492a5f7', + accessor: 'd8cd894f-bedf-5ce3-f1b5-98f7c6cf8ab4', + policies: ['default'], + metadata: { + org: 'hashicorp', + username: 'meirish', + }, + lease_duration: 2764800, + renewable: true, + }, +}; + +module('Integration | Service | auth', function(hooks) { + setupTest(hooks); + + hooks.beforeEach(function() { + this.owner.lookup('service:flash-messages').registerTypes(['warning']); + this.store = storage(); + this.memStore = storage(); + this.server = new Pretender(function() { + this.get('/v1/auth/token/lookup-self', function(request) { + let resp = copy(ROOT_TOKEN_RESPONSE, true); + resp.id = request.requestHeaders['X-Vault-Token']; + resp.data.id = request.requestHeaders['X-Vault-Token']; + return [200, {}, resp]; + }); + this.post('/v1/auth/userpass/login/:username', function(request) { + const { username } = request.params; + let resp = copy(USERPASS_RESPONSE, true); + resp.auth.metadata.username = username; + return [200, {}, resp]; + }); + + this.post('/v1/auth/github/login', function() { + let resp = copy(GITHUB_RESPONSE, true); + return [200, {}, resp]; + }); + }); + + this.server.prepareBody = function(body) { + return body ? JSON.stringify(body) : '{"error": "not found"}'; + }; + + this.server.prepareHeaders = function(headers) { + headers['content-type'] = 'application/javascript'; + return headers; + }; + }); + + hooks.afterEach(function() { + this.server.shutdown(); + }); + + test('token authentication: root token', function(assert) { + let done = assert.async(); + let self = this; + let service = this.owner.factoryFor('service:auth').create({ + storage(tokenName) { + if ( + tokenName && + tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 && + this.environment() !== 'development' + ) { + return self.memStore; + } else { + return self.store; + } + }, + }); + run(() => { + service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => { + const clusterTokenName = service.get('currentTokenName'); + const clusterToken = service.get('currentToken'); + const authData = service.get('authData'); + + const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`; + assert.equal('test', clusterToken, 'token is saved properly'); + assert.equal( + `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`, + clusterTokenName, + 'token name is saved properly' + ); + assert.equal('token', authData.backend.type, 'backend is saved properly'); + assert.equal( + ROOT_TOKEN_RESPONSE.data.display_name, + authData.displayName, + 'displayName is saved properly' + ); + assert.ok( + this.memStore.keys().includes(expectedTokenName), + 'root token is stored in the memory store' + ); + assert.equal(this.store.keys().length, 0, 'normal storage is empty'); + done(); + }); + }); + }); + + test('token authentication: root token in ember development environment', function(assert) { + let done = assert.async(); + let self = this; + let service = this.owner.factoryFor('service:auth').create({ + storage(tokenName) { + if ( + tokenName && + tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 && + this.environment() !== 'development' + ) { + return self.memStore; + } else { + return self.store; + } + }, + environment: () => 'development', + }); + run(() => { + service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => { + const clusterTokenName = service.get('currentTokenName'); + const clusterToken = service.get('currentToken'); + const authData = service.get('authData'); + + const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`; + assert.equal('test', clusterToken, 'token is saved properly'); + assert.equal( + `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`, + clusterTokenName, + 'token name is saved properly' + ); + assert.equal('token', authData.backend.type, 'backend is saved properly'); + assert.equal( + ROOT_TOKEN_RESPONSE.data.display_name, + authData.displayName, + 'displayName is saved properly' + ); + assert.ok(this.store.keys().includes(expectedTokenName), 'root token is stored in the store'); + assert.equal(this.memStore.keys().length, 0, 'mem storage is empty'); + done(); + }); + }); + }); + + test('github authentication', function(assert) { + let done = assert.async(); + let service = this.owner.factoryFor('service:auth').create({ + storage: type => (type === 'memory' ? this.memStore : this.store), + }); + + run(() => { + service.authenticate({ clusterId: '1', backend: 'github', data: { token: 'test' } }).then(() => { + const clusterTokenName = service.get('currentTokenName'); + const clusterToken = service.get('currentToken'); + const authData = service.get('authData'); + const expectedTokenName = `${TOKEN_PREFIX}github${TOKEN_SEPARATOR}1`; + + assert.equal(GITHUB_RESPONSE.auth.client_token, clusterToken, 'token is saved properly'); + assert.equal(expectedTokenName, clusterTokenName, 'token name is saved properly'); + assert.equal('github', authData.backend.type, 'backend is saved properly'); + assert.equal( + GITHUB_RESPONSE.auth.metadata.org + '/' + GITHUB_RESPONSE.auth.metadata.username, + authData.displayName, + 'displayName is saved properly' + ); + assert.equal(this.memStore.keys().length, 0, 'mem storage is empty'); + assert.ok(this.store.keys().includes(expectedTokenName), 'normal storage contains the token'); + done(); + }); + }); + }); + + test('userpass authentication', function(assert) { + let done = assert.async(); + let service = this.owner.factoryFor('service:auth').create({ storage: () => this.store }); + run(() => { + service + .authenticate({ + clusterId: '1', + backend: 'userpass', + data: { username: USERPASS_RESPONSE.auth.metadata.username, password: 'passoword' }, + }) + .then(() => { + const clusterTokenName = service.get('currentTokenName'); + const clusterToken = service.get('currentToken'); + const authData = service.get('authData'); + + assert.equal(USERPASS_RESPONSE.auth.client_token, clusterToken, 'token is saved properly'); + assert.equal( + `${TOKEN_PREFIX}userpass${TOKEN_SEPARATOR}1`, + clusterTokenName, + 'token name is saved properly' + ); + assert.equal('userpass', authData.backend.type, 'backend is saved properly'); + assert.equal( + USERPASS_RESPONSE.auth.metadata.username, + authData.displayName, + 'displayName is saved properly' + ); + done(); + }); + }); + }); + + test('token auth expiry with non-root token', function(assert) { + const tokenResp = TOKEN_NON_ROOT_RESPONSE(); + this.server.map(function() { + this.get('/v1/auth/token/lookup-self', function(request) { + let resp = copy(tokenResp, true); + resp.id = request.requestHeaders['X-Vault-Token']; + resp.data.id = request.requestHeaders['X-Vault-Token']; + return [200, {}, resp]; + }); + }); + + let done = assert.async(); + let service = this.owner.factoryFor('service:auth').create({ storage: () => this.store }); + run(() => { + service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => { + const clusterTokenName = service.get('currentTokenName'); + const clusterToken = service.get('currentToken'); + const authData = service.get('authData'); + + assert.equal('test', clusterToken, 'token is saved properly'); + assert.equal( + `${TOKEN_PREFIX}token${TOKEN_SEPARATOR}1`, + clusterTokenName, + 'token name is saved properly' + ); + assert.equal(authData.backend.type, 'token', 'backend is saved properly'); + assert.equal(authData.displayName, tokenResp.data.display_name, 'displayName is saved properly'); + assert.equal(service.get('tokenExpired'), false, 'token is not expired'); + done(); + }); + }); + }); +}); diff --git a/ui/tests/unit/services/auth-test.js b/ui/tests/unit/services/auth-test.js index 2915f28361b5..837f081c62c5 100644 --- a/ui/tests/unit/services/auth-test.js +++ b/ui/tests/unit/services/auth-test.js @@ -1,343 +1,29 @@ -import { run } from '@ember/runloop'; -import { copy } from '@ember/object/internals'; import { module, test } from 'qunit'; import { setupTest } from 'ember-qunit'; -import { TOKEN_SEPARATOR, TOKEN_PREFIX, ROOT_PREFIX } from 'vault/services/auth'; -import Pretender from 'pretender'; - -function storage() { - return { - items: {}, - getItem(key) { - var item = this.items[key]; - return item && JSON.parse(item); - }, - - setItem(key, val) { - return (this.items[key] = JSON.stringify(val)); - }, - - removeItem(key) { - delete this.items[key]; - }, - - keys() { - return Object.keys(this.items); - }, - }; -} - -let ROOT_TOKEN_RESPONSE = { - request_id: 'e6674d7f-c96f-d51f-4463-cc95f0ad307e', - lease_id: '', - renewable: false, - lease_duration: 0, - data: { - accessor: '1dd25306-fdb9-0f43-8169-48ad702041b0', - creation_time: 1477671134, - creation_ttl: 0, - display_name: 'root', - explicit_max_ttl: 0, - id: '', - meta: null, - num_uses: 0, - orphan: true, - path: 'auth/token/root', - policies: ['root'], - ttl: 0, - }, - wrap_info: null, - warnings: null, - auth: null, -}; - -let TOKEN_NON_ROOT_RESPONSE = function() { - return { - request_id: '3ca32cd9-fd40-891d-02d5-ea23138e8642', - lease_id: '', - renewable: false, - lease_duration: 0, - data: { - accessor: '4ef32471-a94c-79ee-c290-aeba4d63bdc9', - creation_time: Math.floor(Date.now() / 1000), - creation_ttl: 2764800, - display_name: 'token', - explicit_max_ttl: 0, - id: '6d83e912-1b21-9df9-b51a-d201b709f3d5', - meta: null, - num_uses: 0, - orphan: false, - path: 'auth/token/create', - policies: ['default', 'userpass'], - renewable: true, - ttl: 2763327, - }, - wrap_info: null, - warnings: null, - auth: null, - }; -}; - -let USERPASS_RESPONSE = { - request_id: '7e5e8d3d-599e-6ef7-7570-f7057fc7c53d', - lease_id: '', - renewable: false, - lease_duration: 0, - data: null, - wrap_info: null, - warnings: null, - auth: { - client_token: '5313ff81-05cb-699f-29d1-b82b4e2906dc', - accessor: '5c5303e7-56d6-ea13-72df-d85411bd9a7d', - policies: ['default'], - metadata: { - username: 'matthew', - }, - lease_duration: 2764800, - renewable: true, - }, -}; - -let GITHUB_RESPONSE = { - request_id: '4913f9cd-a95f-d1f9-5746-4c3af4e15660', - lease_id: '', - renewable: false, - lease_duration: 0, - data: null, - wrap_info: null, - warnings: null, - auth: { - client_token: '0d39b535-598e-54d9-96e3-97493492a5f7', - accessor: 'd8cd894f-bedf-5ce3-f1b5-98f7c6cf8ab4', - policies: ['default'], - metadata: { - org: 'hashicorp', - username: 'meirish', - }, - lease_duration: 2764800, - renewable: true, - }, -}; module('Unit | Service | auth', function(hooks) { setupTest(hooks); - hooks.beforeEach(function() { - this.owner.lookup('service:flash-messages').registerTypes(['warning']); - this.store = storage(); - this.memStore = storage(); - this.server = new Pretender(function() { - this.get('/v1/auth/token/lookup-self', function(request) { - let resp = copy(ROOT_TOKEN_RESPONSE, true); - resp.id = request.requestHeaders['X-Vault-Token']; - resp.data.id = request.requestHeaders['X-Vault-Token']; - return [200, {}, resp]; - }); - this.post('/v1/auth/userpass/login/:username', function(request) { - const { username } = request.params; - let resp = copy(USERPASS_RESPONSE, true); - resp.auth.metadata.username = username; - return [200, {}, resp]; - }); - - this.post('/v1/auth/github/login', function() { - let resp = copy(GITHUB_RESPONSE, true); - return [200, {}, resp]; - }); - }); - - this.server.prepareBody = function(body) { - return body ? JSON.stringify(body) : '{"error": "not found"}'; - }; - - this.server.prepareHeaders = function(headers) { - headers['content-type'] = 'application/javascript'; - return headers; - }; - }); - - hooks.afterEach(function() { - this.server.shutdown(); - }); - - test('token authentication: root token', function(assert) { - let done = assert.async(); - let self = this; - let service = this.owner.factoryFor('service:auth').create({ - storage(tokenName) { - if ( - tokenName && - tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 && - this.environment() !== 'development' - ) { - return self.memStore; - } else { - return self.store; - } - }, - }); - run(() => { - service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => { - const clusterTokenName = service.get('currentTokenName'); - const clusterToken = service.get('currentToken'); - const authData = service.get('authData'); - - const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`; - assert.equal('test', clusterToken, 'token is saved properly'); - assert.equal( - `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`, - clusterTokenName, - 'token name is saved properly' - ); - assert.equal('token', authData.backend.type, 'backend is saved properly'); - assert.equal( - ROOT_TOKEN_RESPONSE.data.display_name, - authData.displayName, - 'displayName is saved properly' - ); - assert.ok( - this.memStore.keys().includes(expectedTokenName), - 'root token is stored in the memory store' - ); - assert.equal(this.store.keys().length, 0, 'normal storage is empty'); - done(); - }); - }); - }); - - test('token authentication: root token in ember development environment', function(assert) { - let done = assert.async(); - let self = this; - let service = this.owner.factoryFor('service:auth').create({ - storage(tokenName) { - if ( - tokenName && - tokenName.indexOf(`${TOKEN_PREFIX}${ROOT_PREFIX}`) === 0 && - this.environment() !== 'development' - ) { - return self.memStore; - } else { - return self.store; - } - }, - environment: () => 'development', - }); - run(() => { - service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => { - const clusterTokenName = service.get('currentTokenName'); - const clusterToken = service.get('currentToken'); - const authData = service.get('authData'); - - const expectedTokenName = `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`; - assert.equal('test', clusterToken, 'token is saved properly'); - assert.equal( - `${TOKEN_PREFIX}${ROOT_PREFIX}${TOKEN_SEPARATOR}1`, - clusterTokenName, - 'token name is saved properly' - ); - assert.equal('token', authData.backend.type, 'backend is saved properly'); - assert.equal( - ROOT_TOKEN_RESPONSE.data.display_name, - authData.displayName, - 'displayName is saved properly' - ); - assert.ok(this.store.keys().includes(expectedTokenName), 'root token is stored in the store'); - assert.equal(this.memStore.keys().length, 0, 'mem storage is empty'); - done(); - }); - }); - }); - - test('github authentication', function(assert) { - let done = assert.async(); - let service = this.owner.factoryFor('service:auth').create({ - storage: type => (type === 'memory' ? this.memStore : this.store), - }); - - run(() => { - service.authenticate({ clusterId: '1', backend: 'github', data: { token: 'test' } }).then(() => { - const clusterTokenName = service.get('currentTokenName'); - const clusterToken = service.get('currentToken'); - const authData = service.get('authData'); - const expectedTokenName = `${TOKEN_PREFIX}github${TOKEN_SEPARATOR}1`; - - assert.equal(GITHUB_RESPONSE.auth.client_token, clusterToken, 'token is saved properly'); - assert.equal(expectedTokenName, clusterTokenName, 'token name is saved properly'); - assert.equal('github', authData.backend.type, 'backend is saved properly'); - assert.equal( - GITHUB_RESPONSE.auth.metadata.org + '/' + GITHUB_RESPONSE.auth.metadata.username, - authData.displayName, - 'displayName is saved properly' - ); - assert.equal(this.memStore.keys().length, 0, 'mem storage is empty'); - assert.ok(this.store.keys().includes(expectedTokenName), 'normal storage contains the token'); - done(); - }); - }); - }); - - test('userpass authentication', function(assert) { - let done = assert.async(); - let service = this.owner.factoryFor('service:auth').create({ storage: () => this.store }); - run(() => { - service - .authenticate({ - clusterId: '1', - backend: 'userpass', - data: { username: USERPASS_RESPONSE.auth.metadata.username, password: 'passoword' }, - }) - .then(() => { - const clusterTokenName = service.get('currentTokenName'); - const clusterToken = service.get('currentToken'); - const authData = service.get('authData'); - - assert.equal(USERPASS_RESPONSE.auth.client_token, clusterToken, 'token is saved properly'); - assert.equal( - `${TOKEN_PREFIX}userpass${TOKEN_SEPARATOR}1`, - clusterTokenName, - 'token name is saved properly' - ); - assert.equal('userpass', authData.backend.type, 'backend is saved properly'); - assert.equal( - USERPASS_RESPONSE.auth.metadata.username, - authData.displayName, - 'displayName is saved properly' - ); - done(); - }); - }); - }); - - test('token auth expiry with non-root token', function(assert) { - const tokenResp = TOKEN_NON_ROOT_RESPONSE(); - this.server.map(function() { - this.get('/v1/auth/token/lookup-self', function(request) { - let resp = copy(tokenResp, true); - resp.id = request.requestHeaders['X-Vault-Token']; - resp.data.id = request.requestHeaders['X-Vault-Token']; - return [200, {}, resp]; - }); - }); - - let done = assert.async(); - let service = this.owner.factoryFor('service:auth').create({ storage: () => this.store }); - run(() => { - service.authenticate({ clusterId: '1', backend: 'token', data: { token: 'test' } }).then(() => { - const clusterTokenName = service.get('currentTokenName'); - const clusterToken = service.get('currentToken'); - const authData = service.get('authData'); - - assert.equal('test', clusterToken, 'token is saved properly'); - assert.equal( - `${TOKEN_PREFIX}token${TOKEN_SEPARATOR}1`, - clusterTokenName, - 'token name is saved properly' - ); - assert.equal(authData.backend.type, 'token', 'backend is saved properly'); - assert.equal(authData.displayName, tokenResp.data.display_name, 'displayName is saved properly'); - assert.equal(service.get('tokenExpired'), false, 'token is not expired'); - done(); - }); + [ + ['#calculateExpiration w/ttl', { ttl: 30 }, 30], + ['#calculateExpiration w/lease_duration', { ttl: 15 }, 15], + ].forEach(([testName, response, ttlValue]) => { + test(testName, function(assert) { + let now = Date.now(); + let service = this.owner.factoryFor('service:auth').create({ + now() { + return now; + }, + }); + + let resp = service.calculateExpiration(response); + + assert.equal(resp.ttl, ttlValue, 'returns the ttl'); + assert.equal( + resp.tokenExpirationEpoch, + now + ttlValue * 1e3, + 'calculates expiration from ttl as epoch timestamp' + ); }); }); }); From 843696238953a591055fb8b8ab3932c242cbbd40 Mon Sep 17 00:00:00 2001 From: Matthew Irish Date: Mon, 1 Oct 2018 11:01:55 -0500 Subject: [PATCH 5/5] fix auth form tests --- ui/tests/integration/components/auth-form-test.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/ui/tests/integration/components/auth-form-test.js b/ui/tests/integration/components/auth-form-test.js index f863722ff24e..a66146e145b9 100644 --- a/ui/tests/integration/components/auth-form-test.js +++ b/ui/tests/integration/components/auth-form-test.js @@ -32,7 +32,11 @@ const workingAuthService = Service.extend({ const routerService = Service.extend({ transitionTo() { - return resolve(); + return { + followRedirects() { + return resolve(); + }, + }; }, replaceWith() { return resolve(); @@ -142,6 +146,7 @@ module('Integration | Component | auth form', function(hooks) { }); }); + this.set('cluster', EmberObject.create({})); await render(hbs`{{auth-form cluster=cluster }}`); await settled(); assert.equal(component.tabs.length, 2, 'renders a tab for userpass and Other'); @@ -165,6 +170,7 @@ module('Integration | Component | auth form', function(hooks) { }); }); + this.set('cluster', EmberObject.create({})); this.set('selectedAuth', 'foo/'); await render(hbs`{{auth-form cluster=cluster selectedAuth=selectedAuth}}`); await component.login(); @@ -188,6 +194,7 @@ module('Integration | Component | auth form', function(hooks) { return [200, { 'Content-Type': 'application/json' }, JSON.stringify({ data: { auth: methods } })]; }); }); + this.set('cluster', EmberObject.create({})); await render(hbs`{{auth-form cluster=cluster}}`); await settled(); server.shutdown(); @@ -214,6 +221,7 @@ module('Integration | Component | auth form', function(hooks) { let wrappedToken = '54321'; this.set('wrappedToken', wrappedToken); + this.set('cluster', EmberObject.create({})); await render(hbs`{{auth-form cluster=cluster wrappedToken=wrappedToken}}`); later(() => run.cancelTimers(), 50); await settled();