Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: release #109

Merged
merged 5 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
433 changes: 377 additions & 56 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/acs-client/cfg/config_test.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"logger": {
"console": {
"handleExceptions": false,
"level": "silly",
"level": "error",
"colorize": true,
"prettyPrint": true
}
Expand Down
2 changes: 1 addition & 1 deletion packages/acs-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
"build:tsc:watch": "tsc -d --watch",
"build:clean": "rimraf lib",
"build": "npm-run-all lint build:clean build:tsc",
"lint": "eslint src --ext .ts",
"lint": "eslint src --ext .ts --fix",
"test": "npm run build && nyc npm run mocha && npm run mocha:cache",
"mocha": "cross-env NODE_ENV=test mocha --full-trace --exit --timeout 30000",
"mocha:cache": "cross-env NODE_ENV=test CACHE_ENABLED=true mocha --full-trace --exit --timeout 30000",
Expand Down
26 changes: 18 additions & 8 deletions packages/acs-client/src/acs/authz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
NoAuthWhatIsAllowedTarget,
PolicySetRQResponse,
Request,
Resource,
ACSResource,
} from './interfaces';
import { createChannel, createClient } from '@restorecommerce/grpc-client';
import { cfg, updateConfig } from '../config';
Expand Down Expand Up @@ -106,7 +106,7 @@ export const formatResourceType = (type: string, namespacePrefix?: string): stri
}
};

