Treetracker permission system
To define all the permissions and how to organize them on Keyclaok
This document is about how to use the auth system to manage users on treetracker, and how to build new authorization policy, and explain how does this work.
Current Situation (legacy system)
Now we are using self-built auth system, it's based on: user account, role, user-role-mapping, and the role system supports a json-based policy, the policy is composed of permission items, and organization setting.
An example of policy:
Another one:
New Solution
The new solution is based on Keycloak + ambassador filter, with the Keycloak's resource based authorization system, we can provide a much more powerful permission system, and handle much more complicated permission rules in the future.
With ambassaor, we will add a single auth-service app to handle the permission checking, then all our micro-services will get protected, and we can achieve all these without modifying the current existing micro-services.
How to use the auth system
This section talks about how to daily maintain the auth system, create user account, assign permission. This is for the reader who wants to JUST maintain the user system.
How to create an admin user (user-manager)
Log into Keycloak https://dev-k8s.treetracker.org/auth/
-> Select
master
realm-> goto
Users
-> click
Add user
-> goto
role mapping
Add
admin
role for this user.Input user info form and submit
-> goto
Credential
Set password for the user
Done, new user can log in and manage the whole system.
Assign user roles that the same as before
Below roles are the same as before, just assign users with these roles then the user would get the corresponding permission.
Tree Manager (Greenstand operator only)
Planter Manager (Greenstand operator only)
Earnings Manager
Payments Manager
Comms Manager
Capture Matcher
Species Manager
Stakeholder Manager
To create an user and give them the xxx role.
Log into Keycloak https://dev-k8s.treetracker.org/auth/
-> Select
treetracker
realm-> Select
users
-> Click
add user
Fill the user info form and submit
-> Click
credental
Input the password for the user.
-> Click
role mapping
-> Select the
xxx
for the userDone, now the created user is able to log in with all of corresponding permissions.
Organization user
Organization users handle the case like giving the Haiti Tree Project (as an organization) the ablity to operate the data (tree, planter) belonging to the Haiti organization.
To create an organization user:
Create a normal user the same as above.
-> Click
Group
-> Click
new
Input the organization name, e.g.
Tree Boston
-> Click
Attributes
Input key
id
and valuen
here,n
is the id of this organization in the DB-> Click
Add
Input key
uuid
and valuen
, heren
is the uuid, stakeholder id of this organization in DBBack to the user info page that one we just created.
-> Click
group
-> Click the organization name we just created
-> Click
join
(to assign role) -> Click
role mappings
-> Select role:
organization-user
Done, now the user can log in and can visit all those data belongs to that organization.
Note, must choose: organization-user
as the role, this is the key to give the user organization-spesified behaviors.
How does it work and advanced useage of auth system
Glossary
Resource: the unit of a functionality on treetracker, for example: planter-list
Scope: the action against the resource, e.g. view/edit the planter list
Permission: resource + scope, e.g.
planter-list:view
Role: a name, like:
planter manager
Policy: a rule or condition, like: if the user is a role of
planter manager
So on the Keycloak, we compose all these concept above to express something like:
If an user is planter manager
role, then he/her should be able to view
the planter-list
Example: earnings manager
This example explains a workflow of adding a new function: earnings tool into the system, and how to create a new resource: earnings
and give its permission to users, and let the app check if an user has corresponding permission, if the result is false, then they are not able to visit the function, and it includes checking the client and the server-side(micro-service)
Create a new resource:
earnings
on Keycloak (ignore the detailed steps)Create scope
edit
if there isn't one.Create role
earnings-manager
Create a user, and assign the role
earnings-manager
to the user.Now we are all set in terms of the auth side, but if
earnings
resource is a new function in the system, then we need to add checking point in code to let the app know how to check this resource, we need both client and server-side to be aware of theearnings
resource and do the check. These two steps need the developer to do the work.(To add checking into the client, e.g. admin panel) on the menu page, add code similar to below (the real code would be a bit different with this, the user is the object return from Keycloak after logged in):
6. To let earnings micro-service check this permission, we need to change the configuration of auth-service
which is the single point to check all our API requests.
Say, our earnings micro-service is on: https://prod-k8s.treetracker.org/earnings/, and we want all the requests targeting to this URL to have the permission earnings:edit
6.1 Go to auth-service
repository, https://github.com/Greenstand/treetracker-auth
6.2 Open file: keycloak.json
6.3 Add code as below under the path
property:
So here, the name
is the resource name (earnings), and we will check all traffic matching /earnings
and check both get
and post
havings the permission: earnings:edit
7. Done
The case for organization
The organization requires a dynamic checking, we need to analyze the request from clients and check if the resource is granted to the user (by access token).
An example
The best way to explain it is by an example, this is how do we display tree count planted by an organization:
Translate the code above to our auth system:
For API GET /organizations/:organization_id/trees/count
we need the user to have the permission for a resource (for instance count-of-tree-in-organization
) , and the scope/action is view, and we also need to match the organization id, means, the id in the request should be the same as the id we assigned to this user.
Let's go through the whole workflow:
The User opens admin panel and log in by Keycloak
In the response from Keyclaok, we can get the user info, which includes the property:
organization_id=xxx
(similar as before)The user opens the page which displays the count of the organization.
Request
GET /organizations/xxx/trees/count
Ambassador intercept the request and gives it to
auth-service
In advance, we should set up the auth-service with config like below:
Basically, the config above would parse the request and bring the organization_id
as a claim to Keyclaok.
On Keycloak, the permission:
count-of-tree-in-organization:view
will be attached to a policy, say:belongs-to-an-organiztion
this policy will use a javascript-based rule to check the permission, which looks like this:
So this policy will verify if the user's claim (the organization_id) matches the one set for this user (by assigning the user to a group, as we mentioned above)
If the check passed, then Keyclaok grants the permission
The request goes forward to real micro-service.
The micro-service returns the data to the client.
Done
Conclusion
All the explanation above is to demonstrate how to build the auth system, they all are not the final form.
And the whole system has high flexibility and also after the first launch, we can change a lot of things on the auth system to build all kinds of granting rules to fulfill our needs and don't need to change our business code on client and server-side if that change is just about re-organizing our permission rule.
For example, we can control the grain size of the permission, for the earnings, from:
GET /earnings/*
to
Get /earnings/batch/:batch_id
And also, for the organization tree count example, instead of providing resources like above, the count-of-tree-in-organzation
(reflect to GET /organizations/:organization_id/trees/count
, we can use more coarse-grain: operation-in-organization
to reflect to GET /organizations/:organization_id/*
Last updated