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

restorecommerce / access-control-srv / 9698111598

27 Jun 2024 02:07PM UTC coverage: 74.798% (-1.8%) from 76.633%
9698111598

push

github

Arun-KumarH
fix: Up cfg for importing seed data on startup and updated deps

499 of 669 branches covered (74.59%)

Branch coverage included in aggregate %.

2644 of 3533 relevant lines covered (74.84%)

55.5 hits per line

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

64.55
/src/resourceManager.ts
1
import _ from 'lodash-es';
1✔
2
import { ResourcesAPIBase, ServiceBase, FilterOperation } from '@restorecommerce/resource-base-interface';
1✔
3
import { Topic, Events } from '@restorecommerce/kafka-client';
1✔
4
import { AccessController } from './core/accessController.js';
1✔
5
import { createMetadata, checkAccessRequest } from './core/utils.js';
1✔
6
import { AuthZAction, Operation, ACSAuthZ, DecisionResponse, PolicySetRQResponse } from '@restorecommerce/acs-client';
1✔
7
import { RedisClientType } from 'redis';
1✔
8
import { Logger } from 'winston';
1✔
9
import {
1✔
10
  Response_Decision
1✔
11
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control.js';
1✔
12
import {
1✔
13
  PolicySetServiceImplementation,
1✔
14
  PolicySetList, PolicySetListResponse, PolicySet
1✔
15
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/policy_set.js';
1✔
16
import {
1✔
17
  PolicyServiceImplementation,
1✔
18
  PolicyList, PolicyListResponse, Policy
1✔
19
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/policy.js';
1✔
20
import {
1✔
21
  RuleServiceImplementation,
1✔
22
  RuleList, RuleListResponse, Rule
1✔
23
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/rule.js';
1✔
24
import { ReadRequest, Filter_Operation, DeepPartial, DeleteRequest, DeleteResponse } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base.js';
1✔
25
import { PolicyWithCombinables, PolicySetWithCombinables } from './core/interfaces.js';
1✔
26

1✔
27

1✔
28
export interface IAccessControlResourceService<T> {
1✔
29
  load(): Promise<Map<string, T>>;
1✔
30
  readMetaData(id?: string): Promise<any>;
1✔
31
}
1✔
32

1✔
33
const marshallResource = (resource: any, resourceName: string): any => {
1✔
34
  let marshalled: any = _.pick(resource, ['id', 'name', 'description', 'evaluation_cacheable']);
126✔
35
  switch (resourceName) {
126✔
36
    case 'policy_set':
126✔
37
      marshalled = _.assign(marshalled, _.pick(resource, ['target']));
17✔
38
      if (!_.isEmpty(resource)) {
17✔
39
        marshalled.combining_algorithm = resource.combining_algorithm;
17✔
40
      }
17✔
41
      marshalled.combinables = new Map<string, Policy>();
17✔
42
      break;
17✔
43
    case 'policy':
126✔
44
      marshalled = _.assign(marshalled, _.pick(resource, ['target', 'effect']));
25✔
45
      marshalled.combining_algorithm = resource.combining_algorithm;
25✔
46
      marshalled.combinables = new Map<string, Rule>();
25✔
47
      break;
25✔
48
    case 'rule':
126✔
49
      marshalled = _.assign(marshalled, _.pick(resource, ['target', 'effect', 'condition']));
84✔
50
      if (!_.isEmpty(resource) && !_.isEmpty(resource.context_query)
84✔
51
        && !_.isEmpty(resource.context_query.query)) {
84!
52
        marshalled.contextQuery = resource.context_query;
×
53
      }
×
54
      break;
84✔
55
    default: throw new Error('Unknown resource ' + resourceName);
126!
56
  }
126✔
57

126✔
58
  return marshalled;
126✔
59
};
126✔
60

1✔
61
const makeFilter = (ids: string[]): any => {
1✔
62
  return [{
41✔
63
    filters: [{
41✔
64
      field: 'id',
41✔
65
      operation: FilterOperation.in,
41✔
66
      value: ids
41✔
67
    }]
41✔
68
  }];
41✔
69
};
41✔
70

1✔
71
let _accessController: AccessController;
1✔
72
let policySetService: PolicySetService,
1✔
73
  policyService: PolicyService,
1✔
74
  ruleService: RuleService;
1✔
75

1✔
76
/**
1✔
77
* Rule resource service.
1✔
78
*/
1✔
79
export class RuleService extends ServiceBase<RuleListResponse, RuleList> implements IAccessControlResourceService<Rule>, RuleServiceImplementation {
1✔
80
  cfg: any;
10✔
81
  redisClient: RedisClientType<any, any>;
10✔
82
  authZ: ACSAuthZ;
10✔
83
  constructor(logger: any, policyTopic: Topic, db: any, cfg: any,
10✔
84
    redisClient: RedisClientType<any, any>, authZ: ACSAuthZ) {
10✔
85
    let resourceFieldConfig;
10✔
86
    if (cfg.get('fieldHandlers')) {
10✔
87
      resourceFieldConfig = cfg.get('fieldHandlers');
10✔
88
      resourceFieldConfig['bufferFields'] = resourceFieldConfig?.bufferFields?.users;
10!
89
      if (cfg.get('fieldHandlers:timeStampFields')) {
10✔
90
        resourceFieldConfig['timeStampFields'] = [];
10✔
91
        for (let timeStampFiledConfig of cfg.get('fieldHandlers:timeStampFields')) {
10✔
92
          if (timeStampFiledConfig.entities.includes('rules')) {
10✔
93
            resourceFieldConfig['timeStampFields'].push(...timeStampFiledConfig.fields);
10✔
94
          }
10✔
95
        }
10✔
96
      }
10✔
97
    }
10✔
98
    super('rule', policyTopic, logger, new ResourcesAPIBase(db, 'rules', resourceFieldConfig), true);
10✔
99
    this.cfg = cfg;
10✔
100
    this.redisClient = redisClient;
10✔
101
    this.authZ = authZ;
10✔
102
  }
10✔
103

10✔
104
  /**
10✔
105
   * Retrieve and unmarsall Rules data.
10✔
106
   */
10✔
107
  async load(): Promise<Map<string, Rule>> {
10✔
108
    return this.getRules();
×
109
  }
×
110

10✔
111
  async getRules(ruleIDs?: string[]): Promise<Map<string, Rule>> {
10✔
112
    const filters = ruleIDs ? makeFilter(ruleIDs) : {};
25!
113
    const result = await super.read(ReadRequest.fromPartial({ filters }), {});
25✔
114

25✔
115
    const rules = new Map<string, Rule>();
25✔
116
    if (result?.items) {
25✔
117
      _.forEach(result.items, (rule) => {
25✔
118
        if (rule?.payload?.id) {
×
119
          rules.set(rule.payload.id, marshallResource(rule.payload, 'rule'));
×
120
        }
×
121
      });
25✔
122
    }
25✔
123

25✔
124
    return rules;
25✔
125
  }
25✔
126

10✔
127
  async readMetaData(id?: string): Promise<DeepPartial<RuleListResponse>> {
10✔
128
    let result = await super.read(ReadRequest.fromPartial(
13✔
129
      {
13✔
130
        filters: [{
13✔
131
          filters: [{
13✔
132
            field: 'id',
13✔
133
            operation: Filter_Operation.eq,
13✔
134
            value: id
13✔
135
          }]
13✔
136
        }]
13✔
137
      }
13✔
138
    ), {});
13✔
139
    return result;
13✔
140
  }
13✔
141

10✔
142
  async superUpsert(request: RuleList, ctx: any): Promise<DeepPartial<RuleListResponse>> {
10✔
143
    const result = await super.upsert(request, ctx);
×
144
    const policySets = _.cloneDeep(_accessController.policySets);
×
145

×
146
    if (result?.items?.length > 0) {
×
147
      for (let item of result.items) {
×
148
        const rule: Rule = marshallResource(item?.payload, 'rule');
×
149
        for (let [, policySet] of policySets) {
×
150
          for (let [, policy] of (policySet).combinables) {
×
151
            if (!_.isNil(policy) && policy.combinables.has(rule.id)) {
×
152
              _accessController.updateRule(policySet.id, policy.id, rule);
×
153
            }
×
154
          }
×
155
        }
×
156
      }
×
157
    }
×
158
    return result;
×
159
  }
×
160

10✔
161
  async create(request: RuleList, ctx: any): Promise<DeepPartial<RuleListResponse>> {
10✔
162
    let subject = request.subject;
28✔
163
    // update meta data for owner information
28✔
164
    let items = request.items;
28✔
165
    items = await createMetadata(items, AuthZAction.CREATE, subject, this);
28✔
166

28✔
167
    let acsResponse: DecisionResponse;
28✔
168
    try {
28✔
169
      if (!ctx) { ctx = {}; };
28!
170
      ctx.subject = subject;
28✔
171
      ctx.resources = items;
28✔
172
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'rule', id: items.map(item => item.id) }], AuthZAction.CREATE,
28✔
173
        Operation.isAllowed);
28✔
174
    } catch (err) {
28!
175
      this.logger.error('Error occurred requesting access-control-srv for create Rules', { code: err.code, message: err.message, stack: err.stack });
×
176
      return {
×
177
        operation_status: {
×
178
          code: err.code,
×
179
          message: err.message
×
180
        }
×
181
      };
×
182
    }
×
183

28✔
184
    if (acsResponse.decision != Response_Decision.PERMIT) {
28✔
185
      return { operation_status: acsResponse.operation_status };
4✔
186
    }
4✔
187
    const result = await super.create(request, ctx);
24✔
188
    const policySets = _.cloneDeep(_accessController.policySets);
24✔
189

24✔
190
    if (result?.items?.length > 0) {
28✔
191
      for (let item of result.items) {
24✔
192
        const rule: Rule = marshallResource(item?.payload, 'rule');
84✔
193
        for (let [, policySet] of policySets) {
84✔
194
          for (let [, policy] of (policySet).combinables) {
84✔
195
            if (!_.isNil(policy) && policy.combinables.has(rule.id)) {
154✔
196
              _accessController.updateRule(policySet.id, policy.id, rule);
74✔
197
            }
74✔
198
          }
154✔
199
        }
84✔
200
      }
84✔
201
    }
24✔
202
    return result;
24✔
203
  }
24✔
204

10✔
205
  async read(request: ReadRequest, ctx: any): Promise<DeepPartial<RuleListResponse>> {
10✔
206
    let subject = request.subject;;
1✔
207
    let acsResponse: PolicySetRQResponse;
1✔
208
    try {
1✔
209
      if (!ctx) { ctx = {}; };
1!
210
      ctx.subject = subject;
1✔
211
      ctx.resources = [];
1✔
212
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'rule' }], AuthZAction.READ,
1✔
213
        Operation.whatIsAllowed) as PolicySetRQResponse;
1✔
214
    } catch (err) {
1!
215
      this.logger.error('Error occurred requesting access-control-srv for read Rules', { code: err.code, message: err.message, stack: err.stack });
×
216
      return {
×
217
        operation_status: {
×
218
          code: err.code,
×
219
          message: err.message
×
220
        }
×
221
      };
×
222
    }
