mirror of
https://github.com/windmill-labs/windmill.git
synced 2026-09-08 08:04:25 +00:00
make ownership check more consistent, expose ownership api, expose update folder api
This commit is contained in:
+46
-22
@@ -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": [
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!(
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()))?;
|
||||
|
||||
@@ -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" {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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?;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 />
|
||||
|
||||
@@ -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
@@ -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 })
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user