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
|
|
@ -43,4 +43,8 @@ impl Error {
|
|||
pub fn not_found<M: std::fmt::Display>(value: M) -> Self {
|
||||
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<'_>>,
|
||||
auth_request: AuthenticationRequest<'_>,
|
||||
) -> 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?;
|
||||
// TODO: wrap checking in function?
|
||||
|
||||
|
|
@ -38,7 +46,7 @@ async fn authorize_page(
|
|||
auth_request.redirect_uri,
|
||||
)
|
||||
.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?;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
thiserror = { workspace = true }
|
||||
url = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
openidconnect = { version = "3.0.0-alpha.1", default-features = false }
|
||||
itertools = "0.10.5"
|
||||
|
|
@ -6,7 +6,7 @@ mod scopes;
|
|||
/// Exports
|
||||
pub use configuration::configuration;
|
||||
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;
|
||||
|
||||
/// 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.
|
||||
]
|
||||
}
|
||||
|
||||
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 openidconnect::Scope;
|
||||
use std::collections::HashSet;
|
||||
|
||||
pub struct SupportedScopes(pub Vec<Scope>);
|
||||
|
||||
|
|
@ -7,6 +8,16 @@ impl SupportedScopes {
|
|||
pub fn url_format() -> String {
|
||||
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 {
|
||||
|
|
@ -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