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:
{
"policies": [
{
"name": "list_stakeholders",
"description": "Can view stakeholders"
},
{
"name": "manage_stakeholders",
"description": "Can modify stakeholder information"
}
]
}Another one:
{
"policies": [
{
"name": "list_tree",
"description": "Can view trees"
},
{
"name": "approve_tree",
"description": "Can approve/reject trees"
},
{
"name": "list_planter",
"description": "Can view planters"
},
{
"name": "manage_planter",
"description": "Can modify planter information"
}
],
"organization": {
"name": "WRIAddis",
"id": 774,
"uuid": "fb341197-143d-4ad5-9b1d-08024d18a46f"
}
}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
masterrealm-> goto
Users-> click
Add user-> goto
role mappingAdd
adminrole for this user.Input user info form and submit
-> goto
CredentialSet 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
treetrackerrealm-> Select
users-> Click
add userFill the user info form and submit
-> Click
credentalInput the password for the user.
-> Click
role mapping-> Select the
xxxfor 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
newInput the organization name, e.g.
Tree Boston-> Click
AttributesInput key
idand valuenhere,nis the id of this organization in the DB-> Click
AddInput key
uuidand valuen, herenis 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-userDone, 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:viewRole: a name, like:
planter managerPolicy: 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:
earningson Keycloak (ignore the detailed steps)Create scope
editif there isn't one.Create role
earnings-managerCreate a user, and assign the role
earnings-managerto the user.Now we are all set in terms of the auth side, but if
earningsresource 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 theearningsresource 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):
if(user.permissions.includes("earnings:edit"){
... //display the earnings menu item
}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:
{
"name": "earnings",
"path": "/earnings",
"methods": [
{
"method": "GET",
"scopes": [
"edit"
]
},
{
"method": "POST",
"scopes": [
"edit"
]
}
]
},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:
getCaptureCount(filter) {
try {
const query = `${API_ROOT}/api/${getOrganization()}trees/count?where=${JSON.stringify(
filter.getWhereObj()
)}`;
return fetch(query, {
headers: {
Authorization: session.token,
},
}).then(handleResponse);
} catch (error) {
handleError(error);
}
},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/countAmbassador intercept the request and gives it to
auth-serviceIn advance, we should set up the auth-service with config like below:
{
"name": "count-of-tree-in-organization",
"path": "/organizations/{organization_id}/trees/count",
"methods": [
{
"method": "GET",
"scopes": [
"view"
]
}
]
},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:viewwill be attached to a policy, say:belongs-to-an-organiztionthis policy will use a javascript-based rule to check the permission, which looks like this:
var context = $evaluation.getContext();
var identity = context.getIdentity();
var attributes = identity.getAttributes();
var permission = $evaluation.getPermission();
var value = context.getAttributes().getValue("custom");
var attributesMap = attributes.toMap();
var organization_id = attributesMap.organization_id;
if(value.asString(0) === organization_id[0]+""){
print("custom attr match");
$evaluation.grant();
}else{
print("custom attr unmatch");
$evaluation.deny();
}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