"use strict"; var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault"); Object.defineProperty(exports, "__esModule", { value: true }); exports.Authorization = void 0; var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty")); var _boom = _interopRequireDefault(require("@hapi/boom")); var _utils = require("./utils"); var _ = require("."); var _error = require("../common/error"); /* * 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. */ /** * This class handles ensuring that the user making a request has the correct permissions * for the API request. */ class Authorization { constructor({ request, securityAuth, caseOwners, auditLogger }) { (0, _defineProperty2.default)(this, "request", void 0); (0, _defineProperty2.default)(this, "securityAuth", void 0); (0, _defineProperty2.default)(this, "featureCaseOwners", void 0); (0, _defineProperty2.default)(this, "auditLogger", void 0); this.request = request; this.securityAuth = securityAuth; this.featureCaseOwners = caseOwners; this.auditLogger = auditLogger; } /** * Creates an Authorization object. */ static async create({ request, securityAuth, spaces, features, auditLogger, logger }) { const getSpace = async () => { return spaces === null || spaces === void 0 ? void 0 : spaces.spacesService.getActiveSpace(request); }; // Since we need to do async operations, this static method handles that before creating the Auth class let caseOwners; try { var _maybeSpace$disabledF; const maybeSpace = await getSpace(); const disabledFeatures = new Set((_maybeSpace$disabledF = maybeSpace === null || maybeSpace === void 0 ? void 0 : maybeSpace.disabledFeatures) !== null && _maybeSpace$disabledF !== void 0 ? _maybeSpace$disabledF : []); caseOwners = new Set(features.getKibanaFeatures() // get all the features' cases owners that aren't disabled .filter(({ id }) => !disabledFeatures.has(id)).flatMap(feature => { var _feature$cases; return (_feature$cases = feature.cases) !== null && _feature$cases !== void 0 ? _feature$cases : []; })); } catch (error) { throw (0, _error.createCaseError)({ message: `Failed to create Authorization class: ${error}`, error, logger }); } return new Authorization({ request, securityAuth, caseOwners, auditLogger }); } shouldCheckAuthorization() { var _this$securityAuth$mo, _this$securityAuth, _this$securityAuth$mo2; return (_this$securityAuth$mo = (_this$securityAuth = this.securityAuth) === null || _this$securityAuth === void 0 ? void 0 : (_this$securityAuth$mo2 = _this$securityAuth.mode) === null || _this$securityAuth$mo2 === void 0 ? void 0 : _this$securityAuth$mo2.useRbacForRequest(this.request)) !== null && _this$securityAuth$mo !== void 0 ? _this$securityAuth$mo : false; } /** * Checks that the user making the request for the passed in owner, saved object, and operation has the correct authorization. This * function will throw if the user is not authorized for the requested operation and owners. * * @param entities an array of entities describing the case owners in conjunction with the saved object ID attempting * to be authorized * @param operation information describing the operation attempting to be authorized */ async ensureAuthorized({ entities, operation }) { try { const uniqueOwners = Array.from(new Set(entities.map(entity => entity.owner))); await this._ensureAuthorized(uniqueOwners, operation); } catch (error) { this.logSavedObjects({ entities, operation, error }); throw error; } this.logSavedObjects({ entities, operation }); } /** * * Returns all authorized entities for an operation. It throws error if the user is not authorized * to any of the owners * * @param savedObjects an array of saved objects to be authorized. Each saved objects should contain * an ID and an owner * @param operation the operation that should be authorized */ async getAndEnsureAuthorizedEntities({ savedObjects, operation }) { const entities = savedObjects.map(so => ({ id: so.id, owner: so.attributes.owner })); const { authorizedOwners } = await this.getAuthorizedOwners([operation]); if (!authorizedOwners.length) { const error = _boom.default.forbidden(_.AuthorizationAuditLogger.createFailureMessage({ owners: [], operation })); this.logSavedObjects({ entities, error, operation }); throw error; } const { authorized, unauthorized } = (0, _utils.groupByAuthorization)(savedObjects, authorizedOwners); await this.ensureAuthorized({ operation, entities: authorized.map(so => ({ owner: so.attributes.owner, id: so.id })) }); return { authorized, unauthorized }; } async logSavedObjects({ entities, operation, error }) { for (const entity of entities) { this.auditLogger.log({ operation, error, entity }); } } /** * Returns an object to filter the saved object find request to the authorized owners of an entity. */ async getAuthorizationFilter(operation) { try { return await this._getAuthorizationFilter(operation); } catch (error) { this.auditLogger.log({ error, operation }); throw error; } } async _ensureAuthorized(owners, operation) { const { securityAuth } = this; const areAllOwnersAvailable = owners.every(owner => this.featureCaseOwners.has(owner)); if (securityAuth && this.shouldCheckAuthorization()) { const requiredPrivileges = owners.map(owner => securityAuth.actions.cases.get(owner, operation.name)); const checkPrivileges = securityAuth.checkPrivilegesDynamicallyWithRequest(this.request); const { hasAllRequested } = await checkPrivileges({ kibana: requiredPrivileges }); if (!areAllOwnersAvailable) { /** * Under most circumstances this would have been caught by `checkPrivileges` as * a user can't have Privileges to an unknown owner, but super users * don't actually get "privilege checked" so the made up owner *will* return * as Privileged. * This check will ensure we don't accidentally let these through */ throw _boom.default.forbidden(_.AuthorizationAuditLogger.createFailureMessage({ owners, operation })); } if (!hasAllRequested) { throw _boom.default.forbidden(_.AuthorizationAuditLogger.createFailureMessage({ owners, operation })); } } else if (!areAllOwnersAvailable) { throw _boom.default.forbidden(_.AuthorizationAuditLogger.createFailureMessage({ owners, operation })); } // else security is disabled so let the operation proceed } async _getAuthorizationFilter(operation) { const { securityAuth } = this; if (securityAuth && this.shouldCheckAuthorization()) { const { authorizedOwners } = await this.getAuthorizedOwners([operation]); if (!authorizedOwners.length) { throw _boom.default.forbidden(_.AuthorizationAuditLogger.createFailureMessage({ owners: authorizedOwners, operation })); } return { filter: (0, _utils.getOwnersFilter)(operation.savedObjectType, authorizedOwners), ensureSavedObjectsAreAuthorized: entities => { for (const entity of entities) { if (!authorizedOwners.includes(entity.owner)) { const error = _boom.default.forbidden(_.AuthorizationAuditLogger.createFailureMessage({ operation, owners: [entity.owner] })); this.auditLogger.log({ error, operation, entity }); throw error; } this.auditLogger.log({ operation, entity }); } } }; } return { ensureSavedObjectsAreAuthorized: entities => {} }; } async getAuthorizedOwners(operations) { const { securityAuth, featureCaseOwners } = this; if (securityAuth && this.shouldCheckAuthorization()) { const checkPrivileges = securityAuth.checkPrivilegesDynamicallyWithRequest(this.request); const requiredPrivileges = new Map(); for (const owner of featureCaseOwners) { for (const operation of operations) { requiredPrivileges.set(securityAuth.actions.cases.get(owner, operation.name), owner); } } const { hasAllRequested, username, privileges } = await checkPrivileges({ kibana: [...requiredPrivileges.keys()] }); return { hasAllRequested, username, authorizedOwners: hasAllRequested ? Array.from(featureCaseOwners) : privileges.kibana.reduce((authorizedOwners, { authorized, privilege }) => { if (authorized && requiredPrivileges.has(privilege)) { const owner = requiredPrivileges.get(privilege); if (owner) { authorizedOwners.push(owner); } } return authorizedOwners; }, []) }; } else { return { hasAllRequested: true, authorizedOwners: Array.from(featureCaseOwners) }; } } } exports.Authorization = Authorization;