Skip to content
Merged
67 changes: 36 additions & 31 deletions central/src/auth/authentik/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,7 @@ use tracing::{debug, info};

use crate::CLIENT;

use super::{
provider::{compare_provider, get_provider_id},
AuthentikConfig,
};
use super::{client_type, provider::{compare_provider, get_provider_id}, AuthentikConfig};

pub fn generate_app_values(provider: i64, client_id: &str) -> Value {
json!({
Expand All @@ -20,7 +17,7 @@ pub fn generate_app_values(provider: i64, client_id: &str) -> Value {
})
}

pub async fn generate_application(
pub async fn generate_app(
provider: i64,
client_id: &str,
conf: &AuthentikConfig,
Expand All @@ -39,51 +36,59 @@ pub async fn generate_application(
.await
}

pub async fn update_app(
client_id: &str,
provider_pk: i64,
app_name: &str,
conf: &AuthentikConfig
) -> anyhow::Result<bool> {
let url = conf.authentik_url.join(&format!("api/v3/core/applications/{app_name}/"))?;
let st = CLIENT
.patch(url)
.bearer_auth(&conf.authentik_service_api_key)
.json(&generate_app_values(provider_pk, client_id))
.send()
.await?;
debug!("Patching app has status: {:?}", st.status());
Ok(st.status().is_success())
}

pub async fn check_app_result(
client_id: &str,
provider_pk: i64,
conf: &AuthentikConfig,
conf: &AuthentikConfig
) -> anyhow::Result<bool> {
let res = generate_application(provider_pk, client_id, conf).await?;
let res = generate_app(provider_pk, client_id, conf).await?;
match res.status() {
StatusCode::CREATED => {
info!("Application for {client_id} created.");
Ok(true)
}
StatusCode::BAD_REQUEST => {
let conflicting_client = get_application(client_id, conf).await?;
let conflicting_app = get_app(client_id, conf).await?;
if app_configs_match(
&conflicting_client,
&conflicting_app,
&generate_app_values(provider_pk, client_id),
) {
info!("Application {client_id} exists.");
Ok(true)
} else {
info!("Application for {client_id} is updated.");
Ok(CLIENT
.put(
conf.authentik_url.join("api/v3/core/applicaions/")?.join(
conflicting_client
.get("name")
.and_then(Value::as_str)
.expect("No valid client"),
)?,
)
.bearer_auth(&conf.authentik_service_api_key)
.json(&generate_app_values(provider_pk, client_id))
.send()
.await?
.status()
.is_success())
update_app(
client_id,
provider_pk,
conflicting_app["name"].as_str().expect("app name has to be present"),
conf,
).await
}
}
s => anyhow::bail!("Unexpected statuscode {s} while creating authentik client. {res:?}"),
}
}

pub async fn get_application(
pub async fn get_app(
client_id: &str,
conf: &AuthentikConfig,
conf: &AuthentikConfig
) -> reqwest::Result<serde_json::Value> {
CLIENT
.get(
Expand All @@ -103,15 +108,15 @@ pub async fn compare_app_provider(
name: &str,
oidc_client_config: &OIDCConfig,
secret: &str,
conf: &AuthentikConfig,
conf: &AuthentikConfig
) -> anyhow::Result<bool> {
let client_id = oidc_client_config.client_type(name);
let client_id = client_type(oidc_client_config, name);
let provider_pk = get_provider_id(&client_id, conf).await;
match provider_pk {
Some(pr_id) => {
let app_res = get_application(&client_id, conf).await?;
let app_res = get_app(&client_id, conf).await?;
if app_configs_match(&app_res, &generate_app_values(pr_id, &client_id)) {
compare_provider(&client_id, oidc_client_config, conf, secret).await
compare_provider(&client_id, name,oidc_client_config, conf, secret).await
} else {
Ok(false)
}
Expand All @@ -122,4 +127,4 @@ pub async fn compare_app_provider(

pub fn app_configs_match(a: &Value, b: &Value) -> bool {
a.get("name") == b.get("name") && a["group"] == b["group"] && a["provider"] == b["provider"]
}
}
3 changes: 1 addition & 2 deletions central/src/auth/authentik/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ pub async fn post_group(name: &str, conf: &AuthentikConfig) -> anyhow::Result<()
.await?;
match res.status() {
StatusCode::CREATED => info!("Created group {name}"),
StatusCode::OK => info!("Created group {name}"),
StatusCode::BAD_REQUEST => info!("Group {name} already existed"),
s => anyhow::bail!(
"Unexpected statuscode {s} while creating group {name}: {:#?}",
res.json::<serde_json::Value>().await.unwrap_or_default()
),
}
Ok(())
}
}
123 changes: 64 additions & 59 deletions central/src/auth/authentik/mod.rs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
mod app;
mod group;
mod provider;
#[cfg(test)]
mod test;

use crate::auth::authentik::provider::{check_set_federation_id, generate_provider, get_provider_id, update_provider};
use crate::auth::generate_secret;
use std::sync::Mutex;
use crate::CLIENT;
use anyhow::bail;
use app::{check_app_result, compare_app_provider, get_application};
use app::{check_app_result, compare_app_provider, get_app};
use beam_lib::reqwest::{self, Url};
use clap::Parser;
use group::create_groups;
use provider::{compare_provider, generate_provider_values, get_provider, get_provider_id};
use provider::{compare_provider, generate_provider_values, get_provider};
use reqwest::StatusCode;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use shared::{OIDCConfig, SecretResult};
use std::sync::Mutex;
use tracing::{debug, info};
use crate::auth::authentik::provider::check_set_federation_id;

#[derive(Debug, Parser, Clone)]
pub struct AuthentikConfig {
Expand All @@ -41,7 +42,7 @@ pub struct FlowPropertymapping {
pub invalidation_flow: String,
pub signing_key: String,
pub property_mapping: Vec<String>,
pub federation_mapping: Vec<String>
pub federation_mapping: Vec<String>,
}
impl FlowPropertymapping {
async fn new(conf: &AuthentikConfig) -> reqwest::Result<Self> {
Expand All @@ -56,10 +57,7 @@ impl FlowPropertymapping {
let jwt_federation_sources = conf.authentik_federation_names.clone();
//let flow_url = "/api/v3/flows/instances/?name=...";
//let property_url = "/api/v3/propertymappings/all/?name=...";
let flow_url = conf
.authentik_url
.join("api/v3/flows/instances/")
.unwrap();
let flow_url = conf.authentik_url.join("api/v3/flows/instances/").unwrap();
let signing_key_url = conf
.authentik_url
.join("api/v3/crypto/certificatekeypairs/")
Expand All @@ -68,12 +66,10 @@ impl FlowPropertymapping {
.authentik_url
.join("api/v3/propertymappings/all/")
.unwrap();
let federation_url = conf
.authentik_url
.join("api/v3/sources/all/")
.unwrap();
let federation_url = conf.authentik_url.join("api/v3/sources/all/").unwrap();
let property_mapping = get_mappings_uuids(&property_url, property_keys, conf).await;
let federation_mapping = get_mappings_uuids(&federation_url, jwt_federation_sources, conf).await;
let federation_mapping =
get_mappings_uuids(&federation_url, jwt_federation_sources, conf).await;
let authorization_flow = get_uuid(&flow_url, flow_auth, conf)
.await
.expect("No default flow present"); // flow uuid
Expand All @@ -89,14 +85,14 @@ impl FlowPropertymapping {
invalidation_flow,
signing_key,
property_mapping,
federation_mapping
federation_mapping,
};
*PROPERTY_MAPPING_CACHE.lock().unwrap() = Some(mapping.clone());
Ok(mapping)
}
}

pub async fn validate_application(
pub async fn validate_app(
name: &str,
oidc_client_config: &OIDCConfig,
secret: &str,
Expand All @@ -110,38 +106,35 @@ pub async fn create_app_provider(
oidc_client_config: &OIDCConfig,
conf: &AuthentikConfig,
) -> anyhow::Result<SecretResult> {
combine_app_provider(name, oidc_client_config, conf).await
}

pub async fn combine_app_provider(
name: &str,
oidc_client_config: &OIDCConfig,
conf: &AuthentikConfig,
) -> anyhow::Result<SecretResult> {
let client_id = oidc_client_config.client_type(name);
let client_id = client_type(oidc_client_config, name);
let secret = if !oidc_client_config.is_public {
generate_secret()
} else {
String::with_capacity(0)
};
let generated_provider =
generate_provider_values(&client_id, oidc_client_config, &secret, conf).await?;
generate_provider_values(
&client_id,
oidc_client_config,
&secret,
conf,
None,
).await?;
debug!("Provider Values: {:#?}", generated_provider);
let provider_res = CLIENT
.post(conf.authentik_url.join("api/v3/providers/oauth2/")?)
.bearer_auth(&conf.authentik_service_api_key)
.json(&generated_provider)
.send()
.await?;
let provider_res = generate_provider(&generated_provider, conf).await?;
// Create groups for this client
create_groups(name, conf).await?;
debug!("Result Provider: {:#?}", provider_res);
match provider_res.status() {
StatusCode::CREATED => {
let res_provider: serde_json::Value = provider_res.json().await?;
let provider_id = res_provider.get("pk").and_then(|v| v.as_i64()).unwrap();
let provider_name = res_provider.get("name").and_then(|v| v.as_str()).unwrap();
// check and set federation_id
let provider_id = res_provider["pk"]
.as_i64()
.expect("provider id has to be present");
let provider_name = res_provider["name"]
.as_str()
.expect("provider name has to be present");
// check and set federation_id
check_set_federation_id(&name, provider_id, conf, oidc_client_config).await?;
debug!("{:?}", provider_id);
info!("Provider for {provider_name} created.");
Expand All @@ -150,27 +143,23 @@ pub async fn combine_app_provider(
} else {
bail!(
"Unexpected Conflict {name} while overwriting authentik app. {:?}",
get_application(&client_id, conf).await?
get_app(&client_id, conf).await?
);
}
}
StatusCode::BAD_REQUEST => {
let conflicting_provider =
get_provider(&client_id, conf).await?;
let conflicting_provider = get_provider(&client_id, conf).await?;
debug!("{:#?}", conflicting_provider);

let app = conflicting_provider
.get("name")
.and_then(|v| v.as_str())
.unwrap();
if compare_provider(&client_id, oidc_client_config, conf, &secret).await? {
let app = conflicting_provider["name"]
.as_str()
.expect("app name has to be present");
if compare_provider(&client_id, name, oidc_client_config, conf, &secret).await? {
info!("Provider {app} existed.");
if check_app_result(
&client_id,
conflicting_provider
.get("pk")
.and_then(|v| v.as_i64())
.expect("pk id not found"),
conflicting_provider["pk"]
.as_i64()
.expect("provider id has to be present"),
conf,
)
.await?
Expand All @@ -186,18 +175,11 @@ pub async fn combine_app_provider(
} else {
bail!(
"Unexpected Conflict {name} while overwriting authentik app. {:?}",
get_application(&client_id, conf).await?
get_app(&client_id, conf).await?
);
}
} else {
let res = CLIENT
.patch(conf.authentik_url.join(&format!(
"api/v3/providers/oauth2/{}/",
get_provider_id(&client_id, conf).await.unwrap()
))?)
.bearer_auth(&conf.authentik_service_api_key)
.json(&generated_provider)
.send()
let res = update_provider(&generated_provider, &client_id, conf)
.await?
.status()
.is_success()
Expand All @@ -217,7 +199,7 @@ pub async fn combine_app_provider(
} else {
bail!(
"Unexpected Conflict {name} while overwriting authentik app. {:?}",
get_application(&client_id, conf).await?
get_app(&client_id, conf).await?
);
}
}
Expand Down Expand Up @@ -249,7 +231,6 @@ async fn get_mappings_uuids(
search_key: Vec<String>,
conf: &AuthentikConfig,
) -> Vec<String> {
// TODO: async iter to collect
let mut result: Vec<String> = vec![];
for key in search_key {
result.push(
Expand All @@ -259,4 +240,28 @@ async fn get_mappings_uuids(
);
}
result
}

pub fn client_type(oidc_config: &OIDCConfig, name: &str) -> String {
format!(
"{}-{}",
name,
if oidc_config.is_public {
"public"
} else {
"private"
}
)
}
//use case federation id
pub fn flipped_client_type(oidc_config: &OIDCConfig, name: &str) -> String {
format!(
"{}-{}",
name,
if oidc_config.is_public {
"private"
} else {
"public"
}
)
}
Loading