'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
