def test_merge_subscribe_multiple_times(self): """ Given a subscribe previous action set And an subscribe new action set And a third subscribe action set When i merge the three sets I get a copy also with subscribe set """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'subscribe': True}, {'subscribe': True}) actions = merge_actions(actions, {'subscribe': True}) self.assertItemsEqual(actions, ['subscribe'])
def test_merge_grants_multiple_times(self): """ Given a grants previous action set And an grant new action set And a third grant action set When i merge the three sets I get a copy with grants union of all sets And without duplicated grants """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'grant': ['write', 'read']}, {'grant': ['read']}) actions = merge_actions(actions, {'grant': ['flag']}) self.assertItemsEqual(actions, ['grant']) self.assertItemsEqual(actions['grant'], ['read', 'write', 'flag'])
def test_merge_revokes_multiple_times_preserve_grants(self): """ Given a revoke previous action set And an revoke new action set And a third grant action set When I merge the three sets Then I get a copy with revokes intersection of all sets And I get a copy with grants unions of all sets And only revokes also present in grants dissappear """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'revoke': ['write', 'read']}, {'revoke': ['read']}) actions = merge_actions(actions, {'grant': ['read']}) self.assertItemsEqual(actions, ['grant']) self.assertItemsEqual(actions['grant'], ['read'])
def test_merge_grants_multiple_times_preserve_grants(self): """ Given a grant previous action set And an grant new action set And a third revoke action set When I merge the three sets Then I get a copy with revokes intersection of all sets And I get a copy with grants unions of all sets And revokes from third set won't remove grants """ from ulearnhub.models.utils import merge_actions actions = merge_actions(None, {'grant': ['write'], 'revoke': ['read']}) actions = merge_actions(actions, { 'grant': ['read'], 'revoke': ['read'] }) self.assertItemsEqual(actions, ['grant']) self.assertItemsEqual(actions['grant'], ['write', 'read'])
def test_merge_revokes_multiple_times(self): """ Given a revoke previous action set And an revoke new action set And a third revoke action set When i merge the three sets Then I get a copy with revokes intersection of all sets And only revokes present in all sets will remain """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'revoke': ['write', 'read']}, {'revoke': ['read']}) self.assertItemsEqual(actions, ['revoke']) self.assertItemsEqual(actions['revoke'], ['read']) actions = merge_actions(actions, {'revoke': ['flag']}) self.assertItemsEqual(actions, [])
def test_merge_revokes_with_empty(self): """ Given a revoke previous action set And an empty new action set When i merge the two sets The revokes go away """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'revoke': ['write']}, {}) self.assertItemsEqual(actions, [])
def test_merge_subscribe_with_empty(self): """ Given an subscribe previous action set And an empty new action set When i merge the two sets I get a copy of the former """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'subscribe': True}, {}) self.assertItemsEqual(actions, ['subscribe'])
def test_merge_empty_with_subscribe(self): """ Given an empty initial actions set And an subscribe new action set When i merge the two sets I get a copy of the latter """ from ulearnhub.models.utils import merge_actions actions = merge_actions(None, {'subscribe': True}) self.assertItemsEqual(actions, ['subscribe'])
def test_merge_empty_with_empty(self): """ Given an empty initial actions set And an empty new action set When i merge the two sets I get an empty action set """ from ulearnhub.models.utils import merge_actions actions = merge_actions(None, {}) self.assertFalse(actions)
def test_merge_grants_with_empty(self): """ Given a grant previous action set And an empty new action set When i merge the two sets I get a copy of the former """ from ulearnhub.models.utils import merge_actions actions = merge_actions({'grant': ['write']}, {}) self.assertItemsEqual(actions, ['grant']) self.assertItemsEqual(actions['grant'], ['write'])
def test_merge_empty_with_revokes(self): """ Given an empty initial actions set And a revoke new action set When i merge the two sets I get a copy of the latter """ from ulearnhub.models.utils import merge_actions actions = merge_actions(None, {'revoke': ['write']}) self.assertItemsEqual(actions, ['revoke']) self.assertItemsEqual(actions['revoke'], ['write'])
def test_merge_empty_with_all(self): """ Given an empty initial actions set And a subscribe + grant + revoke new action set When i merge the two sets I get a copy of the latter """ from ulearnhub.models.utils import merge_actions actions = merge_actions(None, { 'revoke': ['write'], 'grant': ['read'], 'subscribe': True }) self.assertItemsEqual(actions, ['revoke', 'grant', 'subscribe']) self.assertItemsEqual(actions['revoke'], ['write']) self.assertItemsEqual(actions['grant'], ['read'])
def test_merge_all_with_empty(self): """ Given a subscribe + grant + revoke previous action set And an empty new action set When i merge the two sets I get a copy of the latter And the revokes are gone """ from ulearnhub.models.utils import merge_actions actions = merge_actions( { 'revoke': ['write'], 'grant': ['read'], 'subscribe': True }, {}) self.assertItemsEqual(actions, ['grant', 'subscribe']) self.assertItemsEqual(actions['grant'], ['read'])
def handle_rabbitmq(self, request, *args, **kwargs): """ Update a context's users ACLS Decomposes given groups into a list of users. With that list of users generates the minimum set of tasks (SUB, UNSUB, REVOKE, GRANT) to sync the community status to max context and subscriptions. Each of the final tasks generated is feeded to rabbitmq to be processed asynchronously. Format of request to this service is as follows: { "component": { "type": "communities", "id": "url" } "context": "http://(...)", "acl": { "groups": [ {"id": , "role": ""}, ... ] "users": [ {"id": , "role": ""}, ] }, "permission_mapping": { "reader": ['read'], "writer": ['read', 'write'], "owner": ['read', 'write'] }, "ignore_grants_and_vetos": true, } component: type and id of the component that's triggering this call context: The context on which the syncacl tasks will be performed acl.groups: list of group acls to process. acl.users: list of user acls to process. acl.*: Each acl entry has an id identifing the group/user (cn) and a role permission_mapping: For each role in acls, there must be a list of permissions that a user with that role must have in its max subscription ignore_grants_and_vetos: if true, peristent grants and vetoes on context subscriptions will be overriden so that the final subscription state matches the requested state. This is the default behaviour. """ data = request.json domain = self.__parent__ context_url = data['context'] # Get required components for this service maxserver = domain.get_component(MaxServer) ldapserver = domain.get_component(LdapServer) rabbitserver = domain.get_component(RabbitServer) # Get target context and all of its subscriptions maxclient = maxserver.maxclient authenticated_username, authenticated_token, scope = request.auth_headers maxclient.setActor(authenticated_username) maxclient.setToken(authenticated_token) policy_granted_permissions, subscriptions = get_context_data( maxclient, context_url) acl_groups = data['acl'].get('groups', []) acl_users = data['acl'].get('users', []) permission_mapping = data['permission_mapping'] def expanded_users(): """ Returns iterator with groups in request expanded to get all individual users. Transform each user to mimic entries in requests's ['acl']['users'], picking the role specified in the group. Preserve group for further checks. """ if not acl_groups: raise StopIteration() ldapserver.server.connect() for group in acl_groups: users = ldapserver.server.get_group_users( group['id'].encode('utf-8')) for username in users: yield { 'id': username, 'role': group['role'], 'group': group['id'] } # Disconnect ldap server, we won't need it outside here ldapserver.server.disconnect() # To keep track of overwrites caused by user duplication # At the end of the process, subscribed user NOT IN target users # will be unsubscribed actions_by_user = {} # Iterate over group users and single users acl's at once for user in chain(expanded_users(), acl_users): username = user['id'] role = user['role'] wanted_permissions = set(permission_mapping.get(role, [])) # Get the previous defined actions on this user, if any actions = actions_by_user.get(username, None) # Generate actions based on current permissions, policy, and wanted permissions new_actions = generate_actions(subscriptions.get(username, {}), policy_granted_permissions, wanted_permissions) # Merge new actions into previous actions, preserving the most beneficient actions = merge_actions(actions, new_actions) # Store user to track overwrites actions_by_user[username] = actions client = rabbitserver.notifications # All the users present in subscription and not in the ACL's will be unsubscribed missing_users = set(subscriptions.keys()) - set(actions_by_user.keys()) for username in missing_users: client.sync_acl(domain.name, context_url, username, {"unsubscribe": True}) gevent.sleep() for username, actions in actions_by_user.items(): if actions: client.sync_acl(domain.name, context_url, username, actions) gevent.sleep() gevent.sleep(0.1) return {}