Permissions TutorialΒΆ

In the beginning there was nothing. The world was formless and void. And then Eli in his infinite tutorial-ness created a user

POST https://users.authentise.com/users/
Content-Type: application/json

{
    "email"     : "bob@hotmail.com"
    "name"      : "Bob the First",
    "password"  : "my-secret",
    "username"  : "BobTheFirst",
}
Location: https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/

And thus we see here that Bob the First was given the URI https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/ upon his creation. This is a great way to refer to Bob. The world was, then, after this first operation, thus:

../_images/users-tutorial-bob1.png

Bob is all alone. Let’s make him a thing to play with

POST https://things.authentise.com/wobbit/
{...}
Location: https://things.authentise.com/wobbit/123/

And now, lo, Bob has a Wobbit.

../_images/users-tutorial-add-wobbit1.png

Bob wants to frombulate the Wobbit. He can do this by making a strange and not-very-restful GET request to the Wobbit URL. So he sends a request

GET https://things.authentise.com/wobbit/123/?action=frombulate

======

HTTP/1.1 403

Bob has been denied the ability to frombulate the Wobbit. He has no permissions to do such a thing. Eli then reached forth in his kindness and granted Bob permission on the Wobbit

POST https://users.authentise.com/permissions/
Content-Type: application/json

{
    "holder"    : "https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/",
    "namespace" : "tutorial",
    "object"    : "https://things.authentise.com/wobbit/123/",
    "right"     : "frombulate"
}
Location: https://users.authentise.com/permissions/574d11ef-0d31-4ba3-b906-e41ece55b8fa/

Because Eli is very familiar with the tutorial world of his creation he knows that in order to frombulate the Wobbit Bob must have the frombulate right on the Wobbit in the tutorial namespace. His request, therefore, includes Bob‘s URI as the holder and the Wobbit‘s URI as the object of the new permission. He also includes the proper namespace and right. The world is now

../_images/users-tutorial-first-right1.png

Bob, in a fit of delight, attempts to frombulate the Wobbit again

GET https://things.authentise.com/wobbit/123/?action=frombulate

=========

HTTP/1.1 204

Bob is delighted! He can frombulate the Wobbit. But, how does it work? Well, when Bob makes a request to things.authentise.com the things service makes a request to the users service to find out about Bob. It passes through the cookie that Bob providedto the service

GET https://users.authentise.com/sessions/
Cookie: <Bob's cookie>

{
    "emails"    : [{
        "email" : "bob@hotmail.com",
        "uuid"  : "d4e9053b-3373-46dc-895d-2b99d5bc949b"
    }],
    "name"      : "Bob the First",
    "uri"       : "https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/",
    "username"  : "BobTheFirst",
}

The response here tells the things service that Bob‘s URI is https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/. Cool. Now the service can check if Bob has permission to frombulate the Wobbit. The things service knows that it operates in the tutorial namespace, so it makes a query to the users service

GET https://users.authentise.com/permission/?
    filter[namespace]=tutorial
    &filter[right]=frombulate
    &filter[holder]=https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/
    &filter[object]=https://things.authentise.com/wobbit/123/

{
    "resources" : [{
        "holder"    : "https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/",
        "namespace" : "tutorial",
        "object"    : "https://things.authentise.com/wobbit/123/",
        "right"     : "frombulate",
        "uri"       : "https://users.authentise.com/permissions/574d11ef-0d31-4ba3-b906-e41ece55b8fa/"
    }]
}

I’ve broken up the URL lines for easier reading, but trust me they are meant to be one large URL. Also, please note you’ll need to properly escape the URL characters which I have no included here.

The key point is that the things service was able to make a query to the users service and ask specifically if the given permission exists. It doesn’t even need to look at the contents - if at least one resource is returned the service knows that Bob can frombulate the Wobbit because it asked only for permissions that fit that description.

The source code for the things service may even look like the following:

