'use strict';

var express = require('express');
var Router = require('express-promise-router');
var zod = require('zod');
var zodToJsonSchema = require('zod-to-json-schema');
var errors = require('@backstage/errors');
var pluginPermissionCommon = require('@backstage/plugin-permission-common');
var util = require('./util.cjs.js');

function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }

var express__default = /*#__PURE__*/_interopDefaultCompat(express);
var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
var zodToJsonSchema__default = /*#__PURE__*/_interopDefaultCompat(zodToJsonSchema);

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.union([zod.z.string(), zod.z.array(zod.z.string()).nonempty()]),
      resourceType: zod.z.string(),
      conditions: permissionCriteriaSchema
    })
  )
});
const applyConditions = (criteria, resource, getRule) => {
  if (resource === void 0) {
    return false;
  }
  if (util.isAndCriteria(criteria)) {
    return criteria.allOf.every(
      (child) => applyConditions(child, resource, getRule)
    );
  } else if (util.isOrCriteria(criteria)) {
    return criteria.anyOf.some(
      (child) => applyConditions(child, resource, getRule)
    );
  } else if (util.isNotCriteria(criteria)) {
    return !applyConditions(criteria.not, resource, getRule);
  }
  const rule = getRule(criteria.rule);
  const result = rule.paramsSchema?.safeParse(criteria.params);
  if (result && !result.success) {
    throw new errors.InputError(`Parameters to rule are invalid`, result.error);
  }
  return rule.apply(resource, criteria.params ?? {});
};
function authorizeResult(criteria, resource, getRule) {
  return applyConditions(criteria, resource, getRule) ? pluginPermissionCommon.AuthorizeResult.ALLOW : pluginPermissionCommon.AuthorizeResult.DENY;
}
function createConditionAuthorizer(rules) {
  const getRule = "getRuleByName" in rules ? (n) => rules.getRuleByName(n) : util.createGetRule(rules);
  return (decision, resource) => {
    if (decision.result === pluginPermissionCommon.AuthorizeResult.CONDITIONAL) {
      return applyConditions(decision.conditions, resource, getRule);
    }
    return decision.result === pluginPermissionCommon.AuthorizeResult.ALLOW;
  };
}
class PermissionIntegrationMetadataStore {
  #rulesByTypeByName = /* @__PURE__ */ new Map();
  #permissionsByName = /* @__PURE__ */ new Map();
  #resourcesByType = /* @__PURE__ */ new Map();
  #serializedRules = new Array();
  getSerializedMetadata() {
    return {
      permissions: Array.from(this.#permissionsByName.values()),
      rules: this.#serializedRules
    };
  }
  hasResourceType(type) {
    return this.#resourcesByType.has(type);
  }
  async getResources(resourceType, refs) {
    const resource = this.#resourcesByType.get(resourceType);
    if (!resource?.getResources) {
      throw new errors.NotImplementedError(
        `This plugin does not expose any permission rule or can't evaluate the conditions request for ${resourceType}`
      );
    }
    const uniqueRefs = Array.from(new Set(refs));
    const resources = await resource.getResources(uniqueRefs);
    return Object.fromEntries(
      uniqueRefs.map((ref, index) => [ref, resources[index]])
    );
  }
  getRuleMapper(resourceType) {
    return (name) => {
      const rule = this.#rulesByTypeByName.get(resourceType)?.get(name);
      if (!rule) {
        throw new Error(
          `Permission rule '${name}' does not exist for resource type '${resourceType}'`
        );
      }
      return rule;
    };
  }
  addPermissions(permissions) {
    for (const permission of permissions) {
      this.#permissionsByName.set(permission.name, permission);
    }
  }
  addPermissionRules(rules) {
    for (const rule of rules) {
      const rulesByName = this.#rulesByTypeByName.get(rule.resourceType) ?? /* @__PURE__ */ new Map();
      this.#rulesByTypeByName.set(rule.resourceType, rulesByName);
      if (rulesByName.has(rule.name)) {
        throw new Error(
          `Refused to add permission rule for type '${rule.resourceType}' with name '${rule.name}' because it already exists`
        );
      }
      rulesByName.set(rule.name, rule);
      this.#serializedRules.push({
        name: rule.name,
        description: rule.description,
        resourceType: rule.resourceType,
        paramsSchema: zodToJsonSchema__default.default(rule.paramsSchema ?? zod.z.object({}))
      });
    }
  }
  addResourceType(resource) {
    const { resourceType } = resource;
    if (this.#resourcesByType.has(resourceType)) {
      throw new Error(
        `Refused to add permission resource with type '${resourceType}' because it already exists`
      );
    }
    this.#resourcesByType.set(resourceType, resource);
    if (resource.rules) {
      this.addPermissionRules(resource.rules);
    }
    if (resource.permissions) {
      this.addPermissions(resource.permissions);
    }
  }
}
function createPermissionIntegrationRouter(options) {
  const store = new PermissionIntegrationMetadataStore();
  if (options) {
    if ("resources" in options) {
      if ("permissions" in options) {
        store.addPermissions(options.permissions);
      }
      for (const resource of options.resources) {
        store.addResourceType(resource);
      }
    } else if ("resourceType" in options) {
      store.addResourceType(options);
    } else {
      store.addPermissions(options.permissions);
    }
  }
  const router = Router__default.default();
  router.use("/.well-known/backstage/permissions/", express__default.default.json());
  router.get("/.well-known/backstage/permissions/metadata", (_, res) => {
    res.json(store.getSerializedMetadata());
  });
  router.post(
    "/.well-known/backstage/permissions/apply-conditions",
    async (req, res) => {
      const parseResult = applyConditionsRequestSchema.safeParse(req.body);
      if (!parseResult.success) {
        throw new errors.InputError(parseResult.error.toString());
      }
      const { items: requests } = parseResult.data;
      const invalidResourceTypes = requests.filter(
        (i) => !store.hasResourceType(i.resourceType)
      );
      if (invalidResourceTypes.length) {
        throw new errors.InputError(
          `Unexpected resource types: ${invalidResourceTypes.map((i) => i.resourceType).join(", ")}.`
        );
      }
      const resourcesByType = {};
      for (const requestedType of new Set(requests.map((i) => i.resourceType))) {
        resourcesByType[requestedType] = await store.getResources(
          requestedType,
          requests.filter((r) => r.resourceType === requestedType).map((i) => i.resourceRef).flat()
        );
      }
      res.json({
        items: requests.map((request) => ({
          id: request.id,
          result: Array.isArray(request.resourceRef) ? request.resourceRef.map(
            (resourceRef) => authorizeResult(
              request.conditions,
              resourcesByType[request.resourceType][resourceRef],
              store.getRuleMapper(request.resourceType)
            )
          ) : authorizeResult(
            request.conditions,
            resourcesByType[request.resourceType][request.resourceRef],
            store.getRuleMapper(request.resourceType)
          )
        }))
      });
    }
  );
  return Object.assign(router, {
    addPermissions(permissions) {
      store.addPermissions(permissions);
    },
    addPermissionRules(rules) {
      store.addPermissionRules(rules);
    },
    addResourceType(resource) {
      store.addResourceType(resource);
    },
    getPermissionRuleset(resourceRef) {
      return {
        getRuleByName: store.getRuleMapper(resourceRef.resourceType)
      };
    }
  });
}

exports.createConditionAuthorizer = createConditionAuthorizer;
exports.createPermissionIntegrationRouter = createPermissionIntegrationRouter;
//# sourceMappingURL=createPermissionIntegrationRouter.cjs.js.map
