mirror of
https://gitlab.com/fabinfra/fabaccess/bffh.git
synced 2024-11-23 15:17:57 +01:00
132 lines
4.4 KiB
Rust
132 lines
4.4 KiB
Rust
|
use reqwest;
|
||
|
use rsasl;
|
||
|
use rsasl::callback::{Context, SessionData};
|
||
|
use rsasl::mechanisms::oauthbearer::properties::OAuthBearerError;
|
||
|
use rsasl::validate::Validate;
|
||
|
|
||
|
use miette::IntoDiagnostic;
|
||
|
|
||
|
use crate::authentication::V;
|
||
|
use crate::users::db::{User, UserData};
|
||
|
|
||
|
// Data required for validating access tokens
|
||
|
pub struct OIDC {
|
||
|
configuration_endpoint: String,
|
||
|
userinfo_endpoint: String,
|
||
|
userid_attribute: String,
|
||
|
http: reqwest::blocking::Client,
|
||
|
}
|
||
|
|
||
|
impl OIDC {
|
||
|
pub fn new(config: &crate::config::OIDCConfig) -> Self {
|
||
|
let http = reqwest::blocking::Client::new();
|
||
|
let configuration_endpoint = config.configuration_endpoint.clone();
|
||
|
let userinfo_endpoint = config.userinfo_endpoint.clone();
|
||
|
let userid_attribute = config.userid_attribute.clone();
|
||
|
return Self {
|
||
|
http,
|
||
|
configuration_endpoint,
|
||
|
userinfo_endpoint,
|
||
|
userid_attribute,
|
||
|
};
|
||
|
}
|
||
|
|
||
|
pub fn user_by_token(&self, token: &str) -> miette::Result<User> {
|
||
|
let token_parts = token.split_whitespace().collect::<Vec<_>>();
|
||
|
let only_token;
|
||
|
if token_parts.len() == 1 {
|
||
|
only_token = token_parts[0];
|
||
|
} else if token_parts.len() == 2 && token_parts[0].to_lowercase() == "bearer" {
|
||
|
only_token = token_parts[1];
|
||
|
} else {
|
||
|
return Err(miette::Error::msg("auth token has invalid format"));
|
||
|
}
|
||
|
let response: std::collections::HashMap<String, serde_json::Value> = self
|
||
|
.http
|
||
|
.get(&self.userinfo_endpoint)
|
||
|
.header("Authorization", &format!("Bearer {only_token}"))
|
||
|
.send()
|
||
|
.into_diagnostic()?
|
||
|
.error_for_status()
|
||
|
.into_diagnostic()?
|
||
|
.json()
|
||
|
.into_diagnostic()?;
|
||
|
|
||
|
let id = response
|
||
|
.get(&self.userid_attribute)
|
||
|
.ok_or_else(|| {
|
||
|
miette::Error::msg("configured userid_attribute missing from userinfo response")
|
||
|
})?
|
||
|
.as_str()
|
||
|
.ok_or_else(|| miette::Error::msg("userid attribute value must be a string"))?
|
||
|
.to_owned();
|
||
|
|
||
|
// TODO: check audience == client id - prevents logging in with a token for another service
|
||
|
// TODO: get roles - from userdb or userinfo
|
||
|
Ok(User {
|
||
|
id,
|
||
|
userdata: UserData::new(vec![]),
|
||
|
})
|
||
|
}
|
||
|
pub fn sasl_validate(
|
||
|
&self,
|
||
|
_session: &SessionData,
|
||
|
context: &Context,
|
||
|
validate: &mut Validate,
|
||
|
) {
|
||
|
use rsasl::property as p;
|
||
|
let maybe_token = context.get_ref::<p::OAuthBearerToken>();
|
||
|
let token = match maybe_token {
|
||
|
Some(token) => token,
|
||
|
None => {
|
||
|
return;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
// TODO: dont do it twice (duplicate with callback)
|
||
|
match self.user_by_token(token) {
|
||
|
Ok(user) => {
|
||
|
validate.finalize::<V>(user);
|
||
|
}
|
||
|
Err(e) => {
|
||
|
tracing::warn!("OIDC login failure: {}", e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
pub fn sasl_callback(
|
||
|
&self,
|
||
|
_session: &SessionData,
|
||
|
context: &Context,
|
||
|
request: &mut rsasl::callback::Request,
|
||
|
) -> Result<(), rsasl::prelude::SessionError> {
|
||
|
request.satisfy_with::<rsasl::mechanisms::oauthbearer::properties::OAuthBearerValidate, _>(| | {
|
||
|
let maybe_token = context.get_ref::<rsasl::property::OAuthBearerToken>();
|
||
|
dbg!(&maybe_token);
|
||
|
if let Some(token) = maybe_token {
|
||
|
// TODO: dont do it twice (duplicate with validate)
|
||
|
match self.user_by_token(token) {
|
||
|
Ok(_user) => {
|
||
|
return Ok(Ok(()));
|
||
|
}
|
||
|
Err(e) => {
|
||
|
tracing::warn!("could not get user by token, {e}");
|
||
|
dbg!(e);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// FIXME: construct directly when #TODO is resolve
|
||
|
let e_json = serde_json::json!({"status": "invalid_token", "openid-configuration": self.configuration_endpoint.to_owned()});
|
||
|
let x = serde_json::to_string(&e_json).unwrap();
|
||
|
// FIXME: leaks memory. make static or something.
|
||
|
let x: &'static _ = Box::leak(x.into_boxed_str());
|
||
|
let e : OAuthBearerError = serde_json::from_str(&x).unwrap();
|
||
|
|
||
|
return Ok(Err(e));
|
||
|
|
||
|
})?;
|
||
|
Ok(())
|
||
|
}
|
||
|
}
|