OIDC: Extract username from JWT token
This commit is contained in:
@@ -12,6 +12,7 @@ use crate::{
|
||||
oauth::GrantType,
|
||||
},
|
||||
};
|
||||
use base64::{Engine, engine::general_purpose};
|
||||
use directory::{
|
||||
Credentials, Directory,
|
||||
core::secret::{SecretVerificationResult, verify_mfa_secret_hash, verify_secret_hash},
|
||||
@@ -297,14 +298,20 @@ impl Server {
|
||||
return Ok(AccessToken::new_admin());
|
||||
}
|
||||
|
||||
// Obtain external directory, if any
|
||||
// Obtain external directory, if any. When no username is supplied
|
||||
// (e.g. HTTP bearer auth), peek at the JWT claims to find the
|
||||
// user's domain so per-domain OIDC directories are reachable.
|
||||
let directory = if let Some(username) = username.as_deref().map(UsernameParts::new)
|
||||
{
|
||||
if let Some(domain_name) = username.auth_as().domain() {
|
||||
self.get_directory_for_domain(domain_name).await?
|
||||
} else if let Some(domain_name) = extract_jwt_domain(token) {
|
||||
self.get_directory_for_domain(&domain_name).await?
|
||||
} else {
|
||||
self.get_default_directory()
|
||||
}
|
||||
} else if let Some(domain_name) = extract_jwt_domain(token) {
|
||||
self.get_directory_for_domain(&domain_name).await?
|
||||
} else {
|
||||
self.get_default_directory()
|
||||
};
|
||||
@@ -496,6 +503,27 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_jwt_domain(token: &str) -> Option<String> {
|
||||
let mut parts = token.split('.');
|
||||
let _header = parts.next()?;
|
||||
let payload = parts.next()?;
|
||||
let _signature = parts.next()?;
|
||||
if parts.next().is_some() {
|
||||
return None;
|
||||
}
|
||||
let payload_bytes = general_purpose::URL_SAFE_NO_PAD.decode(payload).ok()?;
|
||||
let claims: serde_json::Value = serde_json::from_slice(&payload_bytes).ok()?;
|
||||
for claim in ["email", "preferred_username", "upn"] {
|
||||
if let Some(val) = claims.get(claim).and_then(|v| v.as_str())
|
||||
&& let Some((_, domain)) = val.rsplit_once('@')
|
||||
&& !domain.is_empty()
|
||||
{
|
||||
return Some(domain.to_ascii_lowercase());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
impl UsernameParts {
|
||||
pub fn new(address: &str) -> Self {
|
||||
let mut account = Username {
|
||||
|
||||
@@ -16,6 +16,12 @@ use store::{
|
||||
use trc::AddContext;
|
||||
use utils::codec::leb128::{Leb128Iterator, Leb128Vec};
|
||||
|
||||
pub const FAILED_TO_DECODE_TOKEN: &str = concat!(
|
||||
"Failed to decode token. If you are using an ",
|
||||
"external OIDC provider, make sure it is configured as the default directory under ",
|
||||
"the Authentication object."
|
||||
);
|
||||
|
||||
pub struct TokenInfo {
|
||||
pub grant_type: GrantType,
|
||||
pub account_id: u32,
|
||||
@@ -115,7 +121,7 @@ impl Server {
|
||||
.map_err(|_| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Reason, "Failed to decode token")
|
||||
.ctx(trc::Key::Reason, FAILED_TO_DECODE_TOKEN)
|
||||
.caused_by(trc::location!())
|
||||
.details(token_.to_string())
|
||||
})?;
|
||||
@@ -135,7 +141,7 @@ impl Server {
|
||||
.ok_or_else(|| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Reason, "Failed to decode token")
|
||||
.ctx(trc::Key::Reason, FAILED_TO_DECODE_TOKEN)
|
||||
.caused_by(trc::location!())
|
||||
.details(token_.to_string())
|
||||
})?;
|
||||
@@ -200,7 +206,7 @@ impl Server {
|
||||
.map_err(|err| {
|
||||
trc::AuthEvent::Error
|
||||
.into_err()
|
||||
.ctx(trc::Key::Details, "Failed to decode token")
|
||||
.ctx(trc::Key::Details, FAILED_TO_DECODE_TOKEN)
|
||||
.caused_by(trc::location!())
|
||||
.reason(err)
|
||||
})?;
|
||||
|
||||
Reference in New Issue
Block a user