This repository contains a plugin for tyranid
that allows for graph-based acl permissions to be enforced / utilized
within tyranid simply by adding a few schema annotations.
Links
Setup
Installation
npm install tyranid-gracl
Annotate your tyranid schemas
import Tyr from 'tyranid';
const Organization = new Tyr.Collection({
id: 'o00',
name: 'organization',
dbName: 'organizations',
fields: {
_id: { is: 'mongoid' },
name: { is: 'string' },
}
});
const Team = new Tyr.Collection({
id: 't00',
name: 'team',
dbName: 'teams',
fields: {
_id: { is: 'mongoid' },
name: { is: 'string' },
organizationId: {
link: 'organization',
relate: 'ownedBy',
graclType: [ 'subject', 'resource' ]
}
}
});
export const Blog = new Tyr.Collection({
id: 'b00',
name: 'blog',
dbName: 'blogs',
fields: {
_id: { is: 'mongoid' },
name: { is: 'string' },
organizationId: {
link: 'organization',
relate: 'ownedBy',
graclType: 'resource'
}
}
});
export const UsageLog = new Tyr.Collection({
id: 'ul0',
name: 'usagelog',
dbName: 'usagelogs',
graclType: ['resource']
fields: {
_id: { is: 'mongoid' },
text: { is: 'string' }
}
});
Register the plugin
With annotated schemas, we can create and register the plugin with tyranid.
import Tyr from 'tyranid';
import pmongo from 'promised-mongo';
import { GraclPlugin } from 'tyranid-gracl';
const secure = new GraclPlugin();
const db = pmongo('mongodb://127.0.0.1:27017/tyranid_gracl_test');
Tyr.config({
db: db,
validate: [{ dir: root + '/test/models', fileMatch: '[a-z].js' }],
secure: secure
});
This will install the gracl plugin in tyranid and validate your permissions hierarchies as declared through the collection schema.
Using permissions
Now, we can utilize the provided tyranid Document prototype extensions to check/set permissions. Additionally, collection.find()
queries will be automatically filtered using the hierarchy.
Method usage examples:
import Tyr from 'tyranid';
export async function giveUserBlogViewAccessToOrg(req, res) {
const user = req.user,
organizationId = req.query.organizationId;
const org = await Tyr.byName.organization.byId(organizationId);
const updatedOrg = await org.$allow('view-blog', user);
return res.json(updatedOrg);
}
export async function checkCanViewUid(req, res) {
const user = req.user,
uid = req.query.uid;
const entity = await Tyr.byUid(uid);
const canView = await entity.$isAllowedForThis('view', user);
return res.json(canView);
}
export async function findBlogs(req, res) {
const blogs = await Tyr.byName.blog.findAll({ query: {}, auth: req.user });
return res.json(blogs);
}
export async function getQueryForBlogsICanEdit(req, res) {
const originalQuery = {
name: {
$in: ['myBlog', 'otherBlog']
}
};
const secured = await Tyr.byName.blog.secureQuery(
originalQuery,
'edit',
req.user
);
return secured;
}
export async function deletePermissionsRelatingToUid(req, res) {
const uid = req.query.uid;
await Tyr.secure.permissionsModel.deletePermissions(await Tyr.byUid(uid));
return res.json({ message: 'Success!' });
}
Explaining Permissions
In order to determine why or why not a subject is allowed access to a resource document,
you can utilize the doc.$explainAccess(permission, subject)
method:
const result = await org.$explainAccess('view-post', user);
result ===
{
explainations: [
{
type: 'ALLOW',
resourcePath: [
'b005aeb2a7199af181806f44856',
'o005aeb2a7199af181806f4484f'
],
subjectPath: [
'u005aeb2a7199af181806f44866',
't005aeb2a7199af181806f44862',
'o005aeb2a7199af181806f4484f'
],
permissionId: '5aeb2a711cb4be9be52c3844',
permissionType: 'edit-post',
property: 'blogId'
},
{
type: 'ALLOW',
resourcePath: [
'b005aeb2a7199af181806f44856',
'o005aeb2a7199af181806f4484f'
],
subjectPath: [
'u005aeb2a7199af181806f44866',
't005aeb2a7199af181806f44863',
'o005aeb2a7199af181806f4484f'
],
permissionId: '5aeb2a711cb4be9be52c3844',
permissionType: 'edit-post',
property: 'blogId'
}
],
hasAccess: true,
resourceId: 'p005aeb2a7199af181806f4485c',
subjectId: 'u005aeb2a7199af181806f44866'
};
const reason = await org.$explainAccess(
'view-post',
user,
true
);