Best Practices: Using Permissions as BitFlags
Developers often miss the opportunity to express permissions as a collection of enumerated bitflags; where complex permissions can be easily grouped by context.
Consider the scenario where a user may have 30 or more permission flags.
See StackBlitz Demo
Managing permissions as bit flags allows each set of permissions to be expressed as a single whole number. The collection of bits can be manipulated as Sets (union, intersect) and flags can be easily toggled.
export enum PermissionsEnum {
NONE = 0,
ALL = 1 << 0,
DISCLAIMER_ACCESS = 1 << 1,
VIEW_DOCUMENTS = 1 << 2,
VIEW_PERMISSIONS = 1 << 3,
VIEW_REPORTS = 1 << 4,
VIEW_USERS = 1 << 5,
NEWFILES_VIEW = 1 << 6,
FAVORITE_VIEW = 1 << 7,
TRASH_VIEW = 1 << 8,
QUEUE_VIEW = 1 << 9,
TASK_VIEW = 1 << 10,
PROJECT_SEND_EMAIL = 1 << 11,
PROJECT_FOLDER_ADD = 1 << 12,
PROJECT_FILEROOM_ADD = 1 << 13,
PROJECT_FILEROOM_ACTIVATE = 1 << 14,
PROJECT_FILEROOM_DEACTIVATE = 1 << 15,
PROJECT_USER_INVITE = 1 << 16,
PROJECT_USER_DEACTIVATE = 1 << 17,
PROJECT_USER_ACTIVATE = 1 << 18,
PROJECT_USER_CAG_CHANGE = 1 << 19,
PROJECT_USER_FAG_CHANGE = 1 << 20,
TEAM_QA_QUESTION = 1 << 21,
TEAM_QA_ANSWER = 1 << 22,
TEAM_QA_ANSWER_VIEW = 1 << 23,
TEAM_QA_APPROVER = 1 << 24,
CONTENT_UPLOAD = 1 << 25,
CONTENT_PERMISSIONS = 1 << 26,
CONTENT_RENAME = 1 << 27,
CONTENT_DELETE = 1 << 28,
CONTENT_MOVE = 1 << 28,
CONTENT_COPY = 1 << 30,
CONTENT_REPLACE = 1 << 31,
CONTENT_SEARCH = 1 << 32,
CONTENT_DOWNLOAD = 1 << 33,
CONTENT_UPDATE = 1 << 34,
CONTENT_DOWNLOAD_BULK = 1 << 35,
CONTENT_PERMANENT_DELETE = 1 << 36,
}
/**
* Abstract base class for common methods
*
* Thx to @Nitin for suggestion!
*/
abstract class AbstractPermissions {
// Enforce 'permissions' as readOnly
get permissions() { return this._permissions };
constructor(private _permissions:number = 0) { }
protected hasPerms = (perms:number) => !!(this._permissions & (PermissionsEnum.ALL | perms));
}
// Approach #1: extending base class
class ProjectPermissions extends AbstractPermissions {
get canViewDocuments() { return this.hasPerms(PermissionsEnum.VIEW_DOCUMENTS );}
get canViewPermissions() { return this.hasPerms(PermissionsEnum.VIEW_PERMISSIONS );}
get canViewReports() { return this.hasPerms(PermissionsEnum.VIEW_REPORTS );}
get canViewUsers() { return this.hasPerms(PermissionsEnum.VIEW_USERS );}
get canViewNewFiles() { return this.hasPerms(PermissionsEnum.NEWFILES_VIEW );}
get canViewFavorites() { return this.hasPerms(PermissionsEnum.FAVORITE_VIEW );}
get canViewTrash() { return this.hasPerms(PermissionsEnum.TRASH_VIEW );}
get canViewQueue() { return this.hasPerms(PermissionsEnum.QUEUE_VIEW );}
get canViewTasks() { return this.hasPerms(PermissionsEnum.TASK_VIEW );}
}
// Approach #2: no inheritance
class ContentPermissions {
get canUpload() { return !!(this.permissions & PermissionsEnum.CONTENT_UPLOAD); }
get canRename() { return !!(this.permissions & PermissionsEnum.CONTENT_RENAME); }
get canExpunge() { return !!(this.permissions & PermissionsEnum.CONTENT_DELETE); }
get canMove() { return !!(this.permissions & PermissionsEnum.CONTENT_MOVE); }
get canCopy() { return !!(this.permissions & PermissionsEnum.CONTENT_COPY); }
get canReplace() { return !!(this.permissions & PermissionsEnum.CONTENT_REPLACE); }
get canSearch() { return !!(this.permissions & PermissionsEnum.CONTENT_SEARCH); }
get canUpdate() { return !!(this.permissions & PermissionsEnum.CONTENT_UPDATE); }
get canDownload() { return !!(this.permissions & PermissionsEnum.CONTENT_DOWNLOAD); }
get canDownloadBulk() { return !!(this.permissions & PermissionsEnum.CONTENT_DOWNLOAD_BULK); }
get canDelete() { return !!(this.permissions & PermissionsEnum.CONTENT_PERMANENT_DELETE); }
constructor(private permissions:number = 0) {}
}
class QAPermissions {
get canQuestion() { return !!(this.permissions & PermissionsEnum.TEAM_QA_QUESTION ); }
get canAnswer() { return !!(this.permissions & PermissionsEnum.TEAM_QA_ANSWER ); }
get canAnswerView() { return !!(this.permissions & PermissionsEnum.TEAM_QA_ANSWER_VIEW); }
get canApprove() { return !!(this.permissions & PermissionsEnum.TEAM_QA_APPROVER ); }
constructor(private permissions:number = 0) {}
}
// ***************************************************************
// Using a UserSession service
// ***************************************************************
interface UserPermissions = {
project ?: number,
content ?: number,
qa ?: number
}
/**
* Load current user permissions for 'project' settings only
*/
function loadPermissions(userSession:UserSession): Observable<UserPermissions> {
returns userSession.select((allUserPerms:UserPermissions) => allUserPerms.project);
}
Here is the original version which manifests many issues:
export class UserPermissionModel {
PROJECT_DOCUMENT_VIEW = false;
PROJECT_PERMISSION_VIEW = false;
PROJECT_USER_VIEW = false;
PROJECT_REPORTS_VIEW = false;
PROJECT_MANG_VIEW = false;
PROJECT_NEWFILES_VIEW = false;
PROJECT_FAVORITE_VIEW = false;
PROJECT_TRASH_VIEW = false;
PROJECT_PROCESSINGQUEUE_VIEW = false;
PROJECT_FOLDER_ADD = false;
PROJECT_FILEROOM_ADD = false;
PROJECT_FILEROOM_ACTIVATE = false;
PROJECT_FILEROOM_DEACTIVATE = false;
PROJECT_CONTENT_UPLOAD = false;
PROJECT_CONTENT_PERMISSIONS = false;
PROJECT_CONTENT_RENAME = false;
PROJECT_CONTENT_DELETE = false;
PROJECT_CONTENT_MOVE = false;
PROJECT_CONTENT_COPY = false;
PROJECT_CONTENT_REPLACE = false;
PROJECT_CONTENT_SEARCH = false;
PROJECT_CONTENT_DOWNLOAD_BULK = false;
PROJECT_CONTENT_PERMANENT_DELETE = false;
PROJECT_CONTENT_DOWNLOAD = false;
PROJECT_CONTENT_UPDATE = false;
PROJECT_TASK_VIEW = false;
PROJECT_SEND_EMAIL = false;
TEAM_QA_QUESTION = false;
TEAM_QA_ANSWER = false;
TEAM_QA_ANSWER_VIEW = false;
TEAM_QA_APPROVER = false;
PROJECT_DISCLAIMER_ACCESS_GRANTED = false;
}
// *************************************************************************
// Using a UserSession service and manually transforming to desired model.
// *************************************************************************
function loadPermissions(userSession:UserSession): void {
returns userSession.select((perms:UserPermissionModel) => {
return {
canAddFileroom : perms.PROJECT_FILEROOM_ADD,
cannotViewAddFolder : perms.PROJECT_FOLDER_ADD,
cannotRename : perms.PROJECT_CONTENT_RENAME,
cannotMove : perms.PROJECT_CONTENT_MOVE,
cannotCopy : perms.PROJECT_CONTENT_COPY,
cannotReplace : perms.PROJECT_CONTENT_REPLACE,
cannotTrash : perms.PROJECT_TRASH_VIEW,
cannotAddCategory : perms.USER_CATEGORY_MANAGE,
cannotRenameCategory: perms.USER_CATEGORY_MANAGE,
cannotPermDelete : perms.PROJECT_CONTENT_PERMANENT_DELETE,
cannotDownload : perms.PROJECT_CONTENT_DOWNLOAD,
cannotViewAddFile : perms.PROJECT_FOLDER_ADD
};
});
}