×
223
    if (acsResponse.decision != Response_Decision.PERMIT) {
1!
224
      return { operation_status: acsResponse.operation_status };
×
225
    }
×
226
    if (acsResponse?.custom_query_args?.length > 0) {
1!
227
      request.custom_queries = acsResponse.custom_query_args[0].custom_queries;
×
228
      request.custom_arguments = acsResponse.custom_query_args[0].custom_arguments;
×
229
    }
×
230
    const result = await super.read(request, ctx);
1✔
231
    return result;
1✔
232
  }
1✔
233

10✔
234
  async update(request: RuleList, ctx: any): Promise<DeepPartial<RuleListResponse>> {
10✔
235
    let subject = request.subject;
2✔
236
    // update meta data for owner information
2✔
237
    let items = request.items;
2✔
238
    items = await createMetadata(items, AuthZAction.MODIFY, subject, this);
2✔
239

2✔
240
    let acsResponse: DecisionResponse;
2✔
241
    try {
2✔
242
      if (!ctx) { ctx = {}; };
2!
243
      ctx.subject = subject;
2✔
244
      ctx.resources = items;
2✔
245
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'rule', id: items.map(item => item.id) }], AuthZAction.MODIFY,
2✔
246
        Operation.isAllowed);
2✔
247
    } catch (err) {
2!
248
      this.logger.error('Error occurred requesting access-control-srv for update Rules', { code: err.code, message: err.message, stack: err.stack });
×
249
      return {
×
250
        operation_status: {
×
251
          code: err.code,
×
252
          message: err.message
×
253
        }
×
254
      };
×
255
    }
