Skip to content

Commit

Permalink
feat: further optimize policy subset loading
Browse files Browse the repository at this point in the history
Only load relavant policies that match subject, resource and actions
Load policies in bulk for bulkEnforce
  • Loading branch information
shreyasadiyodi93 committed Feb 24, 2021
1 parent 935f4b8 commit d4087b6
Showing 1 changed file with 110 additions and 19 deletions.
129 changes: 110 additions & 19 deletions lib/casbin/JsonFilteredEnforcer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/* eslint-disable class-methods-use-this */
import { createQueryBuilder, In, Like } from 'typeorm';
import * as R from 'ramda';
import { createQueryBuilder, In, Like, Raw } from 'typeorm';
import { newEnforcerWithClass } from 'casbin';
import {
convertJSONToStringInOrder,
Expand All @@ -8,6 +9,24 @@ import {
OneKey,
PolicyObj
} from './JsonEnforcer';
import { toLikeQuery } from '../../app/policy/util';

const groupPolicyParameters = (policies: PolicyObj[]) => {
const res = policies.reduce(
(result: any, policy: PolicyObj) => {
result.subjects.push(convertJSONToStringInOrder(policy.subject));
result.resources.push(convertJSONToStringInOrder(policy.resource));
result.actions.push(convertJSONToStringInOrder(policy.action));
return result;
},
{ subjects: [], resources: [], actions: [] }
);
return {
subjects: <string[]>R.uniq(res.subjects),
resources: <string[]>R.uniq(res.resources),
actions: <string[]>R.uniq(res.actions)
};
};

export class JsonFilteredEnforcer extends JsonEnforcer {
public static params: any[];
Expand All @@ -26,42 +45,106 @@ export class JsonFilteredEnforcer extends JsonEnforcer {
return enforcer;
}

private async loadPolicySubset(policyObj: any, enforcer: JsonEnforcer) {
const rawSubjects = await createQueryBuilder()
private async getSubjectsForPolicySubset(subjects: string[]) {
const subjectMappings = await createQueryBuilder()
.select('casbin_rule.v1')
.from('casbin_rule', 'casbin_rule')
.where('casbin_rule.ptype = :type', { type: 'g' })
.andWhere('casbin_rule.v0 like :subject', {
subject: convertJSONToStringInOrder(policyObj.subject)
.andWhere('casbin_rule.v0 in (:...subjects)', {
subjects
})
.getRawMany();
const subjects = rawSubjects
.map((rG) => rG.v1)
.concat(convertJSONToStringInOrder(policyObj.subject));

const anyAction = Like('%*%');
return R.uniq(subjectMappings.map((sM) => sM.v1).concat(subjects));
}

private async getResourcesForPolicySubset(resources: string[]) {
const resourceMappings = await createQueryBuilder()
.select('casbin_rule.v1')
.from('casbin_rule', 'casbin_rule')
.where('casbin_rule.ptype = :type', { type: 'g2' })
.andWhere('casbin_rule.v0 in (:...resources)', {
resources
})
.getRawMany();

const mergedResources = R.mergeAll(
resourceMappings
.map((rM) => rM.v1)
.concat(resources)
.map((str) => JSON.parse(str))
);

return Object.entries(mergedResources).map(([key, val]) => {
return toLikeQuery({ [key]: val });
});
}

private async getActionsForPolicySubset(actions: string[]) {
const actionMappings = await createQueryBuilder()
.select('casbin_rule.v1')
.from('casbin_rule', 'casbin_rule')
.where('casbin_rule.ptype = :type and casbin_rule.v0 in (:...actions)', {
type: 'g3',
actions
})
.orWhere(
'casbin_rule.ptype = :type and casbin_rule.v0 like :allMatchPattern',
{
allMatchPattern: '%*%'
}
)
.getRawMany();

return R.uniq(actionMappings.map((aM) => aM.v1).concat(actions));
}

private async getEnforcerWithPolicies(policies: PolicyObj[]) {
const enforcer = await this.getEnforcer();
const { subjects, resources, actions } = groupPolicyParameters(policies);

const [
subjectsForPolicyFilter,
resourcesForPolicyFilter,
actionsForPolicyFilter
] = await Promise.all([
this.getSubjectsForPolicySubset(subjects),
this.getResourcesForPolicySubset(resources),
this.getActionsForPolicySubset(actions)
]);

const any = Like('%*%');
const queryForPoliciesWithRegex = [{ v0: any }, { v1: any }, { v2: any }];

await enforcer.loadFilteredPolicy({
where: [
{ ptype: 'p', v0: In(subjects) },
{ ptype: 'g', v0: convertJSONToStringInOrder(policyObj.subject) },
{ ptype: 'g2', v0: convertJSONToStringInOrder(policyObj.resource) },
{ ptype: 'g3', v0: convertJSONToStringInOrder(policyObj.action) },
{ ptype: 'g3', v0: anyAction }
{
ptype: 'p',
v0: In(subjectsForPolicyFilter),
v1: Raw((alias) => `${alias} like any (array[:...resources])`, {
resources: resourcesForPolicyFilter
}),
v2: In(actionsForPolicyFilter)
},
{ ptype: 'g', v0: In(subjects) },
{ ptype: 'g2', v0: In(resources) },
{ ptype: 'g3', v0: In(actions) },
...queryForPoliciesWithRegex
]
});

return enforcer;
}

public async enforceJson(
subject: JsonAttributes,
resource: JsonAttributes,
action: JsonAttributes
): Promise<boolean> {
// intantiate new enforcer
const enforcer = await this.getEnforcer();

// load filtered policy
await this.loadPolicySubset({ subject, resource, action }, enforcer);
const enforcer = await this.getEnforcerWithPolicies([
{ subject, resource, action }
]);
// enforceJson

const hasAccess = await enforcer.enforce(
Expand All @@ -74,9 +157,17 @@ export class JsonFilteredEnforcer extends JsonEnforcer {
}

public async batchEnforceJson(policies: PolicyObj[]) {
// load relevant policy subset
const enforcer = await this.getEnforcerWithPolicies(policies);

// enforce using Promise.all on each policy
const enforceBatchResult = await Promise.all(
policies.map(async (policy: PolicyObj) =>
this.enforceJson(policy.subject, policy.resource, policy.action)
enforcer.enforce(
convertJSONToStringInOrder(policy.subject),
convertJSONToStringInOrder(policy.resource),
convertJSONToStringInOrder(policy.action)
)
)
);
return enforceBatchResult;
Expand Down

0 comments on commit d4087b6

Please sign in to comment.