diff --git a/CHANGELOG.md b/CHANGELOG.md index fee4cf8c90b5c55987ad9ba531e7ad5270940a22..ba534987a7340185904c99ce5bd7ac03d823d4c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### +* [gitlab.indiscale.com#200](https://gitlab.indiscale.com/caosdb/src/caosdb-pylib/-/merge_requests/153) + ``linkahead_admin.py`` prints reasonable error messages when users + or roles don't exist. + ### Security ### ### Documentation ### diff --git a/examples/set_permissions.py b/examples/set_permissions.py index a558bde73897cb6827c93373cc8327efc10e6e15..4657f2cca182b567c761a777df838825f8e89aef 100755 --- a/examples/set_permissions.py +++ b/examples/set_permissions.py @@ -37,13 +37,13 @@ from caosdb import administration as admin def assert_user_and_role(): """Make sure that users and roles exist. -After calling this function, there will be a user "jane" with the role "human" -and the user "xaxys" with the role "alien". These users and roles are returned. + After calling this function, there will be a user "jane" with the role "human" + and the user "xaxys" with the role "alien". These users and roles are returned. -Returns -------- -out : tuple - ((human_user, human_role), (alien_user, alien_role)) + Returns + ------- + out : tuple + ((human_user, human_role), (alien_user, alien_role)) """ try: @@ -81,15 +81,15 @@ out : tuple def get_entities(count=1): """Retrieve one or more entities. -Parameters ----------- -count : int, optional - How many entities to retrieve. + Parameters + ---------- + count : int, optional + How many entities to retrieve. -Returns -------- -out : Container - A container of retrieved entities, the length is given by the parameter count. + Returns + ------- + out : Container + A container of retrieved entities, the length is given by the parameter count. """ cont = db.execute_query("FIND RECORD 'Human Food'", flags={ "P": "0L{n}".format(n=count)}) @@ -102,20 +102,20 @@ out : Container def set_permission(role_grant, role_deny, cont=None, general=False): """Set the permissions of some entities. -Parameters ----------- -role_grant : str - Role which is granted permissions. + Parameters + ---------- + role_grant : str + Role which is granted permissions. -role_deny : str - Role which is denied permissions. + role_deny : str + Role which is denied permissions. -cont : Container - Entities for which permissions are set. + cont : Container + Entities for which permissions are set. -general : bool, optional - If True, the permissions for the roles will be set. If False (the default), - permissions for the entities in the container will be set. + general : bool, optional + If True, the permissions for the roles will be set. If False (the default), + permissions for the entities in the container will be set. """ # Set general permissions @@ -143,23 +143,23 @@ general : bool, optional def test_permission(granted_user, denied_user, cont): """Tests if the permissions are set correctly for two users. -Parameters ----------- -granted_user : (str, str) - The user which should have permissions to retrieve the entities in `cont`. - Given as (user, password). + Parameters + ---------- + granted_user : (str, str) + The user which should have permissions to retrieve the entities in `cont`. + Given as (user, password). -denied_user : (str, str) - The user which should have no permission to retrieve the entities in `cont`. - Given as (user, password). + denied_user : (str, str) + The user which should have no permission to retrieve the entities in `cont`. + Given as (user, password). -cont : Container - Entities for which permissions are tested. + cont : Container + Entities for which permissions are tested. -Returns -------- -None + Returns + ------- + None """ diff --git a/src/linkahead/cached.py b/src/linkahead/cached.py index cf1d1d34362335f87c5eca094b5aa9d6b750f68d..f9179e1a54997bf99f7158b9b46e2d1068a21e47 100644 --- a/src/linkahead/cached.py +++ b/src/linkahead/cached.py @@ -107,7 +107,7 @@ If a query phrase is given, the result must be unique. If this is not what you def cached_query(query_string: str) -> Container: """A cached version of :func:`linkahead.execute_query<linkahead.common.models.execute_query>`. -All additional arguments are at their default values. + All additional arguments are at their default values. """ result = _cached_access(AccessType.QUERY, query_string, unique=False) @@ -161,11 +161,12 @@ def cache_clear() -> None: def cache_info(): """Return info about the cache that is used by `cached_query` and `cached_get_entity_by`. -Returns -------- + Returns + ------- -out: named tuple - See the standard library :func:`functools.lru_cache` for details.""" + out: named tuple + See the standard library :func:`functools.lru_cache` for details. + """ return _cached_access.cache_info() @@ -188,21 +189,21 @@ def cache_fill(items: dict[Union[str, int], Any], This allows to fill the cache without actually submitting queries. Note that this does not overwrite existing entries with the same keys. -Parameters ----------- + Parameters + ---------- -items: dict - A dictionary with the entries to go into the cache. The keys must be compatible with the - AccessType given in ``kind`` + items: dict + A dictionary with the entries to go into the cache. The keys must be compatible with the + AccessType given in ``kind`` -kind: AccessType, optional - The AccessType, for example ID, name, path or query. + kind: AccessType, optional + The AccessType, for example ID, name, path or query. -unique: bool, optional - If True, fills the cache for :func:`cached_get_entity_by`, presumably with - :class:`linkahead.Entity<linkahead.common.models.Entity>` objects. If False, the cache should be - filled with :class:`linkahead.Container<linkahead.common.models.Container>` objects, for use with - :func:`cached_query`. + unique: bool, optional + If True, fills the cache for :func:`cached_get_entity_by`, presumably with + :class:`linkahead.Entity<linkahead.common.models.Entity>` objects. If False, the cache should be + filled with :class:`linkahead.Container<linkahead.common.models.Container>` objects, for use with + :func:`cached_query`. """ diff --git a/src/linkahead/common/administration.py b/src/linkahead/common/administration.py index dee341fa84dd85cbd41a77c0e2d510a96f2c4824..28ef107579fccb689b7337aed65e054cfbf36c05 100644 --- a/src/linkahead/common/administration.py +++ b/src/linkahead/common/administration.py @@ -345,20 +345,20 @@ def _get_roles(username, realm=None, **kwargs): def _set_permissions(role, permission_rules, **kwargs): """Set permissions for a role. -Parameters ----------- + Parameters + ---------- -role : str - The role for which the permissions are set. + role : str + The role for which the permissions are set. -permission_rules : iterable<PermissionRule> - An iterable with PermissionRule objects. + permission_rules : iterable<PermissionRule> + An iterable with PermissionRule objects. -**kwargs : - Additional arguments which are passed to the HTTP request. + **kwargs : + Additional arguments which are passed to the HTTP request. -Returns -------- + Returns + ------- None """ xml = etree.Element("PermissionRules") @@ -393,15 +393,15 @@ def _get_permissions(role, **kwargs): class PermissionRule(): """Permission rules. -Parameters ----------- -action : str - Either "grant" or "deny" + Parameters + ---------- + action : str + Either "grant" or "deny" -permission : str - For example ``RETRIEVE:*``. + permission : str + For example ``RETRIEVE:*``. -priority : bool, optional + priority : bool, optional Whether the priority shall be set, defaults is False. """ diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py index a8144286fdacefacadf2b823160e0eb9bfe00c77..5689e0799f51584a39c187fd106c4d1b09e9cfc7 100644 --- a/src/linkahead/common/models.py +++ b/src/linkahead/common/models.py @@ -873,29 +873,29 @@ class Entity: check. Note that, if checked, name or ID should not be None, lest the check fail. -Parameters ----------- + Parameters + ---------- -parent: Entity - Check for this parent. + parent: Entity + Check for this parent. -recursive: bool, optional - Whether to check recursively. + recursive: bool, optional + Whether to check recursively. -check_name: bool, optional - Whether to use the name for ancestry check. + check_name: bool, optional + Whether to use the name for ancestry check. -check_id: bool, optional - Whether to use the ID for ancestry check. + check_id: bool, optional + Whether to use the ID for ancestry check. -retrieve: bool, optional - If False, do not retrieve parents from the server. + retrieve: bool, optional + If False, do not retrieve parents from the server. -Returns -------- -out: bool - True if ``parent`` is a true parent, False otherwise. -""" + Returns + ------- + out: bool + True if ``parent`` is a true parent, False otherwise. + """ if recursive: parents = self.get_parents_recursively(retrieve=retrieve) @@ -930,17 +930,17 @@ out: bool def get_parents_recursively(self, retrieve: bool = True) -> list[Entity]: """Get all ancestors of this entity. -Parameters ----------- + Parameters + ---------- -retrieve: bool, optional - If False, do not retrieve parents from the server. + retrieve: bool, optional + If False, do not retrieve parents from the server. -Returns -------- -out: list[Entity] - The parents of this Entity -""" + Returns + ------- + out: list[Entity] + The parents of this Entity + """ all_parents: list[Entity] = [] self._get_parent_recursively(all_parents, retrieve=retrieve) @@ -1598,15 +1598,15 @@ out: list[Entity] unique=True, flags=None, sync=True): """Update this entity. -There are two possible work-flows to perform this update: -First: - 1) retrieve an entity - 2) do changes - 3) call update method + There are two possible work-flows to perform this update: + First: + 1) retrieve an entity + 2) do changes + 3) call update method -Second: - 1) construct entity with id - 2) call update method. + Second: + 1) construct entity with id + 2) call update method. For slight changes the second one it is more comfortable. Furthermore, it is possible to stay off-line until calling the update method. The name, description, unit, datatype, path, diff --git a/src/linkahead/common/versioning.py b/src/linkahead/common/versioning.py index 2e292e6bb031725fbd6da618c4b888c05072c46b..11cf5f6904b02954eb0b2bddc16478590df167e7 100644 --- a/src/linkahead/common/versioning.py +++ b/src/linkahead/common/versioning.py @@ -105,7 +105,7 @@ class Version(): is_head: Union[bool, str, None] = False, is_complete_history: Union[bool, str, None] = False): """Typically the `predecessors` or `successors` should not "link back" to an existing Version -object.""" + object.""" self.id = id self.date = date self.username = username diff --git a/src/linkahead/utils/create_revision.py b/src/linkahead/utils/create_revision.py index 5f6ecc8148859d0ee0908412ff80d20d465cdb25..cde4bae5b0d919977d220b2c35896dcb20e933e7 100644 --- a/src/linkahead/utils/create_revision.py +++ b/src/linkahead/utils/create_revision.py @@ -34,15 +34,15 @@ def bend_references(from_id, to_id, except_for=None): and those references are changed to point to to_id. entities having an id listed in except_for are excluded. -Parameters ----------- + Parameters + ---------- -from_id : int - the old object to which references where pointing -to_id : int - the new object to which references will be pointing -except_for : list of int - entities with id of this list will not be changed + from_id : int + the old object to which references where pointing + to_id : int + the new object to which references will be pointing + except_for : list of int + entities with id of this list will not be changed """ if except_for is None: except_for = [to_id] @@ -73,16 +73,16 @@ def create_revision(old_id, prop, value): This function changes the record with id old_id. The value of the propertye prop is changed to value. -Parameters ----------- + Parameters + ---------- -old_id : int - id of the record to be changed -prop : string - name of the property to be changed -value : type of corresponding property - the new value of the corresponding property -""" + old_id : int + id of the record to be changed + prop : string + name of the property to be changed + value : type of corresponding property + the new value of the corresponding property + """ record = db.execute_query("FIND {}".format(old_id))[0] new_rec = record.copy() new_rec.get_property(prop).value = value diff --git a/src/linkahead/utils/get_entity.py b/src/linkahead/utils/get_entity.py index 0ffd89e4dc7f214bbc72d4508f6ca4481dad7d9c..dd91cdc27b3f6adb52ddef36a59d1a0965fb662e 100644 --- a/src/linkahead/utils/get_entity.py +++ b/src/linkahead/utils/get_entity.py @@ -30,13 +30,13 @@ from .escape import escape_squoted_text def get_entity_by_name(name: str, role: Optional[str] = None) -> Entity: """Return the result of a unique query that uses the name to find the correct entity. -Submits the query "FIND {role} WITH name='{name}'". + Submits the query "FIND {role} WITH name='{name}'". -Parameters ----------- + Parameters + ---------- -role: str, optional - The role for the query, defaults to ``ENTITY``. + role: str, optional + The role for the query, defaults to ``ENTITY``. """ name = escape_squoted_text(name) if role is None: @@ -48,13 +48,13 @@ role: str, optional def get_entity_by_id(eid: Union[str, int], role: Optional[str] = None) -> Entity: """Return the result of a unique query that uses the id to find the correct entity. -Submits the query "FIND {role} WITH id='{eid}'". + Submits the query "FIND {role} WITH id='{eid}'". -Parameters ----------- + Parameters + ---------- -role: str, optional - The role for the query, defaults to ``ENTITY``. + role: str, optional + The role for the query, defaults to ``ENTITY``. """ if role is None: role = "ENTITY" @@ -65,13 +65,13 @@ role: str, optional def get_entity_by_path(path: str) -> Entity: """Return the result of a unique query that uses the path to find the correct file. -Submits the query "FIND {role} WHICH IS STORED AT '{path}'". + Submits the query "FIND {role} WHICH IS STORED AT '{path}'". -Parameters ----------- + Parameters + ---------- -role: str, optional - The role for the query, defaults to ``ENTITY``. + role: str, optional + The role for the query, defaults to ``ENTITY``. """ # type hint can be ignored, it's a unique query return execute_query(f"FIND FILE WHICH IS STORED AT '{path}'", unique=True) # type: ignore diff --git a/src/linkahead/utils/linkahead_admin.py b/src/linkahead/utils/linkahead_admin.py index f7e3b8b63f18e37e6210f2aa03f34ce5b0f688d4..77ca5fbfe0dc4c5c36956bc6f3ad463335332daa 100755 --- a/src/linkahead/utils/linkahead_admin.py +++ b/src/linkahead/utils/linkahead_admin.py @@ -33,7 +33,7 @@ from argparse import ArgumentParser, RawDescriptionHelpFormatter import linkahead as db from linkahead import administration as admin -from linkahead.exceptions import HTTPClientError +from linkahead.exceptions import HTTPClientError, HTTPResourceNotFoundError, HTTPForbiddenError __all__ = [] __version__ = 0.3 @@ -42,19 +42,35 @@ __updated__ = '2018-12-11' def do_update_role(args): - admin._update_role(name=args.role_name, description=args.role_description) + try: + admin._update_role(name=args.role_name, description=args.role_description) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot update role '{args.role_name}', " + f"reason: '{e.msg}'") def do_create_role(args): - admin._insert_role(name=args.role_name, description=args.role_description) + try: + admin._insert_role(name=args.role_name, description=args.role_description) + except (HTTPClientError, HTTPForbiddenError) as e: + print(f"Error: Cannot create role '{args.role_name}', " + f"reason: '{e.msg}'") def do_retrieve_role(args): - print(admin._retrieve_role(name=args.role_name)) + try: + print(admin._retrieve_role(name=args.role_name)) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot retrieve role '{args.role_name}', " + f"reason: '{e.msg}'") def do_delete_role(args): - admin._delete_role(name=args.role_name) + try: + admin._delete_role(name=args.role_name) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot delete role '{args.role_name}', " + f"reason: '{e.msg}'") def do_retrieve(args): @@ -126,22 +142,25 @@ def do_create_user(args): if args.activate_user: do_activate_user(args) - except HTTPClientError as e: - print(e.msg) + except (HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot create user '{args.user_name}', " + f"reason: '{e.msg}'") def do_activate_user(args): try: admin._update_user(name=args.user_name, status="ACTIVE") - except HTTPClientError as e: - print(e.msg) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot activate user '{args.user_name}', " + f"reason: '{e.msg}'") def do_deactivate_user(args): try: admin._update_user(name=args.user_name, status="INACTIVE") - except HTTPClientError as e: - print(e.msg) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot deactivate user '{args.user_name}', " + f"reason: '{e.msg}'") def do_set_user_password(args): @@ -151,57 +170,109 @@ def do_set_user_password(args): password = args.user_password try: admin._update_user(name=args.user_name, password=password) - except HTTPClientError as e: - print(e.msg) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot set password for user '{args.user_name}', " + f"reason: '{e.msg}'") def do_add_user_roles(args): - roles = admin._get_roles(username=args.user_name, realm=None) + try: + roles = admin._get_roles(username=args.user_name, realm=None) + except (HTTPForbiddenError, HTTPResourceNotFoundError) as e: + print(f"Error: Cannot access roles for user '{args.user_name}', " + f"reason: '{e.msg}'") + return for r in args.user_roles: roles.add(r) - admin._set_roles(username=args.user_name, roles=roles) + try: + admin._set_roles(username=args.user_name, roles=roles) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot add new roles for user '{args.user_name}', " + f"reason: '{e.msg}'") def do_remove_user_roles(args): - roles = admin._get_roles(username=args.user_name, realm=None) + try: + roles = admin._get_roles(username=args.user_name, realm=None) + except (HTTPForbiddenError, HTTPResourceNotFoundError) as e: + print(f"Error: Cannot access roles for user '{args.user_name}', " + f"reason: '{e.msg}'") + return for r in args.user_roles: if r in roles: roles.remove(r) - admin._set_roles(username=args.user_name, roles=roles) + try: + admin._set_roles(username=args.user_name, roles=roles) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot remove roles from user '{args.user_name}', " + f"reason: '{e.msg}'") def do_set_user_entity(args): - admin._update_user(name=args.user_name, entity=args.user_entity) + try: + admin._update_user(name=args.user_name, entity=args.user_entity) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot set entity for user '{args.user_name}', " + f"reason: '{e.msg}'") def do_reset_user_entity(args): - admin._update_user(name=args.user_name, entity="") + try: + admin._update_user(name=args.user_name, entity="") + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot remove entity for user '{args.user_name}', " + f"reason: '{e.msg}'") def do_set_user_email(args): - admin._update_user(name=args.user_name, email=args.user_email) + try: + admin._update_user(name=args.user_name, email=args.user_email) + except (HTTPResourceNotFoundError, HTTPForbiddenError, HTTPClientError) as e: + print(f"Error: Cannot set email for user '{args.user_name}', " + f"reason: '{e.msg}'") def do_retrieve_user(args): - print(admin._retrieve_user(name=args.user_name)) + try: + print(admin._retrieve_user(name=args.user_name)) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot retrieve user '{args.user_name}', " + f"reason: '{e.msg}'") def do_delete_user(args): - admin._delete_user(name=args.user_name) + try: + admin._delete_user(name=args.user_name) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot delete user '{args.user_name}', " + f"reason: '{e.msg}'") def do_retrieve_user_roles(args): - print(admin._get_roles(username=args.user_name)) + try: + print(admin._get_roles(username=args.user_name)) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot retrieve roles for user '{args.user_name}', " + f"reason: '{e.msg}'") def do_retrieve_role_permissions(args): - print(admin._get_permissions(role=args.role_name)) + try: + print(admin._get_permissions(role=args.role_name)) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot retrieve permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") def do_grant_role_permissions(args): - perms = admin._get_permissions(args.role_name) + try: + perms = admin._get_permissions(role=args.role_name) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot access permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") + return for p in args.role_permissions: g = admin.PermissionRule( @@ -215,11 +286,20 @@ def do_grant_role_permissions(args): if d in perms: perms.remove(d) perms.add(g) - admin._set_permissions(role=args.role_name, permission_rules=perms) + try: + admin._set_permissions(role=args.role_name, permission_rules=perms) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot set permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") def do_revoke_role_permissions(args): - perms = admin._get_permissions(args.role_name) + try: + perms = admin._get_permissions(role=args.role_name) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot access permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") + return for p in args.role_permissions: g = admin.PermissionRule( @@ -232,11 +312,20 @@ def do_revoke_role_permissions(args): if d in perms: perms.remove(d) - admin._set_permissions(role=args.role_name, permission_rules=perms) + try: + admin._set_permissions(role=args.role_name, permission_rules=perms) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot revoke permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") def do_deny_role_permissions(args): - perms = admin._get_permissions(args.role_name) + try: + perms = admin._get_permissions(role=args.role_name) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot access permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") + return for p in args.role_permissions: g = admin.PermissionRule( @@ -250,7 +339,11 @@ def do_deny_role_permissions(args): if d in perms: perms.remove(d) perms.add(d) - admin._set_permissions(role=args.role_name, permission_rules=perms) + try: + admin._set_permissions(role=args.role_name, permission_rules=perms) + except (HTTPResourceNotFoundError, HTTPForbiddenError) as e: + print(f"Error: Cannot deny permissions for role '{args.role_name}', " + f"reason: '{e.msg}'") def do_retrieve_entity_acl(args):