UNPKG

@hawtio/react

Version:

A Hawtio reimplementation based on TypeScript + React.

1,301 lines (1,237 loc) 721 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 _chunkURJD3F2Kjs = require('./chunk-URJD3F2K.js'); var _chunkTM6OCU7Kjs = require('./chunk-TM6OCU7K.js'); var _chunkZYPGXT7Qjs = require('./chunk-ZYPGXT7Q.js'); // src/help/registry.ts var HelpRegistry = (_class = class {constructor() { _class.prototype.__init.call(this); } __init() {this.helps = {}} add(id, title, content, order8 = 100) { if (this.helps[id]) { throw new Error(`Help '${id}' already registered`); } this.helps[id] = { id, title, content, order: order8 }; } 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 = _chunkZYPGXT7Qjs.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 = _chunkZYPGXT7Qjs.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 = _chunkZYPGXT7Qjs.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; } /** * Simply checks if Keycloak plugin should be enabled in _native_ mode (using `keycloak.js` library) * using `/keycloak/enabled` endpoint * @private */ async loadKeycloakEnabled() { _chunkZYPGXT7Qjs.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) { _chunkZYPGXT7Qjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config"); } log4.debug("Keycloak enabled:", enabled); return enabled; }).catch(() => { _chunkZYPGXT7Qjs.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 { _chunkZYPGXT7Qjs.configManager.initItem("Keycloak Configuration", 1 /* skipped */, "config"); } return null; }).catch(() => { _chunkZYPGXT7Qjs.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; } _chunkZYPGXT7Qjs.configManager.configureAuthenticationMethod({ method: AUTH_METHOD, login: this.keycloakLogin }).then(() => { _chunkZYPGXT7Qjs.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 }); _chunkZYPGXT7Qjs.userService.setToken(userProfile.token); } this.setupFetch(); helpRegistration(); return { isIgnore: false, isError: false, loginMethod: AUTH_METHOD }; }; _chunkZYPGXT7Qjs.userService.addFetchUserHook(AUTH_METHOD, fetchUser); const logout = async () => { const keycloak2 = await this.keycloak; if (!keycloak2) { return false; } log4.info("Log out Keycloak"); try { await keycloak2.logout(); } catch (error) { log4.error("Error logging out Keycloak:", error); } return true; }; _chunkZYPGXT7Qjs.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"); _chunkZYPGXT7Qjs.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"); _chunkZYPGXT7Qjs.userService.logout(); reject(); } ); }); } init = { ...init }; if (config.jaas) { if (keycloak2.profile && keycloak2.profile.username && keycloak2.token) { init.headers = { ...init.headers, Authorization: _chunkTM6OCU7Kjs.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 = _chunkTM6OCU7Kjs.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', _3 => _3()]); }); } // 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 = _chunkZYPGXT7Qjs.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 // configuration from Hawtio backend - should include provider URL // OIDC plugin is enabled when there's provider URL in the config - may not be reachable though // OIDC metadata may come with 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 // 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 // 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); _chunkZYPGXT7Qjs.configManager.initItem("OIDC Configuration", 0 /* started */, "config"); this.config = fetch("auth/config/oidc").then((response) => response.ok && response.status == 200 ? response.json() : null).then((json) => { return json; }).catch(() => { _chunkZYPGXT7Qjs.configManager.initItem("OIDC Configuration", 1 /* skipped */, "config"); return null; }); this.enabled = this.isOidcEnabled(); this.oidcMetadata = this.enabled.then((enabled) => { if (enabled) { return this.fetchOidcMetadata().then((md) => { return this.processOidcMetadata(md, true); }); } else { _chunkZYPGXT7Qjs.configManager.initItem("OIDC Configuration", 1 /* skipped */, "config"); return null; } }); this.userInfo = this.initialize(); this.originalFetch = fetch; } /** * 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 md * @param initial * @private */ async processOidcMetadata(md, initial = false) { const c = await this.config; if (md) { c["openid-configuration"] = md; } if (initial) { c.login = this.oidcLogin; _chunkZYPGXT7Qjs.configManager.configureAuthenticationMethod(c).then(() => { _chunkZYPGXT7Qjs.configManager.initItem("OIDC Configuration", 2 /* finished */, "config"); }); } return md; } /** * OIDC is enabled when configuration has `oidc` method and `provider` is specified * @private */ async isOidcEnabled() { const cfg = await this.config; return _optionalChain([cfg, 'optionalAccess', _4 => _4.method]) === AUTH_METHOD2 && _optionalChain([cfg, 'optionalAccess', _5 => _5.provider]) != null; } /** * 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 * @private */ async fetchOidcMetadata() { let res = null; const cfg = await this.config; if (!cfg) { log5.debug("OpenID authorization is disabled"); 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 await 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 config = await this.config; const as = await this.oidcMetadata; if (!config || !enabled || !as) { 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', _6 => _6.get, 'call', _7 => _7("error")]), error_description: _optionalChain([urlParams, 'optionalAccess', _8 => _8.get, 'call', _9 => _9("error_description")]), error_uri: _optionalChain([urlParams, 'optionalAccess', _10 => _10.get, 'call', _11 => _11("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(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 }; } /** * This is a method made available to `<HawtioLogin>` UI when user clicks "Login with OIDC" button. * * @param silent whether to start _silen_ (`prompt=none`) Authorization Flow * @return `false` (a promise resolving to `false`) if the login process can't proceed - login screen should * display generic error */ __init3() {this.oidcLogin = async (silent = false) => { const config = await this.config; if (!config) { return 1 /* configuration_error */; } let as = await this.oidcMetadata; if (!as) { this.oidcMetadata = this.fetchOidcMetadata().then((md) => { return this.processOidcMetadata(md, false); }); as = await this.oidcMetadata; if (!as) { return 1 /* configuration_error */; } } const available = await this.checkAvailability(); 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); 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 = _chunkZYPGXT7Qjs.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 using window.location.assign/replace * @private */ async checkAvailability() { try { const cfg = await this.config; if (cfg) { const provider = cfg.provider; return this.originalFetch.bind(window)(provider).then((_r) => true).catch((_e) => 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 }; } _chunkZYPGXT7Qjs.userService.setToken(userInfo.access_token); helpRegistration(); return { isIgnore: false, isError: false, loginMethod: AUTH_METHOD2 }; }; _chunkZYPGXT7Qjs.userService.addFetchUserHook(AUTH_METHOD2, fetchUser); const logout = async () => { const md = await this.oidcMetadata; if (_optionalChain([md, 'optionalAccess', _12 => _12.end_session_endpoint])) { const available = await this.checkAvailability(); if (!available) { return false; } localStorage.removeItem("core.auth.oidc"); const c = await this.config; window.location.assign( `${_optionalChain([md, 'optionalAccess', _13 => _13.end_session_endpoint])}?post_logout_redirect_uri=${document.baseURI}&client_id=${c.client_id}` ); return true; } return false; }; _chunkZYPGXT7Qjs.userService.addLogoutHook(AUTH_METHOD2, logout); } async updateToken(success, failure) { const userInfo = await this.userInfo; if (!userInfo) { return; } if (userInfo.refresh_token) { const config = await this.config; const enabled = await this.enabled; const as = await this.oidcMetadata; 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"); _chunkZYPGXT7Qjs.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"); _chunkZYPGXT7Qjs.userService.logout(); reject(); } ); }); } init = { ...init }; init.headers = { ...init.headers, Authorization: `Bearer ${userInfo.access_token}` }; const token = _chunkTM6OCU7Kjs.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.__init4.call(this); } __init4() {this.preferences = {}} // eslint-disable-next-line @typescript-eslint/no-explicit-any add(id, title, component, order8 = 100) { if (this.preferences[id]) { throw new Error(`Preferences '${id}' already registered`); } this.preferences[id] = { id, title, component, order: order8 }; } 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(); return parser.parseFromString(xml, "text/xml"); } function xmlText(element) { const txt = _optionalChain([element, 'access', _14 => _14.firstChild, 'optionalAccess', _15 => _15.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 = _chunkZYPGXT7Qjs.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 _chunkTM6OCU7Kjs.jolokiaService.readAttributes(ep.objectName); 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 _chunkTM6OCU7Kjs.jolokiaService.execute(ctxNode.objectName, ENDPOINT_OPERATIONS.componentNames); return names; } function notifyError(msg) { _chunkZYPGXT7Qjs.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; } _chunkTM6OCU7Kjs.jolokiaService.execute(ctxNode.objectName, ENDPOINT_OPERATIONS.createEndpoint, [name]).then((value) => { if (value === true) { _chunkTM6OCU7Kjs.workspace.refreshTree(); _chunkZYPGXT7Qjs.eventService.notify({ type: "success", message: "Creating endpoint", duration: 3e3 }); } else { _chunkZYPGXT7Qjs.eventService.notify({ type: "danger", message: "Invalid URI" }); } }).catch((error) => { error = error.replace("org.apache.camel.ResolveEndpointFailedException : ", ""); _chunkZYPGXT7Qjs.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) { _chunkZYPGXT7Qjs.eventService.notify({ type: "danger", message: "Could not find the CamelContext!" }); return null; } if (_chunkZYPGXT7Qjs.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', _16 => _16.parent, 'optionalAccess', _17 => _17.getMetadata, 'call', _18 => _18(contextNodeType)]); const uri = mbean.name; if (context && uri) { let ok = true; const reply = await _chunkTM6OCU7Kjs.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) { _chunkTM6OCU7Kjs.jolokiaService.execute(context, ENDPOINT_OPERATIONS.sendBodyAndHeaders, [uri, body, messageHeaders]).then((ok2) => { notify("success", `Message and headers were sent to the ${uri} endpoint`); }); } else { _chunkTM6OCU7Kjs.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', _19 => _19.parent, 'optionalAccess', _20 => _20.getMetadata, 'call', _21 => _21(contextNodeType)]); if (context && uri && messages && messages.length) { try { await _chunkTM6OCU7Kjs.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 _chunkTM6OCU7Kjs.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', _22 => _22.parent, 'optionalAccess', _23 => _23.getMetadata, 'call', _24 => _24(contextNodeType)]); const browseAll = to === -1; if (context) { let reply; if (browseAll) { reply = await _chunkTM6OCU7Kjs.jolokiaService.execute(_nullishCoalesce(mbean.objectName, () => ( "")), ENDPOINT_OPERATIONS.browseAllMessagesAsXml, [true]); } else { reply = await _chunkTM6OCU7Kjs.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', _25 => _25.getElementsByTagName, 'call', _26 => _26("body"), 'access', _27 => _27[0], 'optionalAccess', _28 => _28.textContent]), () => ( "")), headers }); } return messagesData; } async function getEndpointStatistics(node) { let stats = []; const registry = getDefaultRuntimeEndpointRegistry(node); if (registry && registry.objectName) { const res = await _chunkTM6OCU7Kjs.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 = {}; _chunkZYPGXT7Qjs.__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_http24_default, endpointsNode: () => endpoints_node_default, endpointsPop3: () => pop324_default, endpointsPop3s: () => pop3s24_default, endpointsQuartz: () => quartz24_default, endpointsQuartz2: () => quartz224_default, endpointsRss: () => rss24_default, endpointsSAP: () => SAP24_default, endpointsSAPNetweaver: () => SAPNetweaver24_default, endpointsSalesForce: () => salesForce24_default, endpointsServlet: () => servlet24_default, endpointsSftp: () => sftp24_default, endpointsSmtp: () => smtp24_default, endpointsSmtps: () => smtps24_default, endpointsSnmp: () => snmp24_default, endpointsSql: () => sql24_default, endpointsTimer: () => timer24_default, endpointsTwitter: () => twitter24_default, endpointsWeather: () => weather24_default, endpointsXslt: () => xslt24_default, enrich: () => enrich24_default, envelopeWrapper: () => envelopeWrapper24_default, eventDrivenConsumer: () => eventDrivenConsumer24_default, eventMessage: () => eventMessage24_default, fileTransfer: () => fileTransfer24_default, filter: () => filter24_default, flow: () => flow24_default, generic: () => generic24_default, guaranteedMessaging: () => guaranteedMessaging24_default, idempotentConsumer: () => idempotentConsumer24_default, invalidMessageChannel: () => invalidMessageChannel24_default, loadBalance: () => loadBalance24_default, log: () => log24_default2, marshal: () => marshal24_default, message: () => message24_default, messageBroker: () => messageBroker24_default, messageBus: () => messageBus24_default, messageDispatcher: () => messageDispatcher24_default, messageExpiration: () => messageExpiration24_default, messageSelector: () => messageSelector24_default, messageSequence: () => messageSequence24_default, messageStore: () => messageStore24_default, messaging: () => messaging24_default, messagingAdapter: () => messagingAdapter24_default, messagingBridge: () => messagingBridge24_default, messagingGateway: () => messagingGateway24_default, multicast: () => multicast24_default, node: () => node24_default, normalizer: () => normalizer24_default, pipeline: () => pipeline24_default, pointToPoint: () => pointToPoint24_default, pollEnrich: () => pollEnrich24_default, pollingConsumer: () => pollingConsumer24_default, process: () => process24_default, processManager: () => processManager24_default, processor: () => processor24_default, recipientList: () => recipientList24_default, requestReply: () => requestReply24_default, resequence: () => resequence24_default, returnAddress: () => returnAddress24_default, route: () => route24_default, routingSlip: () => routingSlip24_default, setBody: () => setBody24_default, sharedDatabase: () => sharedDatabase24_default, smartProxy: () => smartProxy24_default, split: () => split24_default, storeInLibrary: () => storeInLibrary24_default, testMessage: () => testMessage24_default, transactionalClient: () => transactionalClient24_default, transform: () => transform24_default, unmarshal: () => unmarshal24_default, wireTap: () => wireTap24_default }); // src/plugins/camel/icons/svg/aggregate24.svg var aggregate24_default = 'data:image/svg+xml,<?xml version="1.0" encoding="UTF-8" standalone="no"?>%0A<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">%0A<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="24px" height="24px" viewBox="0 0 24 24" enable-background="new 0 0 24 24" xml:space="preserve"> <image id="image0" width="24" height="24" x="0" y="0"%0A href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABGdBTUEAALGPC/xhBQAAACBjSFJN%0AAAB6JgAAgIQAAPoAAACA6AAAdTAAAOpgAAA6mAAAF3CculE8AAAABmJLR0QA/wD/AP+gvaeTAAAE%0ATElEQVRIx7VVXYhVVRT+1jn73J8Z74w/6UhBqGXDPOhDgYSlpUQKkUZKWD6UvRiB4EO9FMFAED0U%0A1UNQBAn9FyJJQYhlWBAhIVJzGayxiMqf0flxZu6dc/bea60e9rnn3pkUe6gF+/zutb691rf2twmA%0A5uN/MXP3xg0yOPisqup/ChOXuvDxRx+Q6etbhrs2bbpGEq1/lD933uf/b1kZp06egPHewc1OQUTa%0A4RSodHcBFAFQ2EYTCgWB8lA6JxgVEO1FlrtrsFkKoxAIO6gGKhQEYww+PXSY/jp7Dl3VCnbu2K