chore: SSO EE (#3207)

* chore: SSO EE

* update ee repo ref

* revert EE gating

* update ref to ee repo

* update comment

* Move check_nb_users

* update ee repo ref
This commit is contained in:
Guillaume Bouvignies
2024-02-15 17:31:53 +01:00
committed by GitHub
parent e762fbe2cd
commit cafc8b6ccd
9 changed files with 224 additions and 1225 deletions
+1 -1
View File
@@ -1 +1 @@
8610ca52151b224ce615bbccf3f5ab736ea57eeb
86e415e6a8c767b780bd2843ae8291db70803491
+1 -1
View File
@@ -15,7 +15,7 @@ use tokio::{
};
use uuid::Uuid;
use windmill_api::{
oauth2::{build_oauth_clients, OAuthClient},
oauth2_ee::{build_oauth_clients, OAuthClient},
DEFAULT_BODY_LIMIT, IS_SECURE, OAUTH_CLIENTS, REQUEST_SIZE_LIMIT,
};
use windmill_common::{
+6 -7
View File
@@ -8,11 +8,11 @@
use crate::db::ApiAuthed;
use crate::embeddings::load_embeddings_db;
use crate::oauth2::AllClients;
use crate::oauth2_ee::AllClients;
use crate::scim::has_scim_token;
use crate::tracing_init::MyOnFailure;
use crate::{
oauth2::SlackVerifier,
oauth2_ee::SlackVerifier,
tracing_init::{MyMakeSpan, MyOnResponse},
users::OptAuthed,
webhook_util::WebhookShared,
@@ -44,6 +44,7 @@ use windmill_common::error::AppError;
mod apps;
mod audit;
mod capture;
mod concurrency_groups;
mod configs;
mod db;
mod drafts;
@@ -59,9 +60,7 @@ mod integration;
pub mod job_helpers;
pub mod job_metrics;
pub mod jobs;
pub mod oauth2;
mod concurrency_groups;
pub mod oauth2_ee;
mod oidc;
mod openai;
mod raw_apps;
@@ -194,7 +193,7 @@ pub async fn run_server(
.nest("/job_metrics", job_metrics::workspaced_service())
.nest("/job_helpers", job_helpers::workspaced_service())
.nest("/jobs", jobs::workspaced_service())
.nest("/oauth", oauth2::workspaced_service())
.nest("/oauth", oauth2_ee::workspaced_service())
.nest("/openai", openai::workspaced_service())
.nest("/raw_apps", raw_apps::workspaced_service())
.nest("/resources", resources::workspaced_service())
@@ -264,7 +263,7 @@ pub async fn run_server(
)
.nest(
"/oauth",
oauth2::global_service().layer(Extension(Arc::clone(&sp_extension))),
oauth2_ee::global_service().layer(Extension(Arc::clone(&sp_extension))),
)
.route("/version", get(git_v))
.route("/uptodate", get(is_up_to_date))
File diff suppressed because it is too large Load Diff
+212
View File
@@ -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<String>,
extra_params: Option<HashMap<String, String>>,
_extra_params_callback: Option<HashMap<String, String>>,
_allowed_domains: Option<Vec<String>>,
_userinfo_url: Option<String>,
}
pub type BasicClientsMap = HashMap<String, ClientWithScopes>;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OAuthConfig {
auth_url: String,
token_url: String,
userinfo_url: Option<String>,
scopes: Option<Vec<String>>,
extra_params: Option<HashMap<String, String>>,
extra_params_callback: Option<HashMap<String, String>>,
req_body_auth: Option<bool>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct OAuthClient {
id: String,
secret: String,
allowed_domains: Option<Vec<String>>,
connect_config: Option<OAuthConfig>,
login_config: Option<OAuthConfig>,
}
#[derive(Debug)]
pub struct AllClients {
pub logins: BasicClientsMap,
pub connects: BasicClientsMap,
pub slack: Option<OClient>,
}
pub fn build_oauth_clients(
_base_url: &str,
_oauths_from_config: Option<HashMap<String, OAuthClient>>,
) -> anyhow::Result<AllClients> {
// 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<u64>,
refresh_token: Option<RefreshToken>,
#[serde(deserialize_with = "helpers::deserialize_space_delimited_vec")]
#[serde(serialize_with = "helpers::serialize_space_delimited_vec")]
#[serde(default)]
scope: Option<Vec<Scope>>,
}
#[derive(Serialize)]
struct Logins {
oauth: Vec<String>,
saml: Option<String>,
}
async fn list_logins() -> error::JsonResult<Logins> {
// Implementation is not open source
return Ok(Json(Logins { oauth: vec![], saml: None }));
}
#[derive(Serialize)]
struct ScopesAndParams {
scopes: Vec<String>,
extra_params: Option<HashMap<String, String>>,
}
async fn list_connects() -> error::JsonResult<HashMap<String, ScopesAndParams>> {
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::<HashMap<String, ScopesAndParams>>(),
))
}
pub async fn _refresh_token<'c>(
_tx: Transaction<'c, Postgres>,
_path: &str,
_w_id: &str,
_id: i32,
) -> error::Result<String> {
// 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<S: AsRef<[u8]>>(secret: S) -> anyhow::Result<SlackVerifier> {
HmacSha256::new_from_slice(secret.as_ref())
.map(|mac| SlackVerifier { _mac: mac })
.map_err(|_| anyhow::anyhow!("invalid secret"))
}
}
-8
View File
@@ -8,7 +8,6 @@
#![allow(non_snake_case)]
use axum::{routing::post, Router};
use std::sync::Arc;
pub struct ServiceProviderExt();
@@ -16,13 +15,6 @@ pub async fn build_sp_extension() -> anyhow::Result<ServiceProviderExt> {
return Ok(ServiceProviderExt());
}
pub async fn generate_redirect_url(
_service_provider: Arc<ServiceProviderExt>,
) -> anyhow::Result<Option<String>> {
// Implementation is not open source as it is a Windmill Enterprise Edition feature
return Ok(None);
}
pub fn global_service() -> Router {
Router::new().route("/acs", post(acs))
}
+1 -1
View File
@@ -8,7 +8,7 @@
use crate::{
db::{ApiAuthed, DB},
oauth2::_refresh_token,
oauth2_ee::_refresh_token,
users::{maybe_refresh_folders, require_owner_of_path},
webhook_util::{WebhookMessage, WebhookShared},
};
+1 -12
View File
@@ -6,6 +6,7 @@ use tokio::{select, sync::mpsc};
use windmill_common::METRICS_ENABLED;
use crate::db::DB;
use crate::oauth2_ee::InstanceEvent;
lazy_static::lazy_static! {
// TODO: these aren't synced, they should be moved into the queue abstraction once/if that happens.
@@ -24,18 +25,6 @@ pub enum WebhookPayload {
InstanceEvent(InstanceEvent),
}
#[derive(Serialize)]
#[serde(tag = "type")]
pub enum InstanceEvent {
UserSignupOAuth { email: String },
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(Serialize)]
#[serde(tag = "type")]
pub enum WebhookMessage {
+2 -1
View File
@@ -18,7 +18,7 @@ use crate::{
resources::{Resource, ResourceType},
users::{send_email_if_possible, WorkspaceInvite, VALID_USERNAME},
utils::require_super_admin,
webhook_util::{InstanceEvent, WebhookShared},
webhook_util::WebhookShared,
};
#[cfg(feature = "stripe")]
use axum::response::Redirect;
@@ -56,6 +56,7 @@ use windmill_common::{
};
use windmill_queue::QueueTransaction;
use crate::oauth2_ee::InstanceEvent;
use crate::variables::{decrypt, encrypt};
use hyper::{header, StatusCode};
use serde::{Deserialize, Serialize};