'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var pluginPermissionCommon = require('@backstage/plugin-permission-common'); var errors = require('@backstage/errors'); var express = require('express'); var Router = require('express-promise-router'); var zod = require('zod'); var zodToJsonSchema = require('zod-to-json-schema'); var backendCommon = require('@backstage/backend-common'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var express__default = /*#__PURE__*/_interopDefaultLegacy(express); var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router); var zodToJsonSchema__default = /*#__PURE__*/_interopDefaultLegacy(zodToJsonSchema); const createConditionFactory = (rule) => { return (params) => { return { rule: rule.name, resourceType: rule.resourceType, params }; }; }; const createConditionExports = (options) => { const { pluginId, resourceType, rules } = options; return { conditions: Object.entries(rules).reduce( (acc, [key, rule]) => ({ ...acc, [key]: createConditionFactory(rule) }), {} ), createConditionalDecision: (_permission, conditions) => ({ result: pluginPermissionCommon.AuthorizeResult.CONDITIONAL, pluginId, resourceType, conditions }) }; }; const isAndCriteria = (criteria) => Object.prototype.hasOwnProperty.call(criteria, "allOf"); const isOrCriteria = (criteria) => Object.prototype.hasOwnProperty.call(criteria, "anyOf"); const isNotCriteria = (criteria) => Object.prototype.hasOwnProperty.call(criteria, "not"); const createGetRule = (rules) => { const rulesMap = new Map(Object.values(rules).map((rule) => [rule.name, rule])); return (name) => { const rule = rulesMap.get(name); if (!rule) { throw new Error(`Unexpected permission rule: ${name}`); } return rule; }; }; const mapConditions = (criteria, getRule) => { var _a, _b; if (isAndCriteria(criteria)) { return { allOf: criteria.allOf.map((child) => mapConditions(child, getRule)) }; } else if (isOrCriteria(criteria)) { return { anyOf: criteria.anyOf.map((child) => mapConditions(child, getRule)) }; } else if (isNotCriteria(criteria)) { return { not: mapConditions(criteria.not, getRule) }; } const rule = getRule(criteria.rule); const result = (_a = rule.paramsSchema) == null ? void 0 : _a.safeParse(criteria.params); if (result && !result.success) { throw new errors.InputError(`Parameters to rule are invalid`, result.error); } return rule.toQuery((_b = criteria.params) != null ? _b : {}); }; const createConditionTransformer = (permissionRules) => { const getRule = createGetRule(permissionRules); return (conditions) => mapConditions(conditions, getRule); }; const permissionCriteriaSchema = zod.z.lazy( () => zod.z.union([ zod.z.object({ anyOf: zod.z.array(permissionCriteriaSchema).nonempty() }), zod.z.object({ allOf: zod.z.array(permissionCriteriaSchema).nonempty() }), zod.z.object({ not: permissionCriteriaSchema }), zod.z.object({ rule: zod.z.string(), resourceType: zod.z.string(), params: zod.z.record(zod.z.any()).optional() }) ]) ); const applyConditionsRequestSchema = zod.z.object({ items: zod.z.array( zod.z.object({ id: zod.z.string(), resourceRef: zod.z.string(), resourceType: zod.z.string(), conditions: permissionCriteriaSchema }) ) }); const applyConditions = (criteria, resource, getRule) => { var _a, _b; if (resource === void 0) { return false; } if (isAndCriteria(criteria)) { return criteria.allOf.every( (child) => applyConditions(child, resource, getRule) ); } else if (isOrCriteria(criteria)) { return criteria.anyOf.some( (child) => applyConditions(child, resource, getRule) ); } else if (isNotCriteria(criteria)) { return !applyConditions(criteria.not, resource, getRule); } const rule = getRule(criteria.rule); const result = (_a = rule.paramsSchema) == null ? void 0 : _a.safeParse(criteria.params); if (result && !result.success) { throw new errors.InputError(`Parameters to rule are invalid`, result.error); } return rule.apply(resource, (_b = criteria.params) != null ? _b : {}); }; const createConditionAuthorizer = (rules) => { const getRule = createGetRule(rules); return (decision, resource) => { if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) { return applyConditions(decision.conditions, resource, getRule); } return decision.result === pluginPermissionCommon.AuthorizeResult.ALLOW; }; }; function createPermissionIntegrationRouter(options) { var _a; const optionsWithResources = options; const allOptions = [ optionsWithResources.resources ? optionsWithResources.resources : options ].flat(); const allRules = allOptions.flatMap( (option) => option.rules || [] ); const allPermissions = [ ...options.permissions || [], ...((_a = optionsWithResources.resources) == null ? void 0 : _a.flatMap((o) => o.permissions || [])) || [] ]; const allResourceTypes = allOptions.reduce((acc, option) => { if (isCreatePermissionIntegrationRouterResourceOptions( option )) { acc.push( option.resourceType ); } return acc; }, []); const router = Router__default["default"](); router.use(express__default["default"].json()); router.get("/.well-known/backstage/permissions/metadata", (_, res) => { const serializedRules = allRules.map( (rule) => { var _a2; return { name: rule.name, description: rule.description, resourceType: rule.resourceType, paramsSchema: zodToJsonSchema__default["default"]((_a2 = rule.paramsSchema) != null ? _a2 : zod.z.object({})) }; } ); const responseJson = { permissions: allPermissions, rules: serializedRules }; return res.json(responseJson); }); router.post( "/.well-known/backstage/permissions/apply-conditions", async (req, res) => { const ruleMapByResourceType = {}; const getResourcesByResourceType = {}; for (let option of allOptions) { option = option; if (isCreatePermissionIntegrationRouterResourceOptions(option)) { ruleMapByResourceType[option.resourceType] = createGetRule( option.rules ); getResourcesByResourceType[option.resourceType] = option.getResources; } } const assertValidResourceTypes = (requests) => { const invalidResourceTypes = requests.filter((request) => !allResourceTypes.includes(request.resourceType)).map((request) => request.resourceType); if (invalidResourceTypes.length) { throw new errors.InputError( `Unexpected resource types: ${invalidResourceTypes.join(", ")}.` ); } }; const parseResult = applyConditionsRequestSchema.safeParse(req.body); if (!parseResult.success) { throw new errors.InputError(parseResult.error.toString()); } const body = parseResult.data; assertValidResourceTypes(body.items); const resourceRefsByResourceType = body.items.reduce((acc, item) => { if (!acc[item.resourceType]) { acc[item.resourceType] = /* @__PURE__ */ new Set(); } acc[item.resourceType].add(item.resourceRef); return acc; }, {}); const resourcesByResourceType = {}; for (const resourceType of Object.keys(resourceRefsByResourceType)) { const getResources = getResourcesByResourceType[resourceType]; if (!getResources) { throw new errors.NotImplementedError( `This plugin does not expose any permission rule or can't evaluate the conditions request for ${resourceType}` ); } const resourceRefs = Array.from( resourceRefsByResourceType[resourceType] ); const resources = await getResources(resourceRefs); resourceRefs.forEach((resourceRef, index) => { if (!resourcesByResourceType[resourceType]) { resourcesByResourceType[resourceType] = {}; } resourcesByResourceType[resourceType][resourceRef] = resources[index]; }); } return res.json({ items: body.items.map((request) => ({ id: request.id, result: applyConditions( request.conditions, resourcesByResourceType[request.resourceType][request.resourceRef], ruleMapByResourceType[request.resourceType] ) ? pluginPermissionCommon.AuthorizeResult.ALLOW : pluginPermissionCommon.AuthorizeResult.DENY })) }); } ); router.use(backendCommon.errorHandler()); return router; } function isCreatePermissionIntegrationRouterResourceOptions(options) { return options.resourceType !== void 0; } const createPermissionRule = (rule) => rule; const makeCreatePermissionRule = () => (rule) => createPermissionRule(rule); var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class ServerPermissionClient { constructor(options) { __publicField(this, "permissionClient"); __publicField(this, "tokenManager"); __publicField(this, "permissionEnabled"); this.permissionClient = options.permissionClient; this.tokenManager = options.tokenManager; this.permissionEnabled = options.permissionEnabled; } static fromConfig(config, options) { var _a; const { discovery, tokenManager } = options; const permissionClient = new pluginPermissionCommon.PermissionClient({ discovery, config }); const permissionEnabled = (_a = config.getOptionalBoolean("permission.enabled")) != null ? _a : false; if (permissionEnabled && tokenManager.isInsecureServerTokenManager) { throw new Error( "Service-to-service authentication must be configured before enabling permissions. Read more here https://backstage.io/docs/auth/service-to-service-auth" ); } return new ServerPermissionClient({ permissionClient, tokenManager, permissionEnabled }); } async authorizeConditional(queries, options) { return await this.isEnabled(options == null ? void 0 : options.token) ? this.permissionClient.authorizeConditional(queries, options) : queries.map((_) => ({ result: pluginPermissionCommon.AuthorizeResult.ALLOW })); } async authorize(requests, options) { return await this.isEnabled(options == null ? void 0 : options.token) ? this.permissionClient.authorize(requests, options) : requests.map((_) => ({ result: pluginPermissionCommon.AuthorizeResult.ALLOW })); } async isValidServerToken(token) { if (!token) { return false; } return this.tokenManager.authenticate(token).then(() => true).catch(() => false); } async isEnabled(token) { return this.permissionEnabled && !await this.isValidServerToken(token); } } exports.ServerPermissionClient = ServerPermissionClient; exports.createConditionAuthorizer = createConditionAuthorizer; exports.createConditionExports = createConditionExports; exports.createConditionFactory = createConditionFactory; exports.createConditionTransformer = createConditionTransformer; exports.createPermissionIntegrationRouter = createPermissionIntegrationRouter; exports.createPermissionRule = createPermissionRule; exports.isAndCriteria = isAndCriteria; exports.isNotCriteria = isNotCriteria; exports.isOrCriteria = isOrCriteria; exports.makeCreatePermissionRule = makeCreatePermissionRule; //# sourceMappingURL=index.cjs.js.map