# 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