Skip to content

Commit

Permalink
fix: add OIDC/OAuth2 information into the swagger ui
Browse files Browse the repository at this point in the history
  • Loading branch information
ctron committed Aug 10, 2023
1 parent 2a1a494 commit e38abed
Show file tree
Hide file tree
Showing 18 changed files with 215 additions and 27 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 5 additions & 2 deletions auth/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ async-trait = "0.1"
anyhow = "1"
biscuit = "0.6"
chrono = { version = "0.4.26", default-features = false }
clap = { version = "4", features = ["derive", "env"]}
clap = { version = "4", features = ["derive", "env"] }
futures-util = "0.3"
humantime = "2"
log = "0.4"
Expand All @@ -31,7 +31,10 @@ actix-http = { version = "3.3.1", optional = true }
actix-web-httpauth = { version = "0.8", optional = true }
actix-web-extras = { version = "0.1", optional = true }

utoipa = { version = "3", features = ["actix_extras"], optional = true }
utoipa-swagger-ui = { version = "3", features = ["actix-web"], optional = true }

[features]
default = []
actix = ["actix-web", "actix-http", "actix-web-httpauth", "actix-web-extras"]

swagger = ["utoipa", "utoipa-swagger-ui", "actix"]
6 changes: 3 additions & 3 deletions auth/src/authenticator/config.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::devmode;
use clap::ArgAction;
use serde::Deserialize;
use std::path::PathBuf;
Expand All @@ -24,9 +25,8 @@ impl AuthenticatorConfig {
AuthenticatorConfig {
disabled: false,
clients: SingleAuthenticatorClientConfig {
client_ids: vec!["frontend".to_string(), "walker".to_string()],
issuer_url: std::env::var("ISSUER_URL")
.unwrap_or_else(|_| "http://localhost:8090/realms/chicken".to_string()),
client_ids: devmode::CLIENT_IDS.iter().map(|s| s.to_string()).collect(),
issuer_url: devmode::issuer_url(),
..Default::default()
},
}
Expand Down
4 changes: 2 additions & 2 deletions auth/src/authenticator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use trustification_common::reqwest::ClientFactory;
/// An authenticator to authenticate incoming requests.
#[derive(Clone)]
pub struct Authenticator {
clients: Vec<AuthenticatorClient<ExtendedClaims>>,
pub clients: Vec<AuthenticatorClient<ExtendedClaims>>,
}

impl Authenticator {
Expand Down Expand Up @@ -158,7 +158,7 @@ async fn create_client<P: CompactJson + Claims>(
}

#[derive(Clone)]
struct AuthenticatorClient<P>
pub struct AuthenticatorClient<P>
where
P: CompactJson + Claims,
{
Expand Down
7 changes: 7 additions & 0 deletions auth/src/devmode.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const ISSUER_URL: &str = "http://localhost:8090/realms/chicken";
pub const CLIENT_IDS: &[&str] = &["frontend", "walker"];
pub const SWAGGER_UI_CLIENT_ID: &str = "frontend";

pub fn issuer_url() -> String {
std::env::var("ISSUER_URL").unwrap_or_else(|_| ISSUER_URL.to_string())
}
4 changes: 4 additions & 0 deletions auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
pub mod authenticator;
pub mod client;
pub mod devmode;

#[cfg(feature = "swagger")]
pub mod swagger_ui;

/// A registered user
pub const ROLE_USER: &str = "chicken-user";
Expand Down
95 changes: 95 additions & 0 deletions auth/src/swagger_ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use crate::devmode::{self, SWAGGER_UI_CLIENT_ID};
use openid::{Client, Discovered, Provider, StandardClaims};
use url::Url;
use utoipa::openapi::{
security::{AuthorizationCode, Flow, OAuth2, Scopes, SecurityScheme},
OpenApi, SecurityRequirement,
};
use utoipa_swagger_ui::{oauth, SwaggerUi};

#[derive(Clone, Debug, Default, clap::Args)]
#[command(rename_all_env = "SCREAMING_SNAKE_CASE", next_help_heading = "Swagger UI OIDC")]
pub struct SwaggerUiOidcConfig {
/// The issuer URL used by the Swagger UI, disabled if none.
#[arg(long, env)]
pub swagger_ui_oidc_issuer_url: Option<String>,
/// The client ID use by the swagger UI frontend
#[arg(long, env, default_value = "frontend")]
pub swagger_ui_oidc_client_id: String,
}

impl SwaggerUiOidcConfig {
pub fn devmode() -> Self {
Self {
swagger_ui_oidc_issuer_url: Some(devmode::issuer_url()),
swagger_ui_oidc_client_id: SWAGGER_UI_CLIENT_ID.to_string(),
}
}
}

pub struct SwaggerUiOidc {
pub client_id: String,
pub auth_url: String,
pub token_url: String,
}

impl SwaggerUiOidc {
pub async fn new(config: SwaggerUiOidcConfig) -> anyhow::Result<Option<Self>> {
let issuer_url = match config.swagger_ui_oidc_issuer_url {
None => return Ok(None),
Some(issuer_url) => issuer_url,
};

let client: Client<Discovered, StandardClaims> = openid::Client::discover(
config.swagger_ui_oidc_client_id.clone(),
None,
None,
Url::parse(&issuer_url)?,
)
.await?;

Ok(Some(Self {
token_url: client.provider.token_uri().to_string(),
auth_url: client.provider.auth_uri().to_string(),
client_id: client.client_id,
}))
}

pub async fn from_devmode_or_config(devmode: bool, config: SwaggerUiOidcConfig) -> anyhow::Result<Option<Self>> {
let config = match devmode {
true => SwaggerUiOidcConfig::devmode(),
false => config,
};

Self::new(config).await
}

pub fn apply(&self, swagger: SwaggerUi, openapi: &mut OpenApi) -> SwaggerUi {
if let Some(components) = &mut openapi.components {
// the swagger UI expects the full "well known" endpoint
// let url = format!("{}/.well-known/openid-configuration", self.issuer_url);
//components.add_security_scheme("oidc", SecurityScheme::OpenIdConnect(OpenIdConnect::new(url)));

// The swagger UI OIDC client still is weird, let's use OAuth2

components.add_security_scheme(
"oidc",
SecurityScheme::OAuth2(OAuth2::new([Flow::AuthorizationCode(AuthorizationCode::new(
&self.auth_url,
&self.token_url,
Scopes::one("oidc", "OpenID Connect"),
))])),
);
}

openapi.security = Some(vec![SecurityRequirement::new::<_, _, String>("oidc", [])]);

swagger.oauth(
oauth::Config::new()
.client_id(&self.client_id)
.app_name("Trustification")
.scopes(vec!["openid".to_string()])
.use_pkce_with_authorization_code_grant(true),
)
}
}
2 changes: 1 addition & 1 deletion bombastic/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ log = "0.4"
bombastic-index = { path = "../index" }
bombastic-model = { path = "../model" }
trustification-api = { path = "../../api" }
trustification-auth = { path = "../../auth", features = ["actix"] }
trustification-auth = { path = "../../auth", features = ["actix", "swagger"] }
trustification-infrastructure = { path = "../../infrastructure" }
trustification-storage = { path = "../../storage" }
trustification-index = { path = "../../index" }
Expand Down
12 changes: 11 additions & 1 deletion bombastic/api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use prometheus::Registry;
use tokio::sync::RwLock;
use trustification_auth::authenticator::config::AuthenticatorConfig;
use trustification_auth::authenticator::Authenticator;
use trustification_auth::swagger_ui::{SwaggerUiOidc, SwaggerUiOidcConfig};
use trustification_index::{IndexConfig, IndexStore};
use trustification_infrastructure::app::{new_app, AppOptions};
use trustification_infrastructure::{Infrastructure, InfrastructureConfig};
Expand Down Expand Up @@ -45,6 +46,9 @@ pub struct Run {

#[command(flatten)]
pub oidc: AuthenticatorConfig,

#[command(flatten)]
pub swagger_ui_oidc: SwaggerUiOidcConfig,
}

impl Run {
Expand All @@ -56,6 +60,11 @@ impl Run {
.await?
.map(Arc::new);

let swagger_oidc: Option<Arc<SwaggerUiOidc>> =
SwaggerUiOidc::from_devmode_or_config(self.devmode, self.swagger_ui_oidc)
.await?
.map(Arc::new);

if authenticator.is_none() {
log::warn!("Authentication is disabled");
}
Expand All @@ -72,14 +81,15 @@ impl Run {
let http_metrics = http_metrics.clone();
let cors = Cors::permissive();
let authenticator = authenticator.clone();
let swagger_oidc = swagger_oidc.clone();

new_app(AppOptions {
cors: Some(cors),
metrics: Some(http_metrics),
authenticator: None,
})
.app_data(web::Data::new(state.clone()))
.configure(move |svc| server::config(svc, authenticator.clone()))
.configure(move |svc| server::config(svc, authenticator.clone(), swagger_oidc.clone()))
});
srv = match listener {
Some(v) => srv.listen(v)?,
Expand Down
19 changes: 17 additions & 2 deletions bombastic/api/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use serde::Deserialize;
use trustification_api::search::SearchOptions;
use trustification_auth::{
authenticator::{user::UserDetails, Authenticator},
swagger_ui::SwaggerUiOidc,
ROLE_MANAGER,
};
use trustification_index::Error as IndexError;
Expand All @@ -35,7 +36,11 @@ use utoipa_swagger_ui::SwaggerUi;
)]
pub struct ApiDoc;

pub fn config(cfg: &mut web::ServiceConfig, auth: Option<Arc<Authenticator>>) {
pub fn config(
cfg: &mut web::ServiceConfig,
auth: Option<Arc<Authenticator>>,
swagger_ui_oidc: Option<Arc<SwaggerUiOidc>>,
) {
cfg.service(
web::scope("/api/v1")
.wrap(new_auth!(auth))
Expand All @@ -45,7 +50,16 @@ pub fn config(cfg: &mut web::ServiceConfig, auth: Option<Arc<Authenticator>>) {
.service(publish_sbom)
.service(delete_sbom),
)
.service(SwaggerUi::new("/swagger-ui/{_:.*}").url("/openapi.json", ApiDoc::openapi()));
.service({
let mut openapi = ApiDoc::openapi();
let mut swagger = SwaggerUi::new("/swagger-ui/{_:.*}");

if let Some(swagger_ui_oidc) = &swagger_ui_oidc {
swagger = swagger_ui_oidc.apply(swagger, &mut openapi);
}

swagger.url("/openapi.json", openapi)
});
}

const ACCEPT_ENCODINGS: [&str; 2] = ["bzip2", "zstd"];
Expand Down Expand Up @@ -193,6 +207,7 @@ impl From<&SearchParams> for SearchOptions {
responses(
(status = 200, description = "Search completed"),
(status = BAD_REQUEST, description = "Bad query"),
(status = 401, description = "Not authenticated"),
),
params(
("q" = String, Query, description = "Search query"),
Expand Down
11 changes: 11 additions & 0 deletions integration-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ use tokio::{select, time::timeout};
use trustification_auth::{
authenticator::config::{AuthenticatorConfig, SingleAuthenticatorClientConfig},
client::TokenInjector,
swagger_ui::SwaggerUiOidcConfig,
};
use trustification_event_bus::{EventBusConfig, EventBusType};
use trustification_index::IndexConfig;
Expand Down Expand Up @@ -94,6 +95,7 @@ fn bombastic_api() -> bombastic_api::Run {
enable_tracing: false,
},
oidc: testing_oidc(),
swagger_ui_oidc: testing_swagger_ui_oidc(),
}
}

Expand Down Expand Up @@ -151,6 +153,7 @@ fn vexination_api() -> vexination_api::Run {
enable_tracing: false,
},
oidc: testing_oidc(),
swagger_ui_oidc: testing_swagger_ui_oidc(),
}
}

Expand All @@ -174,6 +177,7 @@ fn spog_api(bport: u16, vport: u16) -> spog_api::Run {
enable_tracing: false,
},
oidc: testing_oidc(),
swagger_ui_oidc: testing_swagger_ui_oidc(),
}
}

Expand All @@ -192,6 +196,13 @@ fn testing_oidc() -> AuthenticatorConfig {
}
}

