Skip to content

Commit

Permalink
LEA -> Move user claims to opendut-auth
Browse files Browse the repository at this point in the history
  • Loading branch information
reimarstier committed Jan 28, 2025
1 parent a39349e commit 6601418
Show file tree
Hide file tree
Showing 10 changed files with 112 additions and 109 deletions.
20 changes: 2 additions & 18 deletions opendut-carl/src/auth/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ pub(crate) mod json_web_key;
pub(crate) mod grpc_auth_layer;

use openidconnect::core::CoreGenderClaim;
use openidconnect::{AdditionalClaims, IdTokenClaims};
use serde::{Deserialize, Serialize};
use openidconnect::IdTokenClaims;
use opendut_auth::types::MyAdditionalClaims;

pub type Claims<AC> = IdTokenClaims<AC, CoreGenderClaim>;

Expand All @@ -14,19 +14,3 @@ pub struct CurrentUser {
pub name: String,
pub claims: Claims<MyAdditionalClaims>,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MyAdditionalClaims {
/// Roles the user belongs to (custom claim)
#[serde(default = "MyAdditionalClaims::empty_vector")]
pub roles: Vec<String>,
/// Groups of the user (custom claim) may be omitted by identity provider, so we need a default value
#[serde(default = "MyAdditionalClaims::empty_vector")]
pub groups: Vec<String>,
}

impl MyAdditionalClaims {
fn empty_vector() -> Vec<String> { Vec::new() }
}

impl AdditionalClaims for MyAdditionalClaims {}
2 changes: 1 addition & 1 deletion opendut-lea/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use crate::user::{provide_authentication_signals_in_context, AuthenticationConfi

#[derive(Clone, Debug)]
pub struct AppGlobals {
#[allow(dead_code)]
#[allow(unused)] // TODO: use carl url as base in use_navigate/navigate_to
pub config: AppConfig,
pub client: CarlClient,
pub auth: Authentication,
Expand Down
13 changes: 4 additions & 9 deletions opendut-lea/src/components/generate_setup_string.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
use leptos::{either::Either, prelude::*};
use leptos_oidc::{Algorithm, AuthSignal};
use opendut_types::peer::PeerId;

use crate::{app::use_app_globals, components::{use_toaster, ButtonColor, ButtonState, SimpleButton, Toast, WarningMessage}, user::UNAUTHENTICATED_USER};
use crate::user::Claims;
use crate::{app::use_app_globals, components::{use_toaster, ButtonColor, ButtonState, SimpleButton, Toast, WarningMessage}};
use crate::user::UserAuthenticationSignal;

#[component]
pub fn GenerateSetupStringForm(kind: GenerateSetupStringKind) -> impl IntoView {
Expand All @@ -16,12 +15,8 @@ pub fn GenerateSetupStringForm(kind: GenerateSetupStringKind) -> impl IntoView {
let mut carl = globals.client.clone();

async move {
let auth = use_context::<AuthSignal>().expect("AuthSignal should be provided in the context for LoggedInUser.");
let auth_data = auth.get();
// TODO: avoid redundant decoding of claims
let user_id = auth_data.authenticated()
.and_then(|user| user.decoded_access_token::<Claims>(Algorithm::RS256, &["account"]).map(|token| token.claims.preferred_username))
.unwrap_or_else(|| UNAUTHENTICATED_USER.to_string());
let user = use_context::<UserAuthenticationSignal>().expect("UserAuthenticationSignal should be provided in the context.");
let user_id = user.get().username();

let trigger = trigger_setup_generation.get();
if trigger {
Expand Down
23 changes: 5 additions & 18 deletions opendut-lea/src/nav.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
use leptos::prelude::*;
use leptos::html::Div;
use leptos::prelude::*;
use leptos_oidc::components::{LoginLink, LogoutLink};
use leptos_use::on_click_outside;

use crate::components::{ButtonColor, ButtonSize, ButtonState, FontAwesomeIcon, IconButton, Initialized, LeaAuthenticated, AppGlobalsResource};
use crate::components::{AppGlobalsResource, ButtonColor, ButtonSize, ButtonState, FontAwesomeIcon, IconButton, Initialized, LeaAuthenticated};
use crate::user::UserAuthenticationSignal;
use crate::{routing, use_context};
use crate::user::{UserAuthentication, UNAUTHENTICATED_USER};

#[component(transparent)]
pub fn Navbar(app_globals: AppGlobalsResource) -> impl IntoView {
Expand Down Expand Up @@ -148,21 +148,8 @@ pub fn Navbar(app_globals: AppGlobalsResource) -> impl IntoView {
#[component]
pub fn LoggedInUser() -> impl IntoView {

let user = use_context::<RwSignal<UserAuthentication>>().expect("RwSignal<UserAuthentication> should be provided in the context.");
// TODO: consolidate methods for this signal
let user_name = move || {
match user.get() {
UserAuthentication::Loading => { "loading".to_string() }
UserAuthentication::Disabled=> { "disabled".to_string() }
UserAuthentication::Unauthenticated=> { "unauthenticated".to_string() }
UserAuthentication::Authenticated(user) => {
match user {
None => { UNAUTHENTICATED_USER.to_string() }
Some(user) => { user.claims.preferred_username }
}
}
}
};
let user = use_context::<UserAuthenticationSignal>().expect("UserAuthenticationSignal should be provided in the context.");
let user_name = move || { user.get().username() };

view! {
<span class="ml-1 is-size-6">"Logged in as: " { user_name }</span>
Expand Down
66 changes: 29 additions & 37 deletions opendut-lea/src/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use leptos::context::provide_context;
use leptos::prelude::{Effect, Get, RwSignal, Set};
use leptos_oidc::{Algorithm, Auth, AuthSignal, TokenData};
use leptos_router::hooks::use_navigate;
use serde::{Deserialize, Serialize};
use opendut_auth::types::Claims;
pub use overview::UserOverview;
use crate::routing::{navigate_to, WellKnownRoutes};

Expand All @@ -19,6 +19,8 @@ pub enum AuthenticationConfigSwitch {
Enabled,
}

pub type UserAuthenticationSignal = RwSignal<UserAuthentication>;

#[derive(Debug, Clone, Default)]
pub enum UserAuthentication {
#[default]
Expand All @@ -28,6 +30,32 @@ pub enum UserAuthentication {
Authenticated(Option<Box<TokenData<Claims>>>),
}

impl UserAuthentication {
#[allow(unused)] // TODO: use this shorthand for authorization
pub fn user(&self) -> Option<Box<TokenData<Claims>>> {
match self {
UserAuthentication::Authenticated(token) => {
token.clone()
}
_ => { None }
}
}
pub fn username(&self) -> String {
let name = match self {
UserAuthentication::Loading => { "loading" }
UserAuthentication::Disabled=> { "disabled" }
UserAuthentication::Unauthenticated=> { "unauthenticated" }
UserAuthentication::Authenticated(user) => {
match user {
None => { UNAUTHENTICATED_USER }
Some(user) => { &user.claims.preferred_username }
}
}
};
name.to_string()
}
}


pub(crate) fn provide_authentication_signals_in_context() -> AuthSignal {
let auth = Auth::signal();
Expand Down Expand Up @@ -90,39 +118,3 @@ pub(crate) fn provide_authentication_signals_in_context() -> AuthSignal {

auth
}

// TODO: move to opendut-types
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct Claims {
/// Audience
#[serde(rename = "aud")]
audience: String,
/// Issued at (as UTC timestamp)
#[serde(rename = "iat")]
issued_at: usize,
/// Issuer
#[serde(rename = "iss")]
issuer: String,
/// Expiration time (as UTC timestamp)
#[serde(rename = "exp")]
expiration_utc: usize,
/// Subject (whom token refers to)
#[serde(rename = "sub")]
subject: String,
// Roles the user belongs to (custom claim if present)
#[serde(default = "Claims::empty_vector")]
roles: Vec<String>,
// Groups of the user (custom claim if present)
#[serde(default = "Claims::empty_vector")]
groups: Vec<String>,
// Name of the user
name: String,
// Email address of the user
email: String,
// Username of the user
pub preferred_username: String,
}

impl Claims {
pub(crate) fn empty_vector() -> Vec<String> { Vec::new() }
}
9 changes: 5 additions & 4 deletions opendut-lea/src/user/overview.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::ops::Not;
use leptos::either::EitherOf3;
use leptos::prelude::*;
use opendut_auth::types::Claims;
use crate::components::{BasePageContainer, Breadcrumb};
use crate::user::{Claims, UserAuthentication, UNAUTHENTICATED_USER};
use crate::user::{UserAuthentication, UserAuthenticationSignal, UNAUTHENTICATED_USER};

const DEFAULT_KEYCLOAK_ROLES: [&str; 4] = [
"offline_access",
Expand All @@ -13,7 +14,7 @@ const DEFAULT_KEYCLOAK_ROLES: [&str; 4] = [

#[component]
pub fn UserOverview() -> impl IntoView {
let user = use_context::<RwSignal<UserAuthentication>>().expect("RwSignal<UserAuthentication> should be provided in the context.");
let user = use_context::<UserAuthenticationSignal>().expect("UserAuthenticationSignal should be provided in the context.");

{ move || {
match user.get() {
Expand Down Expand Up @@ -58,10 +59,10 @@ fn PresentUserTableView(
let user_name = claims.preferred_username;
let name = claims.name;
let email = claims.email;
let roles = claims.roles.into_iter().filter(| role | {
let roles = claims.additional_claims.roles.into_iter().filter(| role | {
DEFAULT_KEYCLOAK_ROLES.contains(&role.as_str()).not()
}).collect::<Vec<_>>().join(", ");
let groups = claims.groups.into_iter().map(| group | {
let groups = claims.additional_claims.groups.into_iter().map(| group | {
group.replace('/', "")
}).collect::<Vec<_>>().join(", ");

Expand Down
12 changes: 6 additions & 6 deletions opendut-types/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
use uuid::{Uuid, uuid};

pub mod cleo;
pub mod cluster;
pub mod lea;
pub mod peer;
pub mod proto;
pub mod topology;
pub mod vpn;
pub mod util;
pub mod resources;
pub mod cleo;

#[cfg(feature = "specs")]
pub mod specs;
pub mod lea;
pub mod topology;
pub mod util;
pub mod vpn;


pub trait ShortName {
fn short_name(&self) -> &'static str;
Expand Down
2 changes: 2 additions & 0 deletions opendut-util/opendut-auth/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
pub mod types;

use cfg_if::cfg_if;

cfg_if! {
Expand Down
16 changes: 0 additions & 16 deletions opendut-util/opendut-auth/src/public/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,3 @@ impl Interceptor for AuthInterceptor {
}
}
}

#[derive(Clone, Debug)]
pub struct OptionalAuthData {
pub auth_data: Option<AuthData>,
}

// TODO: remove
#[derive(Clone, Debug)]
pub struct AuthData {
pub preferred_username: String,
pub name: String,
pub email: String,
pub groups: Vec<String>,
pub roles: Vec<String>,
pub subject: String, // User identity in keycloak
}
58 changes: 58 additions & 0 deletions opendut-util/opendut-auth/src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
use std::fmt::Debug;
use cfg_if::cfg_if;
use serde::{Deserialize, Serialize};

#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Audience {
SingleAudience(String),
MultipleAudiences(Vec<String>),
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
/// Audience
#[serde(rename = "aud")]
pub audience: Audience,
/// Issued at (as UTC timestamp)
#[serde(rename = "iat")]
pub issued_at: usize,
/// Issuer
#[serde(rename = "iss")]
pub issuer: String,
/// Expiration time (as UTC timestamp)
#[serde(rename = "exp")]
pub expiration_utc: usize,
/// Subject (whom token refers to)
#[serde(rename = "sub")]
pub subject: String,
// Name of the user
pub name: String,
// Email address of the user
pub email: String,
// Username of the user
pub preferred_username: String,
#[serde(flatten)]
pub additional_claims: MyAdditionalClaims
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct MyAdditionalClaims {
/// Roles the user belongs to (custom claim)
#[serde(default = "MyAdditionalClaims::empty_vector")]
pub roles: Vec<String>,
/// Groups of the user (custom claim) may be omitted by identity provider, so we need a default value
#[serde(default = "MyAdditionalClaims::empty_vector")]
pub groups: Vec<String>,
}

impl MyAdditionalClaims {
fn empty_vector() -> Vec<String> { Vec::new() }
}

cfg_if! {
if #[cfg(feature = "registration_client")] {
use openidconnect::AdditionalClaims;
impl AdditionalClaims for MyAdditionalClaims {}
}
}

0 comments on commit 6601418

Please sign in to comment.