# 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