From e75c8769f99f6c07beed76c9263eeec6d53d38a9 Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum Date: Mon, 4 Apr 2022 15:56:15 +0200 Subject: [PATCH 1/7] first attempt of problemsolving --- knora/dsplib/models/user.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index 249d3bb6e..bef46a7b1 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -699,6 +699,12 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> list[Any]: for proj in project: if proj["id"] == "http://rdfh.ch/projects/" + proj_shortcode: project_users.append(user) + for user in project_users: + # TODO: This is the wrong approach. The method User.fromJsonObj() wants the project and the groups to be + # inside the user[permissions] as dict + project_info = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/project-memberships') + if 'projects' in project_info and len(project_info['projects']) > 0 and 'shortname' in project_info['projects'][0]: + user['projects'].append(project_info['projects'][0]['shortname']) return list(map(lambda a: User.fromJsonObj(con, a), project_users)) def createDefinitionFileObj(self): From 0f3ee1c685a9b16ea76eaf087cc9aae57991c5a0 Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum Date: Tue, 5 Apr 2022 09:19:05 +0200 Subject: [PATCH 2/7] edit --- knora/dsplib/models/user.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index bef46a7b1..e5959b00c 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -699,12 +699,27 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> list[Any]: for proj in project: if proj["id"] == "http://rdfh.ch/projects/" + proj_shortcode: project_users.append(user) + + # find out the 'groups' and 'projects' of every user + # TODO: The method User.fromJsonObj() wants the project and the groups to be + # inside the user[permissions] as dict + permissions = con.get(f'/admin/permissions/{urllib.parse.quote_plus("http://rdfh.ch/projects/") + proj_shortcode}') + groups = con.get(f'/admin/groups') + projects = con.get(f'/admin/projects') for user in project_users: - # TODO: This is the wrong approach. The method User.fromJsonObj() wants the project and the groups to be - # inside the user[permissions] as dict project_info = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/project-memberships') - if 'projects' in project_info and len(project_info['projects']) > 0 and 'shortname' in project_info['projects'][0]: - user['projects'].append(project_info['projects'][0]['shortname']) + if 'projects' in project_info and len(project_info['projects']) > 0 and 'shortname' in \ + project_info['projects'][0]: + user['groups'].append(f"{ontoname}:project_info['projects'][0]['shortname']") + project_admin_memberships = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/project-admin-memberships') + for proj in project_admin_memberships['projects']: + user['projects'].append(f"{proj['shortname']}:admin") + project_memberships = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/group-memberships') + if 'groups' in project_memberships: + for group in project_memberships['groups']: + user['projects'].append(f"{group['project']['shortname']}:member") + + return list(map(lambda a: User.fromJsonObj(con, a), project_users)) def createDefinitionFileObj(self): From 34f046f25c0f439e1ac376c9944ed5db5de9f4dc Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum Date: Thu, 7 Apr 2022 17:38:04 +0200 Subject: [PATCH 3/7] works --- docs/dsp-tools-create.md | 4 +- knora/dsplib/models/user.py | 73 +++++++++++++++++++++------------- knora/dsplib/utils/onto_get.py | 11 ++--- 3 files changed, 54 insertions(+), 34 deletions(-) diff --git a/docs/dsp-tools-create.md b/docs/dsp-tools-create.md index 58d6bae06..509689206 100644 --- a/docs/dsp-tools-create.md +++ b/docs/dsp-tools-create.md @@ -393,8 +393,8 @@ This object contains user definitions. A user has the following elements: - _familyName_: surname of the user - _password_: password of the user - _lang_: the default language of the user: "en", "de", "fr", "it" (optional, default: "en") -- _groups_: List of groups the user belongs to. The name of the group has to be provided with the ontology's namespace, - p.ex. "onto:editors". The given ontology defined in the same ontology file has no name, so only ":editors" is required +- _groups_: List of groups the user belongs to. The name of the group has to be provided with the project's shortname, + p.ex. "shortname:editors". The project defined in the same ontology file has no name, so only ":editors" is required if the user belongs to the group "editors". (optional) - _projects_: List of projects the user belongs to. The project name has to be followed by a ":" and either "member" or "admin". This indicates if the new user has admin rights in the given project or is an ordinary diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index e5959b00c..7eaf48712 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -1,3 +1,4 @@ +from __future__ import annotations import json import os import sys @@ -368,7 +369,7 @@ def in_projects(self) -> dict[str, bool]: return self._in_projects @in_projects.setter - def in_project(self, value: Any): + def in_projects(self, value: Any): raise BaseError( 'Project membership cannot be modified directly! Use methods "addToProject" and "rmFromProject"') @@ -444,7 +445,7 @@ def has_changed(self, name: str): return name in self._changed @classmethod - def fromJsonObj(cls, con: Connection, json_obj: Any): + def fromJsonObj(cls, con: Connection, json_obj: Any) -> User: """ Internal method! Should not be used directly! @@ -678,7 +679,7 @@ def getAllUsers(con: Connection) -> list[Any]: return list(map(lambda a: User.fromJsonObj(con, a), result['users'])) @staticmethod - def getAllUsersForProject(con: Connection, proj_shortcode: str) -> list[Any]: + def getAllUsersForProject(con: Connection, proj_shortcode: str) -> Optional[list[User]]: """ Get a list of all users that belong to a project(static method) @@ -689,7 +690,7 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> list[Any]: result = con.get(User.ROUTE) if 'users' not in result: - raise BaseError("Request got no users!") + return None all_users = result["users"] project_users = [] for user in all_users: @@ -701,36 +702,54 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> list[Any]: project_users.append(user) # find out the 'groups' and 'projects' of every user - # TODO: The method User.fromJsonObj() wants the project and the groups to be - # inside the user[permissions] as dict - permissions = con.get(f'/admin/permissions/{urllib.parse.quote_plus("http://rdfh.ch/projects/") + proj_shortcode}') - groups = con.get(f'/admin/groups') - projects = con.get(f'/admin/projects') for user in project_users: - project_info = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/project-memberships') - if 'projects' in project_info and len(project_info['projects']) > 0 and 'shortname' in \ - project_info['projects'][0]: - user['groups'].append(f"{ontoname}:project_info['projects'][0]['shortname']") - project_admin_memberships = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/project-admin-memberships') - for proj in project_admin_memberships['projects']: - user['projects'].append(f"{proj['shortname']}:admin") - project_memberships = con.get(f'/admin/users/iri/{urllib.parse.quote_plus(user["id"])}/group-memberships') - if 'groups' in project_memberships: - for group in project_memberships['groups']: - user['projects'].append(f"{group['project']['shortname']}:member") - - - return list(map(lambda a: User.fromJsonObj(con, a), project_users)) - - def createDefinitionFileObj(self): - user = { + user_iri_esc = urllib.parse.quote_plus(user["id"]) + project_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/project-memberships') + admin_group_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/project-admin-memberships') + normal_group_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/group-memberships') + + for adm_group in admin_group_memberships['projects']: + user['projects'].append(f"{adm_group['shortname']}:admin") + + for project in project_memberships['projects']: + if f"{project['shortname']}:admin" not in user['projects']: + user['projects'].append(f"{project['shortname']}:member") + + for group in normal_group_memberships['groups']: + project_prefix = group['project']['shortname'] + user['groups'].append(f"{project_prefix}:{group['name']}") + + user['groups'].reverse() + + # convert to User objects + res: list[User] = list(map(lambda a: User.fromJsonObj(con, a), project_users)) + + # add the informations about projects and groups to the User objects + for project_user, res_user in zip(project_users, res): + res_user._in_groups = res_user._in_groups | set(project_user['groups']) + for proj in project_user['projects']: + proj_name, admin = proj.split(':') + res_user._in_projects[proj_name] = bool(admin=='admin') + + return res + + def createDefinitionFileObj(self) -> dict[str, Union[str, list[str], None]]: + user: dict[str, Union[str, list[str], None]] = { "username": self.username, "email": self.email, "givenName": self.givenName, "familyName": self.familyName, "password": "", - "lang": self.lang.value } + if self.lang: + user["lang"] = self.lang.value + user["groups"] = list(self._in_groups) + user["projects"] = list() + for proj, is_admin in self._in_projects.items(): + if is_admin: + user["projects"].append(f"{proj}:admin") + else: + user["projects"].append(f"{proj}:member") return user def print(self) -> None: diff --git a/knora/dsplib/utils/onto_get.py b/knora/dsplib/utils/onto_get.py index f3ce8bc45..2a8e60ec4 100644 --- a/knora/dsplib/utils/onto_get.py +++ b/knora/dsplib/utils/onto_get.py @@ -60,11 +60,12 @@ def get_ontology(project_identifier: str, outfile: str, server: str, user: str, print("Getting users...") users_obj = [] users = User.getAllUsersForProject(con=con, proj_shortcode=project.shortcode) - for user in users: - users_obj.append(user.createDefinitionFileObj()) - if verbose: - print(f"\tGot user '{user.username}'") - project_obj["users"] = users_obj + if users: + for usr in users: + users_obj.append(usr.createDefinitionFileObj()) + if verbose: + print(f"\tGot user '{usr.username}'") + project_obj["users"] = users_obj # get the lists if verbose: From 0f6bc683d83cd9050f0fe56ab4a608604bf969f7 Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum <39048939+jnussbaum@users.noreply.github.com> Date: Tue, 19 Apr 2022 09:55:29 +0200 Subject: [PATCH 4/7] Apply suggestions from code review Co-authored-by: irinaschubert Co-authored-by: Balduin Landolt <33053745+BalduinLandolt@users.noreply.github.com> --- knora/dsplib/models/user.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index 7eaf48712..3ccc14b7f 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -701,7 +701,7 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> Optional[list if proj["id"] == "http://rdfh.ch/projects/" + proj_shortcode: project_users.append(user) - # find out the 'groups' and 'projects' of every user + # get the 'groups' and 'projects' of every user for user in project_users: user_iri_esc = urllib.parse.quote_plus(user["id"]) project_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/project-memberships') @@ -722,9 +722,9 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> Optional[list user['groups'].reverse() # convert to User objects - res: list[User] = list(map(lambda a: User.fromJsonObj(con, a), project_users)) + res: list[User] = [User.fromJsonObj(con, a) for a in project_users] - # add the informations about projects and groups to the User objects + # add the projects and groups to the User objects for project_user, res_user in zip(project_users, res): res_user._in_groups = res_user._in_groups | set(project_user['groups']) for proj in project_user['projects']: From 7542c10db718cdd3dac46506bf4fb76441b02582 Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum Date: Tue, 19 Apr 2022 10:01:10 +0200 Subject: [PATCH 5/7] replace lambdas by comprehensions --- knora/dsplib/models/project.py | 2 +- knora/dsplib/models/user.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/knora/dsplib/models/project.py b/knora/dsplib/models/project.py index 501317f04..58ac7bc14 100644 --- a/knora/dsplib/models/project.py +++ b/knora/dsplib/models/project.py @@ -516,7 +516,7 @@ def getAllProjects(con: Connection) -> list[Project]: result = con.get(Project.ROUTE) if 'projects' not in result: raise BaseError("Request got no projects!") - return list(map(lambda a: Project.fromJsonObj(con, a), result['projects'])) + return [Project.fromJsonObj(con, a) for a in result['projects']] def print(self) -> None: """ diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index 3ccc14b7f..86639e4ce 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -198,7 +198,7 @@ def __init__(self, if isinstance(lang, Languages): self._lang = lang else: - lmap = dict(map(lambda a: (a.value, a), Languages)) + lmap = {a.value: a for a in Languages} if lmap.get(lang) is None: raise BaseError('Invalid language string "' + lang + '"!') self._lang = lmap[lang] @@ -298,7 +298,7 @@ def lang(self, value: Optional[Union[str, Languages]]): self._lang = value self._changed.add('lang') else: - lmap = dict(map(lambda a: (a.value, a), Languages)) + lmap = {a.value: a for a in Languages} if lmap.get(value) is None: raise BaseError('Invalid language string "' + value + '"!') self._lang = lmap[value] @@ -676,7 +676,7 @@ def getAllUsers(con: Connection) -> list[Any]: result = con.get(User.ROUTE) if 'users' not in result: raise BaseError("Request got no users!") - return list(map(lambda a: User.fromJsonObj(con, a), result['users'])) + return [User.fromJsonObj(con, a) for a in result['users']] @staticmethod def getAllUsersForProject(con: Connection, proj_shortcode: str) -> Optional[list[User]]: From c1362e42c82baf661a198f940a534e6aefb6344f Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum Date: Tue, 19 Apr 2022 15:18:05 +0200 Subject: [PATCH 6/7] edit --- knora/dsplib/models/user.py | 90 ++++++++++++---------------------- knora/dsplib/utils/onto_get.py | 4 +- 2 files changed, 33 insertions(+), 61 deletions(-) diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index 86639e4ce..d03816bab 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -476,18 +476,17 @@ def fromJsonObj(cls, con: Connection, json_obj: Any) -> User: in_groups: set[str] = set() if json_obj.get('permissions') is not None and json_obj['permissions'].get('groupsPerProject') is not None: sysadmin = False - project_groups = json_obj['permissions']['groupsPerProject'] - for project in project_groups: - if project == Project.SYSTEM_PROJECT: - if Group.PROJECT_SYSTEMADMIN_GROUP in project_groups[project]: + for project_iri, group_memberships in json_obj['permissions']['groupsPerProject'].items(): + if project_iri == Project.SYSTEM_PROJECT: + if Group.PROJECT_SYSTEMADMIN_GROUP in group_memberships: sysadmin = True else: - for group in project_groups[project]: + for group in group_memberships: if group == Group.PROJECT_MEMBER_GROUP: - if in_projects.get(project) is None: - in_projects[project] = False + if in_projects.get(project_iri) is None: + in_projects[project_iri] = False elif group == Group.PROJECT_ADMIN_GROUP: - in_projects[project] = True + in_projects[project_iri] = True else: in_groups.add(group) return cls(con=con, @@ -681,59 +680,23 @@ def getAllUsers(con: Connection) -> list[Any]: @staticmethod def getAllUsersForProject(con: Connection, proj_shortcode: str) -> Optional[list[User]]: """ - Get a list of all users that belong to a project(static method) + Get a list of all users that belong to a project (static method) :param con: Connection instance :project_shortcode: Shortcode of the project :return: List of users belonging to that project """ - - result = con.get(User.ROUTE) - if 'users' not in result: + members = con.get(f'/admin/projects/shortcode/{proj_shortcode}/members') + if members is None or len(members) < 1: return None - all_users = result["users"] - project_users = [] - for user in all_users: - project_list = con.get( - User.IRI + urllib.parse.quote_plus(user["id"], safe='') + '/project-memberships') - project = project_list["projects"] - for proj in project: - if proj["id"] == "http://rdfh.ch/projects/" + proj_shortcode: - project_users.append(user) - - # get the 'groups' and 'projects' of every user - for user in project_users: - user_iri_esc = urllib.parse.quote_plus(user["id"]) - project_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/project-memberships') - admin_group_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/project-admin-memberships') - normal_group_memberships = con.get(f'/admin/users/iri/{user_iri_esc}/group-memberships') - - for adm_group in admin_group_memberships['projects']: - user['projects'].append(f"{adm_group['shortname']}:admin") - - for project in project_memberships['projects']: - if f"{project['shortname']}:admin" not in user['projects']: - user['projects'].append(f"{project['shortname']}:member") - - for group in normal_group_memberships['groups']: - project_prefix = group['project']['shortname'] - user['groups'].append(f"{project_prefix}:{group['name']}") - - user['groups'].reverse() - - # convert to User objects - res: list[User] = [User.fromJsonObj(con, a) for a in project_users] - - # add the projects and groups to the User objects - for project_user, res_user in zip(project_users, res): - res_user._in_groups = res_user._in_groups | set(project_user['groups']) - for proj in project_user['projects']: - proj_name, admin = proj.split(':') - res_user._in_projects[proj_name] = bool(admin=='admin') - - return res - - def createDefinitionFileObj(self) -> dict[str, Union[str, list[str], None]]: + return [User.fromJsonObj(con, a) for a in members['members']] + + def createDefinitionFileObj( + self, + con: Connection, + proj_shortname: str, + proj_shortcode: str + ) -> dict[str, Union[str, list[str], None]]: user: dict[str, Union[str, list[str], None]] = { "username": self.username, "email": self.email, @@ -743,13 +706,20 @@ def createDefinitionFileObj(self) -> dict[str, Union[str, list[str], None]]: } if self.lang: user["lang"] = self.lang.value - user["groups"] = list(self._in_groups) + groups = list() + for group_iri in self._in_groups: + group_info = con.get(f'/admin/groups/{urllib.parse.quote_plus(group_iri)}') + if 'group' in group_info and 'name' in group_info['group']: + groupname = group_info['group']['name'] + groups.append(f'{proj_shortname}:{groupname}') + user["groups"] = groups user["projects"] = list() for proj, is_admin in self._in_projects.items(): - if is_admin: - user["projects"].append(f"{proj}:admin") - else: - user["projects"].append(f"{proj}:member") + if proj_shortcode in proj: + if is_admin: + user["projects"].append(f"{proj_shortname}:admin") + else: + user["projects"].append(f"{proj_shortname}:member") return user def print(self) -> None: diff --git a/knora/dsplib/utils/onto_get.py b/knora/dsplib/utils/onto_get.py index f8c90a728..e2662dce2 100644 --- a/knora/dsplib/utils/onto_get.py +++ b/knora/dsplib/utils/onto_get.py @@ -64,7 +64,9 @@ def get_ontology(project_identifier: str, outfile: str, server: str, user: str, users = User.getAllUsersForProject(con=con, proj_shortcode=project.shortcode) if users: for usr in users: - users_obj.append(usr.createDefinitionFileObj()) + users_obj.append(usr.createDefinitionFileObj( + con=con, proj_shortname=project.shortname, proj_shortcode=project.shortcode + )) if verbose: print(f"\tGot user '{usr.username}'") project_obj["users"] = users_obj From 373bf9362c2ec7af3ed068cef42241b4c3a8596f Mon Sep 17 00:00:00 2001 From: Johannes Nussbaum Date: Tue, 19 Apr 2022 15:33:02 +0200 Subject: [PATCH 7/7] inverse order of users --- knora/dsplib/models/user.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/knora/dsplib/models/user.py b/knora/dsplib/models/user.py index d03816bab..64aacdd74 100644 --- a/knora/dsplib/models/user.py +++ b/knora/dsplib/models/user.py @@ -689,7 +689,9 @@ def getAllUsersForProject(con: Connection, proj_shortcode: str) -> Optional[list members = con.get(f'/admin/projects/shortcode/{proj_shortcode}/members') if members is None or len(members) < 1: return None - return [User.fromJsonObj(con, a) for a in members['members']] + res: list[User] = [User.fromJsonObj(con, a) for a in members['members']] + res.reverse() + return res def createDefinitionFileObj( self,