re-enable picking groups

This commit is contained in:
Ruben Fiszel
2022-12-20 05:22:41 +01:00
parent 92dee8b958
commit cd7bc33b6a
7 changed files with 171 additions and 24 deletions
+22
View File
@@ -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": [],
+2 -3
View File
@@ -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::<i32>().ok())
+25 -3
View File
@@ -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<bool> {
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::<Vec<&str>>();
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")))
}
+2 -2
View File
@@ -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`)
@@ -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<void> {
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 @@
<div class="flex flex-col gap-6">
<h1>{name}</h1>
<h2>Permissions</h2>
<h2>Permissions ({perms?.length ?? 0})</h2>
{#if can_write}
<div class="flex items-center gap-1">
<div>
@@ -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 @@
>
</ToggleButtonGroup>
</div>
{:else}
{role}
{/if}</td
>
<td>
+11 -6
View File
@@ -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}
<Skeleton layout={[[4]]} />
{/if}
<h2>Members & Managers</h2>
<h2>Members ({members?.length ?? 0})</h2>
{#if can_write}
<div class="flex items-start">
<AutoComplete items={usernames} bind:selectedItem={username} />
@@ -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.</Tooltip
></ToggleButton
> -->
<ToggleButton position="right" value="member_manager" size="xs"
>Admin</ToggleButton
<ToggleButton position="right" value="admin" size="xs"
>Admin <Tooltip
>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.</Tooltip
></ToggleButton
>
</ToggleButtonGroup>
</div>
{:else}
{role}
{/if}</td
>
<td>
+102 -5
View File
@@ -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<void> {
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()
}
</script>
<Drawer bind:this={newGroup}>
<DrawerContent
title="New Folder"
on:close={() => {
newGroup.closeDrawer()
groupCreated = undefined
}}
>
<div class="flex flex-row">
<input class="mr-2" placeholder="New group name" bind:value={newGroupName} />
<Button size="md" startIcon={{ icon: faPlus }} disabled={!newGroupName} on:click={addGroup}>
New&nbsp;group
</Button>
</div>
{#if groupCreated}
<div class="mt-8" />
<GroupEditor name={groupCreated} />
{/if}
</DrawerContent>
</Drawer>
<Drawer bind:this={viewGroup}>
<DrawerContent title="Folder {meta?.owner}" on:close={viewGroup.closeDrawer}>
<GroupEditor name={meta?.owner ?? ''} />
</DrawerContent>
</Drawer>
<Drawer bind:this={newFolder}>
<DrawerContent title="New Folder" on:close={newFolder.closeDrawer}>
<DrawerContent
title="New Folder"
on:close={() => {
newFolder.closeDrawer()
folderCreated = undefined
}}
>
<div class="flex flex-row">
<input class="mr-2" placeholder="New folder name" bind:value={newFolderName} />
<Button size="md" startIcon={{ icon: faPlus }} disabled={!newFolderName} on:click={addFolder}>
@@ -228,6 +296,8 @@
if (meta) {
if (kind === 'folder') {
meta.owner = $userStore?.folders?.[0] ?? ''
} else if (kind === 'group') {
meta.owner = 'all'
} else {
meta.owner = $userStore?.username ?? ''
}
@@ -235,6 +305,7 @@
}}
>
<ToggleButton light size="xs" value="user" position="left">User</ToggleButton>
<ToggleButton light size="xs" value="group" position="center">Group</ToggleButton>
<ToggleButton light size="xs" value="folder" position="right">Folder</ToggleButton>
</ToggleButtonGroup>
</label>
@@ -249,9 +320,14 @@
disabled={!($superadmin || ($userStore?.is_admin ?? false))}
/>
</label>
{:else}
{:else if meta.ownerKind === 'folder'}
<label class="block grow w-48">
<span class="text-gray-700 text-sm">Folder</span>
<span class="text-gray-700 text-sm"
>Folder <Tooltip
>Read and write permissions are given to groups and users at the folder level and
shared by all items inside the folder.</Tooltip
></span
>
<div class="flex flex-row gap-1 w-full">
<select class="grow w-full" {disabled} bind:value={meta.owner}>
@@ -267,6 +343,27 @@
></div
>
</label>
{:else if meta.ownerKind === 'group'}
<label class="block grow w-48">
<span class="text-gray-700 text-sm"
>Group <Tooltip>Item will be owned by the group and hence all its member</Tooltip
></span
>
<div class="flex flex-row gap-1 w-full">
<select class="grow w-full" {disabled} bind:value={meta.owner}>
{#each groups as g}
<option>{g}</option>
{/each}
</select>
<Button variant="border" size="xs" on:click={viewGroup.openDrawer}>
<Icon scale={0.8} data={faEye} /></Button
>
<Button variant="border" size="xs" on:click={newGroup.openDrawer}>
<Icon scale={0.8} data={faPlus} /></Button
></div
>
</label>
{/if}
</div>
<label class="block grow">