---
# System prepended metadata

title: Role Assignments

---

# Role Assignments

- <https://xwiki.ds-aht.eu/wiki/kb/view/User%20Manual%20-%20Service%20Monitoring%20Tool%20%28SMT%29%20/04-Onboarding%20-%20Set%20up%20a%20new%20user%20%28Operator%29>
- [x] Entity for tying together customer group and user/role assignment
  
  https://gerrit.ds-aht.eu/c/api-spec/+/3053

  ```yml
    RoleAssignment:
      required:
      - "uuid"
      - "customerGroupUuid"
      - "assignableUuid"
      - "assignableType"
      properties:
        uuid:
          type: "string"
          format: "uuid"
        customerGroupUuid:
          type: "string"
          format: "uuid"
        assignableUuid:
          type: "string"
          format: "uuid"
        assignableType:
          type: "string"
          enum:
            - usergroup
            - user
        roleUuids:
          type: "array"
          items:
            type: "string"
            format: "uuid" 
        restrictions:
          type: object
          properties:
            customer:
              type: RoleAssignmentRestriction
            deviceCategory
              type: RoleAssignmentRestriction
     
     RoleAssignmentRestriction
       isInclusive: true/false
       Uuids: array
  ```
  
  `isCustomerRestrictionInclude` if set to false it means that we are not limitting new customers, if is set to true you can see only customers/devices from 
  
  
  - [x] GET (all)
  - [x] PUT
  - [x] POST
  - [x] DELETE
- [x] (UserGroup implementation)
  - existing in api-spec, just needs endpoints for GET/POST/PUT/DELETE and implementation
  - [x] needs MxN assignment entity and GET all/ POST/DELETE:
  
    ```yml
      UserGroupAssignment:
        properties:
          uuid:
          userUuid:
          userGroupUuid:
    ```
- 

## What does it mean?

So if we have the following entity:

```yml
RoleAssignment:
  uuid: {...}
  customerGroupUuid: { to customergroup "LidlGermany" } 
  assignableUuid: {to user "John Doe"}
  assignableType: user
  roleUuids: [Lvl3]
  isCustomerRestrictionInclude: false
  customerRestrictionUuids: []
```

it means, that the user John Doe is allowed to manage
all the customers in LidlGermany with actions that are allowed in the Lvl3 role.

And if we have the following entity:

```yml
RoleAssignment:
  uuid: {...}
  customerGroupUuid: { to customergroup "EdekaAustria" } 
  assignableUuid: {to user group "Austrian Techs"}
  assignableType: usergroup
  roleUuids: [lvl3, lvl4]
  isCustomerRestrictionInclude: false
  customerRestrictionUuids: [ Edeka#5]
```

it means, that all the users in the Austrian Techs group
have Lvl3/Lvl4 permissions for all the customers in EdekaAustria, except Edeka#5

```yml
RoleAssignment:
  uuid: {...}
  customerGroupUuid: { null } 
  assignableUuid: {to user group "AustrianOperators"}
  assignableType: usergroup
  roleUuids: [lvl4]
  isCustomerRestrictionInclude: true
  customerRestrictionUuids: [ Lidl ]
```

it means, that all the users in the AustrianOperators group have Lvl4 permissions on ONLY the customer Lidl


### with Restrictions


* Exlucde - allow to see any new customer
* Include - do not allow to see any new customer


## How to apply these permissions?

So you have Customer C and want to see if you can perform action A on it with User U

```c#
bool canPerform(Action A, Customer C, User U)
{
    var customerGroup=C.CustomerGroup;
    while(customerGroup!=null)
    {
        foreach(var roleAssignment in AllRoleAssignments.Where(r=>r.customerGroup==customerGroup)
            if(roleAssignment.assignableType==user && roleAssignment.assignableUuid==U.Uuid
            || roleAssignment.assignableType==usergroup && isInGroup(U, roleAssignment.assignableUuid))
           {
                if(roleAssignment.CustomerRestriction == exclusive && roleAssignment.customerRestrictions.Contains(C)
                || roleAssignment.CustomerRestriction == inclusive && !roleAssignment.customerRestrictions.Contains(C))
                   return false;
                if(isAllowed(A, roleAssignment.roleUuids))
                    return true;
           } 
        customerGroup=customerGroup.ParentGroup;
    }
    return false;
}

/// tests if the roles allow A
bool isAllowed(Action A, params roleUuid[] roles){...}

bool isIngroup(User U, userGroupUUid group)
{
    foreach (group this user is in)
    {
        var c=group.parentsAndSelf().contains(group);
        if(c)return true;
    }
    return false;
}

```

