Skip to content

Commit d13885d

Browse files
committed
permissions: add can_request_membership
1 parent a64cd8a commit d13885d

File tree

5 files changed

+105
-4
lines changed

5 files changed

+105
-4
lines changed

invenio_communities/generators.py

+21-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from itertools import chain
1818

1919
from flask_principal import UserNeed
20-
from invenio_access.permissions import any_user, system_process
20+
from invenio_access.permissions import any_user, authenticated_user, system_process
2121
from invenio_records_permissions.generators import Generator
2222
from invenio_search.engine import dsl
2323

@@ -199,6 +199,26 @@ def query_filter(self, **kwargs):
199199
#
200200
# Community membership generators
201201
#
202+
203+
class AuthenticatedButNotCommunityMembers(Generator):
204+
"""Authenticated user not part of community."""
205+
206+
def needs(self, record=None, **kwargs):
207+
"""Required needs."""
208+
return [authenticated_user]
209+
210+
def excludes(self, record=None, **kwargs):
211+
"""Exluding needs.
212+
213+
Excludes identities with a role in the community. This assumes all roles at
214+
this point mean valid memberships. This is the same assumption as
215+
`CommunityMembers` below.
216+
"""
217+
if not record:
218+
return []
219+
community_id = str(record.id)
220+
return [CommunityRoleNeed(community_id, r.name) for r in current_roles]
221+
202222
class CommunityRoles(Generator):
203223
"""Base class for community roles generators."""
204224

invenio_communities/permissions.py

+18-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
from .generators import (
2727
AllowedMemberTypes,
28+
AuthenticatedButNotCommunityMembers,
2829
CommunityCurators,
2930
CommunityManagers,
3031
CommunityManagersForRole,
@@ -179,9 +180,25 @@ class CommunityPermissionPolicy(BasePermissionPolicy):
179180
# Permissions to set if communities can have children
180181
can_manage_children = [SystemProcess()]
181182

182-
# Permission for assinging a parent community
183+
# Permission for assigning a parent community
183184
can_manage_parent = [Administration(), SystemProcess()]
184185

186+
# request_membership permission is based on configuration, community settings and
187+
# identity. Other factors (e.g., previous membership requests) are not under
188+
# its purview and are dealt with elsewhere.
189+
can_request_membership = [
190+
IfConfig(
191+
"COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS",
192+
then_=[
193+
IfPolicyClosed(
194+
"member_policy",
195+
then_=[Disable()],
196+
else_=[AuthenticatedButNotCommunityMembers()]
197+
)
198+
],
199+
else_=[Disable()]
200+
),
201+
]
185202

186203
def can_perform_action(community, context):
187204
"""Check if the given action is available on the request."""

invenio_communities/templates/semantic-ui/invenio_communities/details/header.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@
1212
{%- from "invenio_communities/details/macros/access-status-label.html" import access_status_label -%}
1313

1414
{% macro button_to_request_membership(community) %}
15-
{# TODO: replace by permission check when permissions implemented #}
16-
{% if config.COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS %}
15+
{% if permissions.can_request_membership %}
16+
{# TODO: Add relation_to_community for other flows #}
1717
<div
1818
id="request-membership-app"
1919
data-community='{{ community | tojson }}'

invenio_communities/views/communities.py

+1
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@
107107
"search_requests",
108108
"members_search_public",
109109
"moderate",
110+
"request_membership",
110111
}
111112

112113
PRIVATE_PERMISSIONS = HEADER_PERMISSIONS | {

tests/communities/test_permissions.py

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (C) 2024 Northwestern University.
4+
#
5+
# Invenio-RDM-Records is free software; you can redistribute it
6+
# and/or modify it under the terms of the MIT License; see LICENSE file for
7+
# more details.
8+
9+
"""Test permissions."""
10+
11+
from invenio_communities.permissions import CommunityPermissionPolicy
12+
13+
14+
def test_can_request_membership(
15+
app, community, owner, anon_identity, any_user, superuser_identity
16+
):
17+
18+
policy = CommunityPermissionPolicy
19+
community_record = community._record
20+
authenticated_identity = any_user.identity
21+
22+
allow_membership_requests_orig = app.config["COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS"]
23+
24+
# Case - feature disabled
25+
app.config["COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS"] = False
26+
assert not (
27+
policy(action="request_membership", record=community_record).allows(
28+
superuser_identity
29+
)
30+
)
31+
32+
app.config["COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS"] = True
33+
34+
# Case - setting disabled
35+
community_record.access.member_policy = "closed"
36+
assert not (
37+
policy(action="request_membership", record=community_record).allows(
38+
superuser_identity
39+
)
40+
)
41+
42+
community_record.access.member_policy = "open"
43+
44+
# Case - unlogged user
45+
assert not (
46+
policy(action="request_membership", record=community_record).allows(
47+
anon_identity
48+
)
49+
)
50+
51+
# Case - logged user not part of community
52+
assert policy(action="request_membership", record=community_record).allows(
53+
authenticated_identity
54+
)
55+
56+
# Case - member of community
57+
assert not (
58+
policy(action="request_membership", record=community_record).allows(
59+
owner.identity
60+
)
61+
)
62+
63+
app.config["COMMUNITIES_ALLOW_MEMBERSHIP_REQUESTS"] = allow_membership_requests_orig

0 commit comments

Comments
 (0)