Skip to content

Commit

Permalink
feat: added grantedRoleMapReader
Browse files Browse the repository at this point in the history
  • Loading branch information
dereekb committed May 30, 2022
1 parent 9d5b785 commit 11d2f17
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/model/src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './service';
export * from './transform';
1 change: 1 addition & 0 deletions packages/model/src/lib/service/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './permission';
1 change: 1 addition & 0 deletions packages/model/src/lib/service/permission/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './role';
61 changes: 61 additions & 0 deletions packages/model/src/lib/service/permission/role.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { GrantedRoleMap, grantedRoleMapReader, KnownGrantedRole } from './role';

const FIRST_ROLE = 'first';
const SECOND_ROLE = 'second';

type TestRoles = KnownGrantedRole | typeof FIRST_ROLE | typeof SECOND_ROLE;

describe('grantedRoleMapReader()', () => {
const rolesMap: GrantedRoleMap<TestRoles> = {
read: true,
first: true
};

it('should create a reader for the input.', () => {
const result = grantedRoleMapReader(rolesMap);
expect(result).toBeDefined();
});

describe('reader', () => {
const reader = grantedRoleMapReader(rolesMap);

describe('hasRole', () => {
it('should return true if the role is granted.', () => {
expect(reader.hasRole('first')).toBe(true);
});

it('should return false if the role not is granted.', () => {
expect(reader.hasRole('second')).toBe(false);
});
});

describe('containsRoles', () => {
describe('any', () => {
it('should return true if the role is granted.', () => {
expect(reader.containsRoles('any', 'first')).toBe(true);
});

it('should return true if the roles are granted.', () => {
expect(reader.containsRoles('any', ['read', 'first'])).toBe(true);
});

it('should return true if any role is granted.', () => {
expect(reader.containsRoles('any', ['read', 'second'])).toBe(true);
});

it('should return false if none of the input roles are granted.', () => {
expect(reader.containsRoles('any', 'second')).toBe(false);
});
});
describe('all', () => {
it('should return true if all roles are granted.', () => {
expect(reader.containsRoles('all', ['read', 'first'])).toBe(true);
});

it('should return false if only some of the input roles are granted.', () => {
expect(reader.containsRoles('all', ['read', 'second'])).toBe(false);
});
});
});
});
});
115 changes: 115 additions & 0 deletions packages/model/src/lib/service/permission/role.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { ArrayOrValue, asArray, Maybe, SetIncludesMode } from '@dereekb/util';

/**
* A granted role for a model.
*/
export type GrantedRole = string;

export const GRANTED_READ_ROLE_KEY = 'read';

/**
* Communicates that the current context has read access to a model.
*/
export type GrantedReadRole = typeof GRANTED_READ_ROLE_KEY;

export type KnownGrantedRole = GrantedReadRole;

export const GRANTED_FULL_ACCESS_ROLE_KEY = '__FULL__';

/**
* Communicates that the current context has full access to a model.
*/
export type GrantedFullAccessGrantedRole = typeof GRANTED_FULL_ACCESS_ROLE_KEY;

export const NO_ACCESS_ROLES_MAP_KEY = '__EMPTY__';
export type NoAccessGrantedRole = typeof NO_ACCESS_ROLES_MAP_KEY;

export type NoAccessRolesMap = {
[NO_ACCESS_ROLES_MAP_KEY]: true;
};

export type FullAccessRolesMap = {
[GRANTED_FULL_ACCESS_ROLE_KEY]: true;
};

export type GrantedRoleMap<T extends GrantedRole = string> = NoAccessRolesMap | FullAccessRolesMap | GrantedRoleKeysMap<T>;

export type GrantedRoleKeysMap<T extends GrantedRole = string> = {
[key in T]?: Maybe<boolean>;
};

export interface GrantedRoleMapReader<T extends GrantedRole = string> {
/**
* Returns true if no access has been given.
*/
hasNoAccess(): boolean;

/**
* Returns true if the role is granted.
*/
hasRole(role: T): boolean;

/**
* Returns true if the roles are granted.
*/
hasRoles(setIncludes: SetIncludesMode, roles: ArrayOrValue<T>): boolean;

/**
* Returns true if the map explicitly contains the role.
*/
containsRoles(setIncludes: SetIncludesMode, roles: ArrayOrValue<T>): boolean;
}

export function grantedRoleMapReader<T extends GrantedRole = string>(map: GrantedRoleMap<T>): GrantedRoleMapReader<T> {
return new GrantedRoleMapReaderInstance(map);
}

export class GrantedRoleMapReaderInstance<T extends GrantedRole = string> implements GrantedRoleMapReader<T> {
constructor(private readonly _map: GrantedRoleMap<T>) {}

hasNoAccess(): boolean {
return (this._map as NoAccessRolesMap)[NO_ACCESS_ROLES_MAP_KEY];
}

hasRole(role: T): boolean {
return this.hasRoles('any', role);
}

hasRoles(setIncludes: SetIncludesMode, inputRoles: ArrayOrValue<T>): boolean {
if ((this._map as FullAccessRolesMap)[GRANTED_FULL_ACCESS_ROLE_KEY]) {
return true;
} else {
return this.containsRoles(setIncludes, inputRoles);
}
}

containsRoles(setIncludes: SetIncludesMode, inputRoles: ArrayOrValue<T>): boolean {
const roles = asArray(inputRoles);

if (setIncludes === 'any') {
return this.containsAnyRole(roles);
} else {
return this.containsEachRole(roles);
}
}

containsAnyRole(roles: GrantedRole[]): boolean {
for (const role of roles) {
if ((this._map as GrantedRoleKeysMap)[role]) {
return true;
}
}

return false;
}

containsEachRole(roles: GrantedRole[]): boolean {
for (let role of roles) {
if (!(this._map as GrantedRoleKeysMap)[role]) {
return false;
}
}

return true;
}
}
2 changes: 1 addition & 1 deletion packages/util/src/lib/set/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function filterValuesFromSet<T>(values: T[], set: Set<T>, exclude = false
* - all_reverse: All values must be included in the set (values is a subset of set)
* - any: Any value from values is in the set
*/
export type SetIncludesMode = 'all' | 'all_reverse' | 'any';
export type SetIncludesMode = 'all' | 'any';

/**
* Contextual function that checks whether or not the input values are included.
Expand Down

0 comments on commit 11d2f17

Please sign in to comment.