"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.SecureSpacesClientWrapper = exports.LEGACY_URL_ALIAS_TYPE = void 0; exports.getAliasId = getAliasId; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _boom = _interopRequireDefault(require("@hapi/boom")); var _audit = require("../audit"); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0; you may not use this file except in compliance with the Elastic License * 2.0. */ const PURPOSE_PRIVILEGE_MAP = { any: authorization => [authorization.actions.login], copySavedObjectsIntoSpace: authorization => [authorization.actions.ui.get('savedObjectsManagement', 'copyIntoSpace')], findSavedObjects: authorization => { return [authorization.actions.login, authorization.actions.savedObject.get('config', 'find')]; }, shareSavedObjectsIntoSpace: authorization => [authorization.actions.ui.get('savedObjectsManagement', 'shareIntoSpace')] }; /** @internal */ const LEGACY_URL_ALIAS_TYPE = 'legacy-url-alias'; exports.LEGACY_URL_ALIAS_TYPE = LEGACY_URL_ALIAS_TYPE; class SecureSpacesClientWrapper { constructor(spacesClient, request, authorization, auditLogger, errors, securityExtension) { (0, _defineProperty2.default)(this, "useRbac", void 0); this.spacesClient = spacesClient; this.request = request; this.authorization = authorization; this.auditLogger = auditLogger; this.errors = errors; this.securityExtension = securityExtension; this.useRbac = this.authorization.mode.useRbacForRequest(this.request); } async getAll({ purpose = 'any', includeAuthorizedPurposes } = {}) { const allSpaces = await this.spacesClient.getAll({ purpose, includeAuthorizedPurposes }); if (!this.useRbac) { allSpaces.forEach(({ id }) => this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.FIND, savedObject: { type: 'space', id } }))); return allSpaces; } const spaceIds = allSpaces.map(space => space.id); const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); // Collect all privileges which need to be checked const allPrivileges = Object.entries(PURPOSE_PRIVILEGE_MAP).reduce((acc, [getSpacesPurpose, privilegeFactory]) => !includeAuthorizedPurposes && getSpacesPurpose !== purpose ? acc : { ...acc, [getSpacesPurpose]: privilegeFactory(this.authorization) }, {}); // Check all privileges against all spaces const { privileges } = await checkPrivileges.atSpaces(spaceIds, { kibana: Object.values(allPrivileges).flat() }); // Determine which purposes the user is authorized for within each space. // Remove any spaces for which user is fully unauthorized. const checkHasAllRequired = (space, actions) => actions.every(action => privileges.kibana.some(({ resource, privilege, authorized }) => resource === space.id && privilege === action && authorized)); const authorizedSpaces = allSpaces.map(space => { if (!includeAuthorizedPurposes) { // Check if the user is authorized for a single purpose const requiredActions = PURPOSE_PRIVILEGE_MAP[purpose](this.authorization); return checkHasAllRequired(space, requiredActions) ? space : null; } // Check if the user is authorized for each purpose let hasAnyAuthorization = false; const authorizedPurposes = Object.entries(PURPOSE_PRIVILEGE_MAP).reduce((acc, [purposeKey, privilegeFactory]) => { const requiredActions = privilegeFactory(this.authorization); const hasAllRequired = checkHasAllRequired(space, requiredActions); hasAnyAuthorization = hasAnyAuthorization || hasAllRequired; return { ...acc, [purposeKey]: hasAllRequired }; }, {}); if (!hasAnyAuthorization) { return null; } return { ...space, authorizedPurposes }; }).filter(this.filterUnauthorizedSpaceResults); if (authorizedSpaces.length === 0) { const error = _boom.default.forbidden(); this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.FIND, error })); throw error; // Note: there is a catch for this in `SpacesSavedObjectsClient.find`; if we get rid of this error, remove that too } authorizedSpaces.forEach(({ id }) => this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.FIND, savedObject: { type: 'space', id } }))); return authorizedSpaces; } async get(id) { if (this.useRbac) { try { await this.ensureAuthorizedAtSpace(id, this.authorization.actions.login, `Unauthorized to get ${id} space`); } catch (error) { this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.GET, savedObject: { type: 'space', id }, error })); throw error; } } const space = this.spacesClient.get(id); this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.GET, savedObject: { type: 'space', id } })); return space; } async create(space) { if (this.useRbac) { try { await this.ensureAuthorizedGlobally(this.authorization.actions.space.manage, 'Unauthorized to create spaces'); } catch (error) { this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.CREATE, savedObject: { type: 'space', id: space.id }, error })); throw error; } } this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.CREATE, outcome: 'unknown', savedObject: { type: 'space', id: space.id } })); return this.spacesClient.create(space); } async update(id, space) { if (this.useRbac) { try { await this.ensureAuthorizedGlobally(this.authorization.actions.space.manage, 'Unauthorized to update spaces'); } catch (error) { this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.UPDATE, savedObject: { type: 'space', id }, error })); throw error; } } this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.UPDATE, outcome: 'unknown', savedObject: { type: 'space', id } })); return this.spacesClient.update(id, space); } createSavedObjectFinder(id) { return this.spacesClient.createSavedObjectFinder(id); } async delete(id) { if (this.useRbac) { try { await this.ensureAuthorizedGlobally(this.authorization.actions.space.manage, 'Unauthorized to delete spaces'); } catch (error) { this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.DELETE, savedObject: { type: 'space', id }, error })); throw error; } } // Fetch saved objects to be removed for audit logging // If RBAC is enabled, the securityExtension should definitely be defined, but we check just in case const securityExtension = this.securityExtension; if (this.auditLogger.enabled && securityExtension !== undefined) { const finder = this.spacesClient.createSavedObjectFinder(id); try { for await (const response of finder.find()) { var _this$securityExtensi; (_this$securityExtensi = this.securityExtension) === null || _this$securityExtensi === void 0 ? void 0 : _this$securityExtensi.auditObjectsForSpaceDeletion(id, response.saved_objects); } } finally { await finder.close(); } } this.auditLogger.log((0, _audit.spaceAuditEvent)({ action: _audit.SpaceAuditAction.DELETE, outcome: 'unknown', savedObject: { type: 'space', id } })); return this.spacesClient.delete(id); } async disableLegacyUrlAliases(aliases) { try { var _this$securityExtensi2; await ((_this$securityExtensi2 = this.securityExtension) === null || _this$securityExtensi2 === void 0 ? void 0 : _this$securityExtensi2.authorizeDisableLegacyUrlAliases(aliases)); // will throw if unauthorized } catch (err) { throw this.errors.decorateForbiddenError(new Error(`Unable to disable aliases: ${err.message}`)); } return this.spacesClient.disableLegacyUrlAliases(aliases); } async ensureAuthorizedGlobally(action, forbiddenMessage) { const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); const { hasAllRequested } = await checkPrivileges.globally({ kibana: action }); if (!hasAllRequested) { throw _boom.default.forbidden(forbiddenMessage); } } async ensureAuthorizedAtSpace(spaceId, action, forbiddenMessage) { const checkPrivileges = this.authorization.checkPrivilegesWithRequest(this.request); const { hasAllRequested } = await checkPrivileges.atSpace(spaceId, { kibana: action }); if (!hasAllRequested) { throw _boom.default.forbidden(forbiddenMessage); } } filterUnauthorizedSpaceResults(value) { return value !== null; } } /** @internal This is only exported for testing purposes. */ exports.SecureSpacesClientWrapper = SecureSpacesClientWrapper; function getAliasId({ targetSpace, targetType, sourceId }) { return `${targetSpace}:${targetType}:${sourceId}`; }