Authorization
Introduction
Authorization refers to the rules that dictate whether an agent can or can’t do certain actions. This rules take the form of: someone can do something, somewhere, or more generically: a subject has a permission on an object.
Application roles
We’ve talked about one of the possible objects that can act as a subject: agents. But creating rules for a single agent would be too specific for most use cases. Normally, rules are created by stating that a group of agents can or can’t do something. E.g. “The editors can edit these documents”, “The readers can read these other documents”, etc.
These groups are commonly named after their “role” on the application and they are only meaningful in the context of the application itself.
In Carbon, application roles have a hierarchical structure. This structure starts with the app-admin/
role which is created automatically by the platform whenever a new application is created.
This role should only be used by developers, as being a member of it gives access to every document the application contains.
Creating an app role
The first thing to do when structuring your application’s security scheme, is identify and create all the roles needed for your application.
Once the roles are identified, they can be created with the createChild
method of the auth.roles
service of the application context. This method is very similar to the documents
service’s createChild
method (which we already covered). It accepts the parent role (its slug, complete id or the actual role document) as the first parameter, the object that will be used to create the new app role’s document (remember, everything’s a document!), and an optional slug:
let appContext;
// ... app context retrieval
// Provide a human friendly name for the role
let role = App.Role.Factory.create( "Editors" );
appContext.auth.roles.createChild( "app-admin/", role, "editors/" ).then(
( [ editorsRole, responses ] ) => {
// ...
}
).catch( console.error );
import * as App from "carbonldp/App";
import * as HTTP from "carbonldp/HTTP";
let appContext:App.Context;
// ... app context retrieval
// Provide a human friendly name for the role
let role:App.Role.Class = App.Role.Factory.create( "Editors" );
appContext.auth.roles.createChild( "app-admin/", role, "editors/" ).then(
( [ editorsRole, responses ]:[ App.PersistedRole.Class, HTTP.Response.Class[] ] ) => {
// ...
}
).catch( console.error );
var appContext;
// ... app context retrieval
// Provide a human friendly name for the role
var role = App.Role.Factory.create( "Editors" );
appContext.auth.roles.createChild( "app-admin/", role, "editors/" ).then(
function( [ editorsRole, responses ] ) {
// ...
}
).catch( console.error );
PersistedRole
document, you can call its createChild
method just like you would do it with the auth.roles
service (without specifying the parent).Adding agents to a role
An application role is just a group of agents that represents a functional “role” in the application. Therefore, the roles won’t do much unless we add agents to them . This can be done through the method addAgent
or addAgents
of the auth.roles
service (or of a PersistedRole
document):
// ... imports
let appContext;
// ... app context retrieval
appContext.auth.roles.addAgent( "editors/", "john-smith/" ).then(
( response ) => {
// ...
}
).catch( console.error );
import * as App from "carbonldp/App";
import * as HTTP from "carbonldp/HTTP";
let appContext:App.Context;
// ... app context retrieval
appContext.auth.roles.addAgent( "editors/", "john-smith/" ).then(
( response:HTTP.Response.Class ) => {
// ...
}
).catch( console.error );
// ... imports
var appContext;
// ... app context retrieval
appContext.auth.roles.addAgent( "editors/", "john-smith/" ).then(
function( response ) {
// ...
}
).catch( console.error );
Permissions
Now that we’ve talked about the possible subjects, it’s time to talk about the possible actions. These actions are represented by permissions in Carbon.
They can be referenced in code through the Carbon.NS.CS.Class
object:
import * as CS from "carbonldp/NS/CS";
console.log( CS.Class.Read ); // id of the Read permission
import * as CS from "carbonldp/NS/CS";
console.log( CS.Class.Read ); // id of the Read permission
console.log( Carbon.NS.CS.Class.Read ); // id of the Read permission
- Read: Retrieve a document through methods like
documents
service’sget
method - Download: Download a file
- Update: Modify a document and persist the changes, for example with the
documents
service’ssave
method - ManageSecurity: Modify a document’s ACL (read ACL section)
- CreateChild: Create children of a document, for example with the
documents
service’screateChild
method - CreateAccessPoint: Create access points of a document, for example with the
documents
service’screateAccessPoint
method - Upload: Upload a file on a document, for example with the
documents
service’supload
method - AddMember: Add a member to a document, for example with the
documents
service’saddMember
method - RemoveMember: Remove a member of a document, for example with the
documents
service’sremoveMember
method - Delete: Delete a document, for example with the
documents
service’sdelete
method
ACLs
Finally, its time to talk about the objects where the subjects have permissions on. These objects as you may have guessed are any Carbon document available in an application.
These documents store their security schema in what’s called ACLs (or Access Control List). Which store the rules we’ve been talking about that apply to the document itself and to its children.
Retrieving an ACL
To retrieve a document’s ACL you can use the documents
service’s getACL
method:
// ... imports
let appContext;
// ... app context retrieval
appContext.documents.getACL( "some-document/" ).then(
( [ acl, response ] ) => {
// ...
}
).catch( console.error );
import * as App from "carbonldp/App";
import * as HTTP from "carbonldp/HTTP";
import * as PersistedACL from "carbonldp/PersistedACL";
let appContext:App.Context;
// ... app context retrieval
appContext.documents.getACL( "some-document/" ).then(
( [ acl, response ]:[ PersistedACL.Class, HTTP.Response.Class ] ) => {
// ...
}
).catch( console.error );
// ... imports
var appContext;
// ... app context retrieval
appContext.documents.getACL( "some-document/" ).then(
function( [ acl, response ] ) {
// ...
}
).catch( console.error );
Or you can also use the PersistedProtectedDocument
getACL
method. Any document is a PersistedProtectedDocument
(except for ACLs).
// ... imports
let appContext;
// ... app context retrieval
appContext.documents.get( "some-document/" ).then(
( [ someDocument, response ] ) => {
return someDocument.getACL();
}
).then(
( [ someDocumentACL, response ] ) => {
// ...
}
).catch( console.error );
import * as App from "carbonldp/App";
import * as HTTP from "carbonldp/HTTP";
import * as PersistedACL from "carbonldp/PersistedACL";
import * as PersistedProtectedDocument from "carbonldp/PersistedProtectedDocument";
let appContext:App.Context;
// ... app context retrieval
appContext.documents.get( "some-document/" ).then(
( [ someDocument, response ]:[ PersistedProtectedDocument.Class, HTTP.Response.Class ] ) => {
return someDocument.getACL();
}
).then(
( [ someDocumentACL, response ]:[ PersistedACL.Class, HTTP.Response.Class ] ) => {
// ...
}
).catch( console.error );
var appContext;
// ... app context retrieval
appContext.documents.get( "some-document/" ).then(
function( [ someDocument, response ] ) {
return someDocument.getACL();
}
).then(
function( [ someDocumentACL, response ] ) {
// ...
}
).catch( console.error );
Granting or denying permissions
Once you have retrieved a document’s ACL you can start granting permissions to subjects or denying them. This can be done with the PersistedACL
‘s grant
method and PersistedACL
‘s deny
method respectively.
In these methods both the subject and permission(s) need to be specified, along with the subject’s type: Carbon.NS.CS.Class.Agent
or Carbon.NS.CS.Class.AppRole
Like with any other document, don’t forget to save the ACL’s state after modifying it:
import * as CS from "carbonldp/NS/CS";
let editorsRole;
// .. role retrieval
let blogPostACL;
// ... ACL retrieval
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // null, the ACL doesn't have a specific rule about this combination
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // null, same
blogPostACL.grant( editorsRole, CS.Class.AppRole, [
CS.Class.Read,
CS.Class.Update,
CS.Class.Delete,
CS.Class.Upload
] );
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // true
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // false
blogPostACL.deny( editorsRole, CS.Class.AppRole, CS.Class.Upload ); // We won't allow editors to upload files directly to the blog post
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // false
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // true
blogPostACL.save().then(
( response ) => {
// ...
}
).catch( console.error );
import * as CS from "carbonldp/NS/CS";
import * as HTTP from "carbonldp/HTTP";
import * as PersistedACL from "carbonldp/Auth/PersistedACL";
import * as PersistedRole from "carbonldp/Auth/PersistedRole";
let editorsRole:PersistedRole.Class;
// .. role retrieval
let blogPostACL:PersistedACL.Class;
// ... ACL retrieval
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // null, the ACL doesn't have a specific rule about this combination
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // null, same
blogPostACL.grant( editorsRole, CS.Class.AppRole, [
CS.Class.Read,
CS.Class.Update,
CS.Class.Delete,
CS.Class.Upload
] );
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // true
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // false
blogPostACL.deny( editorsRole, CS.Class.AppRole, CS.Class.Upload ); // We won't allow editors to upload files directly to the blog post
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // false
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // true
blogPostACL.save().then(
( response:HTTP.Response.Class ) => {
// ...
}
).catch( console.error );
var editorsRole;
// .. role retrieval
var blogPostACL;
// ... ACL retrieval
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // null, the ACL doesn't have a specific rule about this combination
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // null, same
blogPostACL.grant( editorsRole, CS.Class.AppRole, [
CS.Class.Read,
CS.Class.Update,
CS.Class.Delete,
CS.Class.Upload
] );
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // true
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // false
blogPostACL.deny( editorsRole, CS.Class.AppRole, CS.Class.Upload ); // We won't allow editors to upload files directly to the blog post
console.log( blogPostACL.grants( editorsRole, CS.Class.Upload ) ); // false
console.log( blogPostACL.denies( editorsRole, CS.Class.Upload ) ); // true
blogPostACL.save().then(
function( response ) {
// ...
}
).catch( console.error );
Inheritance
grant
and deny
will only apply rules where the object is the document owner of the ACL. But an ACL can also contain rules that will apply to all of the document’s children (and the children of its children, and so on).
This can be done by using the configureChildInheritance
method. It’s usage is basically the same, except that we need to specify whether the inheritance will grant or deny the permissions:
import * as CS from "carbonldp/NS/CS";
let editorsRole;
// .. role retrieval
// Instead of affecting only one blog post, we'll set the permissions on the parent of all blog posts to apply rules to all of them
let blogPostsACL;
// ... ACL retrieval
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Read ) ); // null, the ACL doesn't have a rule for this combination
blogPostACL.configureChildInheritance( true, editorsRole, CS.Class.AppRole, [
CS.Class.Read,
CS.Class.Update,
CS.Class.Delete
] );
// We won't allow editors to upload files directly to blog posts
blogPostACL.configureChildInheritance( false, editorsRole, CS.Class.AppRole, CS.Class.Upload );
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Read ) ); // true, the ACL is granting the permission
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Upload ) ); // false, the ACL is denying the permission
blogPostACL.save().then(
( response ) => {
// ...
}
).catch( console.error );
import * as CS from "carbonldp/NS/CS";
import * as HTTP from "carbonldp/HTTP";
import * as PersistedACL from "carbonldp/Auth/PersistedACL";
import * as PersistedRole from "carbonldp/Auth/PersistedRole";
let editorsRole:PersistedRole.Class;
// .. role retrieval
// Instead of affecting only one blog post, we'll set the permissions on the parent of all blog posts to apply rules to all of them
let blogPostsACL:PersistedACL.Class;
// ... ACL retrieval
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Read ) ); // null, the ACL doesn't have a rule for this combination
blogPostACL.configureChildInheritance( true, editorsRole, CS.Class.AppRole, [
CS.Class.Read,
CS.Class.Update,
CS.Class.Delete
] );
// We won't allow editors to upload files directly to blog posts
blogPostACL.configureChildInheritance( false, editorsRole, CS.Class.AppRole, CS.Class.Upload );
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Read ) ); // true, the ACL is granting the permission
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Upload ) ); // false, the ACL is denying the permission
blogPostACL.save().then(
( response:HTTP.Response.Class ) => {
// ...
}
).catch( console.error );
var editorsRole;
// .. role retrieval
// Instead of affecting only one blog post, we'll set the permissions on the parent of all blog posts to apply rules to all of them
var blogPostsACL;
// ... ACL retrieval
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Read ) ); // null, the ACL doesn't have a rule for this combination
blogPostACL.configureChildInheritance( true, editorsRole, CS.Class.AppRole, [
CS.Class.Read,
CS.Class.Update,
CS.Class.Delete
] );
// We won't allow editors to upload files directly to blog posts
blogPostACL.configureChildInheritance( false, editorsRole, CS.Class.AppRole, CS.Class.Upload );
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Read ) ); // true, the ACL is granting the permission
console.log( blogPostACL.getChildInheritance( editorsRole, CS.Class.Upload ) ); // false, the ACL is denying the permission
blogPostACL.save().then(
function( response ) {
// ...
}
).catch( console.error );