### How can we optimize a query for a list of customers that a user can perform a specific action on?
e.g. view list of incidents

```c#

Customer[] getAllowedCustomers(Action A, User U)
{
    var allowedCustomers = new HashSet<Customer>();
    
    //build an order in which roleAssignments are checked
    //since subgroups override parent groups, parents must come first
    //afterwards come direct user permissions
    
    var directGroups= getAllUserGroupsFor(U);
    var sortedDirectGroups = directGroups.Sort(g=>groupLevel(g));
    foreach(var g in sortedDirectGroups)
    {
        foreach(var r in getRoleAssignments(g))
        {
            if(!isAllowed(A,r.Roles))
                continue;
            if(r.CustomerRestriction == inclusive)
            {
                allowedCustomers.UnionWith(r.CustomerRestrictions)
            }
            else 
            {
                allowedCustomers.UnionWith(getAllCustomers(r.CustomerGroup));
                allowedCustomers.Except(r.CustomerRestrictions);
            }
        }
    }
    //repeat inner foreach for getRoleAssignments(U)
    return allowedCustomers;
}

/// returns the Level of a UserGroup within the tree, 0=Root, 1=first level under root,...
int groupLevel(UserGroup u)
{
    if(u.Parent!=null)
        return groupLevel(u.Parent)+1;
    return 0;
}

///get all UserGroups this user is in, including their parents
HashSet<UserGroup> getAllUserGroupsFor(User u)
{
    var result=new HashSet<UserGroup>();
    var q = new Queue<CustomerGroup>();
    q.EnqueueAll(u.getDirectGroups());
    while(q.Any())
    {
        var current=q.Dequeue();
        if(result.Contains(current))
            continue;
        result.Add(current);
        if(current.Parent!=null)
            q.Enqueue(current.Parent);
    }
    return result;
}

/// returns all RoleAssignments that relate to a specific UserGroup
RoleAssignments[] getRoleAssignments(UserGroup g)
{
    return AllRoleAssignments.Where(r=>r.assignableUuid==g.Uuid);
}

/// returns all Customers that are in a CustomerGroup and it's subgroups
HashSet<Customer> getAllCustomers(CustomerGroup g)
{
    var q=new Queue<CustomerGroup>();
    q.Enqueue(g);
    var result= new HashSet<Customer>();
    while(q.Any())
    {
        var current=q.Dequeue();
        foreach(var subGroup in current.SubGroups())
        {
            q.Enqueue(subGroup);
        }
        result.AddAll(current.DirectCustomers());
    }
    return result;
}
```

### How can we build a query for returning the list of incidents?

```c#
/// note: this query could be optimized by replacing the Contains with Joins, or by replacing the Unions with one general Where clause joining the conditional Where clauses with "||""
List<Incident> getIncidentsFor(User u)
{
    return AllIncidents
            .Where(i=>getAllowedCustomers(Actions.listAllIncidents,currentUser).Contains(i.Customer)) 
        .Union(AllIncidents
            .Where(i=>getAllowedCustomers(Actions.listUnassignedIncidents,currentUser).Contains(i.Customer) && i.Specialist==null))
        .Union(AllIncidents
            .Where(i=>getAllowedCustomers(Actions.listOwnIncidents,currentUser).Conatins(i.Customer) && i.Specialist==currentUser))
}

```




- Europe
    - Austria
        - Lidl
      
Usergroups:
- ServiceTeam1
    - User1
- ServiceTeam2
    - User2

RoleAssignments:
- Austria-ServiceTeam1-Lvl4
- Europe-ServiceTeam2-Lvl4





Questions to answer:
- [ ] Should we use null as a root in customer groups
- [ ] Find a better name for includeRestriction see above
