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.