diff --git a/CHANGELOG.md b/CHANGELOG.md index 0aefa18..8c156d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Security +## [0.3.6] - 2026-05-06 + +### Changed + +- Upgraded `libsuitecrm` to 0.5.2 to bring in fix for SuiteCRM's over-zealous HTML escaping. + +### Fixed + +- Updated the EDITOR role to have permission to set visibility on datasets. + ## [0.3.5] - 2026-04-28 ### Added diff --git a/pyproject.toml b/pyproject.toml index 5f5c7e6..cfc36ff 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "register-your-data-api" -version = "0.3.5" +version = "0.3.6" requires-python = ">= 3.12.11" readme = "README.md" authors = [{name="IATI Secretariat", email="support@iatistandard.org"}] @@ -15,7 +15,7 @@ dependencies = [ "email_validator>=2.3.0,<3.0.0", "fastapi[standard]==0.116.0", "jinja2>=3.1.6,<4.0.0", - "libsuitecrm @ git+https://github.com/iati/iati-registry-libsuitecrm@v0.5.1", + "libsuitecrm @ git+https://github.com/iati/iati-registry-libsuitecrm@v0.5.2", "prometheus-client>=0.22.1", "pyjwt>=2.10.0", "python-dotenv>=1.1.1", diff --git a/requirements.txt b/requirements.txt index 8347b8a..e29e9f9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -74,7 +74,7 @@ jinja2==3.1.6 # via # fastapi # register-your-data-api (pyproject.toml) -libsuitecrm @ git+https://github.com/iati/iati-registry-libsuitecrm@v0.5.1 +libsuitecrm @ git+https://github.com/iati/iati-registry-libsuitecrm@v0.5.2 # via register-your-data-api (pyproject.toml) mako==1.3.10 # via alembic diff --git a/requirements_dev.txt b/requirements_dev.txt index 12cfae3..248341a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -96,7 +96,7 @@ jinja2==3.1.6 # via # fastapi # register-your-data-api (pyproject.toml) -libsuitecrm @ git+https://github.com/iati/iati-registry-libsuitecrm@v0.5.1 +libsuitecrm @ git+https://github.com/iati/iati-registry-libsuitecrm@v0.5.2 # via register-your-data-api (pyproject.toml) mako==1.3.10 # via alembic diff --git a/src/register_your_data_api/auth/fga/fga_validator.py b/src/register_your_data_api/auth/fga/fga_validator.py index af56f34..585beb2 100644 --- a/src/register_your_data_api/auth/fga/fga_validator.py +++ b/src/register_your_data_api/auth/fga/fga_validator.py @@ -37,8 +37,12 @@ def get_permissions_for_role(self, user_role: FineGrainedAuthorisationRole) -> l FineGrainedAuthorisationRole.CONTRIBUTOR: ["read-org", "create-dataset", "read-dataset", "update-dataset"], } permissions[FineGrainedAuthorisationRole.EDITOR] = [ - *permissions[FineGrainedAuthorisationRole.CONTRIBUTOR], + "read-org", "update-org", + "read-dataset", + "create-dataset", + "update-dataset", + "update-dataset-visibility", "delete-dataset", ] permissions[FineGrainedAuthorisationRole.PROVIDER_ADMIN] = [ @@ -51,11 +55,15 @@ def get_permissions_for_role(self, user_role: FineGrainedAuthorisationRole) -> l "delete-dataset", ] permissions[FineGrainedAuthorisationRole.ADMIN] = [ - *permissions[FineGrainedAuthorisationRole.EDITOR], + "read-org", "update-org", "delete-org", - "set-org-user-authz", + "read-dataset", + "create-dataset", + "update-dataset", "update-dataset-visibility", + "delete-dataset", + "set-org-user-authz", ] return permissions[user_role] diff --git a/tests/integration/test_dataset_routes.py b/tests/integration/test_dataset_routes.py index adca3ef..51130b7 100644 --- a/tests/integration/test_dataset_routes.py +++ b/tests/integration/test_dataset_routes.py @@ -109,7 +109,7 @@ def test_dataset_update_with_invalid_short_name(invalid_short_name: str) -> None @pytest.mark.parametrize( "user,status_code,client_id", [ - (0, 403, "some_client"), # Editor + (0, 200, "some_client"), # Editor (1, 200, "some_client"), # Admin (2, 200, "some_client"), # Superadmin (3, 403, "some_client"), # Contributor diff --git a/tests/unit/test_fga_validator.py b/tests/unit/test_fga_validator.py index 9380c5f..ff5bae0 100644 --- a/tests/unit/test_fga_validator.py +++ b/tests/unit/test_fga_validator.py @@ -10,6 +10,62 @@ from ..helpers.utilities import association_lists_equal_ignore_id, gen_random_client_id +def test_editor() -> None: + """Test validator responses for the editor role""" + + user, org1, org2 = uuid4(), uuid4(), uuid4() + users_fgas = [ + FineGrainedAuthorisationRoleAssociation( + user=user, + reporting_org=org1, + role=FineGrainedAuthorisationRole.EDITOR, + ), + ] + + v = FineGrainedAuthorisationUserValidator( + user_id=user, + fine_grained_authorisations=users_fgas, + is_superadmin=False, + tools=[], + client_id=gen_random_client_id(), + ) + + assert v.get_user_role_for_reporting_org(org1) == FineGrainedAuthorisationRole.EDITOR + assert v.get_user_role_for_reporting_org(org2) is None + + assert v.user_can_create_reporting_org() + + assert v.user_can_read_reporting_org(org1) + assert not v.user_can_read_reporting_org(org2) + + assert v.user_can_update_reporting_org(org1) + assert not v.user_can_update_reporting_org(org2) + + assert not v.user_can_delete_reporting_org(org1) + assert not v.user_can_delete_reporting_org(org2) + + assert v.user_can_create_reporting_org_datasets(org1) + assert not v.user_can_create_reporting_org_datasets(org2) + + assert v.user_can_read_reporting_org_datasets(org1) + assert not v.user_can_read_reporting_org_datasets(org2) + + assert v.user_can_update_reporting_org_datasets(org1) + assert not v.user_can_update_reporting_org_datasets(org2) + + assert v.user_can_update_reporting_org_dataset_visibility(org1) + assert not v.user_can_update_reporting_org_dataset_visibility(org2) + + assert v.user_can_delete_reporting_org_datasets(org1) + assert not v.user_can_delete_reporting_org_datasets(org2) + + assert not v.user_can_modify_user_roles_for_reporting_org(org1) + assert not v.user_can_modify_user_roles_for_reporting_org(org2) + + assert v.user_can_read_users_reporting_orgs(user) + assert not v.user_can_read_users_reporting_orgs(uuid4()) + + def test_provider_admin() -> None: """Test validator responses for a provider admin role"""