fix: remove lockfile on any rawinput change in flows

This commit is contained in:
Ruben Fiszel
2023-07-20 20:24:03 +02:00
parent 07d0295ad0
commit dfb1d8fa44
5 changed files with 327 additions and 73 deletions
+175 -11
View File
@@ -9,6 +9,7 @@
use crate::{
db::{UserDB, DB},
users::{get_groups_for_user, Authed},
utils::require_super_admin,
};
use axum::{
extract::{Extension, Path, Query},
@@ -39,6 +40,15 @@ pub fn workspaced_service() -> Router {
.route("/is_owner", get(is_owner))
}
pub fn global_service() -> Router {
Router::new()
.route("/list", get(list_igroups))
.route("/create", post(create_igroup))
.route("/delete/:name", delete(delete_igroup))
.route("/adduser/:name", post(add_user_igroup))
.route("/removeuser/:name", post(remove_user_igroup))
}
#[derive(FromRow, Serialize, Deserialize)]
pub struct Group {
pub workspace_id: String,
@@ -72,6 +82,11 @@ pub struct Username {
pub username: String,
}
#[derive(Deserialize)]
pub struct Email {
pub email: String,
}
async fn list_groups(
Extension(db): Extension<DB>,
Path(w_id): Path<String>,
@@ -193,8 +208,7 @@ async fn create_group(
check_name_conflict(&mut tx, &w_id, &ng.name).await?;
sqlx::query_as!(
Group,
sqlx::query!(
"INSERT INTO group_ (workspace_id, name, summary, extra_perms) VALUES ($1, $2, $3, $4)",
w_id,
ng.name,
@@ -204,8 +218,7 @@ async fn create_group(
.execute(&mut *tx)
.await?;
sqlx::query_as!(
Group,
sqlx::query!(
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
&w_id,
&authed.username,
@@ -229,6 +242,62 @@ async fn create_group(
Ok(format!("Created group {}", ng.name))
}
async fn create_igroup(
authed: Authed,
Extension(db): Extension<DB>,
Json(ng): Json<NewGroup>,
) -> Result<String> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, &authed.email).await?;
sqlx::query!(
"INSERT INTO instance_group (name, summary) VALUES ($1, $2) ON CONFLICT DO NOTHING",
ng.name,
ng.summary,
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed.username,
"igroup.create",
ActionKind::Create,
"global",
Some(&ng.name.to_string()),
None,
)
.await?;
tx.commit().await?;
Ok(format!("Created group {}", ng.name))
}
async fn delete_igroup(
authed: Authed,
Extension(db): Extension<DB>,
Json(ng): Json<NewGroup>,
) -> Result<String> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, &authed.email).await?;
sqlx::query!("DELETE FROM instance_group WHERE name = $1", ng.name,)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed.username,
"igroup.delete",
ActionKind::Delete,
"global",
Some(&ng.name.to_string()),
None,
)
.await?;
tx.commit().await?;
Ok(format!("Created group {}", ng.name))
}
pub async fn get_group_opt<'c>(
db: &mut Transaction<'c, Postgres>,
w_id: &str,
@@ -294,7 +363,8 @@ async fn delete_group(
if name == "all" {
return Err(Error::BadRequest(
"The group 'all' is a special group that contains all users and cannot be deleted".to_string(),
"The group 'all' is a special group that contains all users and cannot be deleted"
.to_string(),
));
}
@@ -344,8 +414,7 @@ async fn update_group(
}
not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?;
sqlx::query_as!(
Group,
sqlx::query!(
"UPDATE group_ SET summary = $1 WHERE name = $2 AND workspace_id = $3",
eg.summary,
&name,
@@ -382,8 +451,7 @@ async fn add_user(
not_found_if_none(get_group_opt(&mut tx, &w_id, &name).await?, "Group", &name)?;
sqlx::query_as!(
Group,
sqlx::query!(
"INSERT INTO usr_to_group (workspace_id, usr, group_) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING",
&w_id,
user_username,
@@ -406,6 +474,103 @@ async fn add_user(
Ok(format!("Added {} to group {}", user_username, name))
}
async fn add_user_igroup(
authed: Authed,
Extension(db): Extension<DB>,
Path(name): Path<String>,
Json(Email { email }): Json<Email>,
) -> Result<String> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, &authed.email).await?;
let group_opt = sqlx::query_scalar!("SELECT name FROM instance_group WHERE name = $1", name)
.fetch_optional(&mut *tx)
.await?;
not_found_if_none(group_opt, "IGroup", &name)?;
sqlx::query!(
"INSERT INTO email_to_igroup (email, igroup) VALUES ($1, $2) ON CONFLICT DO NOTHING",
email,
name,
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed.username,
"igroup.adduser",
ActionKind::Update,
"global",
Some(&name.to_string()),
Some([("email", email.as_str())].into()),
)
.await?;
tx.commit().await?;
Ok(format!("Added {} to igroup {}", email, name))
}
#[derive(Serialize)]
struct IGroup {
name: String,
emails: Option<Vec<String>>,
}
async fn list_igroups(authed: Authed, Extension(db): Extension<DB>) -> JsonResult<Vec<IGroup>> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, &authed.email).await?;
let groups = sqlx::query_as!(
IGroup,
"SELECT igroup as name, array_agg(email_to_igroup.email) as emails FROM email_to_igroup GROUP BY igroup"
)
.fetch_all(&mut *tx)
.await?;
tx.commit().await?;
return Ok(Json(groups));
}
async fn remove_user_igroup(
authed: Authed,
Extension(db): Extension<DB>,
Path(name): Path<String>,
Json(Email { email }): Json<Email>,
) -> Result<String> {
let mut tx = db.begin().await?;
require_super_admin(&mut tx, &authed.email).await?;
let group_opt = sqlx::query_scalar!("SELECT name FROM instance_group WHERE name = $1", name,)
.fetch_optional(&mut *tx)
.await?;
not_found_if_none(group_opt, "IGroup", &name)?;
sqlx::query!(
"DELETE FROM email_to_igroup WHERE email = $1 AND igroup = $2",
email,
name,
)
.execute(&mut *tx)
.await?;
audit_log(
&mut *tx,
&authed.username,
"igroup.removeuser",
ActionKind::Update,
"global",
Some(&name.to_string()),
Some([("email", email.as_str())].into()),
)
.await?;
tx.commit().await?;
Ok(format!("Added {} to igroup {}", email, name))
}
async fn remove_user(
authed: Authed,
Extension(db): Extension<DB>,
@@ -422,8 +587,7 @@ async fn remove_user(
if &name == "all" {
return Err(Error::BadRequest(format!("Cannot delete users from all")));
}
sqlx::query_as!(
Group,
sqlx::query!(
"DELETE FROM usr_to_group WHERE usr = $1 AND group_ = $2 AND workspace_id = $3",
user_username,
name,
+45 -46
View File
@@ -1292,55 +1292,54 @@ where
) -> Result<Self, Self::Rejection> {
let content_type_header = req.headers().get(CONTENT_TYPE);
let content_type = content_type_header.and_then(|value| value.to_str().ok());
if let Some(content_type) = content_type {
if content_type.starts_with("application/json") {
if req
.uri()
.query()
.map(|x| x.contains("raw=true"))
.unwrap_or(false)
{
let bytes = Bytes::from_request(req, _state)
.await
.map_err(IntoResponse::into_response)?;
let str = String::from_utf8(bytes.to_vec()).map_err(|e| {
Error::BadRequest(format!("invalid utf8: {}", e)).into_response()
if content_type.is_none() || content_type.unwrap().starts_with("application/json") {
if req
.uri()
.query()
.map(|x| x.contains("raw=true"))
.unwrap_or(false)
{
let bytes = Bytes::from_request(req, _state)
.await
.map_err(IntoResponse::into_response)?;
let str = String::from_utf8(bytes.to_vec()).map_err(|e| {
Error::BadRequest(format!("invalid utf8: {}", e)).into_response()
})?;
let payload =
serde_json::from_str::<Option<serde_json::Value>>(&str).map_err(|e| {
Error::BadRequest(format!("invalid json: {}", e)).into_response()
})?;
let payload =
serde_json::from_str::<Option<serde_json::Value>>(&str).map_err(|e| {
Error::BadRequest(format!("invalid json: {}", e)).into_response()
})?;
return match payload {
Some(serde_json::Value::Object(map)) => Ok(Self(Some(map), Some(str))),
None => Ok(Self(None, Some(str))),
Some(x) => {
let mut map = serde_json::Map::new();
map.insert("body".to_string(), x);
Ok(Self(Some(map), Some(str)))
}
};
} else {
let Json(payload): Json<Option<serde_json::Value>> =
req.extract().await.map_err(IntoResponse::into_response)?;
return match payload {
Some(serde_json::Value::Object(map)) => Ok(Self(Some(map), None)),
None => Ok(Self(None, None)),
Some(x) => {
let mut map = serde_json::Map::new();
map.insert("body".to_string(), x);
Ok(Self(Some(map), None))
}
};
}
}
if content_type.starts_with("application/x-www-form-urlencoded") {
let Form(payload) = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(Self(Some(payload), None));
return match payload {
Some(serde_json::Value::Object(map)) => Ok(Self(Some(map), Some(str))),
None => Ok(Self(None, Some(str))),
Some(x) => {
let mut map = serde_json::Map::new();
map.insert("body".to_string(), x);
Ok(Self(Some(map), Some(str)))
}
};
} else {
let Json(payload): Json<Option<serde_json::Value>> =
req.extract().await.map_err(IntoResponse::into_response)?;
return match payload {
Some(serde_json::Value::Object(map)) => Ok(Self(Some(map), None)),
None => Ok(Self(None, None)),
Some(x) => {
let mut map = serde_json::Map::new();
map.insert("body".to_string(), x);
Ok(Self(Some(map), None))
}
};
}
} else if content_type
.unwrap()
.starts_with("application/x-www-form-urlencoded")
{
let Form(payload) = req.extract().await.map_err(IntoResponse::into_response)?;
return Ok(Self(Some(payload), None));
} else {
Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
}
Err(StatusCode::UNSUPPORTED_MEDIA_TYPE.into_response())
}
}
pub struct QueryOrBody<D>(pub Option<D>);
+3 -2
View File
@@ -8,7 +8,7 @@
use crate::oauth2::AllClients;
use crate::saml::{SamlSsoLogin, ServiceProviderExt};
use crate::scim::has_basic_auth;
use crate::scim::has_scim_token;
use crate::{
db::UserDB,
oauth2::{build_oauth_clients, SlackVerifier},
@@ -220,6 +220,7 @@ pub async fn run_server(
)
.nest("/workers", workers::global_service())
.nest("/scripts", scripts::global_service())
.nest("/groups", groups::global_service())
.nest("/flows", flows::global_service())
.nest("/apps", apps::global_service().layer(cors.clone()))
.nest("/schedules", schedule::global_service())
@@ -231,7 +232,7 @@ pub async fn run_server(
)
.nest(
"/scim",
saml::global_service().route_layer(axum::middleware::from_fn(has_basic_auth)),
scim::global_service().route_layer(axum::middleware::from_fn(has_scim_token)),
)
.nest("/scripts_u", scripts::global_unauthed_service())
.nest(
+101 -12
View File
@@ -7,26 +7,37 @@
*/
use axum::{
extract::{Path, Query},
middleware::Next,
response::{IntoResponse, Response},
routing::{get, post},
Router,
Extension, Router,
};
use bytes::{BufMut, BytesMut};
use hyper::{header, http::HeaderValue, Request, StatusCode};
use mime_guess::mime;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use serde_json::json;
use sql_builder::SqlBuilder;
use windmill_common::error::{Error, Result};
use crate::db::DB;
lazy_static::lazy_static! {
static ref SCIM_PASSWORD: Option<String> = std::env::var("SCIM_PASSWORD")
static ref SCIM_TOKEN: Option<String> = std::env::var("SCIM_TOKEN")
.ok();
}
pub fn global_service() -> Router {
Router::new().route("/Users", get(get_users))
// .route("/Groups", get(get_groups).post(create_group))
// .route("/Groups/:id", get(get_group))
}
#[derive(Debug, Clone, Copy, Default)]
pub struct JsonScim<T>(pub T);
pub async fn has_basic_auth<B>(request: Request<B>, next: Next<B>) -> Response {
pub async fn has_scim_token<B>(request: Request<B>, next: Next<B>) -> Response {
let header = request.headers().get("Authorization");
next.run(request).await
}
@@ -63,16 +74,94 @@ where
}
}
pub fn global_service() -> Router {
Router::new()
.route("/authorize", post(authorize))
.route("/Users", get(get_users))
#[derive(Serialize)]
struct User {
id: String,
userName: String,
}
pub fn resource_response<S>(schema: &str, resources: Vec<S>) -> JsonScim<serde_json::Value>
where
S: Serialize,
{
return JsonScim(json!({
"schemas": [schema],
"totalResults": resources.len(),
"Resources": resources,
"startIndex": 1,
"itemsPerPage": 100,
}));
}
pub async fn authorize() -> Result<String> {
Ok("Hello, World!".to_string())
#[derive(Deserialize)]
pub struct ScimQuery {
startIndex: Option<u32>,
count: Option<u32>,
}
pub async fn get_users() -> JsonScimResult<String> {
Ok(JsonScim("Hello, World!".to_string()))
pub async fn get_users(
Extension(db): Extension<DB>,
Query(query): Query<ScimQuery>,
) -> Result<JsonScim<serde_json::Value>> {
let sqlb = SqlBuilder::select_from("usr")
.fields(&["email"])
.limit(query.count.unwrap_or(100000))
.offset(query.startIndex.map(|x| x - 1).unwrap_or(0))
.clone();
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let users = sqlx::query_scalar(&sql)
.fetch_all(&db)
.await?
.into_iter()
.map(|x: String| User { id: x.clone(), userName: x })
.collect();
Ok(resource_response(
"urn:ietf:params:scim:api:messages:2.0:ListResponse",
users,
))
}
pub async fn get_groups(
Extension(db): Extension<DB>,
Query(query): Query<ScimQuery>,
) -> Result<JsonScim<serde_json::Value>> {
let sqlb = SqlBuilder::select_from("instance_group")
.fields(&["email"])
.limit(query.count.unwrap_or(100000))
.offset(query.startIndex.map(|x| x - 1).unwrap_or(0))
.clone();
let sql = sqlb.sql().map_err(|e| Error::InternalErr(e.to_string()))?;
let users = sqlx::query_scalar(&sql)
.fetch_all(&db)
.await?
.into_iter()
.map(|x: String| User { id: x.clone(), userName: x })
.collect();
Ok(resource_response(
"urn:ietf:params:scim:api:messages:2.0:ListResponse",
users,
))
}
// pub async fn get_group(
// Extension(db): Extension<DB>,
// Query(query): Query<ScimQuery>,
// Path(id): Path<String>,
// ) -> Result<JsonScim<serde_json::Value>> {
// let groups = sqlx::query_as!(
// IGroup,
// "SELECT igroup as name, array_agg(email_to_igroup.email) as emails FROM email_to_igroup GROUP BY igroup"
// )
// .fetch_all(&mut *tx)
// .await?;
// Ok(resource_response(
// "urn:ietf:params:scim:api:messages:2.0:ListResponse",
// groups,
// ))
// }
// pub async fn create_group(
// Extension(db): Extension<DB>,
// Query(query): Query<ScimQuery>,
// ) -> Result<JsonScim<serde_json::Value>> {
// }
@@ -97,7 +97,9 @@
validCode = true
inputTransformSchemaForm?.setArgs(input_transforms)
if (flowModule.value.type == 'rawscript' && flowModule.value.lock != undefined) {
flowModule.value.lock = undefined
}
await tick()
if (!deepEqual(schema, $flowStateStore[flowModule.id]?.schema)) {
if (!$flowStateStore[flowModule.id]) {
@@ -150,7 +152,6 @@
}}
on:reload={async () => {
if (flowModule.value.type == 'script') {
console.log('reload')
if (flowModule.value.hash != undefined) {
flowModule.value.hash = await getLatestHashForScript(flowModule.value.path)
}