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

restorecommerce / resource-srv / 9548532913

17 Jun 2024 01:24PM UTC coverage: 72.382% (+1.1%) from 71.315%
9548532913

push

github

Gerald Baulig
fix(meta): fix default meta and subject resolving, load protos from node_modules for testing

70 of 92 branches covered (76.09%)

Branch coverage included in aggregate %.

155 of 157 new or added lines in 3 files covered. (98.73%)

1 existing line in 1 file now uncovered.

863 of 1197 relevant lines covered (72.1%)

3.15 hits per line

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

52.42
/src/utils.ts
1
import {
1✔
2
  AuthZAction,
1✔
3
  accessRequest,
1✔
4
  DecisionResponse,
1✔
5
  Operation,
1✔
6
  PolicySetRQResponse,
1✔
7
  ResolvedSubject,
1✔
8
  HierarchicalScope,
1✔
9
  ACSClientOptions
1✔
10
} from '@restorecommerce/acs-client';
1✔
11
import { createServiceConfig } from '@restorecommerce/service-config';
1✔
12
import {
1✔
13
  UserServiceClient as UserClient,
1✔
14
  UserResponse,
1✔
15
  UserServiceDefinition as UserServiceDefinition
1✔
16
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/user.js';
1✔
17
import { createChannel, createClient } from '@restorecommerce/grpc-client';
1✔
18
import { createLogger } from '@restorecommerce/logger';
1✔
19
import { Response_Decision } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control.js';
1✔
20
import { Subject } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/auth.js';
1✔
21
import { FilterOp } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js';
1✔
22
import {
1✔
23
  GraphServiceClient as GraphClient,
1✔
24
  GraphServiceDefinition,
1✔
25
  Options_Direction as Direction,
1✔
26
  TraversalRequest
1✔
27
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/graph.js';
1✔
28

1✔
29
// Create a ids client instance
1✔
30
let idsClientInstance: UserClient;
1✔
31
const cfg = createServiceConfig(process.cwd());
1✔
32
export const getUserServiceClient = () => {
1✔
33
  if (!idsClientInstance) {
12✔
34
    // identity-srv client to resolve subject ID by token
1✔
35
    const grpcIDSConfig = cfg.get('client:user');
1✔
36
    const loggerCfg = cfg.get('logger');
1✔
37
    const logger = createLogger(loggerCfg);
1✔
38
    if (grpcIDSConfig) {
1✔
39
      idsClientInstance = createClient({
1✔
40
        ...grpcIDSConfig,
1✔
41
        logger
1✔
42
      }, UserServiceDefinition, createChannel(grpcIDSConfig.address));
1✔
43
    }
1✔
44
  }
1✔
45
  return idsClientInstance;
12✔
46
};
12✔
47

1✔
48
// Create a graph client instance for traversal requests
1✔
49
let graphClientInstance: GraphClient;
1✔
50
export const getGraphServiceClient = async () => {
1✔
51
  if (!graphClientInstance) {
2✔
52
    const cfg = createServiceConfig(process.cwd());
1✔
53
    const grpcGraphConfig = cfg.get('client:graph-srv');
1✔
54
    const loggerCfg = cfg.get('logger');
1✔
55
    const logger = createLogger(loggerCfg);
1✔
56
    if (grpcGraphConfig) {
1✔
57
      graphClientInstance = createClient({
1✔
58
        ...grpcGraphConfig,
1✔
59
        logger
1✔
60
      }, GraphServiceDefinition, createChannel(grpcGraphConfig.address));
1✔
61
    }
1✔
62
  }
1✔
63
  return graphClientInstance;
2✔
64
};
2✔
65

1✔
66
export interface Resource {
1✔
67
  resource: string;
1✔
68
  id?: string | string[]; // for what is allowed operation id is not mandatory
1✔
69
  property?: string[];
1✔
70
}
1✔
71

1✔
72
export interface Attribute {
1✔
73
  id: string;
1✔
74
  value: string;
1✔
75
  attributes: Attribute[];
1✔
76
}
1✔
77

1✔
78
export interface CtxResource {
1✔
79
  id: string;
1✔
80
  meta: {
1✔
81
    created?: Date;
1✔
82
    modified?: Date;
1✔
83
    modified_by?: string;
1✔
84
    owners: Attribute[]; // id and owner is mandatory in ctx resource other attributes are optional
1✔
85
  };
1✔
86
  [key: string]: any;
1✔
87
}
1✔
88

1✔
89
export interface GQLClientContext {
1✔
90
  // if subject is missing by default it will be treated as unauthenticated subject
1✔
91
  subject?: Subject;
1✔
92
  resources?: CtxResource[];
1✔
93
}
1✔
94

1✔
95
/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
1✔
96
export async function resolveSubject(subject: Subject) {
1✔
97
  if (subject) {
26✔
98
    const idsClient = getUserServiceClient();
10✔
99
    const resp = await idsClient?.findByToken({ token: subject.token });
10✔
100
    if (resp?.payload?.id) {
10✔
101
      subject.id = resp.payload.id;
10✔
102
    }
10✔
103
  }
10✔
104
  return subject;
26✔
105
}
26✔
106

1✔
107
export async function checkAccessRequest(ctx: GQLClientContext, resource: Resource[], action: AuthZAction, operation: Operation.isAllowed, useCache?: boolean): Promise<DecisionResponse>;
1✔
108
export async function checkAccessRequest(ctx: GQLClientContext, resource: Resource[], action: AuthZAction, operation: Operation.whatIsAllowed, useCache?: boolean): Promise<PolicySetRQResponse>;
1✔
109

1✔
110
/**
1✔
111
 * Perform an access request using inputs from a GQL request
1✔
112
 *
1✔
113
 * @param subject Subject information
1✔
114
 * @param resources resources
1✔
115
 * @param action The action to perform
1✔
116
 * @param entity The entity type to check access against
1✔
117
 */
1✔
118
/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
1✔
119
export async function checkAccessRequest(
1✔
120
  ctx: GQLClientContext,
26✔
121
  resource: Resource[],
26✔
122
  action: AuthZAction,
26✔
123
  operation: Operation,
26✔
124
): Promise<DecisionResponse | PolicySetRQResponse> {
26✔
125
  const subject = ctx.subject as Subject;
26✔
126
  // resolve subject id using findByToken api and update subject with id
26✔
127
  if (!subject?.id && subject?.token) {
26!
NEW
128
    await resolveSubject(subject);
×
UNCOV
129
  }
×
130

26✔
131
  let result: DecisionResponse | PolicySetRQResponse;
26✔
132
  try {
26✔
133
    result = await accessRequest(
26✔
134
      subject,
26✔
135
      resource,
26✔
136
      action,
26✔
137
      ctx,
26✔
138
      {
26✔
139
        operation,
26✔
140
        roleScopingEntityURN: cfg?.get('authorization:urns:roleScopingEntityURN')
26✔
141
      } as ACSClientOptions);
26✔
142
  } catch (err: any) {
26!
143
    return {
×
144
      decision: Response_Decision.DENY,
×
145
      operation_status: {
×
146
        code: err.code ?? 500,
×
147
        message: err.details ?? err.message ?? 'Unknown Error!',
×
148
      }
×
149
    };
×
150
  }
×
151
  return result;
26✔
152
}
26✔
153

1✔
154
/**
1✔
155
 * accessResponse returned from `acs-client` contains the filters for the list of
1✔
156
 * resources requested and it returns resource filter map, below api
1✔
157
 * returns applicable `Filters[]` for the specified resource, it iterates through
1✔
158
 * the ACS response and returns the applicable `Filters[]` for the resource.
1✔
159
 * @param accessResponse ACS response
1✔
160
 * @param enitity enitity name
1✔
161
 */
1✔
162
export const getACSFilters = (
1✔
163
  accessResponse: PolicySetRQResponse,
10✔
164
  resource: string
10✔
165
): FilterOp[] => accessResponse?.filters?.find(
10!
166
  (e) => e?.resource === resource
10✔
NEW
167
    && e?.filters[0]?.filters?.length
×
168
)?.filters ?? [];
1!
169

1✔
170
const setNestedChildOrgs = (hrScope: any, targetOrgID: string, subOrgs: any[]) => {
1✔
171
  if (!hrScope) {
×
172
    return;
×
173
  }
×
174

×
175
  if (!Array.isArray(hrScope)) {
×
176
    hrScope = [hrScope];
×
177
  }
×
178

×
179
  for (let subHrScope of hrScope) {
×
180
    if (subHrScope.id === targetOrgID) {
×
181
      if (subHrScope.children) {
×
182
        subHrScope.children.push(...subOrgs);
×
183
      }
×
184
      else {
×
185
        subHrScope.children = [...subOrgs];
×
186
      }
×
187
      return;
×
188
    }
×
189
    for (let item of subHrScope.children) {
×
190
      if (item.id === targetOrgID) {
×
191
        item.children.push(...subOrgs);
×
192
        return;
×
193
      } else {
×
194
        setNestedChildOrgs(item.children, targetOrgID, subOrgs);
×
195
      }
×
196
    }
×
197
  }
×
198
};
×
199

1✔
200
export const getSubTreeOrgs = async (
1✔
201
  orgID: string,
×
202
  role: string,
×
203
  cfg: any,
×
204
  graphClient: GraphClient,
×
205
): Promise<HierarchicalScope> => {
×
206
  const hrScope: HierarchicalScope = { role, id: orgID, children: [] };
×
207
  let traversalResponse: any = [];
×
208
  const hierarchicalResources = cfg.get('authorization:hierarchicalResources') ?? [];
×
209
  const orgTechUser = cfg.get('techUser');
×
210
  for (let hierarchicalResource of hierarchicalResources) {
×
211
    const { collection, edge } = hierarchicalResource;
×
212
    // search in inbound - org has parent org
×
213
    const traversalRequest: TraversalRequest = {
×
214
      subject: orgTechUser,
×
215
      vertices: { collection_name: collection, start_vertex_ids: [orgID] },
×
216
      opts: {
×
217
        direction: Direction.INBOUND,
×
218
        include_edges: [edge]
×
219
      }
×
220
    };
×
221
    const result = await graphClient.traversal(traversalRequest);
×
222
    for await (const partResp of result) {
×
223
      if ((partResp && partResp.data && partResp.data.value)) {
×
224
        traversalResponse.push(...JSON.parse(partResp.data.value.toString()));
×
225
      }
×
226
    }
×
227
  }
×
228

×
229
  for (let item of traversalResponse) {
×
230
    let targetID = item.id;
×
231
    const subOrgs = traversalResponse.filter((e: any) => e.parent_id === targetID);
×
232
    // find hrScopes id and then get the childer object
×
233
    const filteredSubOrgFields = [];
×
234
    for (let org of subOrgs) {
×
235
      filteredSubOrgFields.push({ id: org.id, role, children: [] });
×
236
    }
×
237
    // leaf node or no more children nodes
×
238
    if (filteredSubOrgFields.length === 0) {
×
239
      filteredSubOrgFields.push({ id: targetID, role, children: [] });
×
240
      targetID = item.parent_id;
×
241
    }
×
242
    else {
×
243
      // set sub orgs on target org
×
244
      setNestedChildOrgs(hrScope, targetID, filteredSubOrgFields);
×
245
    }
×
246
  }
×
247
  return hrScope;
×
248
};
×
249

1✔
250
export const createHRScope = async (
1✔
251
  user: UserResponse,
×
252
  token: string,
×
253
  graphClient: GraphClient,
×
254
  cache: any,
×
255
  cfg: any,
×
256
  logger: any,
×
257
): Promise<ResolvedSubject | undefined> => {
×
258
  const subject = user?.payload as ResolvedSubject;
×
259
  const roleScopingEntityURN = cfg.get('authorization:urns:roleScopingEntity');
×
260
  const roleScopingInstanceURN = cfg.get('authorization:urns:roleScopingInstance');
×
261
  if (subject?.role_associations && !subject?.hierarchical_scopes?.length) {
×
262
    // create HR scopes iterating through the user's assigned role scoping instances
×
263
    let userRoleAssocs = subject.role_associations;
×
264
    let assignedUserScopes = new Set<{ userScope: string | undefined; role: string | undefined }>();
×
265
    let tokenData;
×
266
    // verify the validity of subject tokens
×
267
    if (token && user?.payload?.tokens?.length! > 0) {
×
268
      for (let tokenInfo of user?.payload?.tokens ?? []) {
×
269
        if (tokenInfo.token === token) {
×
270
          tokenData = tokenInfo;
×
271
          const expiresIn = tokenInfo.expires_in;
×
272
          if (expiresIn && expiresIn != new Date(0) && expiresIn < new Date()) {
×
273
            logger.info(`Token name ${tokenInfo.name} has expired`);
×
274
            return undefined;
×
275
          }
×
276
        }
×
277
      }
×
278
    }
×
279

×
280
    const reducedUserRoleAssocs = tokenData?.scopes?.flatMap(
×
281
      (scope: string) => userRoleAssocs?.filter(
×
282
        attr => attr.id === scope
×
283
      )
×
284
    ) ?? userRoleAssocs;
×
285

×
286
    for (let roleObj of reducedUserRoleAssocs) {
×
287
      if (roleObj?.attributes?.length! > 0) {
×
288
        for (let roleAttribute of roleObj?.attributes!) {
×
289
          if (roleAttribute.id === roleScopingEntityURN) {
×
290
            for (let roleScopInstObj of roleAttribute.attributes!) {
×
291
              if (roleScopInstObj.id === roleScopingInstanceURN) {
×
292
                let obj = { userScope: roleScopInstObj.value, role: roleObj.role };
×
293
                assignedUserScopes.add(obj);
×
294
              }
×
295
            }
×
296
          }
×
297
        }
×
298
      }
×
299
    }
×
300
    let hrScopes: HierarchicalScope[] = [];
×
301
    let userScopesRoleArray = Array.from(assignedUserScopes);
×
302
    for (let obj of userScopesRoleArray) {
×
303
      try {
×
304
        let hrScope = await getSubTreeOrgs(obj.userScope, obj.role, cfg, graphClient);
×
305
        if (hrScope) {
×
306
          hrScopes.push(hrScope);
×
307
        }
×
308
      } catch (err) {
×
309
        logger.error('Error computing hierarchical scopes', err);
×
310
      }
×
311
    }
×
312
    subject.hierarchical_scopes = hrScopes;
×
313
  }
×
314
  return subject;
×
315
};
×
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