ezidam, openid: check scopes, check response types before getting app
This commit is contained in:
parent
396856eee5
commit
8ae0c59a25
7 changed files with 98 additions and 3 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -1964,6 +1964,7 @@ version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itertools",
|
"itertools",
|
||||||
"openidconnect",
|
"openidconnect",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"url",
|
"url",
|
||||||
|
|
|
||||||
|
|
@ -43,4 +43,8 @@ impl Error {
|
||||||
pub fn not_found<M: std::fmt::Display>(value: M) -> Self {
|
pub fn not_found<M: std::fmt::Display>(value: M) -> Self {
|
||||||
Self::new(Status::NotFound, value)
|
Self::new(Status::NotFound, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bad_request<M: std::fmt::Display>(value: M) -> Self {
|
||||||
|
Self::new(Status::BadRequest, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,15 @@ async fn authorize_page(
|
||||||
flash: Option<FlashMessage<'_>>,
|
flash: Option<FlashMessage<'_>>,
|
||||||
auth_request: AuthenticationRequest<'_>,
|
auth_request: AuthenticationRequest<'_>,
|
||||||
) -> Result<Template> {
|
) -> Result<Template> {
|
||||||
// TODO: parse "scope" and "response_type" -> from openid local crate
|
// Check for valid response types
|
||||||
|
openid::parse_response_types(auth_request.response_type)
|
||||||
|
.ok_or_else(|| Error::bad_request("Bad response types"))?;
|
||||||
|
|
||||||
|
// Check for supported scopes
|
||||||
|
if !openid::SupportedScopes::check_supported_scopes(auth_request.scope) {
|
||||||
|
return Err(Error::bad_request("Invalid scopes"));
|
||||||
|
}
|
||||||
|
|
||||||
let mut transaction = db.begin().await?;
|
let mut transaction = db.begin().await?;
|
||||||
// TODO: wrap checking in function?
|
// TODO: wrap checking in function?
|
||||||
|
|
||||||
|
|
@ -38,7 +46,7 @@ async fn authorize_page(
|
||||||
auth_request.redirect_uri,
|
auth_request.redirect_uri,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.ok_or_else(|| Error::not_found(auth_request.client_id))?;
|
.ok_or_else(|| Error::bad_request("Invalid application"))?;
|
||||||
|
|
||||||
let settings = Settings::get(&mut transaction).await?;
|
let settings = Settings::get(&mut transaction).await?;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
openidconnect = { version = "3.0.0-alpha.1", default-features = false }
|
openidconnect = { version = "3.0.0-alpha.1", default-features = false }
|
||||||
itertools = "0.10.5"
|
itertools = "0.10.5"
|
||||||
|
|
@ -6,7 +6,7 @@ mod scopes;
|
||||||
/// Exports
|
/// Exports
|
||||||
pub use configuration::configuration;
|
pub use configuration::configuration;
|
||||||
pub use error::Error;
|
pub use error::Error;
|
||||||
pub use response_types::supported_response_types;
|
pub use response_types::{parse_response_types, supported_response_types};
|
||||||
pub use scopes::SupportedScopes;
|
pub use scopes::SupportedScopes;
|
||||||
|
|
||||||
/// Type exports
|
/// Type exports
|
||||||
|
|
|
||||||
|
|
@ -9,3 +9,51 @@ pub fn supported_response_types() -> Vec<ResponseTypes<CoreResponseType>> {
|
||||||
ResponseTypes::new(vec![CoreResponseType::Token, CoreResponseType::IdToken]), // Other flows including hybrid flows may also be specified here.
|
ResponseTypes::new(vec![CoreResponseType::Token, CoreResponseType::IdToken]), // Other flows including hybrid flows may also be specified here.
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse_response_types(raw: &str) -> Option<Vec<CoreResponseType>> {
|
||||||
|
if raw.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = raw
|
||||||
|
.split_whitespace()
|
||||||
|
.flat_map(|s| {
|
||||||
|
// Convert string to json to deserialize with serde_json
|
||||||
|
let value_json = format!("\"{}\"", s);
|
||||||
|
let deserializer = &mut serde_json::Deserializer::from_str(&value_json);
|
||||||
|
// Attempt to deserialize into CoreResponseType
|
||||||
|
serde::Deserialize::deserialize(deserializer).ok()
|
||||||
|
})
|
||||||
|
// If any fails, return None
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// If vec is empty or contains `Extension`, return None
|
||||||
|
if list.is_empty()
|
||||||
|
|| list
|
||||||
|
.iter()
|
||||||
|
.any(|variant| matches!(variant, CoreResponseType::Extension(_)))
|
||||||
|
{
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(list)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::parse_response_types;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_valid() {
|
||||||
|
assert!(parse_response_types("code id_token").is_some());
|
||||||
|
assert!(parse_response_types("code token").is_some());
|
||||||
|
assert!(parse_response_types("code token id_token").is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_invalid() {
|
||||||
|
assert!(parse_response_types("").is_none());
|
||||||
|
assert!(parse_response_types("test").is_none());
|
||||||
|
assert!(parse_response_types("abc def code ghi id_token").is_none());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use openidconnect::Scope;
|
use openidconnect::Scope;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub struct SupportedScopes(pub Vec<Scope>);
|
pub struct SupportedScopes(pub Vec<Scope>);
|
||||||
|
|
||||||
|
|
@ -7,6 +8,16 @@ impl SupportedScopes {
|
||||||
pub fn url_format() -> String {
|
pub fn url_format() -> String {
|
||||||
Self::default().0.iter().map(|s| s.as_str()).join(" ")
|
Self::default().0.iter().map(|s| s.as_str()).join(" ")
|
||||||
}
|
}
|
||||||
|
pub fn check_supported_scopes(scopes: &str) -> bool {
|
||||||
|
if scopes.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let list = Self::default();
|
||||||
|
let scope_set: &HashSet<_> = &list.0.iter().map(|s| s.as_str()).collect();
|
||||||
|
let requested_scopes: HashSet<_> = scopes.split_whitespace().collect();
|
||||||
|
requested_scopes.is_subset(scope_set)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SupportedScopes {
|
impl Default for SupportedScopes {
|
||||||
|
|
@ -18,3 +29,25 @@ impl Default for SupportedScopes {
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::SupportedScopes;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_valid() {
|
||||||
|
assert!(SupportedScopes::check_supported_scopes("openid"));
|
||||||
|
assert!(SupportedScopes::check_supported_scopes("profile email"));
|
||||||
|
assert!(SupportedScopes::check_supported_scopes("email openid"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn check_invalid() {
|
||||||
|
assert!(!SupportedScopes::check_supported_scopes(""));
|
||||||
|
assert!(!SupportedScopes::check_supported_scopes("openid abc"));
|
||||||
|
assert!(!SupportedScopes::check_supported_scopes("test"));
|
||||||
|
assert!(!SupportedScopes::check_supported_scopes(
|
||||||
|
"email testing wrong profile"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue