summaryrefslogtreecommitdiff
path: root/src/app/services/permission/helpers.js
blob: 46a78712865aabd76ff24c58c68b5a4650f3bdb4 (plain)
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
122
123
124
125
126
127
128
129
130
131
132
133
/**
 * Permission helpers.
 * @module app/services/permission/helpers
 */

import { PERMISSIONS } from "app/constants";

/**
 * When assigning a permission (any type), store the ID of the user granting the permission
 */
export function setGrantedByUser(request, response, next) {
  request.body.granted_by_user_id = request.user.user_id;
  next();
}

/**
 * Return a middleware function that checks permissions for a specific API method
 * @param  {object} permissions  Service API permission dictionary. See Service API documentation.
 * @param  {string} action       which action to check, read/create/update/destroy
 * @return {function} middleware function that checks permissions
 */
export function checkPermission(permissions, action) {
  const routePermission = permissions
    ? action
      ? permissions[action]
      : permissions
    : null;
  return async function checkPermissionsMiddleware(request, response, next) {
    const { user } = request;
    if (!routePermission) {
      // API access must be explicitly enabled.
      request.permission = PERMISSIONS.DENY;
      next(new Error("PermissionsError"));
    } else if (routePermission.roles?.includes(user.role)) {
      request.permission = PERMISSIONS.ALLOW;
      next();
    } else if (routePermission.owner) {
      request.permission = PERMISSIONS.ALLOW_FOR_OWNER;
      next();
    } else if (routePermission.check) {
      request.permission = await routePermission.check({
        request,
        user,
        id: request.params?.id,
      });
      if (request.permission === PERMISSIONS.DENY) {
        next(new Error("PermissionsError"));
      } else {
        next();
      }
    } else {
      request.permission = PERMISSIONS.DENY;
      next(new Error("PermissionsError"));
    }
  };
}

/**
 * Return a middleware function that recursively checks permissions on the parent service of an API method.
 * Also checks if the parent object exists to begin with.
 * @param  {Service} parentService  the service to check.
 * @return {function}               the Express middleware
 */
export function checkParentPermission(parentService) {
  return async function checkParentMiddleware(request, response, next) {
    let permission;
    request.parents = {};
    try {
      permission = await checkServicePermission(request, parentService);
    } catch (error) {
      console.error(error);
      permission = PERMISSIONS.DENY;
    }
    request.permission = permission;
    if (permission === PERMISSIONS.DENY) {
      next(new Error("PermissionsError"));
    } else {
      next();
    }
  };
}

/**
 * Helper function to check permissions on a service
 * @param  {Request} request  the Express request we're checking
 * @param  {Service} service  the Service we're checking
 * @return {permission}       the evaluated permissions
 */
async function checkServicePermission(request, service) {
  const user = request.user;
  const readPermissions = service.options.permissions.read;
  if (!readPermissions) {
    return PERMISSIONS.DENY;
  }

  /** Check read permissions on any parent objects first. */
  if (service.parent) {
    const parentPermission = await checkServicePermission(
      request,
      service.parent
    );
    if (parentPermission === PERMISSIONS.DENY) {
      return PERMISSIONS.DENY;
    }
  }

  /** Make sure this service's object exists before proceeding. */
  const idAttribute = service.Model.prototype.idAttribute;
  const objectId = request.params[idAttribute];
  const object = await new service.Model({ [idAttribute]: objectId }).fetch();
  request.parents[service.resource] = object;

  if (!object) {
    /** If there is no object, deny */
    return PERMISSIONS.DENY;
  } else if (readPermissions.roles?.includes(user.role)) {
    /** Pass role permission check */
    return PERMISSIONS.ALLOW;
  } else if (readPermissions.owner && object.get("user_id") === user.user_id) {
    /** Pass owner permission check */
    return PERMISSIONS.ALLOW;
  } else if (readPermissions.check) {
    /** Pass custom permission check */
    return await readPermissions.check({
      request,
      user,
      id: objectId,
    });
  } else {
    /** No checks passed - deny */
    return PERMISSIONS.DENY;
  }
}