From cd7bc33b6a8c40c3fae4db84fb6afa6e427d2f0a Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Tue, 20 Dec 2022 05:22:41 +0100 Subject: [PATCH] re-enable picking groups --- backend/sqlx-data.json | 22 ++++ backend/src/main.rs | 5 +- backend/windmill-api/src/users.rs | 28 ++++- frontend/src/lib/components/EditorBar.svelte | 4 +- .../src/lib/components/FolderEditor.svelte | 12 +- .../src/lib/components/GroupEditor.svelte | 17 ++- frontend/src/lib/components/Path.svelte | 107 +++++++++++++++++- 7 files changed, 171 insertions(+), 24 deletions(-) diff --git a/backend/sqlx-data.json b/backend/sqlx-data.json index a4edfe16c5..7af294595e 100644 --- a/backend/sqlx-data.json +++ b/backend/sqlx-data.json @@ -3806,6 +3806,28 @@ }, "query": "select hash from script where path = $1 AND (workspace_id = $2 OR workspace_id = 'starter') AND\n created_at = (SELECT max(created_at) FROM script WHERE path = $1 AND (workspace_id = $2 OR workspace_id = 'starter')) AND\n deleted = false" }, + "b89fc3a68c10e6b80cb4fbe84e22e139389867fcbaa2938871fb04ab6e354d85": { + "describe": { + "columns": [ + { + "name": "exists", + "ordinal": 0, + "type_info": "Bool" + } + ], + "nullable": [ + null + ], + "parameters": { + "Left": [ + "Text", + "Text", + "Text" + ] + } + }, + "query": "SELECT EXISTS(SELECT 1 FROM usr_to_group where usr = $1 AND group_ = $2 AND workspace_id = $3)" + }, "b9468b9e16f55db11b33d8e9793e6e3ae6c5add6ca02414140adb724120a6800": { "describe": { "columns": [], diff --git a/backend/src/main.rs b/backend/src/main.rs index 1ce54999c1..584e03b2b0 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -50,11 +50,10 @@ async fn main() -> anyhow::Result<()> { let (tx, rx) = tokio::sync::broadcast::channel::<()>(3); let shutdown_signal = windmill_common::shutdown_signal(tx); - let base_internal_url = - std::env::var("BASE_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8000".to_string()); - let base_url = std::env::var("BASE_URL").unwrap_or_else(|_| "http://localhost".to_string()); + let base_internal_url = + std::env::var("BASE_INTERNAL_URL").unwrap_or_else(|_| "http://localhost:8000".to_string()); let timeout = std::env::var("TIMEOUT") .ok() .and_then(|x| x.parse::().ok()) diff --git a/backend/windmill-api/src/users.rs b/backend/windmill-api/src/users.rs index d3fca4a444..bc3a3a7313 100644 --- a/backend/windmill-api/src/users.rs +++ b/backend/windmill-api/src/users.rs @@ -10,7 +10,7 @@ use std::{sync::Arc, time::Duration}; use crate::{ db::{UserDB, DB}, - folders::get_folders_for_user, + folders::{get_folderopt, get_folders_for_user}, utils::require_super_admin, workspaces::invite_user_to_all_auto_invite_worspaces, CookieDomain, IsSecure, @@ -825,6 +825,19 @@ pub async fn get_groups_for_user(w_id: &str, username: &str, db: &DB) -> Result< Ok(groups) } +pub async fn is_user_member(w_id: &str, username: &str, group: &str, db: &DB) -> Result { + let is_member = sqlx::query_scalar!( + "SELECT EXISTS(SELECT 1 FROM usr_to_group where usr = $1 AND group_ = $2 AND workspace_id = $3)", + username, + group, + w_id + ) + .fetch_one(db) + .await? + .unwrap_or(false); + Ok(is_member) +} + pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: &DB) -> Result<()> { let splitted = path.split("/").collect::>(); if splitted[0] == "u" { @@ -837,8 +850,7 @@ pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: & ))); } } else if splitted[0] == "g" { - let groups = get_groups_for_user(w_id, username, db).await?; - if groups.contains(&username.to_string()) { + if is_user_member(w_id, username, splitted[1], db).await? { return Ok(()); } else { return Err(Error::BadRequest(format!( @@ -846,6 +858,16 @@ pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: & username, splitted[1] ))); } + } else if splitted[0] == "f" { + let folder = get_folderopt(&mut db.begin().await?, w_id, splitted[1]).await?; + if folder.is_some() && folder.unwrap().owners.contains(&username.to_string()) { + return Ok(()); + } else { + return Err(Error::BadRequest(format!( + "{} is not an admin of {} and hence is not authorized to perform this destructive operation", + username, splitted[1] + ))); + } } Err(Error::BadRequest(format!("not recognized owner kind"))) } diff --git a/frontend/src/lib/components/EditorBar.svelte b/frontend/src/lib/components/EditorBar.svelte index 6941e57b8e..96d1a340f2 100644 --- a/frontend/src/lib/components/EditorBar.svelte +++ b/frontend/src/lib/components/EditorBar.svelte @@ -148,7 +148,7 @@ editor.insertAtCursor(`v, _ := wmill.GetVariable("${path}")`) } else if (lang == 'bash') { editor.insertAtCursor(`curl -s -H "Authorization: Bearer $WM_TOKEN" \\ - "$WM_BASE_URL/api/w/$WM_WORKSPACE/variables/get/${path}" \\ + "$BASE_INTERNAL_URL/api/w/$WM_WORKSPACE/variables/get/${path}" \\ | jq -r .value`) } sendUserToast(`${name} inserted at cursor`) @@ -194,7 +194,7 @@ editor.insertAtCursor(`r, _ := wmill.GetResource("${path}")`) } else if (lang == 'bash') { editor.insertAtCursor(`curl -s -H "Authorization: Bearer $WM_TOKEN" \\ - "$WM_BASE_URL/api/w/$WM_WORKSPACE/resources/get/${path}" \\ + "$BASE_INTERNAL_URL/api/w/$WM_WORKSPACE/resources/get/${path}" \\ | jq -r .value`) } sendUserToast(`${path} inserted at cursor`) diff --git a/frontend/src/lib/components/FolderEditor.svelte b/frontend/src/lib/components/FolderEditor.svelte index f3a767aaef..5f6cb2021e 100644 --- a/frontend/src/lib/components/FolderEditor.svelte +++ b/frontend/src/lib/components/FolderEditor.svelte @@ -17,7 +17,7 @@ export let name: string let can_write = false - type Role = 'viewer' | 'writer' | 'owner' + type Role = 'viewer' | 'writer' | 'admin' let folder: Folder | undefined let perms: { owner_name: string; role: Role }[] | undefined = undefined let managing_folders: string[] = [] @@ -75,7 +75,7 @@ async function loadFolder(): Promise { folder = await FolderService.getFolder({ workspace: $workspaceStore!, name }) - can_write = canWrite(name!, folder.extra_perms ?? {}, $userStore) + can_write = folder.owners.includes('u/' + $userStore?.username) perms = Array.from( new Set( Object.entries(folder?.extra_perms ?? {}) @@ -99,7 +99,7 @@ const owner = folder?.owners?.includes(x) if (owner) { - return 'owner' + return 'admin' } else if (writer) { return 'writer' } else { @@ -111,7 +111,7 @@

{name}

-

Permissions

+

Permissions ({perms?.length ?? 0})

{#if can_write}
@@ -153,7 +153,7 @@ // const inAcl = ( // folder?.extra_perms ? Object.keys(folder?.extra_perms) : [] // ).includes(folder) - if (role == 'owner') { + if (role == 'admin') { await FolderService.addOwnerToFolder({ workspace: $workspaceStore ?? '', name, @@ -227,6 +227,8 @@ >
+ {:else} + {role} {/if} diff --git a/frontend/src/lib/components/GroupEditor.svelte b/frontend/src/lib/components/GroupEditor.svelte index 4d20ca6af0..c606a1b9b1 100644 --- a/frontend/src/lib/components/GroupEditor.svelte +++ b/frontend/src/lib/components/GroupEditor.svelte @@ -13,7 +13,7 @@ export let name: string let can_write = false - type Role = 'member' | 'manager' | 'member_manager' + type Role = 'member' | 'manager' | 'admin' let group: Group | undefined let members: { member_name: string; role: Role }[] | undefined = undefined let managing_groups: string[] = [] @@ -78,7 +78,7 @@ const member = group?.members?.includes(x) if (writer && member) { - return 'member_manager' + return 'admin' } else if (writer) { return 'manager' } else { @@ -115,7 +115,7 @@ {:else} {/if} -

Members & Managers

+

Members ({members?.length ?? 0})

{#if can_write}
@@ -178,7 +178,7 @@ write: true } }) - } else if (role == 'member_manager') { + } else if (role == 'admin') { await GroupService.addUserToGroup({ workspace: $workspaceStore ?? '', name, @@ -211,11 +211,16 @@ change their roles. Being a manager does not make you a member. --> - AdminAdmin An admin of a group is a member of a group that can also add and remove + members to the group, or make them admin.
+ {:else} + {role} {/if} diff --git a/frontend/src/lib/components/Path.svelte b/frontend/src/lib/components/Path.svelte index e170d59630..37b7af8c77 100644 --- a/frontend/src/lib/components/Path.svelte +++ b/frontend/src/lib/components/Path.svelte @@ -4,6 +4,7 @@ import { FlowService, FolderService, + GroupService, ResourceService, ScheduleService, ScriptService, @@ -19,6 +20,7 @@ import { Icon } from 'svelte-awesome' import Tooltip from './Tooltip.svelte' import FolderEditor from './FolderEditor.svelte' + import GroupEditor from './GroupEditor.svelte' type PathKind = 'resource' | 'script' | 'variable' | 'flow' | 'schedule' | 'app' let meta: Meta | undefined = undefined @@ -36,6 +38,7 @@ const dispatch = createEventDispatcher() let folders: string[] = [] + let groups: string[] = [] $: meta && onMetaChange() @@ -47,7 +50,7 @@ } function metaToPath(meta: Meta): string { - return [meta.ownerKind === 'folder' ? 'f' : 'u', meta.owner, meta.name].join('/') + return [meta.ownerKind?.charAt(0) ?? '', meta.owner, meta.name].join('/') } export function focus() { @@ -92,6 +95,18 @@ ) } + async function loadGroups(): Promise { + let initialGroups: string[] = [] + if (initialPath?.split('/')?.[0] == 'f') { + initialGroups.push(initialPath?.split('/')?.[1]) + } + groups = initialGroups.concat( + await GroupService.listGroupNames({ + workspace: $workspaceStore! + }) + ) + } + async function validate(meta: Meta, path: string, kind: PathKind) { error = '' validateName(meta) && validatePath(path, kind) @@ -148,9 +163,12 @@ } else if (!/^[\w-]+(\/[\w-]+)*$/.test(meta.name)) { error = 'This name is not valid' return false - } else if (meta.owner == '') { + } else if (meta.owner == '' && meta.ownerKind == 'folder') { error = 'Folder need to be chosen' return false + } else if (meta.owner == '' && meta.ownerKind == 'group') { + error = 'Group need to be chosen' + return false } else { return true } @@ -159,6 +177,7 @@ $: { if ($workspaceStore && $userStore) { loadFolders() + loadGroups() initPath() } } @@ -189,10 +208,59 @@ } loadFolders() } + + let newGroup: Drawer + let viewGroup: Drawer + let newGroupName: string + let groupCreated: string | undefined = undefined + + async function addGroup() { + await GroupService.createGroup({ + workspace: $workspaceStore ?? '', + requestBody: { name: newGroupName } + }) + groupCreated = newGroupName + if (meta) { + meta.owner = newGroupName + } + loadGroups() + } + + { + newGroup.closeDrawer() + groupCreated = undefined + }} + > +
+ + +
+ {#if groupCreated} +
+ + {/if} + + + + + + + + - + { + newFolder.closeDrawer() + folderCreated = undefined + }} + >
+
+ {/if}