×
256
    if (acsResponse.decision != Response_Decision.PERMIT) {
2✔
257
      return { operation_status: acsResponse.operation_status };
1✔
258
    }
1✔
259
    const result = await super.update(request, ctx);
1✔
260
    return result;
1✔
261
  }
1✔
262

10✔
263
  async upsert(request: RuleList, ctx: any): Promise<DeepPartial<RuleListResponse>> {
10✔
264
    let subject = request.subject;
2✔
265
    // update meta data for owner information
2✔
266
    let items = request.items;
2✔
267
    items = await createMetadata(items, AuthZAction.MODIFY, subject, this);
2✔
268

2✔
269
    let acsResponse: DecisionResponse;
2✔
270
    try {
2✔
271
      if (!ctx) { ctx = {}; };
2!
272
      ctx.subject = subject;
2✔
273
      ctx.resources = items;
2✔
274
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'rule', id: items.map(item => item.id) }], AuthZAction.MODIFY,
2✔
275
        Operation.isAllowed);
2✔
276
    } catch (err) {
2!
277
      this.logger.error('Error occurred requesting access-control-srv for upsert Rules', { code: err.code, message: err.message, stack: err.stack });
×
278
      return {
×
279
        operation_status: {
×
280
          code: err.code,
×
281
          message: err.message
×
282
        }
×
283
      };
×
284
    }
×
285
    if (acsResponse.decision != Response_Decision.PERMIT) {
2✔
286
      return { operation_status: acsResponse.operation_status };
1✔
287
    }
1✔
288
    const result = await super.upsert(request, ctx);
1✔
289
    return result;
1✔
290
  }
1✔
291

10✔
292
  async delete(request: DeleteRequest, ctx: any): Promise<DeepPartial<DeleteResponse>> {
10✔
293
    let resources = [];
20✔
294
    let subject = request.subject;
20✔
295
    let ruleIDs = request.ids;
20✔
296
    let action, deleteResponse;
20✔
297
    if (ruleIDs) {
20✔
298
      action = AuthZAction.DELETE;
5✔
299
      if (_.isArray(ruleIDs)) {
5✔
300
        for (let id of ruleIDs) {
5✔
301
          resources.push({ id });
9✔
302
        }
9✔
303
      } else {
5!
304
        resources = [{ id: ruleIDs }];
×
305
      }
×
306
      Object.assign(resources, { id: ruleIDs });
5✔
307
      await createMetadata(resources, action, subject, this);
5✔
308
    }
5✔
309
    if (request.collection) {
20✔
310
      action = AuthZAction.DROP;
15✔
311
      resources = [{ collection: request.collection }];
15✔
312
    }
15✔
313

20✔
314
    let acsResponse: DecisionResponse;
20✔
315
    try {
20✔
316
      if (!ctx) { ctx = {}; };
20!
317
      ctx.subject = subject;
20✔
318
      ctx.resources = resources;
20✔
319
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'rule', id: ruleIDs }], action,
20✔
320
        Operation.isAllowed);
20✔
321
    } catch (err) {
20!
322
      this.logger.error('Error occurred requesting access-control-srv for delete Rules', { code: err.code, message: err.message, stack: err.stack });
×
323
      return {
×
324
        operation_status: {
×
325
          code: err.code,
×
326
          message: err.message
×
327
        }
×
328
      };
×
329
    }
×
330
    if (acsResponse.decision != Response_Decision.PERMIT) {
20✔
331
      return { operation_status: acsResponse.operation_status };
1✔
332
    }
1✔
333
    deleteResponse = await super.delete(request, ctx);
19✔
334
    if (request?.ids?.length > 0) {
20✔
335
      for (let id of request.ids) {
19✔
336
        for (let [, policySet] of _accessController.policySets) {
83✔
337
          for (let [, policy] of policySet.combinables) {
8✔
338
            if (policy?.combinables?.has(id)) {
18✔
339
              _accessController.removeRule(policySet.id, policy.id, id);
3✔
340
            }
3✔
341
          }
18✔
342
        }
8✔
343
      }
83✔
344
    } else if (request?.collection && request.collection === true) {
20!
345
      for (let [, policySet] of _accessController.policySets) {
×
346
        for (let [, policy] of policySet.combinables) {
×
347
          policy.combinables = new Map();
×
348
          _accessController.updatePolicy(policySet.id, policy);
×
349
        }
×
350
      }
×
351
    }
×
352
    return deleteResponse;
19✔
353
  }
19✔
354
}
10✔
355

1✔
356
/**
1✔
357
 * Policy resource service.
1✔
358
 */
