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/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;
}
}
diff --git a/ui/app/services/auth.js b/ui/app/services/auth.js
index b4bd43ff2ed6..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);
},
@@ -95,18 +99,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 = this.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() {
@@ -210,17 +211,19 @@ 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() {
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() {
@@ -243,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) {
@@ -264,9 +267,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() {
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}}
-
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();
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'
+ );
});
});
});