'use strict';

var errors = require('@backstage/errors');
var fetch = require('cross-fetch');
var uuid = require('uuid');
var zod = require('zod');
var api = require('./types/api.cjs.js');
var util = require('./permissions/util.cjs.js');

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

function _interopNamespaceCompat(e) {
  if (e && typeof e === 'object' && 'default' in e) return e;
  var n = Object.create(null);
  if (e) {
    Object.keys(e).forEach(function (k) {
      if (k !== 'default') {
        var d = Object.getOwnPropertyDescriptor(e, k);
        Object.defineProperty(n, k, d.get ? d : {
          enumerable: true,
          get: function () { return e[k]; }
        });
      }
    });
  }
  n.default = e;
  return Object.freeze(n);
}

var fetch__default = /*#__PURE__*/_interopDefaultCompat(fetch);
var uuid__namespace = /*#__PURE__*/_interopNamespaceCompat(uuid);

const permissionCriteriaSchema = zod.z.lazy(
  () => zod.z.object({
    rule: zod.z.string(),
    resourceType: zod.z.string(),
    params: zod.z.record(zod.z.any()).optional()
  }).or(zod.z.object({ anyOf: zod.z.array(permissionCriteriaSchema).nonempty() })).or(zod.z.object({ allOf: zod.z.array(permissionCriteriaSchema).nonempty() })).or(zod.z.object({ not: permissionCriteriaSchema }))
);
const authorizePermissionResponseSchema = zod.z.object({
  result: zod.z.literal(api.AuthorizeResult.ALLOW).or(zod.z.literal(api.AuthorizeResult.DENY))
});
const authorizePermissionResponseBatchSchema = zod.z.object({
  result: zod.z.array(
    zod.z.union([
      zod.z.literal(api.AuthorizeResult.ALLOW),
      zod.z.literal(api.AuthorizeResult.DENY)
    ])
  )
}).or(authorizePermissionResponseSchema);
const queryPermissionResponseSchema = zod.z.union([
  zod.z.object({
    result: zod.z.literal(api.AuthorizeResult.ALLOW).or(zod.z.literal(api.AuthorizeResult.DENY))
  }),
  zod.z.object({
    result: zod.z.literal(api.AuthorizeResult.CONDITIONAL),
    pluginId: zod.z.string(),
    resourceType: zod.z.string(),
    conditions: permissionCriteriaSchema
  })
]);
const responseSchema = (itemSchema, ids) => zod.z.object({
  items: zod.z.array(
    zod.z.intersection(
      zod.z.object({
        id: zod.z.string()
      }),
      itemSchema
    )
  ).refine(
    (items) => items.length === ids.size && items.every(({ id }) => ids.has(id)),
    {
      message: "Items in response do not match request"
    }
  )
});
class PermissionClient {
  enabled;
  discovery;
  enableBatchedRequests;
  constructor(options) {
    this.discovery = options.discovery;
    this.enabled = options.config.getOptionalBoolean("permission.enabled") ?? false;
    this.enableBatchedRequests = options.config.getOptionalBoolean(
      "permission.EXPERIMENTAL_enableBatchedRequests"
    ) ?? false;
  }
  /**
   * {@inheritdoc PermissionEvaluator.authorize}
   */
  async authorize(requests, options) {
    if (!this.enabled) {
      return requests.map((_) => ({ result: api.AuthorizeResult.ALLOW }));
    }
    if (this.enableBatchedRequests) {
      return this.makeBatchedRequest(requests, options);
    }
    return this.makeRequest(
      requests,
      authorizePermissionResponseSchema,
      options
    );
  }
  /**
   * {@inheritdoc PermissionEvaluator.authorizeConditional}
   */
  async authorizeConditional(queries, options) {
    if (!this.enabled) {
      return queries.map((_) => ({ result: api.AuthorizeResult.ALLOW }));
    }
    return this.makeRequest(queries, queryPermissionResponseSchema, options);
  }
  async makeRequest(queries, itemSchema, options) {
    const request = {
      items: queries.map((query) => ({
        id: uuid__namespace.v4(),
        ...query
      }))
    };
    const parsedResponse = await this.makeRawRequest(
      request,
      itemSchema,
      options
    );
    const responsesById = parsedResponse.items.reduce((acc, r) => {
      acc[r.id] = r;
      return acc;
    }, {});
    return request.items.map((query) => responsesById[query.id]);
  }
  async makeBatchedRequest(queries, options) {
    const request = {};
    for (const query of queries) {
      const { permission, resourceRef } = query;
      if (util.isResourcePermission(permission)) {
        request[permission.name] ||= {
          permission,
          resourceRef: [],
          id: uuid__namespace.v4()
        };
        if (resourceRef) {
          request[permission.name].resourceRef?.push(resourceRef);
        }
      } else {
        request[permission.name] ||= {
          permission,
          id: uuid__namespace.v4()
        };
      }
    }
    const parsedResponse = await this.makeRawRequest(
      { items: Object.values(request) },
      authorizePermissionResponseBatchSchema,
      options
    );
    const responsesById = parsedResponse.items.reduce((acc, r) => {
      acc[r.id] = r;
      return acc;
    }, {});
    return queries.map((query) => {
      const { id } = request[query.permission.name];
      const item = responsesById[id];
      if (Array.isArray(item.result)) {
        return {
          result: query.resourceRef ? item.result.shift() : item.result[0]
        };
      }
      return { result: item.result };
    });
  }
  async makeRawRequest(request, itemSchema, options) {
    const permissionApi = await this.discovery.getBaseUrl("permission");
    const response = await fetch__default.default(`${permissionApi}/authorize`, {
      method: "POST",
      body: JSON.stringify(request),
      headers: {
        ...this.getAuthorizationHeader(options?.token),
        "content-type": "application/json"
      }
    });
    if (!response.ok) {
      throw await errors.ResponseError.fromResponse(response);
    }
    const responseBody = await response.json();
    return responseSchema(
      itemSchema,
      new Set(request.items.map(({ id }) => id))
    ).parse(responseBody);
  }
  getAuthorizationHeader(token) {
    return token ? { Authorization: `Bearer ${token}` } : {};
  }
}

exports.PermissionClient = PermissionClient;
//# sourceMappingURL=PermissionClient.cjs.js.map
