diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index a94d170e3b..961c7020a0 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -3312,20 +3312,6 @@ }, "query": "SELECT * FROM group_ WHERE workspace_id = $1 ORDER BY name desc LIMIT $2 OFFSET $3" }, - "add01e9e31d64e88b84c9505fe3de553031e581b1bb173413a9a3e3eb0817b43": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Varchar", - "Varchar", - "Varchar" - ] - } - }, - "query": "INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3)" - }, "aeaae7641539d51f4535b04e9a7adb7545bd37d0d706edb54b8f5507403105ee": { "describe": { "columns": [ diff --git a/backend/windmill-api/src/groups.rs b/backend/windmill-api/src/groups.rs index 2b96d04df9..0b0f4ecaef 100644 --- a/backend/windmill-api/src/groups.rs +++ b/backend/windmill-api/src/groups.rs @@ -24,6 +24,7 @@ use windmill_common::{ use serde::{Deserialize, Serialize}; use sqlx::{FromRow, Postgres, Transaction}; +use windmill_queue::CLOUD_HOSTED; pub fn workspaced_service() -> Router { Router::new() @@ -159,6 +160,16 @@ async fn get_group( Extension(user_db): Extension, Path((w_id, name)): Path<(String, String)>, ) -> JsonResult { + if *CLOUD_HOSTED && w_id == "demo" && name == "all" && !authed.is_admin { + return Ok(Json(GroupInfo { + workspace_id: w_id, + name: name, + summary: Some("The group that contains all users".to_string()), + members: vec!["redacted_in_demo_workspace".to_string()], + extra_perms: serde_json::json!({}), + })); + } + let mut tx = user_db.begin(&authed).await?; let group = not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?; @@ -266,7 +277,7 @@ async fn add_user( sqlx::query_as!( Group, - "INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3)", + "INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING", &w_id, user_username, name, diff --git a/frontend/src/lib/components/GroupEditor.svelte b/frontend/src/lib/components/GroupEditor.svelte index abf6367d6b..ee6712dde7 100644 --- a/frontend/src/lib/components/GroupEditor.svelte +++ b/frontend/src/lib/components/GroupEditor.svelte @@ -11,11 +11,13 @@ export let name: string let can_write = false - type Role = 'viewer' | 'member' | 'manager' | 'member_manager' + type Role = 'member' | 'manager' | 'member_manager' let group: Group | undefined - let members: { name: string; role: Role }[] | undefined = undefined + let members: { member_name: string; role: Role }[] | undefined = undefined + let managing_groups: string[] = [] let usernames: string[] | undefined = [] let username: string = '' + let groups: string[] = [] async function loadUsernames(): Promise { usernames = await UserService.listUsernames({ workspace: $workspaceStore! }) @@ -28,8 +30,13 @@ } async function load() { + loadGroups() await loadGroup() - await loadUsernames() + loadUsernames() + } + + async function loadGroups(): Promise { + groups = (await GroupService.listGroups({ workspace: $workspaceStore! })).map((x) => x.name) } async function addToGroup() { @@ -41,41 +48,64 @@ loadGroup() } + async function addToManagingGroup() { + await GranularAclService.addGranularAcls({ + workspace: $workspaceStore ?? '', + path: name, + kind: 'group_', + requestBody: { + owner: 'g/' + new_managing_group, + write: true + } + }) + loadGroup() + } + async function loadGroup(): Promise { group = await GroupService.getGroup({ workspace: $workspaceStore!, name }) - can_write = canWrite(group.name!, group.extra_perms ?? {}, $userStore) - members = Object.keys(group?.extra_perms ?? {}) - .concat(group?.members ?? []) - .map((x) => { - return { - name: x, - role: getRole(x) - } - }) + can_write = canWrite(name!, group.extra_perms ?? {}, $userStore) + members = Array.from( + new Set( + Object.entries(group?.extra_perms ?? {}) + .filter(([k, v]) => k.startsWith('u/') && v) + .map(([k, _]) => k.split('/')[1]) + .concat(group?.members ?? []) + ) + ).map((x) => { + return { + member_name: x, + role: getRole(x) + } + }) + managing_groups = Object.entries(group?.extra_perms ?? {}) + .filter(([k, v]) => k.startsWith('g/') && v) + .map(([k, v]) => k) } function getRole(x: string): Role { - const writer = x in (group?.extra_perms ?? {}) && (group?.extra_perms ?? {})[name] + const writer = 'u/' + x in (group?.extra_perms ?? {}) && (group?.extra_perms ?? {})['u/' + x] const member = group?.members?.includes(x) + console.log(writer, member, x, group?.members, group?.extra_perms) if (writer && member) { return 'member_manager' } else if (writer) { return 'manager' - } else if (member) { + } else { return 'member' } - return 'viewer' } + + let new_managing_group = ''
-

