make ownership check more consistent, expose ownership api, expose update folder api

This commit is contained in:
Ruben Fiszel
2022-12-22 14:48:57 +01:00
parent c28311242d
commit 067da91463
18 changed files with 381 additions and 88 deletions
+46 -22
View File
@@ -1118,6 +1118,29 @@
},
"query": "UPDATE flow SET value = $1 WHERE path = $2 AND workspace_id = $3"
},
"388d6fd335a3f8a405b2d465892cf21a68d4b50ace25ef88c4cdf5b347c3d5eb": {
"describe": {
"columns": [
{
"name": "?column?",
"ordinal": 0,
"type_info": "Bool"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text",
"Text",
"TextArray",
"Text"
]
}
},
"query": "SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists(\n SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f \n WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[])\n AND value::boolean)"
},
"3911bf3bbc82d87366a5297496fd2350a252bcd69f1c7c972bf566ee7eb28b0a": {
"describe": {
"columns": [
@@ -3953,28 +3976,6 @@
},
"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": [],
@@ -5172,6 +5173,29 @@
},
"query": "INSERT INTO workspace_settings\n (workspace_id, slack_team_id, slack_name, slack_email)\n VALUES ($1, $2, $3, $4) ON CONFLICT (workspace_id) DO UPDATE SET slack_team_id = $2, slack_name = $3, slack_email = $4"
},
"ebc06efe51532f7f60e98f62d09a3453fc1d97d93e96ec849eac96dca05236f0": {
"describe": {
"columns": [
{
"name": "?column?",
"ordinal": 0,
"type_info": "Bool"
}
],
"nullable": [
null
],
"parameters": {
"Left": [
"Text",
"Text",
"TextArray",
"Text"
]
}
},
"query": "SELECT EXISTS(SELECT 1 FROM folder WHERE CONCAT('u/', $1::text) = ANY(owners) AND name = $2 AND workspace_id = $4) OR exists(\n SELECT 1 FROM folder, unnest(folder.owners) as o\n WHERE o = ANY($3::text[]) AND folder.name = $2 AND folder.workspace_id = $4)"
},
"ec85a425f88044c6ed4f8fcea223c28eb9fb8c16c89a52d4c4552bd149badafa": {
"describe": {
"columns": [
+50
View File
@@ -201,6 +201,24 @@ paths:
schema:
type: string
/w/{workspace}/users/is_owner/{path}:
get:
summary: is owner of path
operationId: isOwnerOfPath
tags:
- user
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Path"
responses:
"200":
description: is owner
content:
application/json:
schema:
type: boolean
/users/setpassword:
post:
summary: set password
@@ -3699,6 +3717,38 @@ paths:
schema:
type: string
/w/{workspace}/folders/update/{name}:
post:
summary: update folder
operationId: updateFolder
tags:
- folder
parameters:
- $ref: "#/components/parameters/WorkspaceId"
- $ref: "#/components/parameters/Name"
requestBody:
description: update folder
required: true
content:
application/json:
schema:
type: object
properties:
owners:
type: array
items:
type: string
extra_perms:
additionalProperties:
type: boolean
responses:
"200":
description: folder updated
content:
text/plain:
schema:
type: string
/w/{workspace}/folders/delete/{name}:
delete:
summary: delete folder
+2 -1
View File
@@ -327,7 +327,8 @@ async fn update_app(
if let Some(npath) = &ns.path {
if npath != path {
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &path, &db).await?;
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db)
.await?;
}
}
sqlb.set_str("path", npath);
+1 -1
View File
@@ -297,7 +297,7 @@ async fn update_flow(
check_schedule_conflict(&mut tx, &w_id, &nf.path).await?;
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &flow_path, &db).await?;
require_owner_of_path(&w_id, &authed.username, &authed.groups, &flow_path, &db).await?;
}
let mut schedulables = sqlx::query_as!(
+153 -5
View File
@@ -15,15 +15,16 @@ use axum::{
routing::{delete, get, post},
Json, Router,
};
use itertools::Itertools;
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{JsonResult, Result},
error::{self, Error, JsonResult, Result},
users::owner_to_token_owner,
utils::{not_found_if_none, paginate, Pagination},
};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Postgres, Transaction};
use sqlx::{query_scalar, FromRow, Postgres, Transaction};
pub fn workspaced_service() -> Router {
Router::new()
@@ -31,13 +32,15 @@ pub fn workspaced_service() -> Router {
.route("/listnames", get(list_foldernames))
.route("/create", post(create_folder))
.route("/get/:name", get(get_folder))
.route("/update/:name", post(update_folder))
.route("/getusage/:name", get(get_folder_usage))
.route("/delete/:name", delete(delete_folder))
.route("/addowner/:name", post(add_owner))
.route("/removeowner/:name", post(remove_owner))
.route("/is_owner", get(is_owner))
}
#[derive(FromRow, Serialize, Deserialize)]
#[derive(FromRow, Serialize, Deserialize, Clone)]
pub struct Folder {
pub workspace_id: String,
pub name: String,
@@ -54,6 +57,13 @@ pub struct NewFolder {
pub extra_perms: Option<serde_json::Value>,
}
#[derive(Deserialize)]
pub struct UpdateFolder {
pub display_name: Option<String>,
pub owners: Option<Vec<String>>,
pub extra_perms: Option<serde_json::Value>,
}
#[derive(Deserialize)]
pub struct Owner {
pub owner: String,
@@ -136,14 +146,39 @@ async fn create_folder(
check_name_conflict(&mut tx, &w_id, &ng.name).await?;
let owner = owner_to_token_owner(&authed.username, false);
let owners = &ng.owners.unwrap_or(vec![owner.clone()]);
if let Some(extra_perms) = ng.extra_perms.clone() {
for o in owners {
if !extra_perms
.get(&o)
.and_then(|x| x.as_bool())
.unwrap_or(false)
{
return Err(windmill_common::error::Error::BadRequest(format!(
"Owner {} would not have permission to write to folder and that is an inconsistent state",
o
)));
}
}
}
let extra_perms = ng.extra_perms.unwrap_or_else(|| {
let mut map = serde_json::Map::new();
for o in owners {
map.insert(o.clone(), serde_json::json!(true));
}
serde_json::Value::Object(map)
});
sqlx::query_as!(
Folder,
"INSERT INTO folder (workspace_id, name, display_name, owners, extra_perms) VALUES ($1, $2, $3, $4, $5)",
w_id,
ng.name,
ng.display_name.unwrap_or(ng.name.clone()),
&ng.owners.unwrap_or(vec![owner.clone()]),
ng.extra_perms.unwrap_or(serde_json::json!({owner: true}))
owners,
extra_perms,
)
.execute(&mut tx)
.await?;
@@ -163,6 +198,111 @@ async fn create_folder(
Ok(format!("Created folder {}", ng.name))
}
pub async fn is_owner(
Authed { username, is_admin, groups, .. }: Authed,
Extension(db): Extension<DB>,
Path((w_id, name)): Path<(String, String)>,
) -> JsonResult<bool> {
if is_admin {
Ok(Json(true))
} else {
Ok(Json(
require_is_owner(&name, &username, &groups, &w_id, &db)
.await
.is_ok(),
))
}
}
pub async fn require_is_owner(
folder_name: &str,
username: &str,
groups: &Vec<String>,
w_id: &str,
db: &DB,
) -> Result<()> {
let is_owner = query_scalar!(
"SELECT EXISTS(SELECT 1 FROM folder WHERE CONCAT('u/', $1::text) = ANY(owners) AND name = $2 AND workspace_id = $4) OR exists(
SELECT 1 FROM folder, unnest(folder.owners) as o
WHERE o = ANY($3::text[]) AND folder.name = $2 AND folder.workspace_id = $4)",
username,
folder_name,
groups,
w_id,
).fetch_one(db)
.await?
.unwrap_or(false);
if !is_owner {
Err(Error::BadRequest(format!(
"{} is not an owner of {} and hence is not authorized to perform this operation",
username, folder_name
)))
} else {
Ok(())
}
}
async fn update_folder(
authed: Authed,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
Json(ng): Json<UpdateFolder>,
) -> Result<String> {
use sql_builder::prelude::*;
let mut sqlb = SqlBuilder::update_table("folder");
sqlb.and_where_eq("name", "?".bind(&name));
sqlb.and_where_eq("workspace_id", "?".bind(&w_id));
if let Some(display_name) = ng.display_name {
sqlb.set("display_name", display_name);
}
if let Some(owners) = ng.owners {
sqlb.set_str("owners", format!("{{{}}}", owners.into_iter().join(",")));
}
if let Some(extra_perms) = ng.extra_perms {
sqlb.set_str("extra_perms", extra_perms.to_string());
}
sqlb.returning("*");
let mut tx = user_db.begin(&authed).await?;
let sql = sqlb
.sql()
.map_err(|e| error::Error::InternalErr(e.to_string()))?;
let nfolder = sqlx::query_as::<_, Folder>(&sql).fetch_one(&mut tx).await?;
if let Some(extra_perms) = nfolder.extra_perms.as_object() {
for o in nfolder.owners {
if !extra_perms
.get(&o)
.and_then(|x| x.as_bool())
.unwrap_or(false)
{
return Err(windmill_common::error::Error::BadRequest(format!(
"Owner {} would not have permission to write to folder and that is an invalid state",
o
)));
}
}
}
audit_log(
&mut tx,
&authed.username,
"folder.update",
ActionKind::Update,
&w_id,
Some(&name.to_string()),
None,
)
.await?;
tx.commit().await?;
Ok(format!("Updated folder {}", name))
}
pub async fn get_folderopt<'c>(
db: &mut Transaction<'c, Postgres>,
w_id: &str,
@@ -305,6 +445,7 @@ async fn delete_folder(
async fn add_owner(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
Json(Owner { owner }): Json<Owner>,
@@ -312,6 +453,9 @@ async fn add_owner(
let mut tx = user_db.begin(&authed).await?;
not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?;
if !authed.is_admin {
require_is_owner(&name, &authed.username, &authed.groups, &w_id, &db).await?;
}
sqlx::query!(
"UPDATE folder SET owners = array_append(owners, $1) WHERE name = $2 AND workspace_id = $3 AND NOT $1 = ANY(owners) RETURNING name",
@@ -364,6 +508,7 @@ pub async fn get_folders_for_user(
async fn remove_owner(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
Json(Owner { owner }): Json<Owner>,
@@ -371,6 +516,9 @@ async fn remove_owner(
let mut tx = user_db.begin(&authed).await?;
not_found_if_none(get_folderopt(&mut tx, &w_id, &name).await?, "Folder", &name)?;
if !authed.is_admin {
require_is_owner(&name, &authed.username, &authed.groups, &w_id, &db).await?;
}
sqlx::query!(
"UPDATE folder SET owners = array_remove(owners, $1) WHERE name = $2 AND workspace_id = $3 RETURNING name",
+18 -1
View File
@@ -6,7 +6,10 @@
* LICENSE-AGPL for a copy of the license.
*/
use crate::{db::UserDB, users::Authed};
use crate::{
db::{UserDB, DB},
users::{require_owner_of_path, Authed},
};
use axum::{
extract::{Extension, Path},
routing::{get, post},
@@ -34,6 +37,7 @@ pub struct GranularAcl {
async fn add_granular_acl(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, path)): Path<(String, StripPath)>,
Json(GranularAcl { owner, write }): Json<GranularAcl>,
@@ -44,6 +48,15 @@ async fn add_granular_acl(
.ok_or_else(|| Error::BadRequest("Invalid path or kind".to_string()))?;
let mut tx = user_db.begin(&authed).await?;
if !authed.is_admin {
if kind == "folder" {
crate::folders::require_is_owner(&path, &authed.username, &authed.groups, &w_id, &db)
.await?;
} else if kind == "group_" {
} else {
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db).await?;
}
}
let identifier = if kind == "group_" || kind == "folder" {
"name"
} else {
@@ -67,11 +80,15 @@ async fn add_granular_acl(
async fn remove_granular_acl(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, path)): Path<(String, StripPath)>,
Json(GranularAcl { owner, write: _ }): Json<GranularAcl>,
) -> Result<String> {
let path = path.to_path();
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db).await?;
}
let (kind, path) = path
.split_once('/')
.ok_or_else(|| Error::BadRequest("Invalid path or kind".to_string()))?;
+56 -1
View File
@@ -23,7 +23,7 @@ use windmill_common::{
};
use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Postgres, Transaction};
use sqlx::{query_scalar, FromRow, Postgres, Transaction};
use windmill_queue::CLOUD_HOSTED;
pub fn workspaced_service() -> Router {
@@ -36,6 +36,7 @@ pub fn workspaced_service() -> Router {
.route("/delete/:name", delete(delete_group))
.route("/adduser/:name", post(add_user))
.route("/removeuser/:name", post(remove_user))
.route("/is_owner", get(is_owner))
}
#[derive(FromRow, Serialize, Deserialize)]
@@ -137,6 +138,51 @@ async fn check_name_conflict<'c>(
return Ok(());
}
pub async fn is_owner(
Authed { username, is_admin, groups, .. }: Authed,
Extension(db): Extension<DB>,
Path((w_id, name)): Path<(String, String)>,
) -> JsonResult<bool> {
if is_admin {
Ok(Json(true))
} else {
Ok(Json(
require_is_owner(&name, &username, &groups, &w_id, &db)
.await
.is_ok(),
))
}
}
pub async fn require_is_owner(
group_name: &str,
username: &str,
groups: &Vec<String>,
w_id: &str,
db: &DB,
) -> Result<()> {
let is_owner = query_scalar!(
"SELECT EXISTS(SELECT 1 FROM group_ WHERE (group_.extra_perms ->> CONCAT('u/', $1::text))::boolean AND name = $2 AND workspace_id = $4) OR exists(
SELECT 1 FROM group_ g, jsonb_each_text(g.extra_perms) f
WHERE $2 = g.name AND $4 = g.workspace_id AND SPLIT_PART(key, '/', 1) = 'g' AND key = ANY($3::text[])
AND value::boolean)",
username,
group_name,
groups,
w_id,
).fetch_one(db)
.await?
.unwrap_or(false);
if !is_owner {
Err(Error::BadRequest(format!(
"{} is not an owner of {} and hence is not authorized to perform this operation",
username, group_name
)))
} else {
Ok(())
}
}
async fn create_group(
authed: Authed,
Extension(user_db): Extension<UserDB>,
@@ -230,11 +276,13 @@ async fn get_group(
async fn delete_group(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
) -> Result<String> {
let mut tx = user_db.begin(&authed).await?;
require_is_owner(&name, &authed.username, &authed.groups, &w_id, &db).await?;
not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?;
sqlx::query!(
@@ -267,12 +315,14 @@ async fn delete_group(
async fn update_group(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
Json(eg): Json<EditGroup>,
) -> Result<String> {
let mut tx = user_db.begin(&authed).await?;
require_is_owner(&name, &authed.username, &authed.groups, &w_id, &db).await?;
not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?;
sqlx::query_as!(
@@ -301,12 +351,15 @@ async fn update_group(
async fn add_user(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
Json(Username { username: user_username }): Json<Username>,
) -> Result<String> {
let mut tx = user_db.begin(&authed).await?;
require_is_owner(&name, &authed.username, &authed.groups, &w_id, &db).await?;
not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?;
sqlx::query_as!(
@@ -335,11 +388,13 @@ async fn add_user(
async fn remove_user(
authed: Authed,
Extension(db): Extension<DB>,
Extension(user_db): Extension<UserDB>,
Path((w_id, name)): Path<(String, String)>,
Json(Username { username: user_username }): Json<Username>,
) -> Result<String> {
let mut tx = user_db.begin(&authed).await?;
require_is_owner(&name, &authed.username, &authed.groups, &w_id, &db).await?;
not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?;
if &name == "all" {
+1 -1
View File
@@ -374,7 +374,7 @@ async fn update_resource(
check_path_conflict(&mut tx, &w_id, &npath).await?;
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &path, &db).await?;
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db).await?;
}
sqlx::query!(
"UPDATE variable SET path = $1 WHERE path = $2 AND workspace_id = $3",
+2 -1
View File
@@ -260,7 +260,8 @@ async fn create_script(
if ps.path != ns.path {
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &ps.path, &db).await?;
require_owner_of_path(&w_id, &authed.username, &authed.groups, &ps.path, &db)
.await?;
}
}
+27 -32
View File
@@ -10,7 +10,7 @@ use std::{sync::Arc, time::Duration};
use crate::{
db::{UserDB, DB},
folders::{get_folderopt, get_folders_for_user},
folders::get_folders_for_user,
utils::require_super_admin,
workspaces::invite_user_to_all_auto_invite_worspaces,
CookieDomain, IsSecure,
@@ -35,7 +35,7 @@ use tracing::{Instrument, Span};
use windmill_audit::{audit_log, ActionKind};
use windmill_common::{
error::{self, Error, JsonResult, Result},
utils::{not_found_if_none, rd_string, require_admin, Pagination},
utils::{not_found_if_none, rd_string, require_admin, Pagination, StripPath},
};
use windmill_queue::CLOUD_HOSTED;
@@ -52,6 +52,7 @@ pub fn workspaced_service() -> Router {
.route("/exists", post(exists_username))
.route("/update/:user", post(update_workspace_user))
.route("/delete/:user", delete(delete_workspace_user))
.route("/is_owner/:path", get(is_owner_of_path))
.route("/whois/:email", get(whois))
.route("/whoami", get(whoami))
.route("/leave", post(leave_workspace))
@@ -852,21 +853,30 @@ pub async fn get_groups_for_user(w_id: &str, username: &str, db: &DB) -> Result<
.await?;
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 is_owner_of_path(
Authed { username, is_admin, groups, .. }: Authed,
Extension(db): Extension<DB>,
Path((w_id, path)): Path<(String, StripPath)>,
) -> JsonResult<bool> {
let path = path.to_path();
if is_admin {
Ok(Json(true))
} else {
Ok(Json(
require_owner_of_path(&w_id, &username, &groups, path, &db)
.await
.is_ok(),
))
}
}
pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: &DB) -> Result<()> {
pub async fn require_owner_of_path(
w_id: &str,
username: &str,
groups: &Vec<String>,
path: &str,
db: &DB,
) -> Result<()> {
let splitted = path.split("/").collect::<Vec<&str>>();
if splitted[0] == "u" {
if splitted[1] == username {
@@ -878,24 +888,9 @@ pub async fn require_owner_of_path(w_id: &str, username: &str, path: &str, db: &
)));
}
} else if splitted[0] == "g" {
if is_user_member(w_id, username, splitted[1], db).await? {
return Ok(());
} else {
return Err(Error::BadRequest(format!(
"{} is not a member of {} and hence is not authorized to perform this operation",
username, splitted[1]
)));
}
return crate::groups::require_is_owner(w_id, username, groups, splitted[1], db).await;
} 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]
)));
}
return crate::folders::require_is_owner(w_id, username, groups, splitted[1], db).await;
}
Err(Error::BadRequest(format!("not recognized owner kind")))
}
+1 -1
View File
@@ -378,7 +378,7 @@ async fn update_variable(
if npath != path {
check_path_conflict(&mut tx, &w_id, &npath).await?;
if !authed.is_admin {
require_owner_of_path(&w_id, &authed.username, &path, &db).await?;
require_owner_of_path(&w_id, &authed.username, &authed.groups, &path, &db).await?;
}
sqlx::query!(
"UPDATE resource SET path = $1 WHERE path = $2 AND workspace_id = $3",
@@ -65,7 +65,11 @@
async function loadFolder(): Promise<void> {
folder = await FolderService.getFolder({ workspace: $workspaceStore!, name })
can_write =
folder.owners.includes('u/' + $userStore?.username) || ($userStore?.is_admin ?? false)
$userStore != undefined &&
(folder?.owners.includes('u/' + $userStore.username) ||
($userStore.is_admin ?? false) ||
$userStore.pgroups.findIndex((x) => folder?.owners.includes(x)) != -1)
perms = Array.from(
new Set(
Object.entries(folder?.extra_perms ?? {})
@@ -21,10 +21,10 @@
export async function openDrawer(initialPath_l: string, kind_l: Kind) {
kind = kind_l
initialPath = initialPath_l
await loadOwner()
drawer.openDrawer()
}
$: $userStore && $workspaceStore && loadOwner()
async function loadOwner() {
own = await isOwner(path, $userStore!, $workspaceStore!)
@@ -73,7 +73,7 @@
<h1>Move {initialPath} to</h1>
{#if !own}
<Alert type="warning" title="Not owner"
>Since you do not own this item, you cannot move this item(you can however fork it!)</Alert
>Since you do not own this item, you cannot move this item (you can however fork it)</Alert
>
{/if}
<Path disabled={!own} {kind} {initialPath} bind:path />
+1 -1
View File
@@ -305,7 +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="group" position="center">Group</ToggleButton> -->
<ToggleButton light size="xs" value="folder" position="right">Folder</ToggleButton>
</ToggleButtonGroup>
</label>
@@ -38,11 +38,10 @@
loadAcls()
loadGroups()
loadUsernames()
await loadOwner()
drawer.openDrawer()
}
$: $userStore && $workspaceStore && loadOwner()
async function loadOwner() {
own = await isOwner(path, $userStore!, $workspaceStore!)
}
@@ -141,7 +141,8 @@
icon: faFileExport,
action: () => {
moveDrawer.openDrawer(path, 'flow')
}
},
disabled: !canWrite
},
{
displayName: 'Schedule',
@@ -161,7 +161,8 @@
icon: faFileExport,
action: () => {
moveDrawer.openDrawer(path, 'script')
}
},
disabled: !canWrite
},
{
displayName: 'View runs',
+11 -14
View File
@@ -5,8 +5,8 @@ import {
FolderService,
Script,
ScriptService,
UserService,
type Flow,
type FlowModule,
type User
} from '$lib/gen'
import { toast } from '@zerodevx/svelte-toast'
@@ -68,13 +68,12 @@ export function displayDate(dateString: string | undefined, displaySecond = fals
if (date.toString() === 'Invalid Date') {
return ''
} else {
return `${date.getFullYear()}/${
date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: displaySecond ? '2-digit' : undefined
})}`
return `${date.getFullYear()}/${date.getMonth() + 1
}/${date.getDate()} at ${date.toLocaleTimeString([], {
hour: '2-digit',
minute: '2-digit',
second: displaySecond ? '2-digit' : undefined
})}`
}
}
@@ -185,14 +184,12 @@ export function removeItemAll<T>(arr: T[], value: T) {
}
export async function isOwner(path: string, user: UserExt, workspace: string): Promise<boolean> {
if (isObviousOwner(path, user)) {
if (user.is_admin && (workspace != 'starter' || user.is_super_admin)) {
return true
} else if (path.startsWith('f/')) {
let folder = path.split('/')[1]
let res = await FolderService.getFolder({ workspace, name: folder })
return res.owners.includes('u/' + user.username)
} else {
} else if (workspace == 'starter') {
return false
} else {
return await UserService.isOwnerOfPath({ path: path, workspace: workspace })
}
}