"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var FabricAccessControl_exports = {};
__export(FabricAccessControl_exports, {
  AccessDeniedError: () => AccessDeniedError,
  FabricAccessControl: () => FabricAccessControl
});
module.exports = __toCommonJS(FabricAccessControl_exports);
var import_Subject = require("#action/server/Subject.js");
var import_access_control = require("#clusters/access-control");
var import_general = require("#general");
var import_model = require("#model");
var import_types = require("#types");
/**
 * @license
 * Copyright 2022-2023 Project CHIP Authors
 * SPDX-License-Identifier: Apache-2.0
 */
const logger = import_general.Logger.get("FabricAccessControl");
const ImplicitDefaultPaseAclEntry = {
  fabricIndex: import_types.FabricIndex.NO_FABRIC,
  // not fabric-specific
  privilege: import_model.AccessLevel.Administer,
  authMode: import_access_control.AccessControl.AccessControlEntryAuthMode.Pase,
  subjects: [],
  targets: []
  // entire node
};
var AuthModeNone = /* @__PURE__ */ ((AuthModeNone2) => {
  AuthModeNone2[AuthModeNone2["None"] = 0] = "None";
  return AuthModeNone2;
})(AuthModeNone || {});
class AccessDeniedError extends import_types.StatusResponseError {
  constructor(message) {
    super(message ?? "Unauthorized", import_types.StatusCode.UnsupportedAccess);
  }
}
class FabricAccessControl {
  #fabricIndex;
  #aclList = [];
  #extensionEntryAccessCheck = () => true;
  constructor(fabric) {
    if (fabric === void 0) {
      this.#fabricIndex = import_types.FabricIndex.NO_FABRIC;
      this.#aclList.push(ImplicitDefaultPaseAclEntry);
    } else {
      this.#fabricIndex = fabric.fabricIndex;
    }
  }
  /**
   * Public method used to update the Access Control List on changes.
   */
  set aclList(aclList) {
    if (aclList.some(({ fabricIndex }) => fabricIndex !== this.#fabricIndex)) {
      throw new import_general.InternalError("ACL entries must match the fabric index of the manager");
    }
    this.#aclList = [...aclList];
    logger.info(
      "ACL List updated",
      this.#aclList.map((e) => import_general.Diagnostic.dict(e))
    );
  }
  set extensionEntryAccessCheck(func) {
    this.#extensionEntryAccessCheck = func;
  }
  /**
   * Implements the access control check for the given context, location and endpoint and is called by the
   * InteractionServer. The method returns the list of granted Access privileges for the given context, location and
   * endpoint.
   */
  accessLevelsFor(context, location, endpoint) {
    if (location.cluster === void 0) {
      logger.warn(`Access control check without cluster at ${location.path}, returning View access level`);
      return [import_model.AccessLevel.View];
    }
    if (endpoint === void 0) {
      logger.warn(`Access control check without endpoint at ${location.path}, returning View access level`);
      return [import_model.AccessLevel.View];
    }
    return this.#getGrantedPrivileges(context, endpoint, location.cluster);
  }
  /**
   * Subjects must match exactly, or both are CAT with matching CAT ID and acceptable CAT version
   */
  #subjectMatches(aclSubject, isdSubject) {
    if (BigInt(aclSubject) === BigInt(isdSubject)) {
      return true;
    }
    const aclNode = (0, import_types.NodeId)(aclSubject);
    const isdNode = (0, import_types.NodeId)(isdSubject);
    if (!import_types.NodeId.isCaseAuthenticatedTag(aclNode) || !import_types.NodeId.isCaseAuthenticatedTag(isdNode)) {
      return false;
    }
    const aclSubjectCat = import_types.NodeId.extractAsCaseAuthenticatedTag(aclNode);
    const isdSubjectCat = import_types.NodeId.extractAsCaseAuthenticatedTag(isdNode);
    return import_types.CaseAuthenticatedTag.getIdentifyValue(aclSubjectCat) === import_types.CaseAuthenticatedTag.getIdentifyValue(isdSubjectCat) && import_types.CaseAuthenticatedTag.getVersion(isdSubjectCat) >= import_types.CaseAuthenticatedTag.getVersion(aclSubjectCat);
  }
  /**
   * Add the new privilege to the granted privileges set and also add any privileges subsumed by the new privilege.
   */
  #addGrantedPrivilege(grantedPrivileges, privilege) {
    grantedPrivileges.add(privilege);
    switch (privilege) {
      /**
       * Formally removed in Spec in 1.4.2 but SDK and tests still implement it
       * Remove when https://github.com/project-chip/connectedhomeip/issues/41840 is solved
       */
      case import_model.AccessLevel.ProxyView:
        grantedPrivileges.add(import_model.AccessLevel.View);
        break;
      case import_model.AccessLevel.Operate:
        grantedPrivileges.add(import_model.AccessLevel.View);
        break;
      case import_model.AccessLevel.Manage:
        grantedPrivileges.add(import_model.AccessLevel.Operate);
        grantedPrivileges.add(import_model.AccessLevel.View);
        break;
      case import_model.AccessLevel.Administer:
        grantedPrivileges.add(import_model.AccessLevel.Manage);
        grantedPrivileges.add(import_model.AccessLevel.Operate);
        grantedPrivileges.add(import_model.AccessLevel.View);
        break;
    }
  }
  /**
   * Determines the granted privileges for the given session, endpoint, and cluster ID and returns them.
   */
  #getGrantedPrivileges(context, endpoint, clusterId) {
    const endpointId = endpoint.id;
    const subjectDesc = this.#getIsdFromMessage(context);
    const grantedPrivileges = /* @__PURE__ */ new Set();
    if (subjectDesc.authMode === import_access_control.AccessControl.AccessControlEntryAuthMode.Pase && subjectDesc.isCommissioning) {
      this.#addGrantedPrivilege(grantedPrivileges, import_model.AccessLevel.Administer);
    }
    for (const aclEntry of this.#aclList) {
      if (grantedPrivileges.has(import_model.AccessLevel.Administer)) {
        break;
      }
      if (aclEntry.fabricIndex === import_types.FabricIndex.NO_FABRIC || aclEntry.fabricIndex !== subjectDesc.fabricIndex) {
        continue;
      }
      if (aclEntry.authMode !== subjectDesc.authMode) {
        continue;
      }
      if (aclEntry.subjects === null || aclEntry.subjects.length === 0) {
        if (aclEntry.authMode !== import_access_control.AccessControl.AccessControlEntryAuthMode.Case && aclEntry.authMode !== import_access_control.AccessControl.AccessControlEntryAuthMode.Group) {
          throw new import_general.MatterFlowError("ACL error: only CASE and Group auth can have empty subjects");
        }
      } else {
        let matchedSubject = false;
        subjectLoop: for (const aclSubject of aclEntry.subjects) {
          for (const isdSubject of subjectDesc.subjects) {
            if (this.#subjectMatches(aclSubject, isdSubject)) {
              matchedSubject = true;
              break subjectLoop;
            }
          }
        }
        if (!matchedSubject) {
          continue;
        }
      }
      if (aclEntry.targets === null || aclEntry.targets.length === 0) {
      } else {
        let matchedTarget = false;
        for (const {
          cluster: targetClusterId,
          endpoint: targetEndpointId,
          deviceType: targetDeviceType
        } of aclEntry.targets) {
          if (targetClusterId === null && targetEndpointId === null && targetDeviceType === null) {
            throw new import_general.MatterFlowError("ACL error: target cannot be empty");
          }
          if (targetEndpointId !== null && targetDeviceType !== null) {
            throw new import_general.MatterFlowError("ACL error: target cannot specify both endpoint and device type");
          }
          if (targetClusterId !== null && targetClusterId !== clusterId) {
            continue;
          }
          if (targetEndpointId !== null && targetEndpointId !== endpointId) {
            continue;
          }
          if (targetDeviceType !== null && !endpoint.deviceTypes.includes(targetDeviceType)) {
            continue;
          }
          matchedTarget = true;
          break;
        }
        if (!matchedTarget) {
          continue;
        }
      }
      if (!this.#extensionEntryAccessCheck(this.#aclList, aclEntry, subjectDesc, endpoint, clusterId)) {
        continue;
      }
      this.#addGrantedPrivilege(grantedPrivileges, aclEntry.privilege);
    }
    if (subjectDesc.authMode === import_access_control.AccessControl.AccessControlEntryAuthMode.Group && grantedPrivileges.has(import_model.AccessLevel.Administer)) {
      throw new import_general.MatterFlowError("ACL error: should never grant Administer privilege to a Group");
    }
    return [...grantedPrivileges];
  }
  /**
   * Determines the Incoming Subject Descriptor (ISD) from the given session.
   */
  #getIsdFromMessage(session) {
    const fabricIndex = session.fabric;
    const isd = {
      isCommissioning: false,
      authMode: 0 /* None */,
      subjects: new Array(),
      fabricIndex: import_types.FabricIndex.NO_FABRIC
    };
    const { subject } = session;
    if (subject === void 0) {
      throw new import_general.MatterFlowError("ACL error: ACL checks require an authorized subject");
    }
    if (subject.id === import_types.NodeId.UNSPECIFIED_NODE_ID) {
      isd.authMode = import_access_control.AccessControl.AccessControlEntryAuthMode.Pase;
      isd.isCommissioning = true;
      isd.subjects.push(import_types.NodeId.UNSPECIFIED_NODE_ID);
      if (fabricIndex !== void 0 && fabricIndex !== import_types.FabricIndex.NO_FABRIC) {
        isd.fabricIndex = fabricIndex;
      }
    } else if (import_Subject.Subject.isGroup(subject)) {
      if (fabricIndex === void 0 || fabricIndex === import_types.FabricIndex.NO_FABRIC) {
        throw new import_general.MatterFlowError("ACL error: fabric needs to be associated for group sessions");
      }
      if (subject.hasValidMapping) {
        isd.authMode = import_access_control.AccessControl.AccessControlEntryAuthMode.Group;
        isd.subjects.push(subject.id);
        isd.fabricIndex = fabricIndex;
      }
    } else if (import_Subject.Subject.isNode(subject)) {
      if (fabricIndex === void 0 || fabricIndex === import_types.FabricIndex.NO_FABRIC) {
        throw new import_general.MatterFlowError("ACL error: Case session must be associated with a fabric");
      }
      isd.authMode = import_access_control.AccessControl.AccessControlEntryAuthMode.Case;
      isd.subjects.push(subject.id);
      if (subject.catSubjects !== void 0) {
        isd.subjects.push(...subject.catSubjects);
      }
      isd.fabricIndex = fabricIndex;
    }
    return isd;
  }
}
//# sourceMappingURL=FabricAccessControl.js.map
