Skip to content

Commit

Permalink
feat: implement tokenx
Browse files Browse the repository at this point in the history
Co-authored-by: Kim Tore Jensen <kimtjen@gmail.com>
Co-authored-by: Tommy Trøen <tommy.troen@nav.no>
  • Loading branch information
3 people committed Nov 4, 2024
1 parent db3d8b6 commit d835980
Show file tree
Hide file tree
Showing 4 changed files with 85 additions and 51 deletions.
16 changes: 16 additions & 0 deletions hack/roundtrip-tokenx.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/bin/bash -e
user_token_response=$(curl -s -X POST http://localhost:8080/tokenx/token -d "grant_type=authorization_code&code=yolo&client_id=yolo&client_secret=bolo")
user_token=$(echo ${user_token_response} | jq -r .access_token)

response=$(curl -s -X POST http://localhost:3000/token -H "content-type: application/json" -d '{"target": "my-target", "identity_provider": "tokenx", "user_token": "'${user_token}'"}')
token=$(echo ${response} | jq -r .access_token)

validation=$(curl -s -X POST http://localhost:3000/introspect -H "content-type: application/json" -d "{\"token\": \"${token}\"}")

echo
echo "JWT:"
echo "${response}" | jq -S .

echo
echo "Validation:"
echo "${validation}" | jq -S .
25 changes: 15 additions & 10 deletions src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ pub async fn token(
) -> impl IntoResponse {
match &request.identity_provider {
IdentityProvider::AzureAD => state.azure_ad.read().await.get_token(state.clone(), request).await.into_response(),
IdentityProvider::TokenX => todo!(),
IdentityProvider::TokenX => state.token_x.read().await.get_token(state.clone(), request).await.into_response(),
IdentityProvider::Maskinporten => state.maskinporten.read().await.get_token(state.clone(), request).await.into_response(),
}
}
Expand All @@ -45,21 +45,24 @@ pub async fn introspect(

let identity_provider = token_data.claims.identity_provider(state.cfg);
let claims = match identity_provider {
Some(IdentityProvider::Maskinporten) => {
state
.maskinporten
.write()
.await
.introspect(request.token)
.await
}
Some(IdentityProvider::Maskinporten) => state
.maskinporten
.write()
.await
.introspect(request.token)
.await,
Some(IdentityProvider::AzureAD) => state
.azure_ad
.write()
.await
.introspect(request.token)
.await,
Some(IdentityProvider::TokenX) => panic!("not implemented"),
Some(IdentityProvider::TokenX) => state
.token_x
.write()
.await
.introspect(request.token)
.await,
None => panic!("Unknown issuer: {}", token_data.claims.iss),
};

Expand All @@ -76,6 +79,7 @@ impl Claims {
match &self.iss {
s if s == &cfg.maskinporten_issuer => Some(IdentityProvider::Maskinporten),
s if s == &cfg.azure_ad_issuer => Some(IdentityProvider::AzureAD),
s if s == &cfg.token_x_issuer => Some(IdentityProvider::TokenX),
_ => None,
}
}
Expand All @@ -86,6 +90,7 @@ pub struct HandlerState {
pub cfg: Config,
pub maskinporten: Arc<RwLock<Provider<MaskinportenTokenRequest>>>,
pub azure_ad: Arc<RwLock<Provider<AzureADOnBehalfOfTokenRequest>>>,
pub token_x: Arc<RwLock<Provider<TokenXTokenRequest>>>,
// TODO: other providers
}

Expand Down
69 changes: 29 additions & 40 deletions src/identity_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub struct TokenRequestConfig {

#[derive(Clone)]
pub struct Provider<T: Serialize> {
issuer: String,
issuer: String, // unused for now; maskinporten might require this as `aud` in client_assertion
client_id: String,
pub token_endpoint: String,
private_jwk: jwt::EncodingKey,
Expand Down Expand Up @@ -62,6 +62,16 @@ pub struct MaskinportenTokenRequest {
assertion: String,
}

#[derive(Serialize)]
pub struct TokenXTokenRequest {
grant_type: String, // urn:ietf:params:oauth:grant-type:token-exchange
client_assertion: String,
client_assertion_type: String, // urn:ietf:params:oauth:client-assertion-type:jwt-bearer
subject_token_type: String, // urn:ietf:params:oauth:token-type:jwt
subject_token: String,
audience: String,
}

impl TokenRequestFactory for AzureADClientCredentialsTokenRequest {
fn token_request(config: TokenRequestConfig) -> Option<AzureADClientCredentialsTokenRequest> {
Some(Self {
Expand All @@ -73,7 +83,6 @@ impl TokenRequestFactory for AzureADClientCredentialsTokenRequest {
})
}
}

impl TokenRequestFactory for AzureADOnBehalfOfTokenRequest {
fn token_request(config: TokenRequestConfig) -> Option<Self> {
Some(Self {
Expand All @@ -88,6 +97,7 @@ impl TokenRequestFactory for AzureADOnBehalfOfTokenRequest {
})
}
}

impl TokenRequestFactory for MaskinportenTokenRequest {
fn token_request(config: TokenRequestConfig) -> Option<Self> {
Some(Self {
Expand All @@ -97,40 +107,19 @@ impl TokenRequestFactory for MaskinportenTokenRequest {
}
}

// impl Provider<AzureADOnBehalfOfTokenRequest> {
// pub fn on_behalf_of_request(
// &self,
// target: String,
// user_token: String,
// ) -> AzureADOnBehalfOfTokenRequest {
// let client_assertion = AssertionClaims::new(
// self.issuer.clone(),
// self.client_id.clone(),
// None,
// Some(self.client_id.clone()),
// )
// .serialize(&self.client_assertion_header, &self.private_jwk)
// .unwrap();
//
// AzureADOnBehalfOfTokenRequest {}
// }
// }

#[derive(Serialize)]
pub struct TokenXTokenRequest {}

// impl Provider<MaskinportenTokenRequest> {
// fn token_request(&self, target: String) -> MaskinportenTokenRequest {
// let token = AssertionClaims::new(
// self.cfg.maskinporten_issuer.clone(),
// self.cfg.maskinporten_client_id.clone(),
// Some(target),
// None,
// )
// .serialize(&self.client_assertion_header, &self.private_jwk)
// .unwrap();
// }
// }
impl TokenRequestFactory for TokenXTokenRequest {
fn token_request(config: TokenRequestConfig) -> Option<Self> {
Some(Self {
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange".to_string(),
client_assertion: config.assertion,
client_assertion_type: "urn:ietf:params:oauth:client-assertion-type:jwt-bearer"
.to_string(),
subject_token_type: "urn:ietf:params:oauth:token-type:jwt".to_string(),
subject_token: config.user_token?,
audience: config.target,
})
}
}

//impl<T> Provider<T> where T: TokenRequestFactory<T> + Serialize
impl<T> Provider<T>
Expand Down Expand Up @@ -178,7 +167,7 @@ where

fn create_assertion(&self, ass: AssertionClaimType) -> Result<String, jwt::errors::Error> {
AssertionClaims::new(
self.issuer.clone(),
self.token_endpoint.clone(),
self.client_id.clone(),
ass,
).serialize(&self.client_assertion_header, &self.private_jwk)
Expand All @@ -191,7 +180,7 @@ where
) -> Result<impl IntoResponse, ApiError> {
let assertion = match request.identity_provider {
IdentityProvider::AzureAD => self.create_assertion(AssertionClaimType::WithSub(self.client_id.clone())).unwrap(),
IdentityProvider::TokenX => self.create_assertion(AssertionClaimType::WithScope(self.client_id.clone())).unwrap(),
IdentityProvider::TokenX => self.create_assertion(AssertionClaimType::WithSub(self.client_id.clone())).unwrap(),
IdentityProvider::Maskinporten => self.create_assertion(AssertionClaimType::WithScope(request.target.clone())).unwrap()
};

Expand Down Expand Up @@ -248,7 +237,7 @@ enum AssertionClaimType {
}

impl AssertionClaims {
fn new(issuer: String, client_id: String, ass: AssertionClaimType) -> Self {
fn new(token_endpoint: String, client_id: String, ass: AssertionClaimType) -> Self {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
Expand All @@ -268,7 +257,7 @@ impl AssertionClaims {
scope,
sub,
iss: client_id, // issuer of the token is the client itself
aud: issuer, // audience of the token is the issuer
aud: token_endpoint, // audience of the token is the issuer
}
}

Expand Down
26 changes: 25 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use log::{info, LevelFilter};
use std::sync::Arc;
use tokio::sync::RwLock;
use identity_provider::Provider;
use crate::identity_provider::{AzureADOnBehalfOfTokenRequest, MaskinportenTokenRequest};
use crate::identity_provider::{AzureADOnBehalfOfTokenRequest, MaskinportenTokenRequest, TokenXTokenRequest};

pub mod config {
use clap::Parser;
Expand All @@ -22,6 +22,7 @@ pub mod config {
pub struct Config {
#[arg(short, long, env, default_value = "127.0.0.1:3000")]
pub bind_address: String,

#[arg(env)]
pub maskinporten_client_id: String,
#[arg(env)]
Expand All @@ -32,6 +33,7 @@ pub mod config {
pub maskinporten_issuer: String,
#[arg(env)]
pub maskinporten_token_endpoint: String,

#[arg(env = "AZURE_APP_CLIENT_ID")]
pub azure_ad_client_id: String,
#[arg(env = "AZURE_APP_CLIENT_JWK")]
Expand All @@ -42,6 +44,17 @@ pub mod config {
pub azure_ad_issuer: String,
#[arg(env = "AZURE_OPENID_CONFIG_TOKEN_ENDPOINT")]
pub azure_ad_token_endpoint: String,

#[arg(env)]
pub token_x_client_id: String,
#[arg(env)]
pub token_x_client_jwk: String,
#[arg(env)]
pub token_x_jwks_uri: String,
#[arg(env)]
pub token_x_issuer: String,
#[arg(env)]
pub token_x_token_endpoint: String,
}
}

Expand Down Expand Up @@ -94,10 +107,21 @@ async fn main() {
.unwrap(),
).unwrap();

let token_x: Provider<TokenXTokenRequest> = Provider::new(
cfg.token_x_issuer.clone(),
cfg.token_x_client_id.clone(),
cfg.token_x_token_endpoint.clone(),
cfg.token_x_client_jwk.clone(),
jwks::Jwks::new(&cfg.token_x_issuer, &cfg.token_x_jwks_uri)
.await
.unwrap(),
).unwrap();

let state = handlers::HandlerState {
cfg: cfg.clone(),
maskinporten: Arc::new(RwLock::new(maskinporten)),
azure_ad: Arc::new(RwLock::new(azure_ad)),
token_x: Arc::new(RwLock::new(token_x)),
};

let app = Router::new()
Expand Down

0 comments on commit d835980

Please sign in to comment.