• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

restorecommerce / resource-srv / 6259576730

21 Sep 2023 09:07AM UTC coverage: 51.634% (+0.06%) from 51.571%
6259576730

push

github

Arun-KumarH
chore: Release v1.1.0 - See CHANGELOG

87 of 245 branches covered (0.0%)

Branch coverage included in aggregate %.

26 of 26 new or added lines in 3 files covered. (100.0%)

308 of 520 relevant lines covered (59.23%)

1.55 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

25.45
/src/utils.ts
1
import {
1✔
2
  AuthZAction, accessRequest, DecisionResponse, Operation, PolicySetRQResponse,
3
  ResolvedSubject, HierarchicalScope
4
} from '@restorecommerce/acs-client';
5
import * as _ from 'lodash';
1✔
6
import { createServiceConfig } from '@restorecommerce/service-config';
1✔
7
import { UserServiceClient as UserClient, UserServiceDefinition as UserServiceDefinition } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/user';
1✔
8
import { createChannel, createClient } from '@restorecommerce/grpc-client';
1✔
9
import { createLogger } from '@restorecommerce/logger';
1✔
10
import { Response_Decision } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control';
1✔
11
import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth';
12
import { FilterOp } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base';
13
import { GraphServiceClient as GraphClient, GraphServiceDefinition, Options_Direction as Direction } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/graph';
1✔
14

15
// Create a ids client instance
16
let idsClientInstance: UserClient;
17
export const getUserServiceClient = async () => {
1✔
18
  if (!idsClientInstance) {
11✔
19
    const cfg = createServiceConfig(process.cwd());
1✔
20
    // identity-srv client to resolve subject ID by token
21
    const grpcIDSConfig = cfg.get('client:user');
1✔
22
    const loggerCfg = cfg.get('logger');
1✔
23
    loggerCfg.esTransformer = (msg) => {
1✔
24
      msg.fields = JSON.stringify(msg.fields);
×
25
      return msg;
×
26
    };
27
    const logger = createLogger(loggerCfg);
1✔
28
    if (grpcIDSConfig) {
1!
29
      idsClientInstance = createClient({
1✔
30
        ...grpcIDSConfig,
31
        logger
32
      }, UserServiceDefinition, createChannel(grpcIDSConfig.address));
33
    }
34
  }
35
  return idsClientInstance;
11✔
36
};
37

38
// Create a graph client instance for traversal requests
39
let graphClientInstance: GraphClient;
40
export const getGraphServiceClient = async () => {
1✔
41
  if (!graphClientInstance) {
1!
42
    const cfg = createServiceConfig(process.cwd());
1✔
43
    const grpcGraphConfig = cfg.get('client:graph-srv');
1✔
44
    const loggerCfg = cfg.get('logger');
1✔
45
    loggerCfg.esTransformer = (msg) => {
1✔
46
      msg.fields = JSON.stringify(msg.fields);
×
47
      return msg;
×
48
    };
49
    const logger = createLogger(loggerCfg);
1✔
50
    if (grpcGraphConfig) {
1!
51
      graphClientInstance = createClient({
1✔
52
        ...grpcGraphConfig,
53
        logger
54
      }, GraphServiceDefinition, createChannel(grpcGraphConfig.address));
55
    }
56
  }
57
  return graphClientInstance;
1✔
58
};
59

60
export interface Resource {
61
  resource: string;
62
  id?: string | string[]; // for what is allowed operation id is not mandatory
63
  property?: string[];
64
}
65

66
export interface Attribute {
67
  id: string;
68
  value: string;
69
  attributes: Attribute[];
70
}
71

72
export interface CtxResource {
73
  id: string;
74
  meta: {
75
    created?: number;
76
    modified?: number;
77
    modified_by?: string;
78
    owners: Attribute[]; // id and owner is mandatory in ctx resource other attributes are optional
79
  };
80
  [key: string]: any;
81
}
82

83
export interface GQLClientContext {
84
  // if subject is missing by default it will be treated as unauthenticated subject
85
  subject?: Subject;
86
  resources?: CtxResource[];
87
}
88

89
/**
90
 * Perform an access request using inputs from a GQL request
91
 *
92
 * @param subject Subject information
93
 * @param resources resources
94
 * @param action The action to perform
95
 * @param entity The entity type to check access against
96
 */