Summary

+

{name}

{#if group}

{group?.summary ?? 'No summary'}

{:else} {/if} -

Members

+

Members & Managers

{#if can_write}
@@ -92,51 +122,91 @@ - {#each members as { name, role }} - {name} + {#each members as { member_name, role }} + {member_name} - { - const group = e.detail - // const wasInGroup = (group?.members ?? []).includes(group) - // const inAcl = ( - // group?.extra_perms ? Object.keys(group?.extra_perms) : [] - // ).includes(group) - if (group == 'member') { - GroupService.addUserToGroup(group) - - // GranularAclService.removeGranularAcls({ - // workspace: $workspaceStore ?? '', - // path: name, - // kind: 'group_', - // requestBody: $userStore. - // }) - } - loadGroup() - }} - > - Viewer A viewer can see who are the members and managers of a group - Member A Member of a group can see everything the group can see, write to everything - the group can write, and generally act on behalf of the group - Manager A manager of a group can manage the group, adding and removing users and change - their roles. He is not necessarily a member of the group. - Member & Manager - + { + const role = e.detail + // const wasInGroup = (group?.members ?? []).includes(group) + // const inAcl = ( + // group?.extra_perms ? Object.keys(group?.extra_perms) : [] + // ).includes(group) + if (role == 'member') { + await GroupService.addUserToGroup({ + workspace: $workspaceStore ?? '', + name, + requestBody: { + username: member_name + } + }) + await GranularAclService.removeGranularAcls({ + workspace: $workspaceStore ?? '', + path: name, + kind: 'group_', + requestBody: { + owner: 'u/' + member_name + } + }) + } else if (role == 'manager') { + await GroupService.removeUserToGroup({ + workspace: $workspaceStore ?? '', + name, + requestBody: { + username: member_name + } + }) + await GranularAclService.addGranularAcls({ + workspace: $workspaceStore ?? '', + path: name, + kind: 'group_', + requestBody: { + owner: 'u/' + member_name, + write: true + } + }) + } else if (role == 'member_manager') { + await GroupService.addUserToGroup({ + workspace: $workspaceStore ?? '', + name, + requestBody: { + username: member_name + } + }) + await GranularAclService.addGranularAcls({ + workspace: $workspaceStore ?? '', + path: name, + kind: 'group_', + requestBody: { + owner: 'u/' + member_name, + write: true + } + }) + } + loadGroup() + }} + > + Member A Member of a group can see everything the group can see, write to + everything the group can write, and generally act on behalf of the group + Manager A manager of a group can manage the group, adding and removing users and + change their roles. Being a manager does not make you a member. + Member & Manager + +
+ {/if} {#if can_write} @@ -145,8 +215,53 @@ on:click={async () => { await GroupService.removeUserToGroup({ workspace: $workspaceStore ?? '', - name: group?.name ?? '', - requestBody: { username: name } + name, + requestBody: { username: member_name } + }) + loadGroup() + }}>remove + {/if} + {/each} + + +

Groups managing this group Any member of those groups can manage this group

+
+ + +
+ + + group + + + + {#each managing_groups as managing_group} + {managing_group.split('/')[1]} + + {#if can_write} + { @@ -59,14 +58,6 @@ let editGroupName: string = '' - { - loadGroups() - }} -/> - @@ -120,7 +111,7 @@ placement="bottom-end" dropdownItems={[ { - displayName: 'Manage members', + displayName: 'Manage group', icon: faEdit, disabled: !canWrite, action: () => { @@ -128,14 +119,6 @@ groupDrawer.openDrawer() } }, - { - displayName: 'Manage ACL of the group', - icon: faShare, - disabled: !canWrite, - action: () => { - shareModal.openDrawer(name) - } - }, { displayName: 'Delete',