def alter_permissions(user: User, to_add: Set[str], to_ungrant: Set[str], to_delete: Set[str]) -> None: """ Apply the permission changes to the database. The permission model to apply the changes to is passed as a parameter. :param perm_model: The permissions model, must inherit PermissionMixin :param user: The user to apply the permissions to :param to_add: The permissions to add :param to_ungrant: The permissions to ungrant :param to_delete: The permissions to delete """ for permission in to_delete: model = UserPermission.from_attrs(user_id=user.id, permission=permission) db.session.delete(model) db.session.commit() for perm_name in to_add: db.session.add(UserPermission(user_id=user.id, permission=perm_name)) for perm_name in to_ungrant: db.session.add( UserPermission(user_id=user.id, permission=perm_name, granted=False)) db.session.commit()
def permissions(self) -> List[str]: """ A general function to get the permissions of a user from a permission model and attributes of their user classes. Locked users are restricted to the permissions defined for them in the config. :param key: The cache key to cache the permissions under :param model: The model to query custom permissions from :param attr: The attribute of the userclasses that should be queried """ from core.permissions.models import SecondaryClass from core.permissions.models import UserPermission if self.locked: # Locked accounts have restricted permissions. return app.config['LOCKED_ACCOUNT_PERMISSIONS'] key = self.__cache_key_permissions__.format(id=self.id) permissions = cache.get(key) if not permissions: permissions = copy(self.user_class_model.permissions) for class_ in SecondaryClass.from_user(self.id): permissions += class_.permissions permissions = set(permissions) # De-dupe for perm, granted in UserPermission.from_user(self.id).items(): if not granted and perm in permissions: permissions.remove(perm) if granted and perm not in permissions: permissions.add(perm) cache.set(key, permissions) return permissions
def test_change_forum_permissions_failure(app, authed_client): db.engine.execute('DELETE FROM users_permissions') add_permissions(app, 'users_moderate', 'forumaccess_thread_1') db.engine.execute("""UPDATE user_classes SET permissions = '{"forumaccess_forum_2"}'""") response = authed_client.put( '/users/1', data=json.dumps({ 'permissions': { 'forumaccess_forum_2': True, 'forumaccess_thread_1': False, 'forumaccess_thread_4': False, 'forumaccess_thread_2': True, } }), ) check_json_response( response, 'The following permissions could not be added: forumaccess_forum_2. ' 'The following permissions could not be deleted: forumaccess_thread_4.', ) f_perms = UserPermission.from_user(1, prefix='forumaccess') assert f_perms == {'forumaccess_thread_1': True}
def test_change_forum_permissions(app, authed_client): db.engine.execute('DELETE FROM users_permissions') add_permissions(app, 'users_moderate', 'forumaccess_forum_1', 'forumaccess_thread_1') db.engine.execute("""UPDATE user_classes SET permissions = '{"forumaccess_forum_2"}'""") response = authed_client.put( '/users/1', data=json.dumps({ 'permissions': { 'forumaccess_forum_2': False, 'forumaccess_thread_1': False, 'forumaccess_thread_2': True, } }), ).get_json() print(response['response']) assert set(response['response']['forum_permissions']) == { 'forumaccess_forum_1', 'forumaccess_thread_2', } f_perms = UserPermission.from_user(1, prefix='forumaccess') assert f_perms == { 'forumaccess_forum_2': False, 'forumaccess_forum_1': True, 'forumaccess_thread_2': True, }
def can_access(self, permission: str = None, error: bool = False) -> bool: """Determines whether or not the user has the permissions to access the thread.""" if flask.g.user is None: # pragma: no cover if error: raise _403Exception return False # Explicit thread access permission_key = self.__permission_key__.format(id=self.id) if flask.g.user.has_permission(permission_key) or ( permission is not None and flask.g.user.has_permission(permission) ): return True # Access to forum gives access to all threads by default. # If user has been ungranted the thread, they cannot view it regardless. ungranted_threads = [ p for p, g in UserPermission.from_user( flask.g.user.id, prefix='forumaccess_thread' ).items() if g is False ] if permission_key not in ungranted_threads and ( flask.g.user.has_permission( Forum.__permission_key__.format(id=self.forum_id) ) ): return True if error: raise _403Exception return False
def check_permissions( user: User, permissions: Dict[str, bool] # noqa: C901 (McCabe complexity) ) -> Tuple[Set[str], Set[str], Set[str]]: """ The abstracted meat of the permission checkers. Takes the input and some model-specific information and returns permission information. :param user: The recipient of the permission changes :param permissions: A dictionary of permission changes, with permission name and boolean (True = Add, False = Remove) key value pairs :param perm_model: The permission model to be checked :param perm_attr: The attribute of the user classes which represents the permissions """ add: Set[str] = set() ungrant: Set[str] = set() delete: Set[str] = set() errors: Dict[str, Set[str]] = defaultdict(set) uc_permissions: Set[str] = set(user.user_class_model.permissions) for class_ in SecondaryClass.from_user(user.id): uc_permissions |= set(class_.permissions) custom_permissions: Dict[str, bool] = UserPermission.from_user(user.id) for perm, active in permissions.items(): if active is True: if perm in custom_permissions: if custom_permissions[perm] is False: delete.add(perm) add.add(perm) elif perm not in uc_permissions: add.add(perm) if perm not in add.union(delete): errors['add'].add(perm) else: if perm in custom_permissions and custom_permissions[perm] is True: delete.add(perm) if perm in uc_permissions: ungrant.add(perm) if perm not in delete.union(ungrant): errors['delete'].add(perm) if errors: message = [] if 'add' in errors: message.append(f'The following permissions could not be added: ' f'{", ".join(errors["add"])}.') if 'delete' in errors: message.append(f'The following permissions could not be deleted: ' f'{", ".join(errors["delete"])}.') raise APIException(' '.join(message)) return add, ungrant, delete
def test_permissions_from_user(app, client): add_permissions(app, 'perm_one', 'perm_two') db.engine.execute( """INSERT INTO users_permissions (user_id, permission, granted) VALUES (1, 'perm_three', 'f')""") perms = UserPermission.from_user(1) assert perms == { 'perm_one': True, 'perm_two': True, 'perm_three': False, 'userclasses_list': True, 'userclasses_modify': True, }
def test_change_permissions(app, authed_client): add_permissions( app, 'users_change_password', 'userclasses_list', 'userclasses_modify' ) db.engine.execute( """INSERT INTO users_permissions (user_id, permission, granted) VALUES (1, 'invites_send', 'f')""" ) db.engine.execute( """UPDATE user_classes SET permissions = '{"users_moderate", "users_moderate_advanced", "invites_view"}'""" ) response = authed_client.put( '/users/1', data=json.dumps( { 'permissions': { 'users_moderate': False, 'users_change_password': False, 'invites_view': False, 'invites_send': True, } } ), ).get_json() print(response['response']) assert set(response['response']['permissions']) == { 'users_moderate_advanced', 'userclasses_modify', 'invites_send', 'userclasses_list', } u_perms = UserPermission.from_user(1) assert u_perms == { 'userclasses_list': True, 'userclasses_modify': True, 'invites_send': True, 'invites_view': False, 'users_moderate': False, }