From 81be9c4d6012ea5d42b2ceeb60fa64e6bf908e0c Mon Sep 17 00:00:00 2001 From: Ruben Fiszel Date: Fri, 23 Feb 2024 23:12:50 +0100 Subject: [PATCH] fix: make setting owner for folders a transaction --- backend/src/ee.rs | 17 +- backend/windmill-api/openapi.yaml | 8 + backend/windmill-api/src/ee.rs | 7 +- backend/windmill-api/src/folders.rs | 31 ++- backend/windmill-api/src/job_helpers_ee.rs | 6 +- backend/windmill-api/src/oauth2_ee.rs | 213 +++++++++++++++++- backend/windmill-api/src/oidc_ee.rs | 18 +- backend/windmill-api/src/saml_ee.rs | 26 ++- backend/windmill-api/src/scim_ee.rs | 23 +- backend/windmill-api/src/stripe_ee.rs | 8 +- backend/windmill-audit/src/audit_ee.rs | 49 +++- backend/windmill-common/src/ee.rs | 21 +- backend/windmill-git-sync/src/git_sync_ee.rs | 18 +- .../src/lib/components/FolderEditor.svelte | 54 ++--- 14 files changed, 446 insertions(+), 53 deletions(-) mode change 120000 => 100644 backend/src/ee.rs mode change 120000 => 100644 backend/windmill-api/src/ee.rs mode change 120000 => 100644 backend/windmill-api/src/job_helpers_ee.rs mode change 120000 => 100644 backend/windmill-api/src/oauth2_ee.rs mode change 120000 => 100644 backend/windmill-api/src/oidc_ee.rs mode change 120000 => 100644 backend/windmill-api/src/saml_ee.rs mode change 120000 => 100644 backend/windmill-api/src/scim_ee.rs mode change 120000 => 100644 backend/windmill-api/src/stripe_ee.rs mode change 120000 => 100644 backend/windmill-audit/src/audit_ee.rs mode change 120000 => 100644 backend/windmill-common/src/ee.rs mode change 120000 => 100644 backend/windmill-git-sync/src/git_sync_ee.rs diff --git a/backend/src/ee.rs b/backend/src/ee.rs deleted file mode 120000 index ca288038c6..0000000000 --- a/backend/src/ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/src/ee.rs \ No newline at end of file diff --git a/backend/src/ee.rs b/backend/src/ee.rs new file mode 100644 index 0000000000..ef944b984b --- /dev/null +++ b/backend/src/ee.rs @@ -0,0 +1,16 @@ +use anyhow::anyhow; +#[cfg(feature = "enterprise")] +use windmill_common::error::{Error, Result}; + +pub async fn set_license_key(_license_key: String) -> anyhow::Result<()> { + // Implementation is not open source + Err(anyhow!("License cannot be set in Windmill CE")) +} + +#[cfg(feature = "enterprise")] +pub async fn verify_license_key() -> Result<()> { + // Implementation is not open source + Err(Error::InternalErr( + "License always invalid in Windmill CE".to_string(), + )) +} diff --git a/backend/windmill-api/openapi.yaml b/backend/windmill-api/openapi.yaml index 8816365a4d..cb73848e7d 100644 --- a/backend/windmill-api/openapi.yaml +++ b/backend/windmill-api/openapi.yaml @@ -6440,6 +6440,9 @@ paths: properties: owner: type: string + required: + - owner + responses: "200": description: owner added to folder @@ -6467,6 +6470,11 @@ paths: properties: owner: type: string + write: + type: boolean + required: + - owner + - write responses: "200": description: owner removed from folder diff --git a/backend/windmill-api/src/ee.rs b/backend/windmill-api/src/ee.rs deleted file mode 120000 index 0267c6e6fd..0000000000 --- a/backend/windmill-api/src/ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/ee.rs b/backend/windmill-api/src/ee.rs new file mode 100644 index 0000000000..a9d170a3ac --- /dev/null +++ b/backend/windmill-api/src/ee.rs @@ -0,0 +1,6 @@ +use anyhow::anyhow; + +pub async fn validate_license_key(_license_key: String) -> anyhow::Result { + // Implementation is not open source + Err(anyhow!("License can't be validated in Windmill CE")) +} diff --git a/backend/windmill-api/src/folders.rs b/backend/windmill-api/src/folders.rs index 332ec81022..25975fc5f9 100644 --- a/backend/windmill-api/src/folders.rs +++ b/backend/windmill-api/src/folders.rs @@ -76,6 +76,7 @@ pub struct UpdateFolder { #[derive(Deserialize)] pub struct Owner { pub owner: String, + pub write: Option, } async fn list_folders( @@ -521,7 +522,7 @@ async fn add_owner( Extension(user_db): Extension, Extension(webhook): Extension, Path((w_id, name)): Path<(String, String)>, - Json(Owner { owner }): Json, + Json(Owner { owner, .. }): Json, ) -> Result { let mut tx = user_db.begin(&authed).await?; @@ -531,12 +532,22 @@ async fn add_owner( sqlx::query!( "UPDATE folder SET owners = array_append(owners::text[], $1) WHERE name = $2 AND workspace_id = $3 AND NOT $1 = ANY(owners) RETURNING name", owner, - name, + &name, &w_id, ) .fetch_optional(&mut *tx) .await?; + sqlx::query(&format!( + "UPDATE folder SET extra_perms = jsonb_set(extra_perms, '{{\"{owner}\"}}', to_jsonb($1), \ + true) WHERE name = $2 AND workspace_id = $3 RETURNING extra_perms" + )) + .bind(true) + .bind(&name) + .bind(&w_id) + .fetch_optional(&mut *tx) + .await?; + audit_log( &mut *tx, &authed.username, @@ -588,7 +599,7 @@ async fn remove_owner( Extension(user_db): Extension, Extension(webhook): Extension, Path((w_id, name)): Path<(String, String)>, - Json(Owner { owner }): Json, + Json(Owner { owner, write }): Json, ) -> Result { let mut tx = user_db.begin(&authed).await?; @@ -598,12 +609,24 @@ async fn remove_owner( sqlx::query!( "UPDATE folder SET owners = array_remove(owners, $1::varchar) WHERE name = $2 AND workspace_id = $3 RETURNING name", owner, - name, + &name, &w_id, ) .fetch_optional(&mut *tx) .await?; + if let Some(write) = write { + sqlx::query(&format!( + "UPDATE folder SET extra_perms = jsonb_set(extra_perms, '{{\"{owner}\"}}', to_jsonb($1), \ + true) WHERE name = $2 AND workspace_id = $3 RETURNING extra_perms" + )) + .bind(write) + .bind(&name) + .bind(&w_id) + .fetch_optional(&mut *tx) + .await?; + } + audit_log( &mut *tx, &authed.username, diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_ee.rs deleted file mode 120000 index 8cc4fbe8bf..0000000000 --- a/backend/windmill-api/src/job_helpers_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/job_helpers_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/job_helpers_ee.rs b/backend/windmill-api/src/job_helpers_ee.rs new file mode 100644 index 0000000000..61a946bb84 --- /dev/null +++ b/backend/windmill-api/src/job_helpers_ee.rs @@ -0,0 +1,5 @@ +use axum::Router; + +pub fn workspaced_service() -> Router { + Router::new() +} diff --git a/backend/windmill-api/src/oauth2_ee.rs b/backend/windmill-api/src/oauth2_ee.rs deleted file mode 120000 index 1ab1665454..0000000000 --- a/backend/windmill-api/src/oauth2_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/oauth2_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/oauth2_ee.rs b/backend/windmill-api/src/oauth2_ee.rs new file mode 100644 index 0000000000..032e0c9a58 --- /dev/null +++ b/backend/windmill-api/src/oauth2_ee.rs @@ -0,0 +1,212 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2022 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +use std::{collections::HashMap, fmt::Debug}; + +use axum::body::StreamBody; +use axum::response::IntoResponse; +use axum::{routing::get, Json, Router}; +use hmac::Mac; +use hyper::{HeaderMap, StatusCode}; + +use oauth2::{Client as OClient, *}; +use serde::{Deserialize, Serialize}; +use sqlx::{Postgres, Transaction}; +use windmill_common::more_serde::maybe_number_opt; + +use crate::{HTTP_CLIENT, OAUTH_CLIENTS}; +use windmill_common::error::{self, to_anyhow}; +use windmill_common::oauth2::*; + +use crate::db::DB; +use std::str; + +pub fn global_service() -> Router { + Router::new() + .route("/list_supabase", get(list_supabase)) + .route("/list_logins", get(list_logins)) + .route("/list_connects", get(list_connects)) +} + +pub fn workspaced_service() -> Router { + Router::new() +} + +#[derive(Serialize)] +#[serde(tag = "type")] +pub enum InstanceEvent { + UserAdded { email: String }, + // UserDeleted { email: String }, + // UserDeletedWorkspace { workspace: String, email: String }, + UserAddedWorkspace { workspace: String, email: String }, + UserInvitedWorkspace { workspace: String, email: String }, + UserJoinedWorkspace { workspace: String, email: String, username: String }, +} + +#[derive(Debug, Clone)] +pub struct ClientWithScopes { + _client: OClient, + scopes: Vec, + extra_params: Option>, + _extra_params_callback: Option>, + _allowed_domains: Option>, + _userinfo_url: Option, +} + +pub type BasicClientsMap = HashMap; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OAuthConfig { + auth_url: String, + token_url: String, + userinfo_url: Option, + scopes: Option>, + extra_params: Option>, + extra_params_callback: Option>, + req_body_auth: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OAuthClient { + id: String, + secret: String, + allowed_domains: Option>, + connect_config: Option, + login_config: Option, +} + +#[derive(Debug)] +pub struct AllClients { + pub logins: BasicClientsMap, + pub connects: BasicClientsMap, + pub slack: Option, +} + +pub fn build_oauth_clients( + _base_url: &str, + _oauths_from_config: Option>, +) -> anyhow::Result { + // Implementation is not open source + return Ok(AllClients { + logins: HashMap::default(), + connects: HashMap::default(), + slack: None, + }); +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +pub struct TokenResponse { + access_token: AccessToken, + #[serde(deserialize_with = "maybe_number_opt")] + #[serde(default)] + expires_in: Option, + refresh_token: Option, + #[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")] + #[serde(serialize_with = "helpers::serialize_space_delimited_vec")] + #[serde(default)] + scope: Option>, +} + +#[derive(Serialize)] +struct Logins { + oauth: Vec, + saml: Option, +} +async fn list_logins() -> error::JsonResult { + // Implementation is not open source + return Ok(Json(Logins { oauth: vec![], saml: None })); +} + +#[derive(Serialize)] +struct ScopesAndParams { + scopes: Vec, + extra_params: Option>, +} +async fn list_connects() -> error::JsonResult> { + Ok(Json( + (&OAUTH_CLIENTS.read().await.connects) + .into_iter() + .map(|(k, v)| { + ( + k.to_owned(), + ScopesAndParams { + scopes: v.scopes.clone(), + extra_params: v.extra_params.clone(), + }, + ) + }) + .collect::>(), + )) +} + +pub async fn _refresh_token<'c>( + _tx: Transaction<'c, Postgres>, + _path: &str, + _w_id: &str, + _id: i32, +) -> error::Result { + // Implementation is not open source + Err(error::Error::BadRequest( + "Not implemented in Windmill's Open Source repository".to_string(), + )) +} + +async fn list_supabase(headers: HeaderMap) -> impl IntoResponse { + let token = headers + .get("X-Supabase-Token") + .map(|x| x.to_str().unwrap_or("")) + .unwrap_or(""); + let resp = HTTP_CLIENT + .get("https://api.supabase.com/v1/projects") + .bearer_auth(token) + .send() + .await + .map_err(to_anyhow)?; + + let status_code = resp.status(); + let stream = resp.bytes_stream(); + + Ok((status_code, StreamBody::new(stream))) as error::Result<(StatusCode, StreamBody<_>)> +} + +pub async fn check_nb_of_user(db: &DB) -> error::Result<()> { + let nb_users_sso = + sqlx::query_scalar!("SELECT COUNT(*) FROM password WHERE login_type != 'password'",) + .fetch_one(db) + .await?; + if nb_users_sso.unwrap_or(0) >= 10 { + return Err(error::Error::BadRequest( + "You have reached the maximum number of oauth users accounts (10) without an enterprise license" + .to_string(), + )); + } + + let nb_users = sqlx::query_scalar!("SELECT COUNT(*) FROM password",) + .fetch_one(db) + .await?; + if nb_users.unwrap_or(0) >= 50 { + return Err(error::Error::BadRequest( + "You have reached the maximum number of accounts (50) without an enterprise license" + .to_string(), + )); + } + return Ok(()); +} + +#[derive(Clone, Debug)] +pub struct SlackVerifier { + _mac: HmacSha256, +} + +impl SlackVerifier { + pub fn new>(secret: S) -> anyhow::Result { + HmacSha256::new_from_slice(secret.as_ref()) + .map(|mac| SlackVerifier { _mac: mac }) + .map_err(|_| anyhow::anyhow!("invalid secret")) + } +} diff --git a/backend/windmill-api/src/oidc_ee.rs b/backend/windmill-api/src/oidc_ee.rs deleted file mode 120000 index 1e902de563..0000000000 --- a/backend/windmill-api/src/oidc_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/oidc_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/oidc_ee.rs b/backend/windmill-api/src/oidc_ee.rs new file mode 100644 index 0000000000..248b990f54 --- /dev/null +++ b/backend/windmill-api/src/oidc_ee.rs @@ -0,0 +1,17 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2023 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +use axum::Router; + +pub fn global_service() -> Router { + Router::new() +} + +pub fn workspaced_service() -> Router { + Router::new() +} diff --git a/backend/windmill-api/src/saml_ee.rs b/backend/windmill-api/src/saml_ee.rs deleted file mode 120000 index 65286e3f12..0000000000 --- a/backend/windmill-api/src/saml_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/saml_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/saml_ee.rs b/backend/windmill-api/src/saml_ee.rs new file mode 100644 index 0000000000..b3f1d4653c --- /dev/null +++ b/backend/windmill-api/src/saml_ee.rs @@ -0,0 +1,25 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2023 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ +#![allow(non_snake_case)] + +use axum::{routing::post, Router}; + +pub struct ServiceProviderExt(); + +pub async fn build_sp_extension() -> anyhow::Result { + return Ok(ServiceProviderExt()); +} + +pub fn global_service() -> Router { + Router::new().route("/acs", post(acs)) +} + +pub async fn acs() -> String { + // Implementation is not open source as it is a Windmill Enterprise Edition feature + "SAML available only in enterprise version".to_string() +} diff --git a/backend/windmill-api/src/scim_ee.rs b/backend/windmill-api/src/scim_ee.rs deleted file mode 120000 index cd27dc4526..0000000000 --- a/backend/windmill-api/src/scim_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/scim_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/scim_ee.rs b/backend/windmill-api/src/scim_ee.rs new file mode 100644 index 0000000000..8552405296 --- /dev/null +++ b/backend/windmill-api/src/scim_ee.rs @@ -0,0 +1,22 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2023 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ + +use axum::{middleware::Next, response::Response, routing::get, Router}; +use hyper::Request; + +pub fn global_service() -> Router { + Router::new().route("/ee", get(ee)) +} + +pub async fn ee() -> String { + return "Enterprise Edition".to_string(); +} + +pub async fn has_scim_token(request: Request, next: Next) -> Response { + return next.run(request).await; +} diff --git a/backend/windmill-api/src/stripe_ee.rs b/backend/windmill-api/src/stripe_ee.rs deleted file mode 120000 index a6c7d1230e..0000000000 --- a/backend/windmill-api/src/stripe_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-api/src/stripe_ee.rs \ No newline at end of file diff --git a/backend/windmill-api/src/stripe_ee.rs b/backend/windmill-api/src/stripe_ee.rs new file mode 100644 index 0000000000..1aea2ecd2d --- /dev/null +++ b/backend/windmill-api/src/stripe_ee.rs @@ -0,0 +1,7 @@ +#[cfg(feature = "stripe")] +use axum::Router; + +#[cfg(feature = "stripe")] +pub fn add_stripe_routes(router: Router) -> Router { + return router; +} diff --git a/backend/windmill-audit/src/audit_ee.rs b/backend/windmill-audit/src/audit_ee.rs deleted file mode 120000 index f8d4a81d03..0000000000 --- a/backend/windmill-audit/src/audit_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-audit/src/audit_ee.rs \ No newline at end of file diff --git a/backend/windmill-audit/src/audit_ee.rs b/backend/windmill-audit/src/audit_ee.rs new file mode 100644 index 0000000000..5bb52e7b3f --- /dev/null +++ b/backend/windmill-audit/src/audit_ee.rs @@ -0,0 +1,48 @@ +/* + * Author: Ruben Fiszel + * Copyright: Windmill Labs, Inc 2022 + * This file and its contents are licensed under the AGPLv3 License. + * Please see the included NOTICE for copyright information and + * LICENSE-AGPL for a copy of the license. + */ +use std::collections::HashMap; + +use windmill_common::{ + error::{Error, Result}, + utils::Pagination, +}; + +use crate::{ActionKind, AuditLog, ListAuditLogQuery}; +use sqlx::{Postgres, Transaction}; + +#[tracing::instrument(level = "trace", skip_all)] +pub async fn audit_log<'c, E: sqlx::Executor<'c, Database = Postgres>>( + _db: E, + _username: &str, + mut _operation: &str, + _action_kind: ActionKind, + _w_id: &str, + mut _resource: Option<&str>, + _parameters: Option>, +) -> Result<()> { + // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature + Ok(()) +} + +pub async fn list_audit( + _tx: Transaction<'_, Postgres>, + _w_id: String, + _pagination: Pagination, + _lq: ListAuditLogQuery, +) -> Result> { + // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature + return Ok(vec![]); +} + +pub async fn get_audit(tx: Transaction<'_, Postgres>, _id: i32, _w_id: &str) -> Result { + // Implementation is not open source as Audit logs is a Windmill Enterprise Edition feature + tx.commit().await?; + Err(Error::NotFound( + "Audit log not not available in Windmill Community edition".to_string(), + )) +} diff --git a/backend/windmill-common/src/ee.rs b/backend/windmill-common/src/ee.rs deleted file mode 120000 index 3220066a5d..0000000000 --- a/backend/windmill-common/src/ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-common/src/ee.rs \ No newline at end of file diff --git a/backend/windmill-common/src/ee.rs b/backend/windmill-common/src/ee.rs new file mode 100644 index 0000000000..c942447bbc --- /dev/null +++ b/backend/windmill-common/src/ee.rs @@ -0,0 +1,20 @@ +use crate::ee::LicensePlan::Community; +use std::sync::Arc; +use tokio::sync::RwLock; + +lazy_static::lazy_static! { + pub static ref LICENSE_KEY_VALID: Arc> = Arc::new(RwLock::new(true)); + pub static ref LICENSE_KEY_ID: Arc> = Arc::new(RwLock::new("".to_string())); + pub static ref LICENSE_KEY: Arc> = Arc::new(RwLock::new("".to_string())); +} + +pub enum LicensePlan { + Community, + Pro, + Enterprise, +} + +pub async fn get_license_plan() -> LicensePlan { + // Implementation is not open source + return Community; +} diff --git a/backend/windmill-git-sync/src/git_sync_ee.rs b/backend/windmill-git-sync/src/git_sync_ee.rs deleted file mode 120000 index 0824f2c28c..0000000000 --- a/backend/windmill-git-sync/src/git_sync_ee.rs +++ /dev/null @@ -1 +0,0 @@ -/git/windmill/../windmill-ee-private/windmill-git-sync/src/git_sync_ee.rs \ No newline at end of file diff --git a/backend/windmill-git-sync/src/git_sync_ee.rs b/backend/windmill-git-sync/src/git_sync_ee.rs new file mode 100644 index 0000000000..4be2858c27 --- /dev/null +++ b/backend/windmill-git-sync/src/git_sync_ee.rs @@ -0,0 +1,17 @@ +use windmill_common::error::Result; + +use crate::{DeployedObject, DB}; + +pub async fn handle_deployment_metadata<'c, R: rsmq_async::RsmqConnection + Send + Clone + 'c>( + _email: &str, + _created_by: &str, + _db: &DB, + _w_id: &str, + _obj: DeployedObject, + _deployment_message: Option, + _rsmq: Option, + _skip_db_insert: bool, +) -> Result<()> { + // Git sync is an enterprise feature and not part of the open-source version + return Ok(()); +} diff --git a/frontend/src/lib/components/FolderEditor.svelte b/frontend/src/lib/components/FolderEditor.svelte index 9274fc2e76..13479f2356 100644 --- a/frontend/src/lib/components/FolderEditor.svelte +++ b/frontend/src/lib/components/FolderEditor.svelte @@ -257,27 +257,10 @@ owner: owner_name } }) - await GranularAclService.addGranularAcls({ - workspace: $workspaceStore ?? '', - path: name, - kind: 'folder', - requestBody: { - owner: owner_name, - write: true - } - }) } else if (role == 'writer') { await FolderService.removeOwnerToFolder({ workspace: $workspaceStore ?? '', name, - requestBody: { - owner: owner_name - } - }) - await GranularAclService.addGranularAcls({ - workspace: $workspaceStore ?? '', - path: name, - kind: 'folder', requestBody: { owner: owner_name, write: true @@ -287,14 +270,6 @@ await FolderService.removeOwnerToFolder({ workspace: $workspaceStore ?? '', name, - requestBody: { - owner: owner_name - } - }) - await GranularAclService.addGranularAcls({ - workspace: $workspaceStore ?? '', - path: name, - kind: 'folder', requestBody: { owner: owner_name, write: false @@ -337,19 +312,22 @@