Authorization

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 somethingsomewhere, 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 );
As you can see, the previous example is very similar to how a normal document is created. All patterns that applied there apply here too. For example, if you retrieve a 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 );
Like almost all methods in the SDK, to reference a document (in this case an agent), you can use its relative id, its absolute id, or the actual document object (resolved or unresolved).

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
Here’s a list of all the permissions in Carbon:

  • Read: Retrieve a document through methods like documents service’s get method
  • Download: Download a file
  • Update: Modify a document and persist the changes, for example with the documents service’s savemethod
  • ManageSecurity: Modify a document’s ACL (read ACL section)
  • CreateChild: Create children of a document, for example with the documents service’s createChildmethod
  • CreateAccessPoint: Create access points of a document, for example with the documents service’s createAccessPoint method
  • Upload: Upload a file on a document, for example with the documents service’s upload method
  • AddMember: Add a member to a document, for example with the documents service’s addMembermethod
  • RemoveMember: Remove a member of a document, for example with the documents service’s removeMember method
  • Delete: Delete a document, for example with the documents service’s delete 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 );
This can be more expensive or cheaper depending on if you have already retrieved the document or not.

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 );
Note

The methods: grantdeny or getChildInheritance will only give you information about the rules that ACL has about the document. Their output won’t be affected by inheritance.

We are working on an API to ease retrieving what permissions are granted on a document independently on why they are granted.