1✔
359
export class PolicyService extends ServiceBase<PolicyListResponse, PolicyList> implements IAccessControlResourceService<Policy>, PolicyServiceImplementation {
1✔
360
  ruleService: RuleService;
5✔
361
  cfg: any;
5✔
362
  redisClient: RedisClientType<any, any>;
5✔
363
  authZ: ACSAuthZ;
5✔
364
  constructor(logger: any, db: any, policyTopic: Topic, rulesTopic: Topic, cfg: any,
5✔
365
    redisClient: RedisClientType<any, any>, authZ: ACSAuthZ) {
5✔
366
    let resourceFieldConfig;
5✔
367
    if (cfg.get('fieldHandlers')) {
5✔
368
      resourceFieldConfig = cfg.get('fieldHandlers');
5✔
369
      resourceFieldConfig['bufferFields'] = resourceFieldConfig?.bufferFields?.users;
5!
370
      if (cfg.get('fieldHandlers:timeStampFields')) {
5✔
371
        resourceFieldConfig['timeStampFields'] = [];
5✔
372
        for (let timeStampFiledConfig of cfg.get('fieldHandlers:timeStampFields')) {
5✔
373
          if (timeStampFiledConfig.entities.includes('policies')) {
5✔
374
            resourceFieldConfig['timeStampFields'].push(...timeStampFiledConfig.fields);
5✔
375
          }
5✔
376
        }
5✔
377
      }
5✔
378
    }
5✔
379
    super('policy', policyTopic, logger, new ResourcesAPIBase(db, 'policies', resourceFieldConfig), true);
5✔
380
    this.ruleService = new RuleService(this.logger, rulesTopic, db, cfg, redisClient, authZ);
5✔
381
    this.cfg = cfg;
5✔
382
    this.redisClient = redisClient;
5✔
383
    this.authZ = authZ;
5✔
384
  }
5✔
385

5✔
386
  /**
5✔
387
   * Load rules/policies and map them,
5✔
388
   */
5✔
389
  async load(): Promise<Map<string, PolicyWithCombinables>> {
5✔
390
    return this.getPolicies();
×
391
  }
×
392

5✔
393
  async superUpsert(request: PolicyList, ctx: any): Promise<DeepPartial<PolicyListResponse>> {
5✔
394
    const result = await super.upsert(request, ctx);
×
395
    const policySets = _.cloneDeep(_accessController.policySets);
×
396

×
397
    if (result?.items?.length > 0) {
×
398
      for (let item of result.items) {
×
399
        for (let [, policySet] of policySets) {
×
400
          if (policySet.combinables.has(item.payload?.id)) {
×
401
            const policy: PolicyWithCombinables = marshallResource(item.payload, 'policy');
×
402

×
403
            if (_.has(item.payload, 'rules') && !_.isEmpty(item.payload.rules)) {
×
404
              policy.combinables = await ruleService.getRules(item.payload.rules);
×
405

×
406
              if (policy.combinables.size != item?.payload?.rules?.length) {
×
407
                for (let id of item.payload.rules) {
×
408
                  if (!policy.combinables.has(id)) {
×
409
                    policy.combinables.set(id, null);
×
410
                  }
×
411
                }
×
412
              }
×
413
            }
×
414
            _accessController.updatePolicy(policySet.id, policy);
×
415
          }
×
416
        }
×
417
      }
×
418
    }
×
419
    return result;
×
420
  }
×
421

5✔
422
  async create(request: PolicyList, ctx: any): Promise<DeepPartial<PolicyListResponse>> {
5✔
423
    let subject = request.subject;
16✔
424
    // update meta data for owner information
16✔
425
    let items = request.items;
16✔
426
    items = await createMetadata(items, AuthZAction.CREATE, subject, this);
16✔
427

16✔
428
    let acsResponse: DecisionResponse;
16✔
429
    try {
16✔
430
      if (!ctx) { ctx = {}; };
16!
431
      ctx.subject = subject;
16✔
432
      ctx.resources = items;
16✔
433
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy', id: items.map(item => item.id) }], AuthZAction.CREATE,
16✔
434
        Operation.isAllowed);
16✔
435
    } catch (err) {
16!
436
      this.logger.error('Error occurred requesting access-control-srv for create Policies', { code: err.code, message: err.message, stack: err.stack });
×
437
      return {
×
438
        operation_status: {
×
439
          code: err.code,
×
440
          message: err.message
×
441
        }
×
442
      };
×
443
    }
×
444
    if (acsResponse.decision != Response_Decision.PERMIT) {
16!
445
      return { operation_status: acsResponse.operation_status };
×
446
    }
×
447
    const result = await super.create(request, ctx);
16✔
448
    const policySets = _.cloneDeep(_accessController.policySets);
16✔
449

16✔
450
    if (result?.items?.length > 0) {
16✔
451
      for (let item of result.items) {
16✔
452
        for (let [, policySet] of policySets) {
25✔
453
          if (policySet.combinables.has(item.payload?.id)) {
25✔
454
            const policy: PolicyWithCombinables = marshallResource(item.payload, 'policy');
25✔
455

25✔
456
            if (_.has(item.payload, 'rules') && !_.isEmpty(item.payload.rules)) {
25✔
457
              policy.combinables = await ruleService.getRules(item.payload.rules);
25✔
458

25✔
459
              if (policy.combinables.size != item?.payload?.rules?.length) {
25✔
460
                for (let id of item.payload.rules) {
25✔
461
                  if (!policy.combinables.has(id)) {
74✔
462
                    policy.combinables.set(id, null);
74✔
463
                  }
74✔
464
                }
74✔
465
              }
25✔
466
            }
25✔
467
            _accessController.updatePolicy(policySet.id, policy);
25✔
468
          }
25✔
469
        }
25✔
470
      }
25✔
471
    }
16✔
472

16✔
473
    return result;
16✔
474
  }
16✔
475

5✔
476
  async readMetaData(id?: string): Promise<DeepPartial<PolicyListResponse>> {
5✔
477
    let result = await super.read(ReadRequest.fromPartial({
1✔
478
      filters: [{
1✔
479
        filters: [{
1✔
480
          field: 'id',
1✔
481
          operation: Filter_Operation.eq,
1✔
482
          value: id
1✔
483
        }]
1✔
484
      }]
1✔
485
    }), {});
1✔
486
    return result;
1✔
487
  }
1✔
488

5✔
489
  async read(request: ReadRequest, ctx: any): Promise<DeepPartial<PolicyListResponse>> {
5✔
490
    let subject = request.subject;
1✔
491
    let acsResponse: PolicySetRQResponse;
1✔
492
    try {
1✔
493
      if (!ctx) { ctx = {}; };
1!
494
      ctx.subject = subject;
1✔
495
      ctx.resources = [];
1✔
496
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy' }], AuthZAction.READ,
1✔
497
        Operation.whatIsAllowed) as PolicySetRQResponse;
1✔
498
    } catch (err) {
1!
499
      this.logger.error('Error occurred requesting access-control-srv for read Policies', { code: err.code, message: err.message, stack: err.stack });
×
500
      return {
×
501
        operation_status: {
×
502
          code: err.code,
×
503
          message: err.message
×
504
        }
×
505
      };
×
506
    }
×
507
    if (acsResponse.decision != Response_Decision.PERMIT) {
1!
508
      return { operation_status: acsResponse.operation_status };
×
509
    }
×
510
    if (acsResponse?.custom_query_args?.length > 0) {
1!
511
      request.custom_queries = acsResponse.custom_query_args[0].custom_queries;
×
512
      request.custom_arguments = acsResponse.custom_query_args[0].custom_arguments;
×
513
    }
×
514
    const result = await super.read(request, ctx);
1✔
515
    return result;
1✔
516
  }
1✔
517

5✔
518
  async update(request: PolicyList, ctx: any): Promise<DeepPartial<PolicyListResponse>> {
5✔
519
    let subject = request.subject;
×
520
    // update meta data for owner information
×
521
    let items = request.items;
×
522
    items = await createMetadata(items, AuthZAction.MODIFY, subject, this);
×
523

×
524
    let acsResponse: DecisionResponse;
×
525
    try {
×
526
      if (!ctx) { ctx = {}; };
×
527
      ctx.subject = subject;
×
528
      ctx.resources = items;
×
529
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy', id: items.map(item => item.id) }], AuthZAction.MODIFY,
×
530
        Operation.isAllowed);
×
531
    } catch (err) {
×
532
      this.logger.error('Error occurred requesting access-control-srv for update Policies', { code: err.code, message: err.message, stack: err.stack });
×
533
      return {
×
534
        operation_status: {
×
535
          code: err.code,
×
536
          message: err.message
×
537
        }
×
538
      };
×
539
    }
×
540
    if (acsResponse.decision != Response_Decision.PERMIT) {
×
541
      return { operation_status: acsResponse.operation_status };
×
542
    }
×
543
    const result = await super.update(request, ctx);
×
544
    return result;
×
545
  }
×
546

5✔
547
  async upsert(request: PolicyList, ctx: any): Promise<DeepPartial<PolicyListResponse>> {
5✔
548
    let subject = request.subject;
×
549
    // update meta data for owner information
×
550
    let items = request.items;
×
551
    items = await createMetadata(items, AuthZAction.MODIFY, subject, this);
×
552

×
553
    let acsResponse: DecisionResponse;
×
554
    try {
×
555
      if (!ctx) { ctx = {}; };
×
556
      ctx.subject = subject;
×
557
      ctx.resources = items;
×
558
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy', id: items.map(item => item.id) }], AuthZAction.MODIFY,
×
559
        Operation.isAllowed);
×
560
    } catch (err) {
×
561
      this.logger.error('Error occurred requesting access-control-srv for upsert Policies', { code: err.code, message: err.message, stack: err.stack });
×
562
      return {
×
563
        operation_status: {
×
564
          code: err.code,
×
565
          message: err.message
×
566
        }
×
567
      };
×
568
    }
×
569
    if (acsResponse.decision != Response_Decision.PERMIT) {
×
570
      return { operation_status: acsResponse.operation_status };
×
571
    }
×
572
    const result = await super.upsert(request, ctx);
×
573
    return result;
×
574
  }
×
575

5✔
576
  async getPolicies(policyIDs?: string[]): Promise<Map<string, PolicyWithCombinables>> {
5✔
577
    const filters = policyIDs ? makeFilter(policyIDs) : [];
16!
578
    const result = await super.read(ReadRequest.fromPartial({ filters }), {});
16✔
579

16✔
580
    const policies = new Map<string, PolicyWithCombinables>();
16✔
581
    if (result?.items?.length > 0) {
16!
582
      for (let i = 0; i < result.items.length; i += 1) {
×
583
        const policy: PolicyWithCombinables = marshallResource(result.items[i].payload, 'policy');
×
584

×
585
        if (!_.isEmpty(result.items[i]?.payload?.rules)) {
×
586
          policy.combinables = await this.ruleService.getRules(result.items[i].payload.rules);
×
587
          if (policy.combinables.size != result.items[i].payload.rules.length) {
×
588
            for (let ruleID of result.items[i].payload.rules) {
×
589
              const ruleData = await this.ruleService.getRules([ruleID]);
×
590
              if (ruleData.size === 0) {
×
591
                this.logger.info(`No rules were found for rule identifier ${ruleID}`);
×
592
                continue;
×
593
              }
×
594
              if (!policy.combinables.has(ruleID) && ruleData.size === 1) {
×
595
                policy.combinables.set(ruleID, null);
×
596
              }
×
597
            }
×
598
          }
×
599
        }
×
600
        if (!_.isEmpty(policy) && policy.id) {
×
601
          policies.set(policy.id, policy);
×
602
        }
×
603
      }
×
604
    }
×
605

16✔
606
    return policies;
16✔
607
  }
16✔
608

5✔
609
  async delete(request: DeleteRequest, ctx: any): Promise<DeepPartial<DeleteResponse>> {
5✔
610
    let resources = [];
16✔
611
    let subject = request.subject;
16✔
612
    let policyIDs = request.ids;
16✔
613
    let action, deleteResponse;
16✔
614
    if (policyIDs) {
16✔
615
      action = AuthZAction.DELETE;
1✔
616
      if (_.isArray(policyIDs)) {
1✔
617
        for (let id of policyIDs) {
1✔
618
          resources.push({ id });
1✔
619
        }
1✔
620
      } else {
1!
621
        resources = [{ id: policyIDs }];
×
622
      }
×
623
      Object.assign(resources, { id: policyIDs });
1✔
624
      await createMetadata(resources, action, subject, this);
1✔
625
    }
1✔
626
    if (request.collection) {
16✔
627
      action = AuthZAction.DROP;
15✔
628
      resources = [{ collection: request.collection }];
15✔
629
    }
15✔
630

16✔
631
    let acsResponse: DecisionResponse;
16✔
632
    try {
16✔
633
      if (!ctx) { ctx = {}; };
16!
634
      ctx.subject = subject;
16✔
635
      ctx.resources = resources;
16✔
636
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy', id: policyIDs }], action,
16✔
637
        Operation.isAllowed);
16✔
638
    } catch (err) {
16!
639
      this.logger.error('Error occurred requesting access-control-srv for delete Policies', { code: err.code, message: err.message, stack: err.stack });
×
640
      return {
×
641
        operation_status: {
×
642
          code: err.code,
×
643
          message: err.message
×
644
        }
×
645
      };
×
646
    }
×
647
    if (acsResponse.decision != Response_Decision.PERMIT) {
16!
648
      return { operation_status: acsResponse.operation_status };
×
649
    }
×
650
    deleteResponse = await super.delete(request, ctx);
16✔
651

16✔
652
    if (request?.ids?.length > 0) {
16✔
653
      for (let id of request.ids) {
16✔
654
        for (let [, policySet] of _accessController.policySets) {
25✔
655
          if (policySet.combinables.has(id)) {
1✔
656
            _accessController.removePolicy(policySet.id, id);
1✔
657
          }
1✔
658
        }
1✔
659
      }
25✔
660
    } else if (request?.collection && request.collection === true) {
16!
661
      for (let [, policySet] of _accessController.policySets) {
×
662
        policySet.combinables = new Map();
×
663
        _accessController.updatePolicySet(policySet);
×
664
      }
×
665
    }
×
666
    return deleteResponse;
16✔
667
  }
16✔
668
}
5✔
669

1✔
670
export class PolicySetService extends ServiceBase<PolicySetListResponse, PolicySetList> implements IAccessControlResourceService<PolicySet>, PolicySetServiceImplementation {
1✔
671
  cfg: any;
5✔
672
  redisClient: RedisClientType<any, any>;
5✔
673
  authZ: ACSAuthZ;
5✔
674
  constructor(logger: any, db: any, policySetTopic: Topic, cfg: any,
5✔
675
    redisClient: RedisClientType<any, any>, authZ: ACSAuthZ) {
5✔
676
    let resourceFieldConfig;
5✔
677
    if (cfg.get('fieldHandlers')) {
5✔
678
      resourceFieldConfig = cfg.get('fieldHandlers');
5✔
679
      resourceFieldConfig['bufferFields'] = resourceFieldConfig?.bufferFields?.users;
5!
680
      if (cfg.get('fieldHandlers:timeStampFields')) {
5✔
681
        resourceFieldConfig['timeStampFields'] = [];
5✔
682
        for (let timeStampFiledConfig of cfg.get('fieldHandlers:timeStampFields')) {
5✔
683
          if (timeStampFiledConfig.entities.includes('policy_sets')) {
5✔
684
            resourceFieldConfig['timeStampFields'].push(...timeStampFiledConfig.fields);
5✔
685
          }
5✔
686
        }
5✔
687
      }
5✔
688
    }
5✔
689
    super('policy_set', policySetTopic, logger, new ResourcesAPIBase(db, 'policy_sets', resourceFieldConfig), true);
5✔
690
    this.cfg = cfg;
5✔
691
    this.redisClient = redisClient;
5✔
692
    this.authZ = authZ;
5✔
693
  }
5✔
694

5✔
695
  async readMetaData(id?: string): Promise<DeepPartial<PolicySetListResponse>> {
5✔
696
    let result = await super.read(ReadRequest.fromPartial({
2✔
697
      filters: [{
2✔
698
        filters: [{
2✔
699
          field: 'id',
2✔
700
          operation: Filter_Operation.eq,
2✔
701
          value: id
2✔
702
        }]
2✔
703
      }]
2✔
704
    }), {});
2✔
705
    return result;
2✔
706
  }
2✔
707

5✔
708
  /**
5✔
709
   * Load policy sets and map them to policies.
5✔
710
   */
5✔
711
  async load(): Promise<Map<string, PolicySet>> {
5✔
712
    const data = await super.read(ReadRequest.fromPartial({}), {});
5✔
713

5✔
714
    if (!data || !data.items || data.items.length == 0) {
5✔
715
      this.logger.warn('No policy sets retrieved from database');
5✔
716
      return;
5✔
717
    }
5✔
718

×
719
    const items = data?.items ? data.items : [];
5!
720
    const policies = await policyService.load();
5✔
721
    const policySets = new Map<string, PolicySet>();
×
722

×
723
    for (let item of items) {
×
724
      if (!item?.payload?.policies) {
×
725
        this.logger.warn(`No policies were found for policy set ${item.payload.name}`);
×
726
        continue;
×
727
      }
×
728

×
729
      const policySet: PolicySetWithCombinables = marshallResource(item.payload, 'policy_set');
×
730

×
731
      _.forEach(item.payload.policies, (policyID) => {
×
732
        if (policies.has(policyID)) {
×
733
          policySet.combinables.set(policyID, policies.get(policyID));
×
734
        } else {
×
735
          this.logger.info(`No policies were found for policy identifier ${policyID}`);
×
736
        }
×
737
      });
×
738

×
739
      policySets.set(policySet.id, policySet);
×
740
    }
×
741

×
742
    return policySets;
×
743
  }
×
744

5✔
745
  async superUpsert(request: PolicySetList, context: any): Promise<DeepPartial<PolicySetListResponse>> {
5✔
746
    const result = await super.upsert(request, context);
×
747
    if (result?.items?.length > 0) {
×
748
      for (let item of result.items) {
×
749
        const policySet = marshallResource(item?.payload, 'policy_set');
×
750
        const policyIDs = item?.payload?.policies;
×
751
        if (!_.isEmpty(policyIDs)) {
×
752
          policySet.combinables = await policyService.getPolicies(policyIDs);
×
753
          if (policySet.combinables.size != policyIDs.length) {
×
754
            for (let id of policyIDs) {
×
755
              if (!policySet.combinables.has(id)) {
×
756
                policySet.combinables.set(id, null);
×
757
              }
×
758
            }
×
759
          }
×
760
        }
×
761
        _accessController.updatePolicySet(policySet);
×
762
      }
×
763
    }
×
764
    return result;
×
765
  }
×
766

5✔
767
  async create(request: PolicySetList, ctx: any): Promise<DeepPartial<PolicySetListResponse>> {
5✔
768
    let subject = request.subject;
16✔
769
    // update meta data for owner information
16✔
770
    let items = request.items;
16✔
771
    items = await createMetadata(items, AuthZAction.CREATE, subject, this);
16✔
772

16✔
773
    let acsResponse: DecisionResponse;
16✔
774
    try {
16✔
775
      if (!ctx) { ctx = {}; };
16!
776
      ctx.subject = subject;
16✔
777
      ctx.resources = items;
16✔
778
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy_set', id: items.map(item => item.id) }], AuthZAction.CREATE,
16✔
779
        Operation.isAllowed);
16✔
780
    } catch (err) {
16!
781
      this.logger.error('Error occurred requesting access-control-srv for create PolicySets', { code: err.code, message: err.message, stack: err.stack });
×
782
      return {
×
783
        operation_status: {
×
784
          code: err.code,
×
785
          message: err.message
×
786
        }
×
787
      };
×
788
    }
×
789
    if (acsResponse.decision != Response_Decision.PERMIT) {
16!
790
      return { operation_status: acsResponse.operation_status };
×
791
    }
×
792
    const result = await super.create(request, ctx);
16✔
793
    if (result?.items?.length > 0) {
16✔
794
      for (let item of result.items) {
16✔
795
        const policySet = marshallResource(item?.payload, 'policy_set');
16✔
796
        const policyIDs = item?.payload?.policies;
16✔
797
        if (!_.isEmpty(policyIDs)) {
16✔
798
          policySet.combinables = await policyService.getPolicies(policyIDs);
16✔
799
          if (policySet.combinables.size != policyIDs.length) {
16✔
800
            for (let id of policyIDs) {
16✔
801
              if (!policySet.combinables.has(id)) {
25✔
802
                policySet.combinables.set(id, null);
25✔
803
              }
25✔
804
            }
25✔
805
          }
16✔
806
        }
16✔
807
        _accessController.updatePolicySet(policySet);
16✔
808
      }
16✔
809
    }
16✔
810

16✔
811
    return result;
16✔
812
  }
16✔
813

5✔
814
  async update(request: PolicySetList, ctx: any): Promise<DeepPartial<PolicySetListResponse>> {
5✔
815
    let subject = request.subject;
1✔
816
    // update meta data for owner information
1✔
817
    let items = request.items;
1✔
818
    items = await createMetadata(items, AuthZAction.MODIFY, subject, this);
1✔
819

1✔
820
    let acsResponse: DecisionResponse;
1✔
821
    try {
1✔
822
      if (!ctx) { ctx = {}; };
1!
823
      ctx.subject = subject;
1✔
824
      ctx.resources = items;
1✔
825
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy_set', id: items.map(item => item.id) }], AuthZAction.MODIFY,
1✔
826
        Operation.isAllowed);
1✔
827
    } catch (err) {
1!
828
      this.logger.error('Error occurred requesting access-control-srv for update PolicySets', { code: err.code, message: err.message, stack: err.stack });
×
829
      return {
×
830
        operation_status: {
×
831
          code: err.code,
×
832
          message: err.message
×
833
        }
×
834
      };
×
835
    }
×
836
    if (acsResponse.decision != Response_Decision.PERMIT) {
1!
837
      return { operation_status: acsResponse.operation_status };
×
838
    }
×
839
    const result = await super.update(request, ctx);
1✔
840

1✔
841
    // update in memory policies if no exception was thrown
1✔
842
    for (let item of request?.items) {
1✔
843
      let policySet = _accessController.policySets.get(item.id);
1✔
844
      let policies = policySet.combinables;
1✔
845

1✔
846
      if (_.has(item, 'policies')) {
1✔
847
        for (let [policyID, policy] of policies) {
1✔
848
          if (_.indexOf(item.policies, policyID) == -1) {
1!
849
            policies.delete(policyID);
×
850
          }
×
851
        }
1✔
852

1✔
853
        let missingIDs: string[] = [];
1✔
854
        for (let policyID of item.policies) {
1✔
855
          if (!policySet.combinables.has(policyID)) {
2✔
856
            missingIDs.push(policyID);
1✔
857
          }
1✔
858
        }
2✔
859

1✔
860
        if (!_.isEmpty(missingIDs.length)) {
1!
861
          const newPolicies = await policyService.getPolicies(missingIDs);
×
862
          if (newPolicies.size != missingIDs.length) {  // checking for non-existing policies in DB
×
863
            for (let id of missingIDs) {
×
864
              if (!newPolicies.has(id)) {
×
865
                newPolicies.set(id, null);
×
866
              }
×
867
            }
×
868
          }
×
869
          policies = new Map([...policies, ...newPolicies]);
×
870
        }
×
871
      }
1✔
872

1✔
873
      policySet = _.merge(policySet, marshallResource(item, 'policy_set'));
1✔
874
      policySet.combinables = policies;
1✔
875
      _accessController.policySets.set(policySet.id, policySet);
1✔
876
    }
1✔
877

1✔
878
    return result;
1✔
879
  }
1✔
880

5✔
881
  async delete(request: DeleteRequest, ctx: any): Promise<DeepPartial<DeleteResponse>> {
5✔
882
    let resources = [];
16✔
883
    let subject = request.subject;
16✔
884
    let policySetIDs = request.ids;
16✔
885
    let action, deleteResponse;
16✔
886
    if (policySetIDs) {
16✔
887
      action = AuthZAction.DELETE;
1✔
888
      if (_.isArray(policySetIDs)) {
1✔
889
        for (let id of policySetIDs) {
1✔
890
          resources.push({ id });
1✔
891
        }
1✔
892
      } else {
1!
893
        resources = [{ id: policySetIDs }];
×
894
      }
×
895
      Object.assign(resources, { id: policySetIDs });
1✔
896
      await createMetadata(resources, action, subject, this);
1✔
897
    }
1✔
898
    if (request.collection) {
16✔
899
      action = AuthZAction.DROP;
15✔
900
      resources = [{ collection: request.collection }];
15✔
901
    }
15✔
902

16✔
903
    let acsResponse: DecisionResponse;
16✔
904
    try {
16✔
905
      if (!ctx) { ctx = {}; };
16!
906
      ctx.subject = subject;
16✔
907
      ctx.resources = resources;
16✔
908
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy_set', id: policySetIDs }], action,
16✔
909
        Operation.isAllowed);
16✔
910
    } catch (err) {
16!
911
      this.logger.error('Error occurred requesting access-control-srv for delete PolicySets', { code: err.code, message: err.message, stack: err.stack });
×
912
      return {
×
913
        operation_status: {
×
914
          code: err.code,
×
915
          message: err.message
×
916
        }
×
917
      };
×
918
    }