97
/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
98
export async function checkAccessRequest(ctx: GQLClientContext, resource: Resource[], action: AuthZAction,
1✔
99
  operation: Operation, useCache = true): Promise<DecisionResponse | PolicySetRQResponse> {
10✔
100
  let subject = ctx.subject;
10✔
101
  let dbSubject;
102
  // resolve subject id using findByToken api and update subject with id
103
  if (subject && subject.token) {
10!
104
    const idsClient = await getUserServiceClient();
10✔
105
    if (idsClient) {
10!
106
      dbSubject = await idsClient.findByToken({ token: subject.token });
10✔
107
      if (dbSubject && dbSubject.payload && dbSubject.payload.id) {
10!
108
        subject.id = dbSubject.payload.id;
10✔
109
      }
110
    }
111
  }
112

113
  let result: DecisionResponse | PolicySetRQResponse;
114
  try {
10✔
115
    result = await accessRequest(subject, resource, action, ctx, operation, 'arangoDB', useCache);
10✔
116
  } catch (err) {
117
    return {
×
118
      decision: Response_Decision.DENY,
119
      operation_status: {
120
        code: err.code || 500,
×
121
        message: err.details || err.message,
×
122
      }
123
    };
124
  }
125
  return result;
10✔
126
}
127

128
/**
129
 * accessResponse returned from `acs-client` contains the filters for the list of
130
 * resources requested and it returns resource filter map, below api
131
 * returns applicable `Filters[]` for the specified resource, it iterates through
132
 * the ACS response and returns the applicable `Filters[]` for the resource.
133
 * @param accessResponse ACS response
134
 * @param enitity enitity name
135
 */
136
export const getACSFilters = (accessResponse: PolicySetRQResponse, resource: string): FilterOp[] => {
1✔
137
  let acsFilters = [];
×
138
  const resourceFilterMap = accessResponse?.filters;
×
139
  const resourceFilter = resourceFilterMap?.filter((e) => e?.resource === resource);
×
140
  // for a given entity there should be one filter map
141
  if (resourceFilter?.length === 1 && resourceFilter[0].filters && resourceFilter[0].filters[0]?.filters.length > 0) {
×
142
    acsFilters = resourceFilter[0].filters;
×
143
  }
144
  return acsFilters;
×
145
};
146

147
const setNestedChildOrgs = (hrScope: any, targetOrgID, subOrgs) => {
1✔
148
  if (!_.isArray(hrScope)) {
×
149
    hrScope = [hrScope];
×
150
  }
151
  if (_.isArray(hrScope)) {
×
152
    for (let subHrScope of hrScope) {
×
153
      if (subHrScope.id === targetOrgID) {
×
154
        if (!subHrScope.children) {
×
155
          subHrScope.children = [];
×
156
        }
157
        subHrScope.children.push(...subOrgs);
×
158
        return;
×
159
      }
160
      for (let item of subHrScope.children) {
×
161
        if (item.id === targetOrgID) {
×
162
          item.children.push(...subOrgs);
×
163
          return hrScope;
×
164
        } else {
165
          setNestedChildOrgs(item.children, targetOrgID, subOrgs);
×
166
        }
167
      }
168
    }
169
  }
170
};
171

172
export const getSubTreeOrgs = async (orgID: string, role: string, cfg: any, graphClient, logger): Promise<HierarchicalScope> => {
1✔
173
  const hrScope: HierarchicalScope = { role, id: orgID, children: [] };
×
174
  let subOrgTreeList = new Set<string>();
×
175
  let traversalResponse: any = [];
×
176
  const hierarchicalResources = cfg.get('authorization:hierarchicalResources') || [];
×
177
  let orgTechUser;
178
  const techUsersCfg = cfg.get('techUsers');
×
179
  if (techUsersCfg?.length > 0) {
×
180
    orgTechUser = _.find(techUsersCfg, { id: 'upsert_user_tokens' });
×
181
  }
182
  for (let hierarchicalResource of hierarchicalResources) {
×
183
    const { collection, edge } = hierarchicalResource;
×
184
    // search in inbound - org has parent org
185
    const traversalRequest = {
×
186
      subject: orgTechUser,
187
      vertices: { collection_name: collection, start_vertex_id: [orgID] },
188
      opts: {
189
        direction: { direction: Direction.INBOUND },
190
        include_edge: [edge]
191
      }
192
    };
193
    const result = await graphClient.traversal(traversalRequest);
×
194
    for await (const partResp of result) {
×
195
      if ((partResp && partResp.data && partResp.data.value)) {
×
196
        traversalResponse.push(...JSON.parse(partResp.data.value.toString()));
×
197
      }
198
    }
199

200
    for (let org of traversalResponse) {
×
201
      if (org?._id?.indexOf(collection) > -1) {
×
202
        delete org._id;
×
203
        subOrgTreeList.add(org.id);
×
204
      }
205
    }
206
  }
207

208
  for (let i = 0; i < traversalResponse.length; i++) {
×
209
    let targetID = traversalResponse[i].id;
×
210
    const subOrgs = traversalResponse.filter(e => e.parent_id === targetID);
×
211
    // find hrScopes id and then get the childer object
212
    const filteredSubOrgFields = [];
×
213
    for (let org of subOrgs) {
×
214
      filteredSubOrgFields.push({ id: org.id, role, children: [] });
×
215
    }
216
    // leaf node or no more children nodes
217
    if (_.isEmpty(filteredSubOrgFields)) {
×
218
      filteredSubOrgFields.push({ id: targetID, role, children: [] });
×
219
      targetID = traversalResponse[i].parent_id;
×
220
    }
221
    // set sub orgs on target org
222
    setNestedChildOrgs(hrScope, targetID, filteredSubOrgFields);
×
223
  }
224
  return hrScope;
×
225
};
226