def do_frombulate(user, target):
    if _can_frombulate(user, target):
        _really_do_frombulate(user, target)
    else:
        raise PermissionError("{} can't frombulate {} son!".format(user, target)

What we need to understand is how to write _can_masticate.

It’s actually fairly simple. We already have the user (assuming the user object here is a URI). Let’s just ask the user service directly if this is doable.

import requests

def _can_frombulate(user, target):
    response = requests.get((
        "https://users.authentise.com/permissions/?"
        "filter[holder]={}".format(user)
        "&filter[namespace]=tutorial"
        "&filter[object]={}".format(target)
        "&filter[right]=frombulate"))
    return response.ok and bool(response.json()['resources'])

That’s it!

Alright, Eli looked upon his tutorial and saw that it was good. Now he wanted to grow his world by introducing most users and more objects

POST https://users.authentise.com/users/
Content-Type: application/json

{
    "email"     : "gina@msn.com"
    "name"      : "Gina the Second",
    "password"  : "another-secret",
    "username"  : "GinaTheSecond",
}
Location: https://users.authentise.com/users/b3b8c74c-fd56-49c5-ae80-179b0dc50ed7/
POST https://things.authentise.com/vemmik/
{...}
Location: https://things.authentise.com/vemmik/abc/

And thus Gina came to being along with her Vemmik.

../_images/users-tutorial-gina1.png

Eli beheld that it would be easy to add permissions between his many creations, but would be of no educational value and thus refrained. Instead he chose to add a new concept, the group.

POST https://users.authentise.com/groups/
Content-Type: application/json

{
    "name"          : "Everyone",
    "description"   : "All the people in the known universe"
}
Location: https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8

We’ve now created a group. The world is

../_images/users-tutorial-everyone1.png

The group stands alone. No one belongs to it. Eli decided to change that by creating some memberships

POST https://users.authentise.com/memberships/
Content-Type: application/json

{
    "group" : "https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8/",
    "user"  : "https://users.authentise.com/users/b3b8c74c-fd56-49c5-ae80-179b0dc50ed7/"
}
Location: https://users.authentise.com/memberships/eddadb4e-42e4-4851-8406-34cb76d7795d/
POST https://users.authentise.com/memberships/
Content-Type: application/json

{
    "group" : "https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8/",
    "user"  : "https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/"
}
Location: https://users.authentise.com/memberships/244153c9-e8c7-4569-9a6b-7613daa509a1/

This makes Bob and Gina both members of the Everyone group.

../_images/users-tutorial-memberships1.png

So what’s so interesting about a group? Well, for one, we can add permissions to it.

POST https://users.authentise.com/permissions/
Content-Type: application/json

{
    "holder"    : "https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8/",
    "namespace" : "tutorial",
    "object"    : "https://things.authentise.com/vemmik/abc/",
    "right"     : "frombulate"
}
Location: https://users.authentise.com/permissions/5190dc45-eece-4ecf-af8e-f45dcd57e694/
../_images/users-tutorial-group-permission1.png

Now that the Everyone group has permission to frombulate the Vemmik let’s see what happens when we make the same request to get a list of Bob‘s matching permissions. Please note this is exactly the same query as before, we’ve just not limited by object

GET https://users.authentise.com/permission/?
    filter[namespace]=tutorial
    &filter[right]=frombulate
    &filter[holder]=https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/

{
    "resources" : [{
        "holder"    : "https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/",
        "namespace" : "tutorial",
        "object"    : "https://things.authentise.com/wobbit/123/",
        "right"     : "frombulate",
        "uri"       : "https://users.authentise.com/permissions/574d11ef-0d31-4ba3-b906-e41ece55b8fa/"
    },{
        "holder"    : "https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8/",
        "namespace" : "tutorial",
        "object"    : "https://things.authentise.com/vemmik/abc/",
        "right"     : "frombulate",
        "uri"       : "https://users.authentise.com/permissions/5190dc45-eece-4ecf-af8e-f45dcd57e694/"
     }]
}

The astute reader will notice that we filtered against https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/ which is the URI for Bob. Why did the results then include permissions that are granted against the group Everyone? Well, the permissions endpoint specifically returns derived permissions for a user because this is the most common case. We don’t care how Bob got the permission, only that he has it, so we assume the client wanted all permissions Bob has, not just the ones granted specifically to him. If the client only wants the permissions that are granted specifically to Bob it’s easy enough to iterate through the returned permissions and filter them.

What if we want to get the permissions for Gina?

GET https://users.authentise.com/permission/?
    filter[namespace]=tutorial
    &filter[right]=frombulate
    &filter[holder]=https://users.authentise.com/users/b3b8c74c-fd56-49c5-ae80-179b0dc50ed7/

{
    "resources" : [{
        "holder"    : "https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8/",
        "namespace" : "tutorial",
        "object"    : "https://things.authentise.com/vemmik/abc/",
        "right"     : "frombulate",
        "uri"       : "https://users.authentise.com/permissions/5190dc45-eece-4ecf-af8e-f45dcd57e694/"
     }]
}

You’ll notice that the uri field of the returned permission is exactly the same between Gina and Bob. That’s because it is exactly the same permission object. If deleted it would revoke the permission from both of them.

DELETE https://users.authentise.com/permissions/5190dc45-eece-4ecf-af8e-f45dcd57e694/

==========

HTTP/1.1 204

Now the world is

../_images/users-tutorial-delete-permission1.png

Groups are awesome.

What else can we do with groups? Well, we could actually grant permissions on a group

POST https://users.authentise.com/permissions/
Content-Type: application/json

{
    "holder"    : "https://users.authentise.com/users/b3b8c74c-fd56-49c5-ae80-179b0dc50ed7/",
    "namespace" : "users",
    "object"    : "https://users.authentise.com/groups/d3c0c9c5-8198-4ba4-86bf-4071e82a86b8/",
    "right"     : "admin"
}
Location: https://users.authentise.com/permissions/dcd8f3a1-277f-476c-bbdf-a1bec7dcb385/

This grants Gina the ability to administer Everyone. It’s good to notice we used a different namespace, users for this request

../_images/users-tutorial-admin1.png

We could even take this a step farther and grant users rights on other users. No need to get crazy though.

Going back to the permissions request you’ll notice that each part of the filter can be independently modified. We could search for all permissions from any user that grants the frombulate right. We could search for all permisions of a particular namespace. Or other interesting combinations. This can be useful when building UI. Say the user is on a page showing a particular printer and we want to decide whether or not to show various buttons for printing, sharing, viewing current print, etc. One request the UI could make is

GET https://users.authentise.com/permission/?
    filter[namespace]=print
    &filter[object]=https://print.authentise.com/printer/instance/6fa2e0c8-6e12-4f2c-b1cb-e1be64fe0ef1/
    &filter[holder]=https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/

This request says “Give me all of the rights that user X has on printer Y in the print namespace”. The UI can then just iterate over the returned permissions and display the corresponding button for each right represented.

Let’s take the UI example a little farther. What if we want to know whether or not the user should see a link to an administrator dashboard. There’s no discrete object that the permission is on. Or is there?

GET https://users.authentise.com/permission/?
    filter[namespace]=frontend
    &filter[object]=admin-dashboard
    &filter[holder]=https://users.authentise.com/users/38a64a25-0099-4b9d-a738-8001aa15323f/

In this query we’ve moved the namespace to frontend to indicate this is actually a front-end permission directly and the object is set to a string, admin-dashboard that is meaningful to the frontend service. The holder is still the same URI for the user since we’ll be using the user’s session to check the permission. This is one of the benefits of using opaque strings for permission - different services can use permissions in different ways to meet their various use cases.

With this you should be prepared to build services that are clients of the users service to handle users, groups, and arbitrary queries about permissions.

Go build stuff.