All files / libs/kernel/auth/shared/src/subsets SubsetsAccessHelper.ts

47.93% Statements 58/121
83.33% Branches 5/6
50% Functions 2/4
47.93% Lines 58/121

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 1221x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 59x 59x 59x 59x 1x 1x 12x 59x 59x 59x 59x 18x 18x 41x 41x 41x 41x 41x 59x 12x 12x 1x 1x 1x 1x 1x 1x 1x 1x 1x                 1x 1x                                                                                                               1x  
import { uniq } from 'lodash-es';
 
import { assert } from '@amalia/ext/typescript';
import { type AuthenticatedContext } from '@amalia/kernel/auth/types';
 
import { defineAbilityFor } from '../abilities';
import { type Ability } from '../types';
 
import { SubsetAccessEnum } from './enums';
 
// Not importing the type from TypeORM to avoid importing typeorm types in the frontend bundle.
type SelectQueryBuilder = { andWhere: (value: string, parameters?: Record<string, unknown>) => void };
 
export type GetSubsetFromAbility = (ability: Ability) => SubsetAccessEnum;
 
export class SubsetsAccessHelper {
  /**
   * Given an authenticated context, browse the rules of the current action/subject tuple to find a subset.
   *
   * @param authenticatedContext
   * @param action
   * @param subject
   * @private
   */
  public static getSubset(authenticatedContext: AuthenticatedContext, getSubsetFromAbility: GetSubsetFromAbility) {
    assert(!!authenticatedContext, 'Missing authenticated context');
    const ability = defineAbilityFor(authenticatedContext);
    return getSubsetFromAbility(ability);
  }
 
  public static makeSubsetGetter(...args: Parameters<Ability['rulesFor']>): GetSubsetFromAbility {
    return (ability) => {
      const rules = ability.rulesFor(...args);
 
      // If no rules are eligible for subsets, return nothing.
      if (!rules.length) {
        return SubsetAccessEnum.NOTHING;
      }
 
      assert(rules.length === 1, 'More than a rule eligible for subsets');
 
      const conditions = rules[0].conditions as { subset: SubsetAccessEnum } | null;
 
      return conditions?.subset || SubsetAccessEnum.NOTHING;
    };
  }
 
  /**
   * Adds conditions to the query builder regarding of the current access rights.
   * @param authenticatedContext
   * @param getSubsetFromAbility
   * @param field
   * @param qb
   */
  public static addConditionForQueryBuilder(
    authenticatedContext: AuthenticatedContext,
    getSubsetFromAbility: GetSubsetFromAbility,
    field: string,
    qb: SelectQueryBuilder,
  ) {
    const subsetAccess = this.getSubset(authenticatedContext, getSubsetFromAbility);
    this.addSubsetAccessToQueryBuilder(subsetAccess, qb, authenticatedContext, field);
  }
 
  private static addSubsetAccessToQueryBuilder(
    subsetAccess: SubsetAccessEnum,
    qb: SelectQueryBuilder,
    authenticatedContext: AuthenticatedContext,
    field: string,
  ) {
    switch (subsetAccess) {
      case SubsetAccessEnum.NOTHING:
        qb.andWhere('0 = 1');
        break;
      case SubsetAccessEnum.MATCH_MANAGEES: {
        const mySubordinatesIds = authenticatedContext.hierarchy
          .getSubordinates(new Date())
          .map((ta) => ta.user.id)
          .concat(authenticatedContext.user.id);

        qb.andWhere(`${field} IN (:...mymanagees)`, { mymanagees: mySubordinatesIds });
        break;
      }
      case SubsetAccessEnum.MATCH_TEAMS: {
        const myTeamIds = uniq([
          // Empty array if user is not manager of any team.
          ...authenticatedContext.hierarchy.getTeamIdsWhereUserIsManager(new Date()),
          // Add employee assignments.
          ...authenticatedContext.hierarchy.getUserTeamAssignments(new Date()).map((ta) => ta.teamId),
        ]);

        if (myTeamIds.length === 0) {
          qb.andWhere('0 = 1');
        } else {
          qb.andWhere(`${field} IN (:...myteams)`, { myteams: myTeamIds });
        }
        break;
      }
      case SubsetAccessEnum.IN_MY_SCOPE: {
        assert(authenticatedContext.adminScopesContainer, 'Missing admin scope container');

        const allUsersInMyScope = authenticatedContext.adminScopesContainer.allUsersInMyScope(new Date());

        qb.andWhere(`${field} IN (:...myusers)`, { myusers: allUsersInMyScope.map((u) => u.id) });

        break;
      }
      case SubsetAccessEnum.IN_MY_COMPANIES: {
        const companyIds = authenticatedContext.meta?.companyIdsThatUserCanImpersonate || [];

        qb.andWhere(`${field} IN (:...companyIds)`, { companyIds });

        break;
      }
      case SubsetAccessEnum.EVERYTHING:
      default:
        // Do nothing
        break;
    }
  }
}