227
export const createHRScope = async (user, token, graphClient, cache, cfg, logger): Promise<ResolvedSubject> => {
1✔
228
  let subject: ResolvedSubject;
229
  if (user && user.payload) {
×
230
    subject = user.payload;
×
231
  }
232
  const roleScopingEntityURN = cfg.get('authorization:urns:roleScopingEntity');
×
233
  const roleScopingInstanceURN = cfg.get('authorization:urns:roleScopingInstance');
×
234
  if (subject && subject.role_associations && _.isEmpty(subject.hierarchical_scopes)) {
×
235
    // create HR scopes iterating through the user's assigned role scoping instances
236
    let userRoleAssocs = subject.role_associations;
×
237
    let assignedUserScopes = new Set<{ userScope: string; role: string }>();
×
238
    let tokenData;
239
    // verify the validity of subject tokens
240
    if (token && (subject as any).tokens && (subject as any).tokens.length > 0) {
×
241
      for (let tokenInfo of (subject as any).tokens) {
×
242
        if (tokenInfo.token === token) {
×
243
          tokenData = tokenInfo;
×
244
          const currentDate = Math.round(new Date().getTime() / 1000);
×
245
          const expiresIn = tokenInfo.expires_in;
×
246
          if (expiresIn != 0 && expiresIn < currentDate) {
×
247
            logger.info(`Token name ${tokenInfo.name} has expired`);
×
248
            return;
×
249
          }
250
        }
251
      }
252
    }
253
    let reducedUserRoleAssocs = [];
×
254
    if (tokenData && tokenData.scopes && tokenData.scopes.length > 0) {
×
255
      for (let tokenScope of tokenData.scopes) {
×
256
        if (_.find(userRoleAssocs, { id: tokenScope })) {
×
257
          reducedUserRoleAssocs.push(_.find(userRoleAssocs, { id: tokenScope }));
×
258
        }
259
      }
260
    } else {
261
      reducedUserRoleAssocs = userRoleAssocs;
×
262
    }
263
    for (let roleObj of reducedUserRoleAssocs) {
×
264
      if (roleObj?.attributes?.length > 0) {
×
265
        for (let roleAttribute of roleObj?.attributes) {
×
266
          if (roleAttribute.id === roleScopingEntityURN) {
×
267
            for (let roleScopInstObj of roleAttribute.attributes) {
×
268
              if (roleScopInstObj.id === roleScopingInstanceURN) {
×
269
                let obj = { userScope: roleScopInstObj.value, role: roleObj.role };
×
270
                assignedUserScopes.add(obj);
×
271
              }
272
            }
273
          }
274
        }
275
      }
276
    }
277
    let hrScopes: HierarchicalScope[] = [];
×
278
    let userScopesRoleArray = Array.from(assignedUserScopes);
×
279
    for (let obj of userScopesRoleArray) {
×
280
      try {
×
281
        let hrScope = await getSubTreeOrgs(obj.userScope, obj.role, cfg, graphClient, logger);
×
282
        if (hrScope) {
×
283
          hrScopes.push(hrScope);
×
284
        }
285
      } catch (err) {
286
        logger.error('Error computing hierarchical scopes', err);
×
287
      }
288
    }
289
    subject.hierarchical_scopes = hrScopes;
×
290
  }
291
  return subject;
×
292
};
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc