Skip to content

Commit

Permalink
refactor(context_query): check whether context_query is truly empty
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerald Baulig committed Mar 18, 2024
1 parent c7a52b2 commit 7487765
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 23 deletions.
66 changes: 48 additions & 18 deletions src/core/accessController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,23 @@ export class AccessController {
resourceAdapter: ResourceAdapter;
redisClient: RedisClientType<any, any>;
userTopic: Topic;
waiting: any[];
waiting: any;
cfg: any;
userService: UserServiceClient;
constructor(private logger: Logger, opts: AccessControlConfiguration,
userTopic: Topic, cfg: any, userService: UserServiceClient) {

constructor(
private logger: Logger,
opts: AccessControlConfiguration,
userTopic: Topic,
cfg: any,
userService: UserServiceClient
) {
this.policySets = new Map<string, PolicySetWithCombinables>();
this.combiningAlgorithms = new Map<string, any>();

logger.info('Parsing combining algorithms from access control configuration...');
// parsing URNs and mapping them to functions
const combiningAlgorithms: CombiningAlgorithm[] = opts?.combiningAlgorithms || [];
const combiningAlgorithms: CombiningAlgorithm[] = opts?.combiningAlgorithms ?? [];
for (let ca of combiningAlgorithms) {
const urn = ca.urn;
const method = ca.method;
Expand Down Expand Up @@ -115,22 +121,29 @@ export class AccessController {

// policyEffect needed to evalute if the properties should be PERMIT / DENY
let policyEffect: Effect;
if ((!!policySet.target && await this.targetMatches(policySet.target, request, 'isAllowed', obligations))
|| !policySet.target) {
if (
!policySet.target
|| await this.targetMatches(policySet.target, request, 'isAllowed', obligations)
) {
let exactMatch = false;
for (let [, policyValue] of policySet.combinables) {
const policy: Policy = policyValue;
if (policy.effect) {
policyEffect = policy.effect;
} else if (policy.combining_algorithm) {
}
else if (policy.combining_algorithm) {
const method = this.combiningAlgorithms.get(policy.combining_algorithm);
if (method === 'permitOverrides') {
policyEffect = Effect.PERMIT;
} else if (method === 'denyOverrides') {
policyEffect = Effect.DENY;
}
}
if (!!policy.target && await this.targetMatches(policy.target, request, 'isAllowed', obligations, policyEffect)) {

if (
policy.target
&& await this.targetMatches(policy.target, request, 'isAllowed', obligations, policyEffect)
) {
exactMatch = true;
break;
}
Expand All @@ -151,11 +164,18 @@ export class AccessController {
continue;
}
const ruleEffects: EffectEvaluation[] = [];
if ((!!policy.target && exactMatch && await this.targetMatches(policy.target, request, 'isAllowed', obligations, policyEffect))
if (
!policy.target
|| (
exactMatch
&& await this.targetMatches(policy.target, request, 'isAllowed', obligations, policyEffect)
)
// regex match
|| (!!policy.target && !exactMatch && await this.targetMatches(policy.target, request, 'isAllowed', obligations, policyEffect, true))
|| !policy.target) {

|| (
!exactMatch
&& await this.targetMatches(policy.target, request, 'isAllowed', obligations, policyEffect, true)
)
) {
const rules: Map<string, Rule> = policy.combinables;
this.logger.verbose(`Checking policy ${policy.name}`);
// only apply a policy effect if there are no rules
Expand Down Expand Up @@ -184,19 +204,26 @@ export class AccessController {
}

if (matches) {
this.logger.verbose(`Checking rule ${rule.name}`);
this.logger.verbose(`Checking rule HR Scope for ${rule.name}`);
if (matches && rule.target) {
matches = await checkHierarchicalScope(rule.target, request, this.urns, this, this.logger);
}

try {
if (matches && !_.isEmpty(rule.condition)) {
if (matches && rule.condition?.length) {
// context query is only checked when a rule exists
let context: any;
if (!_.isEmpty(rule.context_query) && this.resourceAdapter) {
if (
this.resourceAdapter
&& (
rule.context_query?.filters?.length
|| rule.context_query?.query?.length
)
) {
context = await this.pullContextResources(rule.context_query, request);

if (_.isNil(context)) {
this.logger.debug('Context query response is empty!');
return { // deny by default
decision: Response_Decision.DENY,
obligations,
Expand All @@ -209,12 +236,12 @@ export class AccessController {
}
}

request.context = context || request.context;
request.context = context ?? request.context;
this.logger.debug('Validating rule condition', { name: rule.name, condition: rule.condition });
matches = conditionMatches(rule.condition, request);
this.logger.debug('condition validation response', { matches });
}
} catch (err) {
} catch (err: any) {
this.logger.error('Caught an exception while applying rule condition to request', { code: err.code, message: err.message, stack: err.stack });
return { // if an exception is caught deny by default
decision: Response_Decision.DENY,
Expand Down Expand Up @@ -296,7 +323,10 @@ export class AccessController {
let obligations: Attribute[] = [];
for (let [, value] of this.policySets) {
let pSet: PolicySetRQ;
if (_.isEmpty(value.target) || await this.targetMatches(value.target, request, 'whatIsAllowed', obligations)) {
if (
_.isEmpty(value.target)
|| await this.targetMatches(value.target, request, 'whatIsAllowed', obligations)
) {
pSet = _.merge({}, { combining_algorithm: value.combining_algorithm }, _.pick(value, ['id', 'target', 'effect'])) as any;
pSet.policies = [];

Expand Down
10 changes: 5 additions & 5 deletions src/core/hierarchicalScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ export const checkHierarchicalScope = async (ruleTarget: Target,
}

if (_.isNil(entityOrOperation) || _.isEmpty(entityOrOperation)) {
logger.debug('No Entity or operation name found');
logger.debug('No entity or operation name found');
// return false; // no entity found
}

Expand Down Expand Up @@ -179,7 +179,7 @@ export const checkHierarchicalScope = async (ruleTarget: Target,
for (let roleScopeInstObj of attribute.attributes) { // role-attributes-attributes -> roleScopingInstance
if (roleScopeInstObj.id == urns.get('roleScopingInstance') && !!scopingEntity) { // if scoping instance is found within the attributes
const instances = entities.get(scopingEntity);
if (!_.isEmpty(_.remove(instances, i => i == roleScopeInstObj.value))) { // if any element was removed
if (!_.isEmpty(_.remove(instances, (i: string) => i == roleScopeInstObj.value))) { // if any element was removed
if (_.isEmpty(instances)) {
entities.delete(scopingEntity);
if (entities.size == 0) {
Expand Down Expand Up @@ -217,11 +217,11 @@ export const checkHierarchicalScope = async (ruleTarget: Target,
if (!check && hierarchicalRoleScopeCheck && hierarchicalRoleScopeCheck === 'true') {
const hierarchicalScopes = context.subject.hierarchical_scopes;
for (let hierarchicalScope of hierarchicalScopes) {
let subTreeRole = null;
let subTreeRole: string = null;
let level = -1;
traverse(hierarchicalScope).forEach(function (node: any): void { // depth-first search
let subtreeFound = false;
if (!!node.id) {
if (node.id) {
if (level > -1 && this.level >= level) {
subTreeRole = null;
level = -1;
Expand All @@ -239,7 +239,7 @@ export const checkHierarchicalScope = async (ruleTarget: Target,
}
if (subtreeFound) {
const entities = scopedRoles.get(subTreeRole);
let eligibleOrgScopes = [];
let eligibleOrgScopes: string[] = [];
getAllValues(node, eligibleOrgScopes);
if (entities) {
for (let [entity, instances] of entities) {
Expand Down

0 comments on commit 7487765

Please sign in to comment.