fn testing_swagger_ui_oidc() -> SwaggerUiOidcConfig {
SwaggerUiOidcConfig {
swagger_ui_oidc_issuer_url: Some(SSO_ENDPOINT.to_string()),
swagger_ui_oidc_client_id: "frontend".to_string(),
}
}

pub async fn get_response(
port: u16,
api_endpoint: &str,
Expand Down
2 changes: 1 addition & 1 deletion spog/api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ trustification-api = { path = "../../api" }
trustification-common = { path = "../../common" }
trustification-infrastructure = { path = "../../infrastructure" }
trustification-version = { path = "../../version", features = ["actix-web"] }
trustification-auth = { path = "../../auth", features = ["actix"] }
trustification-auth = { path = "../../auth", features = ["actix", "swagger"] }

[build-dependencies]
trustification-version = { path = "../../version", features = ["build"] }
5 changes: 4 additions & 1 deletion spog/api/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::process::ExitCode;
use std::{net::TcpListener, path::PathBuf};
use trustification_auth::authenticator::config::AuthenticatorConfig;

use trustification_auth::swagger_ui::SwaggerUiOidcConfig;
use trustification_infrastructure::{Infrastructure, InfrastructureConfig};

mod advisory;
Expand Down Expand Up @@ -62,6 +62,9 @@ pub struct Run {

#[command(flatten)]
pub oidc: AuthenticatorConfig,

#[command(flatten)]
pub swagger_ui_oidc: SwaggerUiOidcConfig,
}

impl Run {
Expand Down
Loading

0 comments on commit e38abed

Please sign in to comment.