-
Notifications
You must be signed in to change notification settings - Fork 2
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
Pluggable permissions #43
Comments
Make a permission class that is easy to modify from the outside. Sketch: Suppose you have a documents plugin with two types of permissions pertaining to managing those documents class DocumentPermissions extends AppSingleton {
list = new Permission<Document>();
read = new Permission<Document>();
create = new Permission<Document>();
modify = new Permission<Document>();
}
type PermissionResult = 'allow' | 'deny' | 'pass'
getSingleton(DocumentPermissions).list
.add({
name: string,
priority: number,
check: (doc: Document, ctx: ServiceContext) => Promise<PermisisonResult>
}) Algorithm:
Desireables:
Building a simple role based system on top of this function makeRolePermission<T extends ObjectId>(actionName: string) {
return {
get nameAsync() {
let rolesForThisAction = await ctx.getService(Roles).getAllRolesWithAction(actionName);
//return rolesForthisAction.map(r => ({name: `User has role: ${r}`, results: ['allow', 'pass']}));
return `Allow user with one of the roles: ${rolesForThisAction.join(' ')}`
},
priority: 10,
check: async (object:T, ctx) => {
let userId = this.getService(UserId).id;
let roleCheck = await ctx.getService(Roles).checkRole(actionName, userId, object.id);
return roleCheck ? 'allow' : 'deny'
}
}
}
documentPermissions.read.addRule(makeRolePermission('document.read')); Considerations:
|
Building simple permissions like cancan and pundit: documentPermissions.read.addRule({
name: 'Superadmin can access everything'
priority: 100,
check: (_, ctx) => ctx.getService(UserAdminInfo).isSuperAdmin ? 'allow' : 'pass'
} Pass means the rule will defer to other rules if the user is not super admin. Considerations
|
To be able to keep track of all permissions, you could have a singleton: app.getSingleton(Permissions).create(name: string) You could also plug into this now app.getSingleton(Permissions).onPermissionQueried((result, ctx) => {
ctx.getService(AuditLogBuilder).record({permissionCheckResult: result.checkedRules})
}) |
More rule analysis: we can protect the check: checkFn(['allow', 'deny'], fn) Now fn is not allowed to return "pass" That way you can detect anomalies like:
|
Rule ordering alternative: Instead of having numbers, create subrules as "slots" in other rules and export the slot only. Then the order is strictly determined. export class ConfigurablePermissions extends AppSingleton {
read = permission.create()
}
// not exported
class RealPermissions extends AppSingleton {
private exported = app.getSingleton(ConfigurablePermissions);
read = permission.create().add(
allowAdmins, allowTrusted, denyDeactivatedAccounts, this.exported.read, defaultDeny
);
write = permission.create().add(
allowAdmins, allowTrusted, denyDeactivatedAccounts, this.exported.write, defaultDeny
);
}
// exported
export class ConfigurablePermissions extends AppSingleton {
public read = permission.create();
public write = permission.create();
} edit: Conviniently this doubles as inheritance too. |
Here is how a report tree might look like for a single permission Permission name: document.read
Here is an example report where the tree seems to have been "cut off" Permission name: document.write
|
Consideration about graying out buttons on the frontend... Currently, our frontends are littered with code that performs the same checks that the backend does, in order to disable some input and prevent a request from happening. This is ugly for obvious reasons, like forgetting to sync the checks on both ends. For example: if (!roles.user.includes(['editor']) { makeUploadButtonGray() }` Instead, it would be nice if you can ask a backend service whether I can perform the action and receive a boolean answer. myService.checkPermissions.then(perms => { if (!perms.read) makeUploadButtonGray() }` Behind the scenes, the service would calculate the outcome of Of course, this won't work if the answer depends on the params ("can I delete this particular set of documents?") but that's fine, since you'd get 403 as a response -- rarely enough not to annoy the users. |
The role system can provide (in the rule) more details by returning
The results would probably look like
|
How to get a typesafe permission installation for RPC methods class MyService {
method1(...) { ... }
method2(...) { ... }
}
setPermissions(MyService, {
method1: permission1
method2: permission2
}); Advantages to decorators:
Advantages to checking in method body
Disadvantages
|
Simplified "slot" design:
|
space for final design.
The text was updated successfully, but these errors were encountered: