mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-01 11:08:10 +00:00
UI: fix token expiry banner for batch tokens (#27479)
* fix: calculate expiration of all batch tokens to ensure expire warning banner is shown * fix: ensure allowExpiration doesn't get overridden * fix: set expirationCalcTS outside of calculateExpression * tests: verify expirationEpoch is calculated when only expiry_time is passed in * fix: calculate expireTime using expire_time if its passed in * tests: clean up auth tests * tests: organize batch token vs. service token tests into separate module * chore: update changelog * Update changelog/27479.txt Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> * fix: ensure tokens in test envs do not expire * cleanup: pull setExpiration settings into own method & add tests --------- Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
This commit is contained in:
3
changelog/27479.txt
Normal file
3
changelog/27479.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
ui: Ensure token expired banner displays when batch token expires
|
||||||
|
```
|
||||||
@@ -46,6 +46,7 @@ export default class TokenExpireWarning extends Component {
|
|||||||
if ('vault.cluster.oidc-provider' === currentRoute) {
|
if ('vault.cluster.oidc-provider' === currentRoute) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!this.args.expirationDate;
|
return !!this.args.expirationDate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,8 +81,10 @@ export default Service.extend({
|
|||||||
if (!tokenName) {
|
if (!tokenName) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { tokenExpirationEpoch } = this.getTokenData(tokenName);
|
const { tokenExpirationEpoch } = this.getTokenData(tokenName);
|
||||||
const expirationDate = new Date(0);
|
const expirationDate = new Date(0);
|
||||||
|
|
||||||
return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null;
|
return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -215,15 +217,20 @@ export default Service.extend({
|
|||||||
return this.ajax(url, 'POST', { namespace });
|
return this.ajax(url, 'POST', { namespace });
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateExpiration(resp) {
|
calculateExpiration(resp, now) {
|
||||||
const now = this.now();
|
|
||||||
const ttl = resp.ttl || resp.lease_duration;
|
const ttl = resp.ttl || resp.lease_duration;
|
||||||
const tokenExpirationEpoch = now + ttl * 1e3;
|
const tokenExpirationEpoch = resp.expire_time ? new Date(resp.expire_time).getTime() : now + ttl * 1e3;
|
||||||
|
|
||||||
|
return { ttl, tokenExpirationEpoch };
|
||||||
|
},
|
||||||
|
|
||||||
|
setExpirationSettings(resp, now) {
|
||||||
|
if (resp.renewable) {
|
||||||
this.set('expirationCalcTS', now);
|
this.set('expirationCalcTS', now);
|
||||||
return {
|
this.set('allowExpiration', false);
|
||||||
ttl,
|
} else {
|
||||||
tokenExpirationEpoch,
|
this.set('allowExpiration', true);
|
||||||
};
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
calculateRootNamespace(currentNamespace, namespace_path, backend) {
|
calculateRootNamespace(currentNamespace, namespace_path, backend) {
|
||||||
@@ -296,21 +303,22 @@ export default Service.extend({
|
|||||||
resp.policies
|
resp.policies
|
||||||
);
|
);
|
||||||
|
|
||||||
if (resp.renewable) {
|
const now = this.now();
|
||||||
Object.assign(data, this.calculateExpiration(resp));
|
|
||||||
} else if (resp.type === 'batch') {
|
Object.assign(data, this.calculateExpiration(resp, now));
|
||||||
// if it's a batch token, it's not renewable but has an expire time
|
this.setExpirationSettings(resp, now);
|
||||||
// so manually set tokenExpirationEpoch and allow expiration
|
|
||||||
data.tokenExpirationEpoch = new Date(resp.expire_time).getTime();
|
// ensure we don't call renew-self within tests
|
||||||
this.set('allowExpiration', true);
|
// this is intentionally not included in setExpirationSettings so we can unit test that method
|
||||||
}
|
if (Ember.testing) this.set('allowExpiration', false);
|
||||||
|
|
||||||
if (!data.displayName) {
|
if (!data.displayName) {
|
||||||
data.displayName = (this.getTokenData(tokenName) || {}).displayName;
|
data.displayName = (this.getTokenData(tokenName) || {}).displayName;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.set('tokens', addToArray(this.tokens, tokenName));
|
this.set('tokens', addToArray(this.tokens, tokenName));
|
||||||
this.set('allowExpiration', false);
|
|
||||||
this.setTokenData(tokenName, data);
|
this.setTokenData(tokenName, data);
|
||||||
|
|
||||||
return resolve({
|
return resolve({
|
||||||
namespace: currentNamespace || data.userRootNamespace,
|
namespace: currentNamespace || data.userRootNamespace,
|
||||||
token: tokenName,
|
token: tokenName,
|
||||||
@@ -333,9 +341,9 @@ export default Service.extend({
|
|||||||
renew() {
|
renew() {
|
||||||
const tokenName = this.currentTokenName;
|
const tokenName = this.currentTokenName;
|
||||||
const currentlyRenewing = this.isRenewing;
|
const currentlyRenewing = this.isRenewing;
|
||||||
if (currentlyRenewing) {
|
|
||||||
return;
|
if (currentlyRenewing) return;
|
||||||
}
|
|
||||||
this.isRenewing = true;
|
this.isRenewing = true;
|
||||||
return this.renewCurrentToken().then(
|
return this.renewCurrentToken().then(
|
||||||
(resp) => {
|
(resp) => {
|
||||||
|
|||||||
@@ -123,6 +123,90 @@ const GITHUB_RESPONSE = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const BATCH_TOKEN_RESPONSE = {
|
||||||
|
request_id: '60bcef62-cc20-facf-8c0d-1418d05e9a42',
|
||||||
|
lease_id: '',
|
||||||
|
renewable: false,
|
||||||
|
lease_duration: 0,
|
||||||
|
data: {
|
||||||
|
accessor: '',
|
||||||
|
creation_time: 1718672331,
|
||||||
|
creation_ttl: 60,
|
||||||
|
display_name: 'token',
|
||||||
|
entity_id: '',
|
||||||
|
expire_time: '2024-06-17T17:59:51-07:00',
|
||||||
|
explicit_max_ttl: 0,
|
||||||
|
id: 'hvb.AAAAAQIUMVkhx9rnA',
|
||||||
|
issue_time: '2024-06-17T17:58:51-07:00',
|
||||||
|
meta: null,
|
||||||
|
num_uses: 0,
|
||||||
|
orphan: false,
|
||||||
|
path: 'auth/token/create',
|
||||||
|
policies: ['default'],
|
||||||
|
renewable: false,
|
||||||
|
ttl: 45,
|
||||||
|
type: 'batch',
|
||||||
|
},
|
||||||
|
wrap_info: null,
|
||||||
|
warnings: null,
|
||||||
|
auth: null,
|
||||||
|
mount_type: 'token',
|
||||||
|
};
|
||||||
|
|
||||||
|
const USERPASS_BATCH_TOKEN_RESPONSE = {
|
||||||
|
request_id: 'eb4c31a0-1745-5701-cce7-1668f5839dbf',
|
||||||
|
lease_id: '',
|
||||||
|
renewable: false,
|
||||||
|
lease_duration: 0,
|
||||||
|
data: null,
|
||||||
|
wrap_info: null,
|
||||||
|
warnings: null,
|
||||||
|
auth: {
|
||||||
|
client_token: 'hvb.AAAAAQJ0eGwP5e48S61kBRYmR',
|
||||||
|
accessor: '',
|
||||||
|
policies: ['default'],
|
||||||
|
token_policies: ['default'],
|
||||||
|
metadata: {
|
||||||
|
username: 'bob',
|
||||||
|
},
|
||||||
|
lease_duration: 360,
|
||||||
|
renewable: false,
|
||||||
|
entity_id: 'b52f8591-02b6-828b-7f36-620afa539126',
|
||||||
|
token_type: 'batch',
|
||||||
|
orphan: true,
|
||||||
|
mfa_requirement: null,
|
||||||
|
num_uses: 0,
|
||||||
|
},
|
||||||
|
mount_type: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const USERPASS_SERVICE_TOKEN_RESPONSE = {
|
||||||
|
request_id: 'e735ffad-f2fe-5d1b-14b8-90aeb9d05976',
|
||||||
|
lease_id: '',
|
||||||
|
renewable: false,
|
||||||
|
lease_duration: 0,
|
||||||
|
data: null,
|
||||||
|
wrap_info: null,
|
||||||
|
warnings: null,
|
||||||
|
auth: {
|
||||||
|
client_token: 'hvs.CAESINY6Qbs8rm',
|
||||||
|
accessor: '9bDizzlcIHiXwEOK5mZ6gjHI',
|
||||||
|
policies: ['default'],
|
||||||
|
token_policies: ['default'],
|
||||||
|
metadata: {
|
||||||
|
username: 'bob',
|
||||||
|
},
|
||||||
|
lease_duration: 360,
|
||||||
|
renewable: true,
|
||||||
|
entity_id: 'd9a0cac8-779c-e766-716a-6f80552f0e81',
|
||||||
|
token_type: 'service',
|
||||||
|
orphan: true,
|
||||||
|
mfa_requirement: null,
|
||||||
|
num_uses: 0,
|
||||||
|
},
|
||||||
|
mount_type: '',
|
||||||
|
};
|
||||||
|
|
||||||
module('Integration | Service | auth', function (hooks) {
|
module('Integration | Service | auth', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
setupMirage(hooks);
|
setupMirage(hooks);
|
||||||
@@ -334,4 +418,72 @@ module('Integration | Service | auth', function (hooks) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
module('token types', function (hooks) {
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.server.post('/auth/userpass/login/:username', (_, request) => {
|
||||||
|
const { username } = request.params;
|
||||||
|
const resp =
|
||||||
|
username === 'batch'
|
||||||
|
? { ...USERPASS_BATCH_TOKEN_RESPONSE }
|
||||||
|
: { ...USERPASS_SERVICE_TOKEN_RESPONSE };
|
||||||
|
resp.auth.metadata.username = username;
|
||||||
|
return resp;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.service = this.owner.factoryFor('service:auth').create({ storage: () => this.store });
|
||||||
|
});
|
||||||
|
|
||||||
|
module('batch tokens', function () {
|
||||||
|
test('batch tokens generated by token auth method', async function (assert) {
|
||||||
|
this.server.get('/auth/token/lookup-self', () => {
|
||||||
|
return { ...BATCH_TOKEN_RESPONSE };
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.service.authenticate({
|
||||||
|
clusterId: '1',
|
||||||
|
backend: 'token',
|
||||||
|
data: { token: 'test' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// exact expiration time is calculated in unit tests
|
||||||
|
assert.notEqual(
|
||||||
|
this.service.tokenExpirationDate,
|
||||||
|
undefined,
|
||||||
|
'expiration is calculated for batch tokens'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('batch tokens generated by auth methods', async function (assert) {
|
||||||
|
await this.service.authenticate({
|
||||||
|
clusterId: '1',
|
||||||
|
backend: 'userpass',
|
||||||
|
data: { username: 'batch', password: 'password' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// exact expiration time is calculated in unit tests
|
||||||
|
assert.notEqual(
|
||||||
|
this.service.tokenExpirationDate,
|
||||||
|
undefined,
|
||||||
|
'expiration is calculated for batch tokens'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('service token authentication', async function (assert) {
|
||||||
|
await this.service.authenticate({
|
||||||
|
clusterId: '1',
|
||||||
|
backend: 'userpass',
|
||||||
|
data: { username: 'service', password: 'password' },
|
||||||
|
});
|
||||||
|
|
||||||
|
// exact expiration time is calculated in unit tests
|
||||||
|
assert.notEqual(
|
||||||
|
this.service.tokenExpirationDate,
|
||||||
|
undefined,
|
||||||
|
'expiration is calculated for service tokens'
|
||||||
|
);
|
||||||
|
assert.false(this.service.allowExpiration, 'allowExpiration is false for service tokens');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,19 +9,19 @@ import { setupTest } from 'ember-qunit';
|
|||||||
module('Unit | Service | auth', function (hooks) {
|
module('Unit | Service | auth', function (hooks) {
|
||||||
setupTest(hooks);
|
setupTest(hooks);
|
||||||
|
|
||||||
|
hooks.beforeEach(function () {
|
||||||
|
this.service = this.owner.lookup('service:auth');
|
||||||
|
});
|
||||||
|
|
||||||
|
module('#calculateExpiration', function () {
|
||||||
[
|
[
|
||||||
['#calculateExpiration w/ttl', { ttl: 30 }, 30],
|
['#calculateExpiration w/ttl', { ttl: 30 }, 30],
|
||||||
['#calculateExpiration w/lease_duration', { ttl: 15 }, 15],
|
['#calculateExpiration w/lease_duration', { lease_duration: 15 }, 15],
|
||||||
].forEach(([testName, response, ttlValue]) => {
|
].forEach(([testName, response, ttlValue]) => {
|
||||||
test(testName, function (assert) {
|
test(testName, function (assert) {
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
const service = this.owner.factoryFor('service:auth').create({
|
|
||||||
now() {
|
|
||||||
return now;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const resp = service.calculateExpiration(response);
|
const resp = this.service.calculateExpiration(response, now);
|
||||||
|
|
||||||
assert.strictEqual(resp.ttl, ttlValue, 'returns the ttl');
|
assert.strictEqual(resp.ttl, ttlValue, 'returns the ttl');
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
@@ -31,4 +31,47 @@ module('Unit | Service | auth', function (hooks) {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#calculateExpiration w/ expire_time', function (assert) {
|
||||||
|
const now = Date.now();
|
||||||
|
const expirationString = '2024-06-13T09:10:21-07:00';
|
||||||
|
const expectedExpirationEpoch = new Date(expirationString).getTime();
|
||||||
|
|
||||||
|
const resp = this.service.calculateExpiration(
|
||||||
|
{ ttl: 30, expire_time: '2024-06-13T09:10:21-07:00' },
|
||||||
|
now
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.strictEqual(resp.ttl, 30, 'returns ttl');
|
||||||
|
assert.strictEqual(
|
||||||
|
resp.tokenExpirationEpoch,
|
||||||
|
expectedExpirationEpoch,
|
||||||
|
'calculates expiration from expire_time'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module('#setExpirationSettings', function () {
|
||||||
|
test('#setExpirationSettings for a renewable token', function (assert) {
|
||||||
|
const now = Date.now();
|
||||||
|
const ttl = 30;
|
||||||
|
const response = { ttl, renewable: true };
|
||||||
|
|
||||||
|
this.service.setExpirationSettings(response, now);
|
||||||
|
|
||||||
|
assert.false(this.service.allowExpiration, 'sets allowExpiration to false');
|
||||||
|
assert.strictEqual(this.service.expirationCalcTS, now, 'sets expirationCalcTS to now');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('#setExpirationSettings for a non-renewable token', function (assert) {
|
||||||
|
const now = Date.now();
|
||||||
|
const ttl = 30;
|
||||||
|
const response = { ttl, renewable: false };
|
||||||
|
|
||||||
|
this.service.setExpirationSettings(response, now);
|
||||||
|
|
||||||
|
assert.true(this.service.allowExpiration, 'sets allowExpiration to true');
|
||||||
|
assert.strictEqual(this.service.expirationCalcTS, null, 'keeps expirationCalcTS as null');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user