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:
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.
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
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.
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
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.
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/
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
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
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.