{"version":3,"file":"mapper.mjs","sources":["../../../../../src/lib/auth/active-directory/core/mapper.ts"],"sourcesContent":["/**\n * Active Directory Group Mapping - Core Mapper\n *\n * Maps AD groups to application roles and permissions using configurable\n * mapping rules. Supports pattern matching, hierarchical roles, and\n * dynamic conditions.\n *\n * @module auth/active-directory/core\n */\n\nimport type { ADGroup, ADUser, ADGroupRoleMapping, ADGroupMappingConfig } from '../types';\nimport type { Role, Permission } from '../../types';\nimport { DEFAULT_GROUP_MAPPING_CONFIG } from '../ad-config';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Result of group mapping evaluation.\n */\nexport interface GroupMappingResult {\n  /** Resolved role (highest priority) */\n  role: Role | undefined;\n  /** Combined permissions from all matched mappings */\n  permissions: Permission[];\n  /** Groups that matched mappings */\n  matchedGroups: ADGroup[];\n  /** Mappings that were applied */\n  appliedMappings: ADGroupRoleMapping[];\n}\n\n// =============================================================================\n// Group Mapping Engine\n// =============================================================================\n\n/**\n * AD Group Mapper for role and permission resolution.\n *\n * Evaluates user group memberships against configured mapping rules\n * to determine appropriate application roles and permissions.\n *\n * @example\n * ```typescript\n * const mapper = new ADGroupMapper({\n *   mappings: [\n *     {\n *       groupIdentifier: 'sg-app-admins',\n *       matchType: 'exact',\n *       role: 'admin',\n *       additionalPermissions: ['manage:users', 'manage:settings'],\n *       priority: 100,\n *     },\n *     {\n *       groupIdentifier: 'sg-app-*',\n *       matchType: 'pattern',\n *       role: 'user',\n *       priority: 10,\n *     },\n *   ],\n *   defaultRole: 'guest',\n *   defaultPermissions: ['read:public'],\n * });\n *\n * const { role, permissions } = mapper.mapUserGroups(user.adGroups);\n * ```\n */\nexport class ADGroupMapper {\n  private readonly config: ADGroupMappingConfig;\n  private cache: Map<string, GroupMappingResult>;\n  private cacheExpiry: number;\n\n  /**\n   * Create a new group mapper.\n   *\n   * @param config - Group mapping configuration\n   */\n  constructor(config: Partial<ADGroupMappingConfig> = {}) {\n    this.config = {\n      ...DEFAULT_GROUP_MAPPING_CONFIG,\n      ...config,\n      mappings: config.mappings ?? DEFAULT_GROUP_MAPPING_CONFIG.mappings,\n    };\n    this.cache = new Map();\n    this.cacheExpiry = 0;\n  }\n\n  /**\n   * Map user's AD groups to application roles and permissions.\n   *\n   * @param groups - User's AD group memberships\n   * @param user - Optional user for condition evaluation\n   * @returns Mapped role and permissions\n   */\n  mapUserGroups(groups: ADGroup[], user?: ADUser): GroupMappingResult {\n    const cacheKey = this.getCacheKey(groups);\n\n    // Check cache\n    if (\n      this.config.cacheDuration != null &&\n      this.config.cacheDuration > 0 &&\n      this.cache.has(cacheKey)\n    ) {\n      if (Date.now() < this.cacheExpiry) {\n        const cachedResult = this.cache.get(cacheKey);\n        if (cachedResult != null) {\n          return cachedResult;\n        }\n      }\n    }\n\n    const result = this.evaluateMappings(groups, user);\n\n    // Update cache\n    if (this.config.cacheDuration != null && this.config.cacheDuration > 0) {\n      this.cache.set(cacheKey, result);\n      this.cacheExpiry = Date.now() + this.config.cacheDuration;\n    }\n\n    return result;\n  }\n\n  /**\n   * Get the highest-priority role for a user based on their groups.\n   *\n   * @param groups - User's AD group memberships\n   * @param user - Optional user for condition evaluation\n   * @returns Mapped role (or default role if none matched)\n   */\n  getRole(groups: ADGroup[], user?: ADUser): Role {\n    const result = this.mapUserGroups(groups, user);\n    return result.role ?? this.config.defaultRole;\n  }\n\n  /**\n   * Get all permissions for a user based on their groups.\n   *\n   * @param groups - User's AD group memberships\n   * @param user - Optional user for condition evaluation\n   * @returns Array of permissions\n   */\n  getPermissions(groups: ADGroup[], user?: ADUser): Permission[] {\n    const result = this.mapUserGroups(groups, user);\n    return result.permissions;\n  }\n\n  /**\n   * Check if any of the user's groups match a specific mapping.\n   *\n   * @param groups - User's AD group memberships\n   * @param groupIdentifier - Group identifier to check\n   * @param matchType - How to match the identifier\n   * @returns Whether any group matches\n   */\n  hasMatchingGroup(\n    groups: ADGroup[],\n    groupIdentifier: string,\n    matchType: ADGroupRoleMapping['matchType'] = 'exact'\n  ): boolean {\n    return groups.some((group) => this.matchesGroup(group, groupIdentifier, matchType));\n  }\n\n  /**\n   * Get all matched mappings for a set of groups.\n   *\n   * @param groups - User's AD group memberships\n   * @param user - Optional user for condition evaluation\n   * @returns Array of matched mappings with their matched groups\n   */\n  getMatchedMappings(\n    groups: ADGroup[],\n    user?: ADUser\n  ): Array<{ mapping: ADGroupRoleMapping; matchedGroups: ADGroup[] }> {\n    const matches: Array<{ mapping: ADGroupRoleMapping; matchedGroups: ADGroup[] }> = [];\n\n    for (const mapping of this.config.mappings) {\n      if (mapping.enabled === false) continue;\n\n      const matchedGroups = groups.filter((group) => {\n        const matches = this.matchesGroup(group, mapping.groupIdentifier, mapping.matchType);\n\n        if (!matches) return false;\n\n        // Evaluate condition if present\n        if (mapping.condition && user) {\n          return mapping.condition(user, group);\n        }\n\n        return true;\n      });\n\n      if (matchedGroups.length > 0) {\n        matches.push({ mapping, matchedGroups });\n      }\n    }\n\n    return matches.sort((a, b) => (b.mapping.priority ?? 0) - (a.mapping.priority ?? 0));\n  }\n\n  /**\n   * Add a new mapping at runtime.\n   *\n   * @param mapping - Mapping to add\n   */\n  addMapping(mapping: ADGroupRoleMapping): void {\n    this.config.mappings.push({\n      ...mapping,\n      enabled: mapping.enabled ?? true,\n      priority: mapping.priority ?? 0,\n    });\n    this.clearCache();\n  }\n\n  /**\n   * Remove a mapping by group identifier.\n   *\n   * @param groupIdentifier - Identifier of the mapping to remove\n   */\n  removeMapping(groupIdentifier: string): void {\n    this.config.mappings = this.config.mappings.filter(\n      (m) => m.groupIdentifier !== groupIdentifier\n    );\n    this.clearCache();\n  }\n\n  /**\n   * Update an existing mapping.\n   *\n   * @param groupIdentifier - Identifier of the mapping to update\n   * @param updates - Partial updates to apply\n   */\n  updateMapping(groupIdentifier: string, updates: Partial<ADGroupRoleMapping>): void {\n    const index = this.config.mappings.findIndex((m) => m.groupIdentifier === groupIdentifier);\n\n    if (index >= 0) {\n      const existing = this.config.mappings[index];\n      if (!existing) {\n        return;\n      }\n      this.config.mappings[index] = {\n        ...existing,\n        ...updates,\n        groupIdentifier: updates.groupIdentifier ?? existing.groupIdentifier,\n        matchType: updates.matchType ?? existing.matchType,\n        role: updates.role ?? existing.role,\n      };\n      this.clearCache();\n    }\n  }\n\n  /**\n   * Clear the mapping cache.\n   */\n  clearCache(): void {\n    this.cache.clear();\n    this.cacheExpiry = 0;\n  }\n\n  /**\n   * Get the current configuration.\n   */\n  getConfig(): ADGroupMappingConfig {\n    return { ...this.config };\n  }\n\n  // ===========================================================================\n  // Private Methods\n  // ===========================================================================\n\n  /**\n   * Evaluate all mappings against user groups.\n   */\n  private evaluateMappings(groups: ADGroup[], user?: ADUser): GroupMappingResult {\n    const matchedMappings = this.getMatchedMappings(groups, user);\n\n    if (matchedMappings.length === 0) {\n      return {\n        role: this.config.defaultRole,\n        permissions: [...this.config.defaultPermissions],\n        matchedGroups: [],\n        appliedMappings: [],\n      };\n    }\n\n    // Get highest priority role\n    const [highestPriorityMapping] = matchedMappings;\n    if (!highestPriorityMapping) {\n      return {\n        role: this.config.defaultRole,\n        permissions: [...this.config.defaultPermissions],\n        matchedGroups: [],\n        appliedMappings: [],\n      };\n    }\n    const { role } = highestPriorityMapping.mapping;\n\n    // Collect all permissions from all matched mappings\n    const permissionSet = new Set<Permission>();\n    this.config.defaultPermissions.forEach((p) => permissionSet.add(p));\n\n    const appliedMappings: ADGroupRoleMapping[] = [];\n    const allMatchedGroups: ADGroup[] = [];\n\n    for (const { mapping, matchedGroups } of matchedMappings) {\n      appliedMappings.push(mapping);\n      allMatchedGroups.push(...matchedGroups);\n\n      mapping.additionalPermissions?.forEach((p) => permissionSet.add(p));\n    }\n\n    return {\n      role,\n      permissions: Array.from(permissionSet),\n      matchedGroups: [...new Set(allMatchedGroups)],\n      appliedMappings,\n    };\n  }\n\n  /**\n   * Check if a group matches the identifier based on match type.\n   */\n  private matchesGroup(\n    group: ADGroup,\n    identifier: string,\n    matchType: ADGroupRoleMapping['matchType']\n  ): boolean {\n    const groupName = group.displayName.toLowerCase();\n    const groupId = group.id.toLowerCase();\n    const searchId = identifier.toLowerCase();\n\n    switch (matchType) {\n      case 'exact':\n        return groupName === searchId || groupId === searchId;\n\n      case 'prefix':\n        return groupName.startsWith(searchId) || groupId.startsWith(searchId);\n\n      case 'suffix':\n        return groupName.endsWith(searchId) || groupId.endsWith(searchId);\n\n      case 'pattern':\n        return this.matchPattern(groupName, searchId) || this.matchPattern(groupId, searchId);\n\n      default:\n        return false;\n    }\n  }\n\n  /**\n   * Match using glob-like pattern (* wildcard).\n   */\n  private matchPattern(value: string, pattern: string): boolean {\n    // Convert glob pattern to regex\n    const regexPattern = pattern\n      .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&') // Escape special chars\n      .replace(/\\*/g, '.*') // Convert * to .*\n      .replace(/\\?/g, '.'); // Convert ? to .\n\n    const regex = new RegExp(`^${regexPattern}$`, 'i');\n    return regex.test(value);\n  }\n\n  /**\n   * Generate cache key from groups.\n   */\n  private getCacheKey(groups: ADGroup[]): string {\n    return groups\n      .map((g) => g.id)\n      .sort()\n      .join('|');\n  }\n}\n"],"names":["ADGroupMapper","config","DEFAULT_GROUP_MAPPING_CONFIG","groups","user","cacheKey","cachedResult","result","groupIdentifier","matchType","group","matches","mapping","matchedGroups","a","b","m","updates","index","existing","matchedMappings","highestPriorityMapping","role","permissionSet","p","appliedMappings","allMatchedGroups","identifier","groupName","groupId","searchId","value","pattern","regexPattern","g"],"mappings":"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"}