UNPKG

@hawtio/react

Version:

A Hawtio reimplementation based on TypeScript + React.

1,322 lines (1,258 loc) 729 kB
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; } var _class; var _class2; var _class3; var _class4; var _class5; var _class6; var _chunkBLJGIIMVjs = require('./chunk-BLJGIIMV.js'); var _chunkWYFZRJ4Cjs = require('./chunk-WYFZRJ4C.js'); var _chunkBJ6TSPQKjs = require('./chunk-BJ6TSPQK.js'); var _chunkBHIEXRGKjs = require('./chunk-BHIEXRGK.js'); // src/help/registry.ts var HelpRegistry = (_class = class {constructor() { _class.prototype.__init.call(this); } __init() {this.helps = {}} add(id, title, content, order9 = 100) { if (this.helps[id]) { throw new Error(`Help '${id}' already registered`); } this.helps[id] = { id, title, content, order: order9 }; } getHelps() { return Object.values(this.helps).sort((a, b) => a.order - b.order); } reset() { this.helps = {}; } }, _class); var helpRegistry = new HelpRegistry(); // src/plugins/auth/keycloak/help.md var help_default = "## Keycloak\n\nThe Keycloak plugin allows you to integrate Hawtio console with Keycloak authentication.\n"; // src/util/globals.ts var log2 = _chunkBHIEXRGKjs.Logger.get("hawtio-util"); // src/util/fetch.ts async function fetchPath(path, callback) { try { const res = await fetch(path); if (!res.ok) { log2.error("Failed to fetch", path, ":", res.status, res.statusText); return callback.error(); } const data = await res.text(); return callback.success(data); } catch (err) { log2.error("Failed to fetch", path, ":", err); return callback.error(); } } // src/plugins/auth/keycloak/keycloak-service.ts var _keycloakjs = require('keycloak-js'); var _keycloakjs2 = _interopRequireDefault(_keycloakjs); // src/plugins/auth/keycloak/globals.ts var pluginName = "hawtio-auth-keycloak"; var log3 = _chunkBHIEXRGKjs.Logger.get(pluginName); var PATH_KEYCLOAK_ENABLED = "keycloak/enabled"; var PATH_KEYCLOAK_CLIENT_CONFIG = "keycloak/client-config"; var PATH_KEYCLOAK_VALIDATE = "keycloak/validate-subject-matches"; // src/plugins/auth/keycloak/keycloak-service.ts var pluginName2 = "hawtio-keycloak"; var AUTH_METHOD = "keycloak"; var log4 = _chunkBHIEXRGKjs.Logger.get(pluginName2); var KEYCLOAK_TOKEN_MINIMUM_VALIDITY = 5; var KeycloakService = (_class2 = class { // promises created during construction // configuration from Hawtio backend - should include provider URL and realm. defined in keycloak.js // Keycloak is enabled when /keycloak/client-config endpoint returns `true` // Keycloak instance - from keycloak.js which performs all Keycloak interaction // promise related to logged-in user. Contains JWT access_token from Keycloak and information about // user from id_token. // when user is logged in, the credentials are represented by the token (usually JWT), which has to // be sent with every HTTP request using built-in fetch, which we wrap constructor() {;_class2.prototype.__init2.call(this); this.enabled = this.loadKeycloakEnabled(); this.config = this.loadKeycloakConfig(); this.keycloak = this.createKeycloak(); this.userProfile = this.loadUserProfile(); this.originalFetch = fetch; this.originalFetch = this.originalFetch.bind(window); } /** * Simply checks if Keycloak plugin should be enabled in _native_ mode (using `keycloak.js` library) * using `/keycloak/enabled` endpoint * @private */ async loadKeycloakEnabled() { _chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 0 /* started */, "config"); return fetch(PATH_KEYCLOAK_ENABLED).then((response) => response.ok && response.status == 200 ? response.text() : null).then((data) => { const enabled = data ? data.trim() === "true" : false; if (!enabled) { _chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config"); } log4.debug("Keycloak enabled:", enabled); return enabled; }).catch(() => { _chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config"); return false; }); } /** * Load Keycloak Client configuration required by `keycloak.js` * @private */ async loadKeycloakConfig() { const enabled = await this.enabled; if (!enabled) { return null; } return fetch(PATH_KEYCLOAK_CLIENT_CONFIG).then((response) => response.ok && response.status == 200 ? response.json() : null).then((json) => { if (json) { log4.debug("Loaded", PATH_KEYCLOAK_CLIENT_CONFIG, ":", json); return json; } else { _chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config"); } return null; }).catch(() => { _chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config"); return null; }); } /** * Create Keycloak object to communicate with Identity Provider * @private */ async createKeycloak() { const enabled = await this.enabled; const config = await this.config; if (!enabled || !config) { log4.debug("Keycloak disabled"); return null; } _chunkBHIEXRGKjs.configManager.configureAuthenticationMethod({ method: AUTH_METHOD, login: this.keycloakLogin }).then(() => { _chunkBHIEXRGKjs.configManager.initItem("Keycloak Configuration", 2 /* finished */, "config"); }); return new (0, _keycloakjs2.default)(config); } __init2() {this.keycloakLogin = async (silent = false) => { const keycloak2 = await this.keycloak; if (!keycloak2) { return 1 /* configuration_error */; } if (!window.isSecureContext) { log4.error("Can't perform Keycloak authentication in non-secure context"); return 3 /* security_context_error */; } const options = {}; if (silent) { options.prompt = "none"; } await keycloak2.login(options); return 0 /* ok */; }} async loadUserProfile() { const keycloak2 = await this.keycloak; if (!keycloak2) { return null; } const initOptions = await this.getKeycloakInitOptions(); try { const authenticated = await keycloak2.init(initOptions); log4.info("Initialised Keycloak: authenticated =", authenticated); if (!authenticated) { return null; } try { const profile = await keycloak2.loadUserProfile(); log4.debug("Loaded Keycloak profile:", profile); profile.token = keycloak2.token; return profile; } catch (error) { log4.error("Failed to load user profile:", error); } } catch (error) { log4.error("Failed to initialise Keycloak:", error); } return null; } /** * Options for `Keycloak.init()` * @private */ async getKeycloakInitOptions() { const config = await this.config; const pkceMethod = _optionalChain([config, 'optionalAccess', _2 => _2.pkceMethod]); return { // safer than 'query' responseMode: "fragment", // means responseType = 'code' flow: "standard", // check silent login by default onLoad: "check-sso", // onLoad: 'login-required', pkceMethod, // this may be reset to false by Keycloak in check3pCookiesSupported() // reason may be strict cookie policy. But we'll use this option for silent SSO login checkLoginIframe: true }; } isKeycloakEnabled() { return this.enabled; } registerUserHooks(helpRegistration) { const fetchUser = async (resolve) => { const keycloak2 = await this.keycloak; const userProfile = await this.userProfile; if (!keycloak2 || !userProfile) { return { isIgnore: true, isError: false, loginMethod: AUTH_METHOD }; } if (userProfile.username && userProfile.token) { resolve({ username: userProfile.username, isLogin: true, loginMethod: AUTH_METHOD }); _chunkBHIEXRGKjs.userService.setToken(userProfile.token); } this.setupFetch(); helpRegistration(); return { isIgnore: false, isError: false, loginMethod: AUTH_METHOD }; }; _chunkBHIEXRGKjs.userService.addFetchUserHook(AUTH_METHOD, fetchUser); const logout = async () => { const keycloak2 = await this.keycloak; if (!keycloak2) { return false; } const config = await this.config; let logoutUri = _optionalChain([config, 'optionalAccess', _3 => _3.logoutUri]); if (logoutUri) { if (!URL.canParse(logoutUri)) { logoutUri = new URL(logoutUri, document.baseURI).href; } } log4.info("Log out Keycloak"); try { await keycloak2.logout({ redirectUri: logoutUri }); } catch (error) { log4.error("Error logging out Keycloak:", error); } return true; }; _chunkBHIEXRGKjs.userService.addLogoutHook(AUTH_METHOD, logout); } async setupFetch() { const keycloak2 = await this.keycloak; const config = await this.config; if (!keycloak2 || !config) { return; } log4.debug("Intercept Fetch API to attach Keycloak token to authorization header"); const { fetch: originalFetch } = window; window.fetch = async (input, init) => { const logPrefix = "Fetch -"; log4.debug(logPrefix, "Fetch intercepted for Keycloak authentication"); if (!keycloak2.authenticated || keycloak2.isTokenExpired(KEYCLOAK_TOKEN_MINIMUM_VALIDITY)) { log4.debug(logPrefix, "Try to update token for request:", input); return new Promise((resolve, reject) => { this.updateToken( (token2) => { if (token2) { log4.debug(logPrefix, "Keycloak token refreshed. Set new value to userService"); _chunkBHIEXRGKjs.userService.setToken(token2); } log4.debug(logPrefix, "Re-sending request after successfully updating Keycloak token:", input); resolve(fetch(input, init)); }, () => { log4.debug(logPrefix, "Logging out due to token update failed"); _chunkBHIEXRGKjs.userService.logout(); reject(); } ); }); } init = { ...init }; if (config.jaas) { if (keycloak2.profile && keycloak2.profile.username && keycloak2.token) { init.headers = { ...init.headers, Authorization: _chunkBJ6TSPQKjs.basicAuthHeaderValue.call(void 0, keycloak2.profile.username, keycloak2.token) }; } else { log4.error(logPrefix, "Keycloak username or token not found in JAAS mode:", keycloak2.profile, keycloak2.token); } } else { init.headers = { ...init.headers, Authorization: `Bearer ${keycloak2.token}` }; } const token = _chunkBJ6TSPQKjs.getCookie.call(void 0, "XSRF-TOKEN"); if (token) { log4.debug(logPrefix, "Set XSRF token header from cookies"); init.headers = { ...init.headers, "X-XSRF-TOKEN": token }; } return originalFetch(input, init); }; } async updateToken(successFn, errorFn) { const keycloak2 = await this.keycloak; if (!keycloak2) { return; } keycloak2.updateToken(KEYCLOAK_TOKEN_MINIMUM_VALIDITY).then((refreshed) => { if (refreshed) { const token = keycloak2.token; if (token) { successFn(token); } } else { log4.debug("Token is still valid"); } }).catch((reason) => { log4.error("Couldn't update token:", reason); _optionalChain([errorFn, 'optionalCall', _4 => _4()]); }); } // TODO: validate-subject-matches validateSubjectMatches(user) { const url = `${PATH_KEYCLOAK_VALIDATE}?keycloakUser=${encodeURIComponent(user)}`; return fetchPath(url, { success: (data) => { log4.debug("Got response for validate subject matches:", data); return JSON.parse(data); }, error: () => false }); } }, _class2); var keycloakService = new KeycloakService(); // src/plugins/auth/keycloak/index.ts var keycloak = () => { let helpRegistered = false; keycloakService.registerUserHooks(() => { if (!helpRegistered) { helpRegistry.add("keycloak", "Keycloak", help_default, 21); helpRegistered = true; } }); }; // src/plugins/auth/oidc/help.md var help_default2 = "## OpenID Connect\n\nThe OIDC plugin allows you to integrate Hawtio console with generic OIDC provider.\n"; // src/plugins/auth/oidc/oidc-service.ts var _jwtdecode = require('jwt-decode'); var _oauth4webapi = require('oauth4webapi'); var oidc = _interopRequireWildcard(_oauth4webapi); var pluginName3 = "hawtio-oidc"; var AUTH_METHOD2 = "oidc"; var log5 = _chunkBHIEXRGKjs.Logger.get(pluginName3); var clientAuth = (_as, client, parameters, _headers) => { parameters.set("client_id", client.client_id); return Promise.resolve(); }; var OidcService = (_class3 = class { // promises created during construction // OIDC plugin is enabled when there's any configuration with a provider URL in the config - may not be reachable though // configuration from Hawtio backend - should include provider URL // we keep an array (usually 1-elem) for all OIDC providers - one promise for all configs (fetched from a single endpoint) // OIDC metadata may come with its config. When not provided we have to get it from .well-known/openid-configuration // if we can't access it during initialization, we have to try on login // this is an array of promises, because each OidcConfig has own metadata which may be fetched at different time __init3() {this.oidcMetadata = []} // promise related to logged-in user. contains user name and tokens. This promise is resolved // after completing OAuth2 authorization flow or retrieving existing user using OIDC session // if there's no information about the user however we resolve this promise with null and we // don't start Authorization Flow - it's started only on user request // we have to know which particular provider was used for OIDC login __init4() {this.selectedProvider = -1} // when user is logged in, the credentials are represented by the token (usually JWT), which has to // be sent with every HTTP request using built-in fetch, which we wrap constructor() {;_class3.prototype.__init3.call(this);_class3.prototype.__init4.call(this);_class3.prototype.__init5.call(this);_class3.prototype.__init6.call(this); _chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 0 /* started */, "config"); this.config = fetch("auth/config/oidc").then((response) => { if (response.ok && response.status == 200) { return response.json(); } else if (response.status == 404) { return fetch("auth/config").then((response2) => response2.ok && response2.status == 200 ? response2.json() : null); } else { return null; } }).then((json) => { return !json ? [] : Array.isArray(json) ? json : [json]; }).catch(() => { _chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 1 /* skipped */, "config"); return []; }); this.enabled = this.isOidcEnabled(); Promise.all([this.enabled, this.config]).then(([enabled, config]) => { if (enabled) { this.oidcMetadata = []; config.forEach((c, idx) => { const mdPromise = this.fetchOidcMetadata(c).then((md) => { return this.processOidcMetadata(c, md, idx, true); }).catch((_e) => { return null; }); this.oidcMetadata.push(mdPromise); }); Promise.all(this.oidcMetadata).then(() => { _chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 2 /* finished */, "config"); }); } else { _chunkBHIEXRGKjs.configManager.initItem("OIDC Configuration", 1 /* skipped */, "config"); this.oidcMetadata = []; } }); this.userInfo = this.initialize(); this.originalFetch = fetch; this.originalFetch = this.originalFetch.bind(window); } /** * This should happen during initialization, but when OIDC is enabled and we can't fetch OIDC metadata initially, * we'll be trying to do it during login until we get the metadata. * @param c * @param md * @param idx * @param initial * @private */ async processOidcMetadata(c, md, idx, initial = false) { if (md) { c["openid-configuration"] = md; } if (initial) { if (!c.position) { c.position = idx; } c.login = this.oidcLoginFunction(idx); _chunkBHIEXRGKjs.configManager.configureAuthenticationMethod(c); } return md; } /** * OIDC is enabled when configuration has `oidc` method and `provider` is specified * @private */ async isOidcEnabled() { const cfg = await this.config; if (_optionalChain([cfg, 'optionalAccess', _5 => _5.length]) == 0) { return false; } return cfg.find((c) => _optionalChain([c, 'optionalAccess', _6 => _6.method]) === AUTH_METHOD2 && _optionalChain([c, 'optionalAccess', _7 => _7.provider]) != null) != void 0; } /** * Returns information about target IDP - either from initial configuration (prepared at server side by * `/auth/config/oidc` endpoint) or from OIDC metadata using additional request. Called only when OIDC * method is enabled * @param cfg * @private */ async fetchOidcMetadata(cfg) { let res = null; if (!cfg) { return null; } if (cfg["openid-configuration"]) { log5.info("Using pre-fetched openid-configuration"); return cfg["openid-configuration"]; } else { log5.info("Fetching .well-known/openid-configuration"); const cfgUrl = new URL(cfg.provider); res = await oidc.discoveryRequest(cfgUrl, { [oidc.allowInsecureRequests]: true // to also allow http requests (it's not "trust-all" flag) }).catch((e) => { log5.error("Failed OIDC discovery request", e); }); if (!res || !res.ok) { return null; } return oidc.processDiscoveryResponse(cfgUrl, res); } } /** * OIDC initialization - we can check existing _state_ which is available in URI (after redirect from IdP) * and/or in localStorage (state kept in between cross origin requests) * @private */ async initialize() { const enabled = await this.enabled; const configArray = await this.config; if (configArray.length == 0 || !enabled) { return null; } const selectedIdx = localStorage.getItem("hawtio-oidc-login-idx"); if (!selectedIdx) { return null; } const idx = parseInt(selectedIdx); if (idx < 0 || idx >= configArray.length) { return null; } if (idx >= configArray.length || idx >= this.oidcMetadata.length) { throw new Error(`Invalid OIDC authentication configuration with idx=${idx}`); } this.selectedProvider = idx; const config = configArray[idx]; const as = await this.oidcMetadata[idx]; if (!as || !config) { return null; } let urlParams = null; if (config.response_mode === "fragment") { if (window.location.hash && window.location.hash.length > 0) { urlParams = new URLSearchParams(window.location.hash.substring(1)); } } else if (config.response_mode === "query") { if (window.location.search || window.location.search.length > 0) { urlParams = new URLSearchParams(window.location.search.substring(1)); } } const goodParamsRequired = ["code", "state"]; const errorParamsRequired = ["error"]; if (as["authorization_response_iss_parameter_supported"]) { goodParamsRequired.push("iss"); } let oauthSuccess = urlParams != null; let oauthError = false; if (urlParams != null) { goodParamsRequired.forEach((p) => { oauthSuccess &&= urlParams.get(p) != null; }); errorParamsRequired.forEach((p) => { oauthError ||= urlParams.get(p) != null; }); } if (oauthError) { const error = { error: _optionalChain([urlParams, 'optionalAccess', _8 => _8.get, 'call', _9 => _9("error")]), error_description: _optionalChain([urlParams, 'optionalAccess', _10 => _10.get, 'call', _11 => _11("error_description")]), error_uri: _optionalChain([urlParams, 'optionalAccess', _12 => _12.get, 'call', _13 => _13("error_uri")]) }; log5.error("OpenID Connect error", error); return error; } if (!oauthSuccess) { const ts = localStorage.getItem("core.auth.oidc"); localStorage.removeItem("core.auth.oidc"); if (ts) { const exp_at = parseInt(ts); const now = Date.now(); if (!isNaN(exp_at) && now < exp_at * 1e3) { localStorage.setItem("core.auth.silentLogin", "1"); this.oidcLogin(idx, true); } } return null; } const client = { client_id: config.client_id, token_endpoint_auth_method: "none" }; const state = urlParams.get("state"); let authResponse; try { authResponse = oidc.validateAuthResponse(as, client, urlParams, state); } catch (e) { if (e instanceof _oauth4webapi.AuthorizationResponseError) { log5.error("OpenID Authorization error", e); return { error: e.message }; } } log5.info("Getting localStore data, because we have params", urlParams); const loginDataString = localStorage.getItem("hawtio-oidc-login"); localStorage.removeItem("hawtio-oidc-login"); if (!loginDataString) { log5.warn("No local data, can't proceed with OpenID authorization grant"); return null; } const loginData = JSON.parse(loginDataString); if (!loginData.cv || !loginData.st) { log5.warn("Missing local data, can't proceed with OpenID authorization grant"); return null; } const res = await oidc.authorizationCodeGrantRequest(as, client, clientAuth, authResponse, config.redirect_uri, loginData.cv, { [oidc.allowInsecureRequests]: true }).catch((e) => { log5.warn("Problem accessing OpenID token endpoint", e); return null; }); if (!res) { const msg = "No authorization code grant response available"; log5.warn(msg); return { error: msg }; } const tokenResponse = await oidc.processAuthorizationCodeResponse(as, client, res, { expectedNonce: loginData.n, maxAge: oidc.skipAuthTimeCheck }).catch((e) => { log5.error("Problem processing OpenID token response", e); return null; }); if (!tokenResponse) { return { error: "Problem processing OpenID token response" }; } const access_token = tokenResponse["access_token"]; const refresh_token = tokenResponse["refresh_token"]; const id_token = tokenResponse["id_token"]; let at_exp = 0; try { const access_token_decoded = _jwtdecode.jwtDecode.call(void 0, access_token); if (access_token_decoded["exp"]) { at_exp = access_token_decoded["exp"]; } else { at_exp = 0; log5.warn(`Access token doesn't contain "exp" information`); } } catch (e) { log5.warn("Problem determining access token validity", e); } const claims = oidc.getValidatedIdTokenClaims(tokenResponse); if (!claims) { const msg = "No ID token returned"; log5.warn(msg); return { error: msg }; } const user = _nullishCoalesce(_nullishCoalesce(claims.name, () => ( claims.preferred_username)), () => ( claims.sub)); window.history.replaceState(null, "", loginData.h); localStorage.setItem("core.auth.oidc", `${at_exp}`); this.setupFetch(); return { user, access_token, refresh_token, id_token, at_exp }; } __init5() {this.oidcLoginFunction = (oidcIndex) => { return () => { return this.oidcLogin(oidcIndex, false); }; }} /** * This is a method made available to `<HawtioLogin>` UI when user clicks "Login with OIDC" button. * * @param idx index of the OIDC configuration used * @param silent whether to start _silent_ (`prompt=none`) Authorization Flow * @return `false` (a promise resolving to `false`) if the login process can't proceed - login screen should * display generic error */ __init6() {this.oidcLogin = async (idx, silent = false) => { const cfg = await this.config; if (!cfg || cfg.length == 0) { return 1 /* configuration_error */; } if (idx >= cfg.length) { throw new Error(`Invalid OIDC authentication configuration with idx=${idx}`); } const config = cfg[idx]; if (!config) { return 1 /* configuration_error */; } if (idx >= this.oidcMetadata.length) { throw new Error(`Invalid OIDC authentication configuration with idx=${idx}`); } this.selectedProvider = idx; let as = await this.oidcMetadata[idx]; if (!as) { this.oidcMetadata[idx] = this.fetchOidcMetadata(config).then((md) => { return this.processOidcMetadata(config, md, idx, false); }); as = await this.oidcMetadata[idx]; if (!as) { return 1 /* configuration_error */; } } const available = await this.checkAvailability(idx); if (!available) { return 2 /* connect_error */; } const code_challenge_method = config.code_challenge_method; const code_verifier = oidc.generateRandomCodeVerifier(); if (!window.isSecureContext) { log5.error("Can't perform OpenID Connect authentication in non-secure context"); return 3 /* security_context_error */; } const code_challenge = await oidc.calculatePKCECodeChallenge(code_verifier); const state = oidc.generateRandomState(); const nonce = oidc.generateRandomNonce(); const verifyData = JSON.stringify({ st: state, cv: code_verifier, n: nonce, h: window.location.href }); localStorage.setItem("hawtio-oidc-login", verifyData); localStorage.setItem("hawtio-oidc-login-idx", JSON.stringify(idx)); log5.info("Added to local storage", verifyData); const authorizationUrl = new URL(as.authorization_endpoint); authorizationUrl.searchParams.set("response_type", "code"); authorizationUrl.searchParams.set("response_mode", config.response_mode); authorizationUrl.searchParams.set("client_id", config.client_id); authorizationUrl.searchParams.set("redirect_uri", config.redirect_uri); const basePath = _chunkBHIEXRGKjs.hawtio.getBasePath(); const u = new URL(window.location.href); u.hash = ""; let redirect = u.pathname; if (basePath && redirect.startsWith(basePath)) { redirect = redirect.slice(basePath.length); if (redirect.startsWith("/")) { redirect = redirect.slice(1); } } authorizationUrl.searchParams.set("scope", config.scope); if (code_challenge_method) { authorizationUrl.searchParams.set("code_challenge_method", code_challenge_method); authorizationUrl.searchParams.set("code_challenge", code_challenge); } authorizationUrl.searchParams.set("state", state); authorizationUrl.searchParams.set("nonce", nonce); if (silent) { authorizationUrl.searchParams.set("prompt", "none"); } log5.info("Redirecting to ", authorizationUrl); window.location.replace(authorizationUrl); return new Promise((_resolve, _reject) => { log5.debug("Waiting for redirect"); }); }} /** * Attempt to communicate with IdP before login/logout without redirection. Actual login/logout * will redirect (or fail with blank browser page), so it'll be too late to handle situation where IdP is * simply not responding. * @param idx * @private */ async checkAvailability(idx) { try { const cfg = (await this.config)[idx]; if (cfg) { let provider = cfg.provider; if (provider && !provider.toString().endsWith("/")) provider = provider + "/"; const url = new URL(".well-known/openid-configuration", provider); return this.originalFetch.bind(window)(url).then((_r) => true).catch((_e) => { return false; }); } return false; } catch (e2) { return false; } } /** * Check whether the expiration date from access token means the token is expiring in less than 5 seconds. * @param at_exp * @private */ isTokenExpiring(at_exp) { const now = Math.floor(Date.now() / 1e3); return at_exp - 5 < now; } /** * Integrates this OidcService with UserService for user management. * @param helpRegistration */ registerUserHooks(helpRegistration) { const fetchUser = async (resolveUser, proceed) => { if (proceed && !proceed()) { return { isIgnore: true, isError: false, loginMethod: AUTH_METHOD2 }; } const userInfo = await this.userInfo; if (!userInfo) { return { isIgnore: true, isError: false, loginMethod: AUTH_METHOD2 }; } localStorage.removeItem("core.auth.silentLogin"); if (!userInfo.error) { resolveUser({ username: userInfo.user, isLogin: true, loginMethod: AUTH_METHOD2 }); } else { const errorMessage = '"oidc" plugin error: ' + (_nullishCoalesce(userInfo.error_description, () => ( userInfo.error))); return { isIgnore: false, isError: true, errorMessage, loginMethod: AUTH_METHOD2 }; } _chunkBHIEXRGKjs.userService.setToken(userInfo.access_token); helpRegistration(); return { isIgnore: false, isError: false, loginMethod: AUTH_METHOD2 }; }; _chunkBHIEXRGKjs.userService.addFetchUserHook(AUTH_METHOD2, fetchUser); const logout = async () => { if (this.selectedProvider == -1) { return false; } const md = await this.oidcMetadata[this.selectedProvider]; if (_optionalChain([md, 'optionalAccess', _14 => _14.end_session_endpoint])) { const available = await this.checkAvailability(this.selectedProvider); if (!available) { return false; } localStorage.removeItem("core.auth.oidc"); const c = (await this.config)[this.selectedProvider]; const userInfo = await this.userInfo; if (userInfo && userInfo.id_token) { window.location.assign( `${_optionalChain([md, 'optionalAccess', _15 => _15.end_session_endpoint])}?post_logout_redirect_uri=${document.baseURI}&client_id=${c.client_id}&id_token_hint=${userInfo.id_token}` ); } else { window.location.assign( `${_optionalChain([md, 'optionalAccess', _16 => _16.end_session_endpoint])}?post_logout_redirect_uri=${document.baseURI}&client_id=${c.client_id}` ); } return true; } return false; }; _chunkBHIEXRGKjs.userService.addLogoutHook(AUTH_METHOD2, logout); } async updateToken(success, failure) { const userInfo = await this.userInfo; if (!userInfo || this.selectedProvider == -1) { return; } if (userInfo.refresh_token) { const config = (await this.config)[this.selectedProvider]; const enabled = await this.enabled; const as = await this.oidcMetadata[this.selectedProvider]; if (!config || !enabled || !as) { return; } const client = { client_id: config.client_id, token_endpoint_auth_method: "none" }; const options = { [oidc.customFetch]: this.originalFetch, [oidc.allowInsecureRequests]: true }; const res = await oidc.refreshTokenGrantRequest(as, client, clientAuth, userInfo.refresh_token, options).catch((e) => { log5.error("Problem refreshing token", e); if (failure) { failure(); } }); if (!res) { return; } const refreshResponse = await oidc.processRefreshTokenResponse(as, client, res).catch((e) => { log5.error("Problem processing refresh token response", e); }); if (!refreshResponse) { return; } userInfo.access_token = refreshResponse["access_token"]; userInfo.refresh_token = refreshResponse["refresh_token"]; const access_token_decoded = _jwtdecode.jwtDecode.call(void 0, userInfo.access_token); if (access_token_decoded["exp"]) { userInfo.at_exp = access_token_decoded["exp"]; } else { userInfo.at_exp = 0; log5.warn(`Access token doesn't contain "exp" information`); } this.userInfo = Promise.resolve(userInfo); success(userInfo); } else { log5.error("No refresh token available"); } } /** * Replace global `fetch` function with a delegated call that handles authorization for remote Jolokia agents * and target agent that may run as proxy (to remote Jolokia agent). * `fetch` wrapper will also refresh access token if needed. * @private */ async setupFetch() { let userInfo = await this.userInfo; if (!userInfo || userInfo.error || userInfo.error_description) { return; } window.fetch = async (input, init) => { const logPrefix = "Fetch -"; if (userInfo && (!userInfo.access_token || this.isTokenExpiring(userInfo.at_exp))) { log5.debug(logPrefix, "Refreshing access token"); return new Promise((resolve, reject) => { this.updateToken( (_userInfo) => { if (_userInfo) { userInfo = _userInfo; log5.debug(logPrefix, "OIDC token refreshed. Set new value to userService"); _chunkBHIEXRGKjs.userService.setToken(userInfo.access_token); } log5.debug(logPrefix, "Re-sending request after successfully updating OIDC token:", input); resolve(fetch(input, init)); }, () => { log5.debug(logPrefix, "Logging out due to token update failed"); _chunkBHIEXRGKjs.userService.logout(); reject(); } ); }); } init = { ...init }; init.headers = { ...init.headers, Authorization: `Bearer ${userInfo.access_token}` }; const token = _chunkBJ6TSPQKjs.getCookie.call(void 0, "XSRF-TOKEN"); if (token) { init.headers = { ...init.headers, "X-XSRF-TOKEN": token }; } return this.originalFetch(input, init); }; } }, _class3); var oidcService = new OidcService(); // src/plugins/auth/oidc/index.ts var oidc2 = () => { let helpRegistered = false; oidcService.registerUserHooks(() => { if (!helpRegistered) { helpRegistry.add("oidc", "OpenID Connect", help_default2, 22); helpRegistered = true; } }); }; // src/preferences/registry.ts var PreferencesRegistry = (_class4 = class {constructor() { _class4.prototype.__init7.call(this); } __init7() {this.preferences = {}} // eslint-disable-next-line @typescript-eslint/no-explicit-any add(id, title, component, order9 = 100) { if (this.preferences[id]) { throw new Error(`Preferences '${id}' already registered`); } this.preferences[id] = { id, title, component, order: order9 }; } getPreferences() { return Object.values(this.preferences).sort((a, b) => a.order - b.order); } reset() { this.preferences = {}; } }, _class4); var preferencesRegistry = new PreferencesRegistry(); // src/util/xml.ts function parseXML(xml) { if (!window.DOMParser) throw new Error("Cannot parse xml due to no available native parser"); const parser = new DOMParser(); const doc = parser.parseFromString(xml, "text/xml"); const errorNode = doc.querySelector("parsererror"); if (errorNode) { throw new Error(`Failed to parse XML: ${errorNode.textContent}`); } return doc; } function xmlText(element) { const txt = _optionalChain([element, 'access', _17 => _17.firstChild, 'optionalAccess', _18 => _18.textContent]); return !txt ? null : txt; } function childText(element, childTag) { const childEl = element.querySelector(childTag); if (!childEl) return null; return xmlText(childEl); } // src/plugins/camel/globals.ts var jmxDomain = "org.apache.camel"; var pluginPath2 = "/camel"; var pluginName4 = "hawtio-camel"; var log6 = _chunkBHIEXRGKjs.Logger.get(pluginName4); var camelContexts = "Camel Contexts"; var domainNodeType = "Camel Domain"; var contextsType = "contexts"; var routesType = "routes"; var routeGroupsType = "routeGroups"; var endpointsType = "endpoints"; var componentsType = "components"; var dataformatsType = "dataformats"; var contextNodeType = "context"; var routeNodeType = "routeNode"; var routeXmlNodeType = "routeXmlNode"; var endpointNodeType = "endpointNode"; var componentNodeType = "componentNode"; var defaultRouteGroupsType = "default"; var mbeansType = "MBeans"; var xmlNodeLocalName = "xmlNodeLocalName"; // src/plugins/camel/endpoints/endpoints-service.ts var ENDPOINT_OPERATIONS = { createEndpoint: "createEndpoint(java.lang.String)", componentNames: "componentNames()", canSendToEndpoint: "canSendToEndpoint(java.lang.String)", sendBodyAndHeaders: "sendBodyAndHeaders(java.lang.String, java.lang.Object, java.util.Map)", sendStringBody: "sendStringBody(java.lang.String, java.lang.String)", browseAllMessagesAsXml: "browseAllMessagesAsXml(java.lang.Boolean)", browseRangeMessagesAsXml: "browseRangeMessagesAsXml(java.lang.Integer,java.lang.Integer, java.lang.Boolean)", endpointStatistics: "endpointStatistics()" }; async function getEndpoints(node) { const ctxNode = findContext(node); if (!ctxNode || ctxNode.childCount() === 0) return []; const endpointsNode = ctxNode.get(endpointsType, true); if (!endpointsNode) return []; const endpoints = []; for (const ep of endpointsNode.getChildren()) { if (!ep.objectName) continue; const attributes = await _chunkBJ6TSPQKjs.jolokiaService.readAttributes(ep.objectName).catch((e) => { _chunkBHIEXRGKjs.eventService.notify({ type: "warning", message: _chunkBJ6TSPQKjs.jolokiaService.errorMessage(e) }); return {}; }); endpoints.push({ uri: attributes.EndpointUri, state: attributes.State, mbean: ep.objectName }); } return endpoints; } function canCreateEndpoints(node) { const contextNode = findContext(node); if (!contextNode) { return false; } return contextNode.hasInvokeRights(ENDPOINT_OPERATIONS.createEndpoint); } async function componentNames(node) { const ctxNode = findContext(node); if (!ctxNode || ctxNode.childCount() === 0 || !ctxNode.objectName) return []; const names = await _chunkBJ6TSPQKjs.jolokiaService.execute(ctxNode.objectName, ENDPOINT_OPERATIONS.componentNames); return names; } function notifyError(msg) { _chunkBHIEXRGKjs.eventService.notify({ type: "danger", message: msg }); } async function createEndpoint(node, name) { const ctxNode = findContext(node); if (!ctxNode) { notifyError("Could not find the CamelContext!"); return; } if (!ctxNode.objectName) { notifyError("Could not find the CamelContext MBean!"); return; } _chunkBJ6TSPQKjs.jolokiaService.execute(ctxNode.objectName, ENDPOINT_OPERATIONS.createEndpoint, [name]).then((value) => { if (value === true) { _chunkBJ6TSPQKjs.workspace.refreshTree(); _chunkBHIEXRGKjs.eventService.notify({ type: "success", message: "Creating endpoint", duration: 3e3 }); } else { _chunkBHIEXRGKjs.eventService.notify({ type: "danger", message: "Invalid URI" }); } }).catch((error) => { error = error.replace("org.apache.camel.ResolveEndpointFailedException : ", ""); _chunkBHIEXRGKjs.eventService.notify({ type: "danger", message: error }); }); } function createEndpointFromData(node, componentName, endPointPath, parameters) { if (!componentName) log6.error("createEndpointFromData: component name must be defined"); if (!endPointPath) log6.error("createEndpointFromData: endpoint path must be defined"); log6.debug("Have endpoint data " + JSON.stringify(parameters)); const uri = componentName + "://" + endPointPath + (parameters ? "?" + Object.entries(parameters).map((entry) => entry.join("=")).join("&") : ""); log6.debug("Creating endpoint for uri: " + uri); createEndpoint(node, uri); } async function loadEndpointSchema(node, componentName) { const ctxNode = findContext(node); if (!ctxNode) { _chunkBHIEXRGKjs.eventService.notify({ type: "danger", message: "Could not find the CamelContext!" }); return null; } if (_chunkBHIEXRGKjs.isBlank.call(void 0, componentName)) return null; const camelModel = await getCamelModel(ctxNode); log6.info("Endpoints - Use Camel model version:", camelModel.apacheCamelModelVersion); return _nullishCoalesce(camelModel.components[componentName], () => ( null)); } async function doSendMessage(mbean, body, headers, notify) { const messageHeaders = {}; headers.forEach((header) => { const key = header.name; if (key && key !== "") { messageHeaders[key] = header.value; } }); const context = _optionalChain([mbean, 'access', _19 => _19.parent, 'optionalAccess', _20 => _20.getMetadata, 'call', _21 => _21(contextNodeType)]); const uri = mbean.name; if (context && uri) { let ok = true; const reply = await _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.canSendToEndpoint, [uri]); if (!reply) { notify("warning", "Camel does not support sending to this endpoint."); ok = false; } if (ok) { if (Object.keys(messageHeaders).length > 0) { _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendBodyAndHeaders, [uri, body, messageHeaders]).then((ok2) => { notify("success", `Message and headers were sent to the ${uri} endpoint`); }); } else { _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendStringBody, [uri, body]).then((ok2) => { notify("success", `Message was sent to the ${uri} endpoint`); }); } } } else { if (!mbean) { notify("danger", "Could not find CamelContext MBean!"); } else { notify("danger", "Failed to determine endpoint name!"); } log6.debug("Parsed context and endpoint:", context, mbean); } } async function forwardMessagesToEndpoint(mBean, uri, messages, notify) { const context = _optionalChain([mBean, 'access', _22 => _22.parent, 'optionalAccess', _23 => _23.getMetadata, 'call', _24 => _24(contextNodeType)]); if (context && uri && messages && messages.length) { try { await _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.createEndpoint, [uri]); } catch (err) { notify("danger", `Error: ${err}`); return; } let forwarded = 0; for (const message of messages) { const body = message.body; const messageHeaders = {}; if (message.headers.length > 0) { message.headers.forEach((header) => { if (header.key && header.key !== "") { messageHeaders[header.key] = header.value; } }); } try { await _chunkBJ6TSPQKjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendBodyAndHeaders, [uri, body, messageHeaders]); forwarded++; } catch (err) { notify("danger", `Error: ${err}`); return; } } const m = forwarded > 1 ? "messages" : "message"; notify("success", `Forwarded ${forwarded} ${m} to endpoint ${uri}`); } } async function getMessagesFromTheEndpoint(mbean, from, to) { let messageData = []; const context = _optionalChain([mbean, 'access', _25 => _25.parent, 'optionalAccess', _26 => _26.getMetadata, 'call', _27 => _27(contextNodeType)]); const browseAll = to === -1; if (context) { let reply; if (browseAll) { reply = await _chunkBJ6TSPQKjs.jolokiaService.execute(_nullishCoalesce(mbean.objectName, () => ( "")), ENDPOINT_OPERATIONS.browseAllMessagesAsXml, [true]); } else { reply = await _chunkBJ6TSPQKjs.jolokiaService.execute(_nullishCoalesce(mbean.objectName, () => ( "")), ENDPOINT_OPERATIONS.browseRangeMessagesAsXml, [ from, to, true ]); } const messagesXml = parseXML(reply); messageData = parseMessagesFromXml(messagesXml); } return messageData; } function parseMessagesFromXml(pDoc) { const messagesData = []; const messages = pDoc.getElementsByTagName("message"); for (const message of messages) { const headers = []; for (const header of message.getElementsByTagName("header")) { headers.push({ key: _nullishCoalesce(header.getAttribute("key"), () => ( "")), value: _nullishCoalesce(header.textContent, () => ( "")), type: _nullishCoalesce(header.getAttribute("type"), () => ( "")) }); } messagesData.push({ messageId: _nullishCoalesce(message.getAttribute("exchangeId"), () => ( "")), body: _nullishCoalesce(_optionalChain([message, 'access', _28 => _28.getElementsByTagName, 'call', _29 => _29("body"), 'access', _30 => _30[0], 'optionalAccess', _31 => _31.textContent]), () => ( "")), headers }); } return messagesData; } async function getEndpointStatistics(node) { let stats = []; const registry = getDefaultRuntimeEndpointRegistry(node); if (registry && registry.objectName) { const res = await _chunkBJ6TSPQKjs.jolokiaService.execute(registry.objectName, ENDPOINT_OPERATIONS.endpointStatistics); stats = Object.values(res); } else { log6.error("Error with the retrieving the registry"); } return stats; } // src/plugins/camel/icons/CamelImageIcon.tsx var _jsxruntime = require('react/jsx-runtime'); var CamelImageIcon = (props) => { const { name, svg, size = 16 } = props; return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "img", { src: svg, width: size + "px", height: size + "px", alt: name }); }; // src/plugins/camel/icons/svg/index.ts var svg_exports = {}; _chunkBHIEXRGKjs.__export.call(void 0, svg_exports, { IconNames: () => IconNames, aggregate: () => aggregate24_default, bean: () => bean24_default, breakpoint: () => breakpoint_default, camel: () => camel_default, camelContext: () => camel_context_icon_default, camelRoute: () => camel_route_default, camelRouteFolder: () => camel_route_folder_default, camelTracing: () => camel_tracing_default, channel: () => channel24_default, channeladapter: () => channeladapter24_default, channelpurger: () => channelpurger24_default, choice: () => choice24_default, commandmessage: () => commandmessage24_default, competingconsumers: () => competingconsumers24_default, componentFolder: () => component_folder_default, contentbasedrouter: () => contentbasedrouter24_default, contentfilter: () => contentfilter24_default, controlbus: () => controlbus24_default, convertbodyto: () => convertbodyto24_default, correlationidentifier: () => correlationidentifier24_default, customdataformat: () => customdataformat24_default, datatypechannel: () => datatypechannel24_default, deadletterchannel: () => deadletterchannel24_default, detour: () => detour24_default, distributionaggregate: () => distributionaggregate24_default, documentmessage: () => documentmessage24_default, durablesubscription: () => durablesubscription24_default, dynamicrouter: () => dynamicrouter24_default, editCamelRoute: () => edit_camel_route_default, encapsulatedsynchronous: () => encapsulatedsynchronous24_default, endpoint: () => endpoint24_default, endpointFile: () => endpointFile24_default, endpointFolder: () => endpointFolder24_default, endpointQueue: () => endpointQueue24_default, endpointRepository: () => endpointRepository24_default, endpointTimer: () => endpointTimer24_default, endpointdrools: () => endpointdrools24_default, endpoints: () => endpoints_default, endpointsActivemq: () => activemq24_default, endpointsAtom: () => atom24_default, endpointsBean: () => bean24_default2, endpointsCxf: () => cxf24_default, endpointsCxfrs: () => cxfrs24_default, endpointsEjb: () => ejb24_default, endpointsFacebook: () => facebook24_default, endpointsFile: () => file24_default, endpointsFolder: () => endpoints_folder_default, endpointsFtp: () => ftp24_default, endpointsFtps: () => ftps24_default, endpointsImap: () => imap24_default, endpointsImaps: () => imaps24_default, endpointsJdbc: () => jdbc24_default, endpointsJms: () => jms24_default, endpointsLanguage: () => language24_default, endpointsLinkedin: () => linkedin24_default, endpointsLog: () => log24_default, endpointsMqtt: () => mqtt24_default, endpointsNetty: () => netty24_default, endpointsNetty4: () => netty424_default, endpointsNetty4Http: () => netty4_http24_default, endpointsNettyHttp: () => netty_http2