Skip to content

Commit

Permalink
Allow users to call the add-membership API
Browse files Browse the repository at this point in the history
Allow users (not just authclients) to call the add-membership API.
  • Loading branch information
seanh committed Dec 12, 2024
1 parent a3a946a commit 8491ea9
Show file tree
Hide file tree
Showing 9 changed files with 401 additions and 28 deletions.
2 changes: 1 addition & 1 deletion h/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from h.models.feature import Feature
from h.models.feature_cohort import FeatureCohort, FeatureCohortUser
from h.models.flag import Flag
from h.models.group import Group, GroupMembership, GroupMembershipRoles
from h.models.group import DEFAULT_ROLES, Group, GroupMembership, GroupMembershipRoles
from h.models.group_scope import GroupScope
from h.models.job import Job
from h.models.organization import Organization
Expand Down
4 changes: 4 additions & 0 deletions h/models/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ class GroupMembershipRoles(enum.StrEnum):
OWNER = "owner"


DEFAULT_ROLES = [GroupMembershipRoles.MEMBER]
"""The default roles to be used when creating new memberships."""


class GroupMembership(Base):
__tablename__ = "user_group"

Expand Down
5 changes: 4 additions & 1 deletion h/security/permission_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,10 @@
[p.group_has_user_as_admin],
[p.group_has_user_as_moderator],
],
Permission.Group.MEMBER_ADD: [[p.group_matches_authenticated_client_authority]],
Permission.Group.MEMBER_ADD: [
[p.group_member_add],
[p.group_matches_authenticated_client_authority],
],
Permission.Group.MEMBER_REMOVE: [[p.group_member_remove]],
Permission.Group.MEMBER_EDIT: [[p.group_member_edit]],
Permission.Group.MODERATE: GROUP_MODERATE_PREDICATES.values(),
Expand Down
36 changes: 35 additions & 1 deletion h/security/predicates.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@
from itertools import chain

from h.models.group import GroupMembershipRoles, JoinableBy, ReadableBy, WriteableBy
from h.traversal import EditGroupMembershipContext, GroupMembershipContext
from h.traversal import (
AddGroupMembershipContext,
EditGroupMembershipContext,
GroupMembershipContext,
)


def requires(*parent_predicates):
Expand Down Expand Up @@ -217,6 +221,36 @@ def get_authenticated_users_membership():
)


@requires(authenticated_user, group_found)
def group_member_add(identity, context: AddGroupMembershipContext):
assert (
context.new_roles is not None
), "new_roles must be set before checking permissions"

def get_authenticated_users_roles():
"""Return the authenticated users roles in the target group."""
for membership in identity.user.memberships:
if membership.group.id == context.group.id:
return membership.roles

return []

authenticated_users_roles = get_authenticated_users_roles()

if GroupMembershipRoles.OWNER in authenticated_users_roles:
# Owners can add members with any roles.
return True

if GroupMembershipRoles.ADMIN in authenticated_users_roles:
# Admins can add members with any role below admin.
return (
GroupMembershipRoles.OWNER not in context.new_roles
and GroupMembershipRoles.ADMIN not in context.new_roles
)

return False


@requires(authenticated_user, group_found)
def group_member_edit(
identity, context: EditGroupMembershipContext
Expand Down
4 changes: 2 additions & 2 deletions h/services/group_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from sqlalchemy import func, nulls_first, or_, select

from h import session
from h.models import Group, GroupMembership, GroupMembershipRoles, User
from h.models import DEFAULT_ROLES, Group, GroupMembership, GroupMembershipRoles, User

log = logging.getLogger(__name__)

Expand Down Expand Up @@ -127,7 +127,7 @@ def member_join(self, group, userid, roles=None) -> GroupMembership:
:raise ConflictError: if a membership already exists with the given
group and userid but different roles
"""
roles = roles or [GroupMembershipRoles.MEMBER]
roles = roles or DEFAULT_ROLES

user = self.user_fetcher(userid)

Expand Down
13 changes: 7 additions & 6 deletions h/views/api/group_members.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from pyramid.config import not_
from pyramid.httpexceptions import HTTPConflict, HTTPNoContent, HTTPNotFound

from h.models import DEFAULT_ROLES
from h.presenters import GroupMembershipJSONPresenter
from h.schemas.api.group_membership import EditGroupMembershipAPISchema
from h.schemas.pagination import PaginationQueryParamsSchema
Expand Down Expand Up @@ -99,25 +100,25 @@ def remove_member(context: GroupMembershipContext, request):
request_method="POST",
link_name="group.member.add",
description="Add a user to a group",
permission=Permission.Group.MEMBER_ADD,
)
def add_member(context: AddGroupMembershipContext, request):
if context.user.authority != context.group.authority:
raise HTTPNotFound()

if request.body:
appstruct = EditGroupMembershipAPISchema().validate(json_payload(request))
roles = appstruct["roles"]
context.new_roles = appstruct["roles"]
else:
# This doesn't mean the membership will be created with no roles:
# default roles will be applied by the service.
roles = None
context.new_roles = DEFAULT_ROLES

if not request.has_permission(Permission.Group.MEMBER_ADD, context):
raise HTTPNotFound()

group_members_service = request.find_service(name="group_members")

try:
membership = group_members_service.member_join(
context.group, context.user.userid, roles
context.group, context.user.userid, context.new_roles
)
except ConflictError as err:
raise HTTPConflict(str(err)) from err
Expand Down
Loading

0 comments on commit 8491ea9

Please sign in to comment.