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)

  1. -> Select master realm

  2. -> goto Users

  3. -> click Add user

  4. -> goto role mapping

  5. Add admin role for this user.

  6. Input user info form and submit

  7. -> goto Credential

  8. Set password for the user

  9. 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.

  1. -> Select treetracker realm

  2. -> Select users

  3. -> Click add user

  4. Fill the user info form and submit

  5. -> Click credental

  6. Input the password for the user.

  7. -> Click role mapping

  8. -> Select the xxx for the user

  9. Done, 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:

  1. Create a normal user the same as above.

  2. -> Click Group

  3. -> Click new

  4. Input the organization name, e.g. Tree Boston

  5. -> Click Attributes

  6. Input key id and value n here, n is the id of this organization in the DB

  7. -> Click Add

  8. Input key uuid and value n , here n is the uuid, stakeholder id of this organization in DB

  9. Back to the user info page that one we just created.

  10. -> Click group

  11. -> Click the organization name we just created

  12. -> Click join

  13. (to assign role) -> Click role mappings

  14. -> Select role: organization-user

  15. 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)

  1. Create a new resource: earnings on Keycloak (ignore the detailed steps)

  2. Create scope edit if there isn't one.

  3. Create role earnings-manager

  4. 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 the earnings resource and do the check. These two steps need the developer to do the work.

  5. (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/count

  • Ambassador intercept the request and gives it to auth-service

  • In 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: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:

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