diff --git a/invenio_communities/config.py b/invenio_communities/config.py index 5d26c537f..f48a3aef6 100644 --- a/invenio_communities/config.py +++ b/invenio_communities/config.py @@ -2,8 +2,8 @@ # # This file is part of Invenio. # Copyright (C) 2016-2024 CERN. -# Copyright (C) 2023 Graz University of Technology. -# Copyright (C) 2023 KTH Royal Institute of Technology. +# Copyright (C) 2023 Graz University of Technology. +# Copyright (C) 2023-2024 KTH Royal Institute of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -334,3 +334,6 @@ COMMUNITIES_DEFAULT_RECORD_SUBMISSION_POLICY = RecordSubmissionPolicyEnum.OPEN """Default value of record submission policy community access setting.""" + +COMMUNITIES_CREATOR_ROLE = "community-creator" +"""Depends on 'RDM_COMMUNITY_REQUIRED_TO_PUBLISH' set to True.""" diff --git a/invenio_communities/generators.py b/invenio_communities/generators.py index 6d5cb9031..b362a0c39 100644 --- a/invenio_communities/generators.py +++ b/invenio_communities/generators.py @@ -5,6 +5,7 @@ # Copyright (C) 2021 Graz University of Technology. # Copyright (C) 2021 TU Wien. # Copyright (C) 2022 Northwestern University. +# Copyright (C) 2024 KTH Royal Institute of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -16,7 +17,8 @@ from functools import partial, reduce from itertools import chain -from flask_principal import UserNeed +from flask import current_app +from flask_principal import RoleNeed, UserNeed from invenio_access.permissions import any_user, authenticated_user, system_process from invenio_records.dictutils import dict_lookup from invenio_records_permissions.generators import Generator @@ -342,6 +344,17 @@ def roles(self, **kwargs): return [r.name for r in current_roles.can("manage")] +class CommunityCreator(Generator): + """Allows users with the "trusted-user" role.""" + + def needs(self, **kwargs): + """Enabling Needs.""" + role_name = current_app.config.get( + "COMMUNITIES_CREATOR_ROLE", "community-creator" + ) + return [RoleNeed(role_name)] + + class CommunityManagersForRole(CommunityRoles): """Roles representing all managers of a community for a role update.""" diff --git a/invenio_communities/permissions.py b/invenio_communities/permissions.py index cd6eb27fc..e74f8d9aa 100644 --- a/invenio_communities/permissions.py +++ b/invenio_communities/permissions.py @@ -5,6 +5,7 @@ # Copyright (C) 2021 Graz University of Technology. # Copyright (C) 2021 TU Wien. # Copyright (C) 2022 Northwestern University. +# Copyright (C) 2024 KTH Royal Institute of Technology. # # Invenio is free software; you can redistribute it and/or modify it # under the terms of the MIT License; see LICENSE file for more details. @@ -26,6 +27,7 @@ from .generators import ( AllowedMemberTypes, AuthenticatedButNotCommunityMembers, + CommunityCreator, CommunityCurators, CommunityManagers, CommunityManagersForRole, @@ -45,7 +47,15 @@ class CommunityPermissionPolicy(BasePermissionPolicy): """Permissions for Community CRUD operations.""" # Community - can_create = [AuthenticatedUser(), SystemProcess()] + _can_create = [AuthenticatedUser(), SystemProcess()] + + can_create = [ + IfConfig( + "RDM_COMMUNITY_REQUIRED_TO_PUBLISH", + then_=[CommunityCreator(), Administration(), SystemProcess()], + else_=_can_create, + ) + ] can_read = [ IfRestricted("visibility", then_=[CommunityMembers()], else_=[AnyUser()]), diff --git a/tests/communities/test_permissions.py b/tests/communities/test_permissions.py index 63f789859..0baddace7 100644 --- a/tests/communities/test_permissions.py +++ b/tests/communities/test_permissions.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2024 Northwestern University. +# Copyright (C) 2024 KTH Royal Institute of Technology. # # Invenio-RDM-Records is free software; you can redistribute it # and/or modify it under the terms of the MIT License; see LICENSE file for # more details. """Test permissions.""" +from flask_principal import Identity, RoleNeed +from invenio_access.permissions import system_identity from invenio_communities.permissions import CommunityPermissionPolicy @@ -61,3 +64,54 @@ def test_can_request_membership( ) app.config["COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS"] = allow_membership_requests_orig + + +def test_can_create(app, anon_identity, any_user, superuser_identity): + policy = CommunityPermissionPolicy + + # Save the original configuration value + original_config = app.config.get("RDM_COMMUNITY_REQUIRED_TO_PUBLISH", False) + + authenticated_identity = any_user.identity + + # Create an identity for a user with the 'community-creator' role + # Copy the provides from authenticated_identity to ensure 'authenticated_user' is included + community_creator_identity = Identity(any_user.id) + community_creator_identity.provides.update(authenticated_identity.provides) + community_creator_identity.provides.add(RoleNeed("community-creator")) + + # RDM_COMMUNITY_REQUIRED_TO_PUBLISH is True + app.config["RDM_COMMUNITY_REQUIRED_TO_PUBLISH"] = True + + # Anonymous user cannot create communities + assert not policy(action="create").allows(anon_identity) + + # Authenticated user without 'community-creator' role cannot create + assert not policy(action="create").allows(authenticated_identity) + + # Authenticated user with 'community-creator' role can create + assert policy(action="create").allows(community_creator_identity) + + # Superuser (admin) can create communities + assert policy(action="create").allows(superuser_identity) + + # System process can create communities + assert policy(action="create").allows(system_identity) + + # RDM_COMMUNITY_REQUIRED_TO_PUBLISH is False + app.config["RDM_COMMUNITY_REQUIRED_TO_PUBLISH"] = False + + # Anonymous user cannot create communities + assert not policy(action="create").allows(anon_identity) + + # Authenticated user without 'community-creator' role can create + assert policy(action="create").allows(authenticated_identity) + + # Authenticated user with 'community-creator' role can create + assert policy(action="create").allows(community_creator_identity) + + # Superuser (admin) can create communities + assert policy(action="create").allows(superuser_identity) + + # System process can create communities + assert policy(action="create").allows(system_identity)