export const createResourceTarget = (resource: Resource[], action: AuthZAction) => {
export const createResourceTarget = (resource: ACSResource[], action: AuthZAction) => {
const flattened: Attribute[] = [];
resource.forEach((resourceObj) => {
if (action != AuthZAction.EXECUTE) {
Expand Down Expand Up @@ -211,9 +211,14 @@ export class UnAuthZ implements IAuthZ {

let response: DecisionResponse;
try {
const isAllowed = await getOrFill(authZRequest, async (req) => {
return await this.acs.isAllowed(authZRequest);
}, useCache, 'UnAuthZ:isAllowed');
const isAllowed = await getOrFill(
authZRequest,
async (_) => {
return await this.acs.isAllowed(authZRequest);
},
useCache,
'UnAuthZ:isAllowed'
);

response = {
decision: isAllowed.decision,
Expand Down Expand Up @@ -257,9 +262,14 @@ export class UnAuthZ implements IAuthZ {
};
let response: PolicySetRQResponse;
try {
const whatIsAllowed = await getOrFill(authZRequest, async (req) => {
return await this.acs.whatIsAllowed(authZRequest);
}, useCache, 'UnAuthZ:whatIsAllowed');
const whatIsAllowed = await getOrFill(
authZRequest,
async (req) => {
return await this.acs.whatIsAllowed(authZRequest);
},
useCache,
'UnAuthZ:whatIsAllowed'
);

response = {
...whatIsAllowed,
Expand Down
34 changes: 15 additions & 19 deletions packages/acs-client/src/acs/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ import logger from '../logger';
import * as crypto from 'crypto';
import { createClient, RedisClientType } from 'redis';
import { _ } from '../utils';
import {
PolicySetRQResponse,
DecisionResponse,
} from './interfaces';

let attempted = false;
let redisInstance: RedisClientType<any, any>;
Expand Down Expand Up @@ -50,7 +54,7 @@ export const initializeCache = async () => {
* @param prefix The prefix to apply to the object key in the cache
*/
export const getOrFill = async <T, M>(keyData: T, filler: (data: T) => Promise<M>,
useCache: boolean, prefix?: string): Promise<M | undefined> => {
useCache: boolean, prefix?: string): Promise<M> => {
if (!redisInstance || !cacheEnabled) {
return filler(keyData);
}
Expand All @@ -63,26 +67,18 @@ export const getOrFill = async <T, M>(keyData: T, filler: (data: T) => Promise<M
}
let redisKeyResponse = await redisInstance.get(redisKey);
if (redisKeyResponse && useCache) {
const response = JSON.parse(redisKeyResponse);
let evaluation_cacheable = response.evaluation_cacheable;
if (response && !_.isEmpty(response.policy_sets)) {
const policies = response.policy_sets[0].policies;
if (policies?.length > 0) {
for (let policy of policies) {
for (let rule of policy.rules) {
if (!rule.evaluation_cacheable || (rule.evaluation_cacheable === false)) {
evaluation_cacheable = rule.evaluation_cacheable;
break;
} else if (rule.evaluation_cacheable) {
evaluation_cacheable = rule.evaluation_cacheable;
}
}
}
}
}
const response = JSON.parse(redisKeyResponse) as PolicySetRQResponse & DecisionResponse;
const evaluation_cacheable = response?.evaluation_cacheable || response?.policy_sets?.some(
policy_set => policy_set?.policies?.some(
policy => policy?.evaluation_cacheable !== false && policy.rules?.some(
rule => rule?.evaluation_cacheable
)
)
);

if (evaluation_cacheable) {
logger.debug('Found key in cache: ' + redisKey);
return response;
return response as M;
}
}

Expand Down
9 changes: 5 additions & 4 deletions packages/acs-client/src/acs/decorators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import {
} from './cache';
import {
Operation,
Resource,
ACSResource,
AuthZAction,
ACSClientContext,
DecisionResponse,
PolicySetRQResponse,
Obligation,
} from './interfaces';
import {
accessRequest,
Expand All @@ -44,7 +45,7 @@ import {
/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
export type DatabaseProvider = 'arangoDB' | 'postgres';
export type ACSClientContextFactory<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<ACSClientContext>;
export type ResourceFactory<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<Resource[]>;
export type ResourceFactory<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<ACSResource[]>;
export type DatabaseSelector<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<DatabaseProvider>;
export type MetaDataInjector<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<T>;
export type SubjectResolver<T extends ResourceList> = (self: any, request: T, ...args: any) => Promise<T>;
Expand Down Expand Up @@ -192,7 +193,7 @@ export function access_controlled_function<T extends ResourceList>(kwargs: {
action: AuthZAction;
operation: Operation;
context?: ACSClientContext | ACSClientContextFactory<T>;
resource?: Resource[] | ResourceFactory<T>;
resource?: ACSResource[] | ResourceFactory<T>;
database?: DatabaseProvider | DatabaseSelector<T>;
useCache?: boolean;
}) {
Expand Down Expand Up @@ -259,7 +260,7 @@ export function access_controlled_function<T extends ResourceList>(kwargs: {

const appResponse = await method.apply(this, arguments);
const property = acsResponse.obligations?.filter(
o => resource.some(r => r.resource === o.resource)
(o: Obligation) => resource.some((r) => r.resource === o.resource)
).flatMap(
o => o.property
).flatMap(
Expand Down
87 changes: 39 additions & 48 deletions packages/acs-client/src/acs/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,35 @@ import { Meta } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/
import { FilterOp } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/resource_base';
import {
Response_Decision,
ReverseQuery,
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control';
import { Effect } from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/rule';

export {
import {
PolicySetRQ,
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/policy_set';
import {
PolicyRQ,
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/policy';
import {
RuleRQ,
Target as AttributeTarget,
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/rule';
import {
Response_Decision as Decision,
Context,
Response,
} from '@restorecommerce/rc-grpc-clients/dist/generated-server/io/restorecommerce/access_control';

export {
Decision,
Context,
RuleRQ,
PolicyRQ,
PolicySetRQ,
Response as ACSResponse,
AttributeTarget,
};

export enum AuthZAction {
CREATE = 'CREATE',
READ = 'READ',
Expand All @@ -31,7 +52,7 @@ export enum Operation {
whatIsAllowed = 'whatIsAllowed'
}

export interface Resource {
export interface ACSResource {
resource: string;
id?: string | string[]; // for what is allowed operation id is not mandatory
property?: string[];
Expand Down Expand Up @@ -82,13 +103,8 @@ export interface Obligation {
property: string[];
}

export interface DecisionResponse {
decision?: Response_Decision;
export type DecisionResponse = Response & {
obligations?: Obligation[];
operation_status?: {
code?: number;
message?: string;
};
};

export interface Target<TSubject, TResource, TAction> {
Expand All @@ -102,14 +118,10 @@ export interface Request<TTarget, TContext> {
context: TContext;
}

export interface Response {
decision: Response_Decision;
}

/**
* isAllowed Authorization interface
*/
export interface AuthZ<TSubject, TContext = any, TResource = Resource, TAction = AuthZAction> {
export interface AuthZ<TSubject, TContext = any, TResource = ACSResource, TAction = AuthZAction> {
/**
* Check is the subject is allowed to do an action on a specific resource
*/
Expand All @@ -122,11 +134,11 @@ export interface Credentials {
[key: string]: any;
}

export type AuthZTarget = Target<Subject, Resource[], AuthZAction>;
export type NoAuthTarget = Target<UnauthenticatedData, Resource[], AuthZAction>;
export type AuthZTarget = Target<Subject, ACSResource[], AuthZAction>;
export type NoAuthTarget = Target<UnauthenticatedData, ACSResource[], AuthZAction>;

export type AuthZWhatIsAllowedTarget = Target<Subject, Resource[], AuthZAction>;
export type NoAuthWhatIsAllowedTarget = Target<UnauthenticatedData, Resource[], AuthZAction>;
export type AuthZWhatIsAllowedTarget = Target<Subject, ACSResource[], AuthZAction>;
export type NoAuthWhatIsAllowedTarget = Target<UnauthenticatedData, ACSResource[], AuthZAction>;

export interface AuthZContext {
// session-related tokens
Expand All @@ -149,9 +161,13 @@ export interface AuthZResponse extends Response {
obligation: string;
}

export interface IAuthZ extends AuthZ<Subject | UnauthenticatedData, AuthZContext, Resource[], AuthZAction> {
whatIsAllowed: (request: Request<AuthZWhatIsAllowedTarget | NoAuthWhatIsAllowedTarget, AuthZContext>,
ctx: ACSClientContext, useCache: boolean, roleScopingEntityURN: string) => Promise<PolicySetRQResponse>;
export interface IAuthZ extends AuthZ<Subject | UnauthenticatedData, AuthZContext, ACSResource[], AuthZAction> {
whatIsAllowed: (
request: Request<AuthZWhatIsAllowedTarget | NoAuthWhatIsAllowedTarget, AuthZContext>,
ctx: ACSClientContext,
useCache: boolean,
roleScopingEntityURN: string
) => Promise<PolicySetRQResponse>;
}

export interface UserCredentials extends Credentials {
Expand Down Expand Up @@ -191,11 +207,6 @@ export interface AccessControlObjectInterface {
condition?: string;
}

export interface PolicySetRQ extends AccessControlObjectInterface {
// CA and policies
combining_algorithm?: string;
policies?: PolicyRQ[];
}

export interface ResourceFilterMap {
resource: string;
Expand All @@ -209,32 +220,12 @@ export interface CustomQueryArgs {
}

// Reverse query response
export interface PolicySetRQResponse extends AccessControlObjectInterface {
policy_sets?: PolicySetRQ[];
export type PolicySetRQResponse = ReverseQuery & {
filters?: ResourceFilterMap[];
custom_query_args?: CustomQueryArgs[];
obligations?: Obligation[];
decision?: Response_Decision;
operation_status?: {
code?: number;
message?: string;
};
}

export interface PolicyRQ extends AccessControlObjectInterface {
rules?: RuleRQ[];
has_rules?: boolean;
combining_algorithm?: string;
}

export interface RuleRQ extends AccessControlObjectInterface { }

export interface AttributeTarget {
// each map is an attribute with (key, value) pairs
subjects: Attribute[];
resources: Attribute[];
actions: Attribute[];
}
};

export interface TargetReq {
subjects: Attribute[];
Expand Down
10 changes: 5 additions & 5 deletions packages/acs-client/src/acs/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
DecisionResponse,
PolicySetRQResponse,
Operation,
Resource,
ACSResource,
AuthZAction,
ACSClientOptions,
} from './interfaces';
Expand All @@ -40,7 +40,7 @@ const subjectIsUnauthenticated = (subject: any): subject is UnauthenticatedConte
return subject?.unauthenticated === true;
};

const whatIsAllowedRequest = async (subject: DeepPartial<Subject>, resources: Resource[],
const whatIsAllowedRequest = async (subject: DeepPartial<Subject>, resources: ACSResource[],
actions: AuthZAction, ctx: ACSClientContext, useCache: boolean) => {
if (subjectIsUnauthenticated(subject)) {
return await unauthZ.whatIsAllowed({
Expand All @@ -66,7 +66,7 @@ const whatIsAllowedRequest = async (subject: DeepPartial<Subject>, resources: Re
};

export const isAllowedRequest = async (subject: Subject,
resources: Resource[], actions: AuthZAction, ctx: ACSClientContext, useCache: boolean): Promise<DecisionResponse> => {
resources: ACSResource[], actions: AuthZAction, ctx: ACSClientContext, useCache: boolean): Promise<DecisionResponse> => {
if (subjectIsUnauthenticated(subject)) {
return await unauthZ.isAllowed({
target: {
Expand Down Expand Up @@ -97,7 +97,7 @@ export const isAllowedRequest = async (subject: Subject,
* or policy set reverse query `PolicySetRQ` depending on the requeste operation `isAllowed()` or
* `whatIsAllowed()` respectively.
* @param {Subject} subject Contains subject information
* @param {Resource[]} resource Contains resource name, resource instance and optional resource properties
* @param {ACSResource[]} resource Contains resource name, resource instance and optional resource properties
* @param {AuthZAction} action Action to be performed on resource
* @param {ACSClientContext} ctx Context containing Subject and Context Resources for ACS
* @param {Operation} operation Operation to perform `isAllowed` or `whatIsAllowed`,
Expand All @@ -110,7 +110,7 @@ export const isAllowedRequest = async (subject: Subject,
*/
export const accessRequest = async (
subject: DeepPartial<Subject>,
resource: Resource[],
resource: ACSResource[],
action: AuthZAction,
ctx: ACSClientContext,
options?: ACSClientOptions
Expand Down
6 changes: 3 additions & 3 deletions packages/acs-client/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import deepdash from 'deepdash';
export const _ = deepdash(lodash);
import {
PolicySetRQ, PolicySetRQResponse, AttributeTarget, HierarchicalScope,
ResourceFilterMap, CustomQueryArgs, DecisionResponse, Resource, AuthZAction,
ResourceFilterMap, CustomQueryArgs, DecisionResponse, ACSResource, AuthZAction,
ResolvedSubject, Obligation
} from './acs/interfaces';
import { QueryArguments, UserQueryArguments } from './acs/resolver';
Expand Down Expand Up @@ -620,7 +620,7 @@ export interface FilterMapResponse {
* It iterates through each resource and filter the applicable policies and
* provide them to buildFilterPermissions to create filters for each of the resource requested
*
* @param {Resource[]} resource Contains resource name, resource instance and optional resource properties
* @param {ACSResource[]} resource Contains resource name, resource instance and optional resource properties
* @param {PolicSetResponse} policySetResponse contains set of applicable policies for entities list
* @param {any} resources context resources
* @param {AuthZAction} action Action to be performed on resource
Expand All @@ -633,7 +633,7 @@ export interface FilterMapResponse {
*
*/
export const createResourceFilterMap = async (
resource: Resource[],
resource: ACSResource[],
policySetResponse: PolicySetRQResponse,
resources: any, action: AuthZAction,
subject: DeepPartial<Subject>,
Expand Down
Loading
Loading