mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +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) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !!this.args.expirationDate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,8 +81,10 @@ export default Service.extend({
|
||||
if (!tokenName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { tokenExpirationEpoch } = this.getTokenData(tokenName);
|
||||
const expirationDate = new Date(0);
|
||||
|
||||
return tokenExpirationEpoch ? expirationDate.setUTCMilliseconds(tokenExpirationEpoch) : null;
|
||||
}),
|
||||
|
||||
@@ -215,15 +217,20 @@ export default Service.extend({
|
||||
return this.ajax(url, 'POST', { namespace });
|
||||
},
|
||||
|
||||
calculateExpiration(resp) {
|
||||
const now = this.now();
|
||||
calculateExpiration(resp, now) {
|
||||
const ttl = resp.ttl || resp.lease_duration;
|
||||
const tokenExpirationEpoch = now + ttl * 1e3;
|
||||
this.set('expirationCalcTS', now);
|
||||
return {
|
||||
ttl,
|
||||
tokenExpirationEpoch,
|
||||
};
|
||||
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('allowExpiration', false);
|
||||
} else {
|
||||
this.set('allowExpiration', true);
|
||||
}
|
||||
},
|
||||
|
||||
calculateRootNamespace(currentNamespace, namespace_path, backend) {
|
||||
@@ -296,21 +303,22 @@ export default Service.extend({
|
||||
resp.policies
|
||||
);
|
||||
|
||||
if (resp.renewable) {
|
||||
Object.assign(data, this.calculateExpiration(resp));
|
||||
} else if (resp.type === 'batch') {
|
||||
// if it's a batch token, it's not renewable but has an expire time
|
||||
// so manually set tokenExpirationEpoch and allow expiration
|
||||
data.tokenExpirationEpoch = new Date(resp.expire_time).getTime();
|
||||
this.set('allowExpiration', true);
|
||||
}
|
||||
const now = this.now();
|
||||
|
||||
Object.assign(data, this.calculateExpiration(resp, now));
|
||||
this.setExpirationSettings(resp, now);
|
||||
|
||||
// ensure we don't call renew-self within tests
|
||||
// this is intentionally not included in setExpirationSettings so we can unit test that method
|
||||
if (Ember.testing) this.set('allowExpiration', false);
|
||||
|
||||
if (!data.displayName) {
|
||||
data.displayName = (this.getTokenData(tokenName) || {}).displayName;
|
||||
}
|
||||
|
||||
this.set('tokens', addToArray(this.tokens, tokenName));
|
||||
this.set('allowExpiration', false);
|
||||
this.setTokenData(tokenName, data);
|
||||
|
||||
return resolve({
|
||||
namespace: currentNamespace || data.userRootNamespace,
|
||||
token: tokenName,
|
||||
@@ -333,9 +341,9 @@ export default Service.extend({
|
||||
renew() {
|
||||
const tokenName = this.currentTokenName;
|
||||
const currentlyRenewing = this.isRenewing;
|
||||
if (currentlyRenewing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentlyRenewing) return;
|
||||
|
||||
this.isRenewing = true;
|
||||
return this.renewCurrentToken().then(
|
||||
(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) {
|
||||
setupTest(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,26 +9,69 @@ import { setupTest } from 'ember-qunit';
|
||||
module('Unit | Service | auth', function (hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
[
|
||||
['#calculateExpiration w/ttl', { ttl: 30 }, 30],
|
||||
['#calculateExpiration w/lease_duration', { ttl: 15 }, 15],
|
||||
].forEach(([testName, response, ttlValue]) => {
|
||||
test(testName, function (assert) {
|
||||
const now = Date.now();
|
||||
const service = this.owner.factoryFor('service:auth').create({
|
||||
now() {
|
||||
return now;
|
||||
},
|
||||
hooks.beforeEach(function () {
|
||||
this.service = this.owner.lookup('service:auth');
|
||||
});
|
||||
|
||||
module('#calculateExpiration', function () {
|
||||
[
|
||||
['#calculateExpiration w/ttl', { ttl: 30 }, 30],
|
||||
['#calculateExpiration w/lease_duration', { lease_duration: 15 }, 15],
|
||||
].forEach(([testName, response, ttlValue]) => {
|
||||
test(testName, function (assert) {
|
||||
const now = Date.now();
|
||||
|
||||
const resp = this.service.calculateExpiration(response, now);
|
||||
|
||||
assert.strictEqual(resp.ttl, ttlValue, 'returns the ttl');
|
||||
assert.strictEqual(
|
||||
resp.tokenExpirationEpoch,
|
||||
now + ttlValue * 1e3,
|
||||
'calculates expiration from ttl as epoch timestamp'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const resp = service.calculateExpiration(response);
|
||||
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();
|
||||
|
||||
assert.strictEqual(resp.ttl, ttlValue, 'returns the ttl');
|
||||
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,
|
||||
now + ttlValue * 1e3,
|
||||
'calculates expiration from ttl as epoch timestamp'
|
||||
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