From 91e8ac425cb658bd6b28529eb214a08eb75a8e2d Mon Sep 17 00:00:00 2001 From: Sean Hammond Date: Wed, 16 Oct 2024 15:36:48 +0100 Subject: [PATCH] wip --- h/security/permission_map.py | 1 + h/security/policy/_api_cookie.py | 2 + .../components/CreateEditGroupForm.tsx | 98 ++++++++++++++----- h/static/scripts/group-forms/utils/api.ts | 12 ++- h/views/api/groups.py | 11 ++- 5 files changed, 95 insertions(+), 29 deletions(-) diff --git a/h/security/permission_map.py b/h/security/permission_map.py index 1a6ecfe541d..0e93cc5c0ba 100644 --- a/h/security/permission_map.py +++ b/h/security/permission_map.py @@ -51,6 +51,7 @@ Permission.Group.EDIT: [ [p.group_matches_authenticated_client_authority], [p.group_has_user_as_owner], + [p.group_has_user_as_admin], ], Permission.Group.MEMBER_ADD: [[p.group_matches_authenticated_client_authority]], Permission.Group.MEMBER_REMOVE: [[p.group_member_remove]], diff --git a/h/security/policy/_api_cookie.py b/h/security/policy/_api_cookie.py index 5bc3c3eab5a..626d50c25ce 100644 --- a/h/security/policy/_api_cookie.py +++ b/h/security/policy/_api_cookie.py @@ -9,6 +9,8 @@ ("api.groups", "POST"), # Create a new group. ("api.group", "PATCH"), # Edit an existing group. ("api.group_members", "GET"), # Get a list of a group's members. + ("api.group_member", "DELETE"), # Remove a user from a group. + ("api.group_member", "PATCH"), # Change a user's role in a group. ] diff --git a/h/static/scripts/group-forms/components/CreateEditGroupForm.tsx b/h/static/scripts/group-forms/components/CreateEditGroupForm.tsx index 839823a2e17..08ef249ab5e 100644 --- a/h/static/scripts/group-forms/components/CreateEditGroupForm.tsx +++ b/h/static/scripts/group-forms/components/CreateEditGroupForm.tsx @@ -4,6 +4,7 @@ import { Button, CancelIcon, TrashIcon, + IconButton, Input, RadioGroup, Table, @@ -356,6 +357,80 @@ export default function CreateEditGroupForm() { } } ,[]) + function uppercaseFirstLetter(str) { + return str[0].toUpperCase() + str.slice(1); + } + + function selectOptions(member) { + const options = [] + Object.keys(member.api.role).forEach((role) => { + options.push( + + {uppercaseFirstLetter(role)} + + ); + }); + return options; + } + + async function onClickDelete(api) { + await callAPI(api.url, { + method: api.method, + headers: api.headers, + parseResponseBody: false, + }); + getMembers(); + } + + async function onChangeRole(api, newRole) { + await callAPI(api[newRole].url, { + method: api[newRole].method, + headers: api[newRole].headers, + json: api[newRole].body + }); + getMembers(); + } + + function tableRows(members: Array) { + const rows = [] + + if (members.length > 0) { + Object.keys(members[0].api.role).forEach((key) => { + const value = members[0].api.role[key]; + console.log(`${key}: ${value}`); + }); + } + members.forEach((member) => { + rows.push( + + {member.username} + + {Object.keys(member.api.role).length > 0 + ? + : + } + + + {member.api.delete + ? onClickDelete(member.api.delete)}> + + + : + + + } + + + ); + }); + + return rows; + } + return (

@@ -425,28 +500,7 @@ export default function CreateEditGroupForm() { - {members.map((member) => ( - - {member.username} - - - - - - ))} + {tableRows(members)} diff --git a/h/static/scripts/group-forms/utils/api.ts b/h/static/scripts/group-forms/utils/api.ts index af24c9f6b85..949ed65612b 100644 --- a/h/static/scripts/group-forms/utils/api.ts +++ b/h/static/scripts/group-forms/utils/api.ts @@ -68,10 +68,12 @@ export async function callAPI( method = 'GET', json = null, headers = {}, + parseResponseBody = true, }: { method?: string; json?: object | null; headers?: any; + parseResponseBody?: boolean; } = {}, ): Promise { const options: RequestInit = { @@ -99,10 +101,12 @@ export async function callAPI( let responseJSON; let responseJSONError; - try { - responseJSON = await response.json(); - } catch (jsonError) { - responseJSONError = jsonError; + if (parseResponseBody) { + try { + responseJSON = await response.json(); + } catch (jsonError) { + responseJSONError = jsonError; + } } if (!response.ok) { diff --git a/h/views/api/groups.py b/h/views/api/groups.py index db81145373d..fefcdd77e63 100644 --- a/h/views/api/groups.py +++ b/h/views/api/groups.py @@ -1,4 +1,5 @@ from pyramid.httpexceptions import HTTPConflict, HTTPNoContent, HTTPNotFound +from pyramid.csrf import get_csrf_token from sqlalchemy import select from sqlalchemy.orm import selectinload @@ -159,11 +160,13 @@ def read_members(context: GroupContext, request): .where(GroupMembership.group == context.group) ) + csrf_token = get_csrf_token(request) + for membership in memberships: membership_dict = { "role": membership.role, **UserJSONPresenter(membership.user).asdict(), - "api": {}, + "api": {"role": {}}, } # If the authenticated user has permission to remove this member from @@ -175,13 +178,14 @@ def read_members(context: GroupContext, request): if request.has_permission( Permission.Group.MEMBER_REMOVE, membership_remove_context ): - membership_dict["api"]["delete"] = { + membership_dict.setdefault("api", {})["delete"] = { "method": "DELETE", "url": request.route_url( "api.group_member", pubid=context.group.pubid, userid=membership.user.userid, ), + "headers": {"X-CSRF-Token": csrf_token}, } # For each possible role if the authenticated user has permission to @@ -195,7 +199,7 @@ def read_members(context: GroupContext, request): if membership.role != role and request.has_permission( Permission.Group.MEMBER_EDIT, edit_membership_context ): - membership_dict["api"][f"make_{role}"] = { + membership_dict.setdefault("api", {}).setdefault("role", {})[role] = { "method": "PATCH", "url": request.route_url( "api.group_member", @@ -203,6 +207,7 @@ def read_members(context: GroupContext, request): userid=membership.user.userid, ), "body": {"role": role}, + "headers": {"X-CSRF-Token": csrf_token}, } membership_dicts.append(membership_dict)