use anyhow::Context;
use manta_shared::common::jwt_ops;
use crate::http_client::{MantaClient, OpenApiResultExt};
#[derive(Debug, Clone)]
pub struct SessionContext {
pub username: String,
pub name: String,
pub is_admin: bool,
pub accessible_groups: Vec<String>,
}
impl SessionContext {
fn from_parts(
token: &str,
accessible_groups: Vec<String>,
) -> anyhow::Result<Self> {
let username = jwt_ops::get_preferred_username(token)
.context("decode preferred_username from token")?;
let name = jwt_ops::get_name(token).context("decode name from token")?;
let roles = jwt_ops::get_roles(token)
.context("decode realm_access.roles from token")?;
let is_admin = roles.iter().any(|r| r == jwt_ops::PA_ADMIN);
Ok(Self {
username,
name,
is_admin,
accessible_groups,
})
}
pub async fn build(
client: &MantaClient,
token: &str,
) -> anyhow::Result<Self> {
let accessible_groups = client
.openapi
.get_available_groups(client.site_name())
.await
.into_anyhow()
.context("fetch accessible groups")?;
Self::from_parts(token, accessible_groups)
}
}
#[cfg(test)]
mod tests {
use super::*;
use base64::Engine as _;
fn jwt(name: &str, username: &str, roles: &[&str]) -> String {
let header = base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(br#"{"alg":"none"}"#);
let roles_json = serde_json::to_string(roles).unwrap();
let payload_json = format!(
r#"{{"name":"{name}","preferred_username":"{username}","realm_access":{{"roles":{roles_json}}}}}"#
);
let payload = base64::engine::general_purpose::URL_SAFE_NO_PAD
.encode(payload_json.as_bytes());
format!("{header}.{payload}.sig")
}
#[test]
fn from_parts_populates_all_fields_from_admin_token() {
let token = jwt("Alice Smith", "alice", &["pa_admin", "compute"]);
let session = SessionContext::from_parts(
&token,
vec!["alps".to_string(), "compute".to_string()],
)
.expect("build session");
assert_eq!(session.username, "alice");
assert_eq!(session.name, "Alice Smith");
assert!(session.is_admin);
assert_eq!(
session.accessible_groups,
vec!["alps".to_string(), "compute".to_string()]
);
}
#[test]
fn from_parts_marks_non_admin_for_plain_user() {
let token = jwt("Carol", "carol", &["compute"]);
let session = SessionContext::from_parts(&token, vec![]).unwrap();
assert!(!session.is_admin);
}
#[test]
fn from_parts_propagates_jwt_decode_failure() {
let result = SessionContext::from_parts("not.a.jwt", vec![]);
assert!(result.is_err(), "expected Err on malformed JWT");
}
}