Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,12 +631,23 @@ pub struct Options {
long = "oidc-scope",
name = "oidc-scope",
env = "P_OIDC_SCOPE",
default_value = "openid profile email offline_access",
default_value = "openid profile email",
required = false,
help = "OIDC scope to request (default: openid profile email offline_access)"
help = "OIDC scope to request (default: openid profile email)"
)]
pub scope: String,

// additional query params for oidc auth url
#[arg(
long,
name = "oidc-query-params",
env = "P_OIDC_QUERY_PARAMS",
value_parser = validation::validate_query_params,
required = false,
help = "Additional query params to add to the auth url"
)]
pub oidc_query_params: Option<String>,

// event's maximum chunk age in hours
#[arg(
long,
Expand Down
16 changes: 14 additions & 2 deletions src/handlers/http/oidc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,13 @@ pub async fn login(
let scope = PARSEABLE.options.scope.to_string();
let mut auth_url: String = client.read().await.auth_url(&scope, Some(redirect)).into();

auth_url.push_str("&access_type=offline");
if let Some(query_params) = PARSEABLE.options.oidc_query_params.as_ref() {
if !query_params.starts_with('&') {
auth_url = format!("{auth_url}&{query_params}");
} else {
auth_url.push_str(query_params.as_str());
}
}
return Ok(HttpResponse::TemporaryRedirect()
.insert_header((actix_web::http::header::LOCATION, auth_url))
.finish());
Expand Down Expand Up @@ -153,7 +159,13 @@ pub async fn login(
.await
.auth_url(&scope, Some(redirect))
.into();
auth_url.push_str("&access_type=offline");
if let Some(query_params) = PARSEABLE.options.oidc_query_params.as_ref() {
if !query_params.starts_with('&') {
auth_url = format!("{auth_url}&{query_params}");
} else {
auth_url.push_str(query_params.as_str());
}
}
HttpResponse::TemporaryRedirect()
.insert_header((actix_web::http::header::LOCATION, auth_url))
.finish()
Expand Down
50 changes: 50 additions & 0 deletions src/option.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,56 @@ pub mod validation {
}
}

pub fn validate_query_params(mut query: &str) -> Result<String, String> {
fn valid_component(value: &str) -> bool {
let bytes = value.as_bytes();
let mut i = 0;

while i < bytes.len() {
match bytes[i] {
b'%' if i + 2 < bytes.len()
&& bytes[i + 1].is_ascii_hexdigit()
&& bytes[i + 2].is_ascii_hexdigit() =>
{
i += 3;
}
b if b.is_ascii_alphanumeric()
|| matches!(b, b'-' | b'.' | b'_' | b'~' | b'+') =>
{
i += 1;
}
_ => return false,
}
}

true
}

if query.is_empty() {
return Err("query string cannot be empty".into());
}
query = if let Some(query) = query.strip_prefix('&') {
query
} else {
query
};
for param in query.split('&') {
let (key, value) = param
.split_once('=')
.ok_or_else(|| format!("invalid parameter: {param}"))?;
Comment thread
coderabbitai[bot] marked this conversation as resolved.

if key.is_empty() {
return Err("parameter key cannot be empty".into());
}

if !valid_component(key) || !valid_component(value) {
return Err(format!("invalid characters in parameter: {param}"));
}
}

Ok(query.to_owned())
}

pub fn validate_payload_size(s: &str) -> Result<usize, String> {
const MIN_SIZE: usize = 100; // 100 bytes
const MAX_SIZE: usize = 100 * 1024 * 1024; // 100 MB
Expand Down
Loading