Skip to content

Role Management

Apps typically require multiple types of personas or Roles to interact with their functionallity. A role is a way to group a set of like-permissioned users into the same category.

On the technical level, role management just requires a contract that stores an association between a user address and the name of their assigned role. Roles are typically represented by named enum types. Roles are most typically used with a corresponding Permission Manager contract, that maps a set of defined actions that a given Role can do.

Sample User Role Manager

// define the Roles for the app
contract Role {
    enum RoleEnum {
          NULL,
          DISTRIBUTOR,
          MANUFACTURER,
          AUDITOR,
          ADMIN,
          MAX
     }
}

// define a basic record to map a user's address to a Role
contract UserRole is Role {

     address public userAddress;
     RoleEnum public role;

     constructor(address _u, RoleEnum _r) {
          userAddress = _u;
          role = _r;
     }
}

contract RoleManager is Role {

     address[] public users;
     mapping(address => address) userRoles;
     address public contractOwner;

     constructor() {
          contractOwner = tx.origin;
     }

     function grantRole(address _u, RoleEnum _r) {
          // this will typically call a permission manager to check that the caller
          // is allowed to grant roles
          UserRole ur = new UserRole(_u, _r);
          users.push(address(ur));
          userRoles[_u] = address(ur);
     }

     function getUserRole(address _user) returns (address) {
          return userRoles[_u];
     }
}

Accessing User Roles

Your app can query the UserRole Cirrus table to fetch a specific user’s role:

GET /cirrus/search/<Org>-RoleManager-UserRole

Alternatively if your application needs to find the role of a particular user from within a contract, it can call the getUserRole from the RoleManager:

contract MyContract {

     RoleManager public roleManager;

     constructor() {...}

     function useRole() {
          RoleEnum r = roleManager.getUserRole(tx.origin).role();
          // do something with the role of the transaction origin...
     }

}

Accessing Users by Role

Apps typically need a way to access all of the users with a given role. To accomplish this within our contract, we can add a new "get user by role" function. Because all data stored in contracts is stored in arrays or mappings, searching through every user in the app is not very efficient. For that reason this implementation keeps separate data structures of users for each role. It is a tradeoff between time and storage. However, we can combine these data structures if we ever need to access every user at once.

contract RoleManager is Role {

     address[] public users;
     // store arrays for each type of role
     address[] public distributors;
     address[] public manufacturers;
     address[] public auditors;
     address[] public administrators;

     mapping(address => address) userRoles;
     address public contractOwner;

     constructor() {
          contractOwner = tx.origin;
     }

     function grantRole(address _u, RoleEnum _r) {
          // this will typically call a permission manager to check that the caller
          // is allowed to grant roles
          UserRole ur = new UserRole(_u, _r);
          users.push(address(ur));
          userRoles[_u] = address(ur);

          if (_r == Role.DISTRIBUTOR) {
               distributors.push(address(ur));
          }
          if (_r == Role.MANUFACTURER) {
               manufacturers.push(address(ur));
          }
          if (_r == Role.AUDITOR) {
               auditors.push(address(ur));
          }
          if (_r == Role.ADMIN) {
               administrators.push(address(ur));
          }
     }

     function getUserRole(address _user) returns (address) {
          return userRoles[_u];
     }

     function getUsersByRole(RoleEnum _role) returns (address) {
          if (_r == Role.DISTRIBUTOR) {
               return distributors;
          }
          if (_r == Role.MANUFACTURER) {
               return manufacturers;
          }
          if (_r == Role.AUDITOR) {
               return auditors;
          }
          if (_r == Role.ADMIN) {
               return administrators;
          }
          else {
               return address(0);
          }
     }
}

Other contracts can access the list of user role contracts by accessing the array state variables directly, or by calling the getUsersByRole function on the RoleManager.

Grant Roles from Application Server

Here is sample code on how to call the grantRole function to onboard a new user from your application's server.

const UserRole = {
     NULL: 0,
     DISTRIBUTOR: 1,
     MANUFACTURER: 2,
     AUDITOR: 3,
     ADMIN: 4,
     MAX: 5,
}

async function grantAdmin(user, contract, userAddress, options) {

     const args = {
          u: userAddress,
          role: UserROle.ADMIN,
     }

     const callArgs = {
          contract,
          method: 'grantRole',
          args: util.usc(args),
     }

     await rest.call(user, callArgs, options)
}