mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-22 07:01:09 +02:00
* Allow Managed clusters to see Secrets Sync Overview and Sidebar nav (#26649) * update badge text and allow hvd on secrets sync views * update logic in Secrets Sync overview and cta for hvd. * spacing * rearrange based on pr feedback * fix return on badgeText and cluster nav test * fix landing cta tests * update test to reflect new changes * moved call to feature-flags from application route to the service to match patterns * add managed test coverage on overview component test and remove premium feature so cta message appplies to both managed and non-managed clusters * missed service name and unskip admin test * clean up * fix tests * flags test fix * Rename isManaged and managedNamespaceRoot (#26697) * renames * lowercase HVD to match * missed some * test failure * [Secrets Sync] enable access to Sync clients page for HVD clusters (#26713) * feat: split client counts navbar into separate component * acceptance/clients/counts/overview-test: remove tests now covered by int tests * clients counts route: rename isSecretsSyncActivated to showSecretsSync * sync clients page: show unactivated state unless sync client history or feature is activated * client counts navbar: show sync tab only if client history or is /able to be/ activated * clients overview page: only show sync charts if activated * fix: rename isManaged to isHvd * acceptance/counts/overview-test: add HVD tests * acceptance/counts/overview-test: clean up unused cruft * aceptance/clients/counts/overview-test: ensure we dont get false negatives * chore: move Clients::Error to Clients::Counts::Error * chore: calculate showSecretSync in page component instead of route * chore: add copyright headers * acceptance/clients/counts/overview-test: stub activated flags to fix test * [Secrets sync] update sync test selectors (#26824) * acceptance/clients/counts/overview-test: use imported test selectors * general-selectors: add missing emptyStateSubtitle property * acceptance/clients/counts/sync: nest tests in top level module for easier test runs * Add permissions check to show/hide activate button (#26840) * add permissions check to flags service and consume in overview template * add back missing refresh * fix test failures * add test coverage * clean up * address flaky test * grr * address test failures * add changelog * try to fix test failure only on gh * fix fetch to match previous implementation of feature-flags * fix failing test * update comment --------- Co-authored-by: Noelle Daley <noelledaley@users.noreply.github.com> Co-authored-by: clairebontempo@gmail.com <clairebontempo@gmail.com>
209 lines
6.7 KiB
JavaScript
209 lines
6.7 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import { service } from '@ember/service';
|
|
// ARG NOTE: Once you remove outer-html after glimmerizing you can remove the outer-html component
|
|
import Component from './outer-html';
|
|
import { task, timeout, waitForEvent } from 'ember-concurrency';
|
|
import { debounce } from '@ember/runloop';
|
|
|
|
const WAIT_TIME = 500;
|
|
const ERROR_WINDOW_CLOSED =
|
|
'The provider window was closed before authentication was complete. Your web browser may have blocked or closed a pop-up window. Please check your settings and click Sign In to try again.';
|
|
const ERROR_MISSING_PARAMS =
|
|
'The callback from the provider did not supply all of the required parameters. Please click Sign In to try again. If the problem persists, you may want to contact your administrator.';
|
|
const ERROR_JWT_LOGIN = 'OIDC login is not configured for this mount';
|
|
export { ERROR_WINDOW_CLOSED, ERROR_MISSING_PARAMS, ERROR_JWT_LOGIN };
|
|
|
|
export default Component.extend({
|
|
store: service(),
|
|
flagsService: service('flags'),
|
|
|
|
selectedAuthPath: null,
|
|
selectedAuthType: null,
|
|
roleName: null,
|
|
role: null,
|
|
errorMessage: null,
|
|
isOIDC: true,
|
|
|
|
onRoleName() {},
|
|
onLoading() {},
|
|
onError() {},
|
|
onNamespace() {},
|
|
|
|
didReceiveAttrs() {
|
|
this._super(...arguments);
|
|
// if mount path or type changes we need to check again for JWT configuration
|
|
const didChangePath = this._authPath !== this.selectedAuthPath;
|
|
const didChangeType = this._authType !== this.selectedAuthType;
|
|
|
|
if (didChangePath || didChangeType) {
|
|
// path updates as the user types so we need to debounce that event
|
|
const wait = didChangePath ? 500 : 0;
|
|
debounce(this, 'fetchRole', wait);
|
|
}
|
|
this._authPath = this.selectedAuthPath;
|
|
this._authType = this.selectedAuthType;
|
|
},
|
|
|
|
getWindow() {
|
|
return this.window || window;
|
|
},
|
|
|
|
async fetchRole() {
|
|
const path = this.selectedAuthPath || this.selectedAuthType;
|
|
const id = JSON.stringify([path, this.roleName]);
|
|
this.setProperties({ role: null, errorMessage: null, isOIDC: true });
|
|
|
|
try {
|
|
const role = await this.store.findRecord('role-jwt', id, {
|
|
adapterOptions: { namespace: this.namespace },
|
|
});
|
|
this.set('role', role);
|
|
} catch (e) {
|
|
const error = (e.errors || [])[0];
|
|
const errorMessage =
|
|
e.httpStatus === 400 ? 'Invalid role. Please try again.' : `Error fetching role: ${error}`;
|
|
// assume OIDC until it's known that the mount is configured for JWT authentication via static keys, JWKS, or OIDC discovery.
|
|
this.setProperties({ isOIDC: error !== ERROR_JWT_LOGIN, errorMessage });
|
|
}
|
|
},
|
|
|
|
cancelLogin(oidcWindow, errorMessage) {
|
|
this.closeWindow(oidcWindow);
|
|
this.handleOIDCError(errorMessage);
|
|
},
|
|
|
|
closeWindow(oidcWindow) {
|
|
this.watchPopup.cancelAll();
|
|
this.watchCurrent.cancelAll();
|
|
oidcWindow.close();
|
|
},
|
|
|
|
handleOIDCError(err) {
|
|
this.onLoading(false);
|
|
this.prepareForOIDC.cancelAll();
|
|
this.onError(err);
|
|
},
|
|
|
|
prepareForOIDC: task(function* (oidcWindow) {
|
|
const thisWindow = this.getWindow();
|
|
// show the loading animation in the parent
|
|
this.onLoading(true);
|
|
// start watching the popup window and the current one
|
|
this.watchPopup.perform(oidcWindow);
|
|
this.watchCurrent.perform(oidcWindow);
|
|
// wait for message posted from oidc callback
|
|
// see issue https://github.com/hashicorp/vault/issues/12436
|
|
// ensure that postMessage event is from expected source
|
|
while (true) {
|
|
const event = yield waitForEvent(thisWindow, 'message');
|
|
if (event.origin === thisWindow.origin && event.isTrusted && event.data.source === 'oidc-callback') {
|
|
return this.exchangeOIDC.perform(event.data, oidcWindow);
|
|
}
|
|
// continue to wait for the correct message
|
|
}
|
|
}),
|
|
|
|
watchPopup: task(function* (oidcWindow) {
|
|
while (true) {
|
|
yield timeout(WAIT_TIME);
|
|
if (!oidcWindow || oidcWindow.closed) {
|
|
return this.handleOIDCError(ERROR_WINDOW_CLOSED);
|
|
}
|
|
}
|
|
}),
|
|
|
|
watchCurrent: task(function* (oidcWindow) {
|
|
// when user is about to change pages, close the popup window
|
|
yield waitForEvent(this.getWindow(), 'beforeunload');
|
|
oidcWindow.close();
|
|
}),
|
|
|
|
exchangeOIDC: task(function* (oidcState, oidcWindow) {
|
|
if (oidcState === null || oidcState === undefined) {
|
|
return;
|
|
}
|
|
this.onLoading(true);
|
|
|
|
let { namespace, path, state, code } = oidcState;
|
|
|
|
// The namespace can be either be passed as a query parameter, or be embedded
|
|
// in the state param in the format `<state_id>,ns=<namespace>`. So if
|
|
// `namespace` is empty, check for namespace in state as well.
|
|
if (namespace === '' || this.flagsService.hvdManagedNamespaceRoot) {
|
|
const i = state.indexOf(',ns=');
|
|
if (i >= 0) {
|
|
// ",ns=" is 4 characters
|
|
namespace = state.substring(i + 4);
|
|
state = state.substring(0, i);
|
|
}
|
|
}
|
|
|
|
if (!path || !state || !code) {
|
|
return this.cancelLogin(oidcWindow, ERROR_MISSING_PARAMS);
|
|
}
|
|
const adapter = this.store.adapterFor('auth-method');
|
|
this.onNamespace(namespace);
|
|
let resp;
|
|
// do the OIDC exchange, set the token on the parent component
|
|
// and submit auth form
|
|
try {
|
|
resp = yield adapter.exchangeOIDC(path, state, code);
|
|
this.closeWindow(oidcWindow);
|
|
} catch (e) {
|
|
// If there was an error on Vault's end, close the popup
|
|
// and show the error on the login screen
|
|
return this.cancelLogin(oidcWindow, e);
|
|
}
|
|
yield this.onSubmit(null, null, resp.auth.client_token);
|
|
}),
|
|
|
|
async startOIDCAuth() {
|
|
this.onError(null);
|
|
|
|
await this.fetchRole();
|
|
|
|
const error =
|
|
this.role && !this.role.authUrl
|
|
? 'Missing auth_url. Please check that allowed_redirect_uris for the role include this mount path.'
|
|
: this.errorMessage || null;
|
|
|
|
if (error) {
|
|
this.onError(error);
|
|
} else {
|
|
const win = this.getWindow();
|
|
const POPUP_WIDTH = 500;
|
|
const POPUP_HEIGHT = 600;
|
|
const left = win.screen.width / 2 - POPUP_WIDTH / 2;
|
|
const top = win.screen.height / 2 - POPUP_HEIGHT / 2;
|
|
const oidcWindow = win.open(
|
|
this.role.authUrl,
|
|
'vaultOIDCWindow',
|
|
`width=${POPUP_WIDTH},height=${POPUP_HEIGHT},resizable,scrollbars=yes,top=${top},left=${left}`
|
|
);
|
|
|
|
this.prepareForOIDC.perform(oidcWindow);
|
|
}
|
|
},
|
|
|
|
actions: {
|
|
onRoleChange(event) {
|
|
this.onRoleName(event.target.value);
|
|
debounce(this, 'fetchRole', 500);
|
|
},
|
|
signIn(event) {
|
|
event.preventDefault();
|
|
|
|
if (this.isOIDC) {
|
|
this.startOIDCAuth();
|
|
} else {
|
|
const { jwt, roleName: role } = this;
|
|
this.onSubmit({ role, jwt });
|
|
}
|
|
},
|
|
},
|
|
});
|