From ef6646e8b692cb96592bcf3e0c4c0eef71c48020 Mon Sep 17 00:00:00 2001 From: index-git Date: Wed, 29 Nov 2023 17:53:59 +0100 Subject: [PATCH] On PATCH Workspace Layer/Map update role names to DB --- CHANGELOG.md | 2 +- doc/about.md | 2 +- .../common/prime_db_schema/publications.py | 20 +++++++++++-------- .../prime_db_schema/publications_test.py | 14 +++++++++++-- src/layman/common/prime_db_schema/rights.py | 14 ++++++++++++- .../publications/access_rights/test_role.py | 1 + 6 files changed, 40 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 504743ebe..9e107b216 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ - [#165](https://github.com/LayerManager/layman/issues/165) Add column `role_name` to table `rights` in prime DB schema. Add constraint that exactly one of columns `role_name` and `id_user` is not null. #### Data migrations ### Changes -- [#165](https://github.com/LayerManager/layman/issues/165) POST Workspace [Layers](doc/rest.md#post-workspace-layers)/[Maps](doc/rest.md#post-workspace-maps) saves [role names](doc/models.md#role) mentioned in `access_rights.read` and `access_rights.write` parameters into DB. +- [#165](https://github.com/LayerManager/layman/issues/165) POST Workspace [Layers](doc/rest.md#post-workspace-layers)/[Maps](doc/rest.md#post-workspace-maps) and PATCH Workspace [Layer](doc/rest.md#patch-workspace-layer)/[Map](doc/rest.md#patch-workspace-map) saves [role names](doc/models.md#role) mentioned in `access_rights.read` and `access_rights.write` parameters into DB. - [#165](https://github.com/LayerManager/layman/issues/165) Many endpoints return previously associated [role names](doc/models.md#role) in `access_rights.read` and `access_rights.write` keys: - [GET](doc/rest.md#get-workspace-layer)/[PATCH](doc/rest.md#patch-workspace-layer) Workspace Layer - [GET](doc/rest.md#get-workspace-map)/[PATCH](doc/rest.md#patch-workspace-map) Workspace Map diff --git a/doc/about.md b/doc/about.md index a7ef77b04..09407985e 100644 --- a/doc/about.md +++ b/doc/about.md @@ -12,7 +12,7 @@ Layman supports two main models of geospatial data: layers and maps. **Layer** i There are multiple client applications for communication with Layman through its REST API: simple web test client shipped with Layman, QGIS desktop client, and HSLayers library. Published data are accessible through standardized OGC APIs: Web Map Service, Web Feature Service, and Catalogue Service. ### Security -Layman`s security system uses two well-known concepts: authentication and authorization. Common configuration consists of authentication based on widely used OAuth2 protocol and authorization allows users to give read and write access rights to each user on publication level. +Layman`s security system uses two well-known concepts: authentication and authorization. Common configuration consists of authentication based on widely used OAuth2 protocol and authorization allows users to give read and write access rights to users and roles on publication level. ### Scalability Large data files can be easily published thanks to chunk upload. Asynchronous processing ensures fast communication with REST API. Processing tasks can be distributed on multiple servers. Layman also stands on the shoulders of widely used programs like Flask, PostgreSQL, PostGIS, GDAL, GeoServer, QGIS Server, Celery, and Redis. diff --git a/src/layman/common/prime_db_schema/publications.py b/src/layman/common/prime_db_schema/publications.py index c53bd9e70..74629bed1 100644 --- a/src/layman/common/prime_db_schema/publications.py +++ b/src/layman/common/prime_db_schema/publications.py @@ -495,8 +495,10 @@ def update_publication(workspace_name, info): for right_type in right_type_list: access_rights_changes[right_type] = { 'EVERYONE': None, - 'add': set(), - 'remove': set(), + 'add_users': set(), + 'add_roles': set(), + 'remove_users': set(), + 'remove_roles': set(), } external_table_uri = psycopg2.extras.Json({ @@ -521,10 +523,12 @@ def update_publication(workspace_name, info): if info['access_rights'].get(right_type): usernames_list = info["access_rights"].get(right_type) access_rights_changes[right_type]['EVERYONE'] = ROLE_EVERYONE in usernames_list - usernames_list_clear = get_user_and_role_names_for_db(usernames_list, workspace_name)[0] - usernames_old_list_clear = get_user_and_role_names_for_db(access_rights_changes[right_type]['username_list_old'], workspace_name)[0] - access_rights_changes[right_type]['add'] = usernames_list_clear.difference(usernames_old_list_clear) - access_rights_changes[right_type]['remove'] = usernames_old_list_clear.difference(usernames_list_clear) + usernames_list_clear, roles_list_clear = get_user_and_role_names_for_db(usernames_list, workspace_name) + usernames_old_list_clear, roles_old_list_clear = get_user_and_role_names_for_db(access_rights_changes[right_type]['username_list_old'], workspace_name) + access_rights_changes[right_type]['add_users'] = usernames_list_clear.difference(usernames_old_list_clear) + access_rights_changes[right_type]['add_roles'] = roles_list_clear.difference(roles_old_list_clear) + access_rights_changes[right_type]['remove_users'] = usernames_old_list_clear.difference(usernames_list_clear) + access_rights_changes[right_type]['remove_roles'] = roles_old_list_clear.difference(roles_list_clear) update_publications_sql = f'''update {DB_SCHEMA}.publications set title = coalesce(%s, title), @@ -555,8 +559,8 @@ def update_publication(workspace_name, info): pub_id = db_util.run_query(update_publications_sql, data)[0][0] for right_type in right_type_list: - rights.insert_rights(pub_id, access_rights_changes[right_type]['add'], set(), right_type) - rights.remove_rights(pub_id, access_rights_changes[right_type]['remove'], right_type) + rights.insert_rights(pub_id, access_rights_changes[right_type]['add_users'], access_rights_changes[right_type]['add_roles'], right_type) + rights.remove_rights(pub_id, access_rights_changes[right_type]['remove_users'], access_rights_changes[right_type]['remove_roles'], right_type) return pub_id diff --git a/src/layman/common/prime_db_schema/publications_test.py b/src/layman/common/prime_db_schema/publications_test.py index eb10c9df0..a84ac85d5 100644 --- a/src/layman/common/prime_db_schema/publications_test.py +++ b/src/layman/common/prime_db_schema/publications_test.py @@ -346,6 +346,8 @@ class TestUpdateRights: workspace_name = 'test_update_rights_workspace' username = 'test_update_rights_user' username2 = 'test_update_rights_user2' + role1 = 'TEST_UPDATE_RIGHTS_ROLE1' + role2 = 'TEST_UPDATE_RIGHTS_ROLE2' publication_name = 'test_update_rights_publication_name' publication_type = MAP_TYPE @@ -354,8 +356,8 @@ class TestUpdateRights: "publ_type_name": publication_type, "actor_name": username, "uuid": uuid.uuid4(), - "access_rights": {"read": {settings.RIGHTS_EVERYONE_ROLE, }, - "write": {settings.RIGHTS_EVERYONE_ROLE, }, + "access_rights": {"read": {settings.RIGHTS_EVERYONE_ROLE, role1, }, + "write": {settings.RIGHTS_EVERYONE_ROLE, role1, }, }, "image_mosaic": False, } @@ -416,6 +418,14 @@ def provide_data(self, request): [username, settings.RIGHTS_EVERYONE_ROLE, ], [username, settings.RIGHTS_EVERYONE_ROLE, ], id='personal_everyone_as_anonym', ), + pytest.param( + username, + {"access_rights": {"read": {username, role2, }, + "write": {username, role2, }}, + "actor_name": username}, + [username, role2, ], [username, role2, ], + id='personal_owner_role', + ), ]) def test_rights(self, username, publication_update_info, read_to_test, write_to_test, ): publication_info_original = self.publication_insert_info diff --git a/src/layman/common/prime_db_schema/rights.py b/src/layman/common/prime_db_schema/rights.py index 5e6032e11..90161fa5a 100644 --- a/src/layman/common/prime_db_schema/rights.py +++ b/src/layman/common/prime_db_schema/rights.py @@ -44,7 +44,7 @@ def delete_rights_for_publication(id_publication): ) -def remove_rights(id_publication, users_list, right_type): +def remove_rights(id_publication, users_list, roles_list, right_type): sql = f'''delete from {DB_SCHEMA}.rights where id_publication = %s and type = %s @@ -59,3 +59,15 @@ def remove_rights(id_publication, users_list, right_type): username, ) ) + + sql = f'''delete from {DB_SCHEMA}.rights +where id_publication = %s + and type = %s + and role_name = %s;''' + for role in roles_list: + db_util.run_statement(sql, + (id_publication, + right_type, + role, + ) + ) diff --git a/tests/dynamic_data/publications/access_rights/test_role.py b/tests/dynamic_data/publications/access_rights/test_role.py index 97cd851fa..49f0e04dc 100644 --- a/tests/dynamic_data/publications/access_rights/test_role.py +++ b/tests/dynamic_data/publications/access_rights/test_role.py @@ -25,6 +25,7 @@ class TestPublication(base_test.TestSingleRestPublication): rest_parametrization = [ PublicationTypes, + base_test_classes.RestMethod ] usernames_to_reserve = [