×
919
    if (acsResponse.decision != Response_Decision.PERMIT) {
16!
920
      return { operation_status: acsResponse.operation_status };
×
921
    }
×
922
    deleteResponse = await super.delete(request, ctx);
16✔
923

16✔
924
    if (request?.ids?.length > 0) {
16✔
925
      for (let id of request.ids) {
16✔
926
        _accessController.removePolicySet(id);
16✔
927
      }
16✔
928
    } else if (request.collection && request.collection == true) {
16!
929
      _accessController.clearPolicies();
×
930
    }
×
931
    return deleteResponse;
16✔
932
  }
16✔
933

5✔
934
  async read(request: ReadRequest, ctx: any): Promise<DeepPartial<PolicySetListResponse>> {
5✔
935
    let subject = request.subject;
1✔
936
    let acsResponse: PolicySetRQResponse;
1✔
937
    try {
1✔
938
      if (!ctx) { ctx = {}; };
1!
939
      ctx.subject = subject;
1✔
940
      ctx.resources = [];
1✔
941
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy_set' }], AuthZAction.READ,
1✔
942
        Operation.whatIsAllowed) as PolicySetRQResponse;
1✔
943
    } catch (err) {
1!
944
      this.logger.error('Error occurred requesting access-control-srv for read PolicySets', { code: err.code, message: err.message, stack: err.stack });
×
945
      return {
×
946
        operation_status: {
×
947
          code: err.code,
×
948
          message: err.message
×
949
        }
×
950
      };
×
951
    }
×
952
    if (acsResponse.decision != Response_Decision.PERMIT) {
1!
953
      return { operation_status: acsResponse.operation_status };
×
954
    }
×
955
    if (acsResponse?.custom_query_args?.length > 0) {
1!
956
      request.custom_queries = acsResponse.custom_query_args[0].custom_queries;
×
957
      request.custom_arguments = acsResponse.custom_query_args[0].custom_arguments;
×
958
    }
×
959
    const result = await super.read(request, ctx);
1✔
960
    return result;
1✔
961
  }
1✔
962

5✔
963
  async upsert(request: PolicySetList, ctx: any): Promise<DeepPartial<PolicySetListResponse>> {
5✔
964
    let subject = request.subject;
×
965
    // update meta data for owner information
×
966
    let items = request.items;
×
967
    items = await createMetadata(items, AuthZAction.MODIFY, subject, this);
×
968

×
969
    let acsResponse: DecisionResponse;
×
970
    try {
×
971
      if (!ctx) { ctx = {}; };
×
972
      ctx.subject = subject;
×
973
      ctx.resources = items;
×
974
      acsResponse = await checkAccessRequest(ctx, [{ resource: 'policy_set', id: items.map(item => item.id) }], AuthZAction.MODIFY,
×
975
        Operation.isAllowed);
×
976
    } catch (err) {
×
977
      this.logger.error('Error occurred requesting access-control-srv for upsert PolicySets', { code: err.code, message: err.message, stack: err.stack });
×
978
      return {
×
979
        operation_status: {
×
980
          code: err.code,
×
981
          message: err.message
×
982
        }
×
983
      };
×
984
    }
×
985
    if (acsResponse.decision != Response_Decision.PERMIT) {
×
986
      return { operation_status: acsResponse.operation_status };
×
987
    }
×
988
    const result = await super.upsert(request, ctx);
×
989
    return result;
×
990
  }
×
991
}
5✔
992

1✔
993
export class ResourceManager {
1✔
994

5✔
995
  cfg: any;
5✔
996
  logger: Logger;
5✔
997
  events: Events;
5✔
998
  db: any;
5✔
999
  redisClient: RedisClientType<any, any>;
5✔
1000
  authZ: any;
5✔
1001

5✔
1002
  constructor(cfg: any, logger: Logger, events: Events, db: any,
5✔
1003
    accessController: AccessController, redisClient: RedisClientType<any, any>, authZ: ACSAuthZ) {
5✔
1004
    _accessController = accessController;
5✔
1005
    this.cfg = cfg;
5✔
1006
    this.logger = logger;
5✔
1007
    this.events = events;
5✔
1008
    this.db = db;
5✔
1009
    this.redisClient = redisClient;
5✔
1010
    this.authZ = authZ;
5✔
1011
  }
5✔
1012

5✔
1013
  async setup() {
5✔
1014
    const kafkaCfg = this.cfg.get('events:kafka');
5✔
1015
    const rulesTopic = await this.events.topic(kafkaCfg.topics['rule.resource'].topic);
5✔
1016
    const policyTopic = await this.events.topic(kafkaCfg.topics['policy.resource'].topic);
5✔
1017
    const policySetTopic = await this.events.topic(kafkaCfg.topics['policy_set.resource'].topic);
5✔
1018

5✔
1019
    policySetService = new PolicySetService(this.logger, this.db, policySetTopic, this.cfg, this.redisClient, this.authZ);
5✔
1020
    policyService = new PolicyService(this.logger, this.db, policyTopic, rulesTopic, this.cfg, this.redisClient, this.authZ);
5✔
1021
    ruleService = new RuleService(this.logger, rulesTopic, this.db, this.cfg, this.redisClient, this.authZ);
5✔
1022
  }
5✔
1023

5✔
1024
  getResourceService(resource: string): RuleService | PolicyService | PolicySetService {
5✔
1025
    switch (resource) {
20✔
1026
      case 'policy_set':
20✔
1027
        return policySetService;
10✔
1028
      case 'policy':
20✔
1029
        return policyService;
5✔
1030
      case 'rule':
20✔
1031
        return ruleService;
5✔
1032
      default: throw new Error(`Unknown resource ${resource}`);
20!
1033
    }
20✔
1034
  }
20✔
1035
}
5✔
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