Skip to content

Commit

Permalink
Issue-201: Link OIDC client with metadata to user
Browse files Browse the repository at this point in the history
  • Loading branch information
voelkera authored and mtwardawski committed Jul 15, 2024
1 parent 2832049 commit c161e51
Show file tree
Hide file tree
Showing 15 changed files with 106 additions and 28 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ message ListPeerDescriptorsFailureInternal {
//
message GeneratePeerSetupRequest {
opendut.types.peer.PeerId peer = 1;
string userId = 2;
}

message GeneratePeerSetupResponse {
Expand All @@ -190,6 +191,7 @@ message GeneratePeerSetupFailure {
// GenerateCleoSetupRequest
//
message GenerateCleoSetupRequest {
string userId = 2;
}

message GenerateCleoSetupResponse {
Expand Down
8 changes: 5 additions & 3 deletions opendut-carl/opendut-carl-api/src/carl/peer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,10 +230,11 @@ mod client {
}
}

pub async fn create_peer_setup(&mut self, peer_id: PeerId) -> Result<PeerSetup, CreateSetupError> {
pub async fn create_peer_setup(&mut self, peer_id: PeerId, user_id: String) -> Result<PeerSetup, CreateSetupError> {
let request = tonic::Request::new(
peer_manager::GeneratePeerSetupRequest {
peer: Some(peer_id.into())
peer: Some(peer_id.into()),
user_id,
}
);

Expand All @@ -260,9 +261,10 @@ mod client {
}
}

pub async fn create_cleo_setup(&mut self) -> Result<CleoSetup, CreateSetupError> {
pub async fn create_cleo_setup(&mut self, user_id: String) -> Result<CleoSetup, CreateSetupError> {
let request = tonic::Request::new(
peer_manager::GenerateCleoSetupRequest {
user_id
}
);

Expand Down
18 changes: 9 additions & 9 deletions opendut-carl/src/actions/peers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,9 @@ pub enum GeneratePeerSetupError {
}

#[tracing::instrument(skip(params), level="trace")]
pub async fn generate_peer_setup(params: GeneratePeerSetupParams) -> Result<PeerSetup, GeneratePeerSetupError> {
pub async fn generate_peer_setup(params: GeneratePeerSetupParams, user_id: String) -> Result<PeerSetup, GeneratePeerSetupError> {

async fn inner(params: GeneratePeerSetupParams) -> Result<PeerSetup, GeneratePeerSetupError> {
async fn inner(params: GeneratePeerSetupParams, user_id: String) -> Result<PeerSetup, GeneratePeerSetupError> {

let peer_id = params.peer;

Expand Down Expand Up @@ -314,7 +314,7 @@ pub async fn generate_peer_setup(params: GeneratePeerSetupParams) -> Result<Peer
let resource_id = peer_id.into();
debug!("Generating OIDC client for peer '{peer_name}' <{peer_id}>.");
let issuer_url = registration_client.config.issuer_remote_url.clone();
let client_credentials = registration_client.register_new_client(resource_id)
let client_credentials = registration_client.register_new_client_for_user(resource_id, user_id)
.await
.map_err(|cause| GeneratePeerSetupError::Internal { peer_id, peer_name: Clone::clone(&peer_name), cause: cause.to_string() })?;
debug!("Successfully generated peer setup for peer '{peer_name}' <{peer_id}>. OIDC client_id='{}'.", client_credentials.client_id.clone().value());
Expand All @@ -331,7 +331,7 @@ pub async fn generate_peer_setup(params: GeneratePeerSetupParams) -> Result<Peer
})
}

inner(params).await
inner(params, user_id).await
.inspect_err(|err| error!("{err}"))
}

Expand All @@ -351,9 +351,9 @@ pub enum GenerateCleoSetupError {
}

#[tracing::instrument(skip(params), level="trace")]
pub async fn generate_cleo_setup(params: GenerateCleoSetupParams) -> Result<CleoSetup, GenerateCleoSetupError> {
pub async fn generate_cleo_setup(params: GenerateCleoSetupParams, user_id: String) -> Result<CleoSetup, GenerateCleoSetupError> {

async fn inner(params: GenerateCleoSetupParams) -> Result<CleoSetup, GenerateCleoSetupError> {
async fn inner(params: GenerateCleoSetupParams, user_id: String) -> Result<CleoSetup, GenerateCleoSetupError> {

let cleo_id = params.cleo;
debug!("Generating CleoSetup");
Expand All @@ -366,7 +366,7 @@ pub async fn generate_cleo_setup(params: GenerateCleoSetupParams) -> Result<Cleo
let resource_id = cleo_id.into();
debug!("Generating OIDC client for CLEO: <{cleo_id}>.");
let issuer_url = registration_client.config.issuer_remote_url.clone();
let client_credentials = registration_client.register_new_client(resource_id)
let client_credentials = registration_client.register_new_client_for_user(resource_id, user_id)
.await
.map_err(|cause| GenerateCleoSetupError::Internal { cause: cause.to_string() })?;
debug!("Successfully generated cleo setup with id <{cleo_id}>. OIDC client_id='{}'.", client_credentials.client_id.clone().value());
Expand All @@ -382,7 +382,7 @@ pub async fn generate_cleo_setup(params: GenerateCleoSetupParams) -> Result<Cleo
})
}

inner(params).await
inner(params, user_id).await
.inspect_err(|err| error!("{err}"))
}

Expand Down Expand Up @@ -621,7 +621,7 @@ mod test {
oidc_registration_client: None,
};

let cleo_setup = generate_cleo_setup(generate_cleo_setup_params).await?;
let cleo_setup = generate_cleo_setup(generate_cleo_setup_params, String::from("testUser")).await?;
assert_that!(cleo_setup.id, eq(CleoId::try_from("787d0b11-51f3-4cfe-8131-c7d89d53f0e9")?));
assert_that!(cleo_setup.auth_config, eq(AuthConfig::Disabled));
assert_that!(cleo_setup.carl, eq(Url::parse("https://example.com:1234").unwrap()));
Expand Down
4 changes: 2 additions & 2 deletions opendut-carl/src/grpc/peer_manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ impl PeerManagerService for PeerManagerFacade {
ca: Clone::clone(&self.ca),
vpn: Clone::clone(&self.vpn),
oidc_registration_client: self.oidc_registration_client.clone(),
}).await.map_err(|cause| Status::internal(format!("Peer setup could not be created: {}", cause)))?;
}, message.user_id).await.map_err(|cause| Status::internal(format!("Peer setup could not be created: {}", cause)))?;

peer_manager::generate_peer_setup_response::Reply::Success(peer_manager::GeneratePeerSetupSuccess { peer: Some(peer_id.into()), setup: Some(setup.into()) })
}
Expand All @@ -249,7 +249,7 @@ impl PeerManagerService for PeerManagerFacade {
carl_url: Clone::clone(&self.carl_url),
ca: Clone::clone(&self.ca),
oidc_registration_client: self.oidc_registration_client.clone(),
}).await.map_err(|cause| Status::internal(format!("Cleo setup could not be created: {}", cause)))?;
}, request.into_inner().user_id).await.map_err(|cause| Status::internal(format!("Cleo setup could not be created: {}", cause)))?;

let response = generate_cleo_setup_response::Reply::Success(GenerateCleoSetupSuccess {
cleo: Some(cleo_id.into()),
Expand Down
4 changes: 2 additions & 2 deletions opendut-cleo/src/commands/generate_setup_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ pub struct GenerateSetupStringCli {

impl GenerateSetupStringCli {
//TODO: what happens if peer with the ID is already set up?
pub async fn execute(self, carl: &mut CarlClient) -> crate::Result<()> {
pub async fn execute(self, carl: &mut CarlClient, cleo_oidc_client_id: String,) -> crate::Result<()> {
let peer_id = PeerId::from(self.id);
let created_setup = carl
.peers
.create_peer_setup(peer_id)
.create_peer_setup(peer_id, cleo_oidc_client_id)
.await
.map_err(|error| format!("Could not create setup string.\n {}", error))?;

Expand Down
12 changes: 10 additions & 2 deletions opendut-cleo/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,8 @@ async fn execute_command(commands: Commands, settings: &LoadedConfig) -> Result<
}
Commands::GenerateSetupString(implementation) => {
let mut carl = create_carl_client(&settings.config).await;
implementation.execute(&mut carl).await?;
let cleo_oidc_client_id = get_cleo_oidc_client_id(&settings.config).await;
implementation.execute(&mut carl, cleo_oidc_client_id).await?;
}
Commands::DecodeSetupString(implementation) => {
implementation.execute().await?;
Expand Down Expand Up @@ -349,7 +350,7 @@ pub async fn create_carl_client(config: &config::Config) -> CarlClient {

let port = config.get_int("network.carl.port")
.expect("Configuration should contain a valid port number to connect to CARL");

let ca_cert_info = match config.get_string("network.tls.ca.content") {
Ok(content_string) => CaCertInfo::Content(content_string),
Err(_) => {
Expand All @@ -366,3 +367,10 @@ pub async fn create_carl_client(config: &config::Config) -> CarlClient {
CarlClient::create(host, port as u16, &ca_cert_info, &domain_name_override, config).await
.expect("Failed to create CARL client")
}

pub async fn get_cleo_oidc_client_id(config: &config::Config) -> String {
match config.get_string("network.oidc.client.id") {
Ok(id) => { id }
Err(_) => { String::from("cleoCli") }
}
}
11 changes: 9 additions & 2 deletions opendut-lea/src/cleo/setup.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use leptos::{component, create_local_resource, IntoView, MaybeSignal, RwSignal, SignalGet, SignalSet, view};
use leptos::{component, create_local_resource, IntoView, MaybeSignal, ReadSignal, RwSignal, SignalGet, SignalSet, use_context, view, WriteSignal};
use opendut_auth::public::OptionalAuthData;
use crate::app::{ExpectGlobals, use_app_globals};
use crate::components::{BasePageContainer, Breadcrumb, ButtonColor, ButtonStateSignalProvider, Initialized, SimpleButton};

Expand All @@ -11,11 +12,17 @@ pub fn CleoSetup() -> impl IntoView {

let trigger_cleo_setup_generation: RwSignal<bool> = RwSignal::new(false);

let (auth_data_signal, _) = use_context::<(ReadSignal<OptionalAuthData>, WriteSignal<OptionalAuthData>)>().expect("AuthData should be provided in the context.");

let setup_string = create_local_resource(move || trigger_cleo_setup_generation.get(), move |action| {
async move {
let user_id = match auth_data_signal.get().auth_data {
None => { String::from("UNKNOWN USER") }
Some(auth_data) => { auth_data.subject }
};
if action {
let mut carl = globals.expect_client();
let setup = carl.peers.create_cleo_setup().await
let setup = carl.peers.create_cleo_setup(user_id.clone()).await
.expect("Failed to request the setup string.");
let setup_string = setup.encode()
.expect("PeerSetup should be encodable into a setup-string");
Expand Down
1 change: 1 addition & 0 deletions opendut-lea/src/components/auth.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ pub fn LeaAuthenticated(
email: data.claims.email.clone(),
groups: data.claims.groups.clone(),
roles: data.claims.roles.clone(),
subject: data.claims.subject.clone()
}
)
});
Expand Down
11 changes: 9 additions & 2 deletions opendut-lea/src/peers/configurator/tabs/setup/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use leptos::{component, create_local_resource, IntoView, ReadSignal, RwSignal, SignalGet, SignalSet, view};
use leptos::{component, create_local_resource, IntoView, ReadSignal, RwSignal, SignalGet, SignalSet, use_context, view, WriteSignal};
use opendut_auth::public::OptionalAuthData;

use opendut_types::peer::PeerId;

Expand All @@ -14,11 +15,17 @@ pub fn SetupTab(peer_configuration: ReadSignal<UserPeerConfiguration>) -> impl I

let trigger_generation: RwSignal<Option<PeerId>> = RwSignal::new(None);

let (auth_data_signal, _) = use_context::<(ReadSignal<OptionalAuthData>, WriteSignal<OptionalAuthData>)>().expect("AuthData should be provided in the context.");

let setup_string = create_local_resource(move || trigger_generation.get(), move |peer_id| {
async move {
let user_id = match auth_data_signal.get().auth_data {
None => { String::from("UNKNOWN USER") }
Some(auth_data) => { auth_data.subject }
};
if let Some(peer_id) = peer_id {
let mut carl = globals.expect_client();
let setup = carl.peers.create_peer_setup(peer_id).await
let setup = carl.peers.create_peer_setup(peer_id, user_id).await
.expect("Failed to request the setup string.");
let setup_string = setup.encode()
.expect("PeerSetup should be encodable into a setup-string");
Expand Down
2 changes: 2 additions & 0 deletions opendut-util/opendut-auth/opendut-auth-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ openidconnect = { workspace = true, default-features = false, features = ["reqwe
oauth2 = { workspace = true, default-features = false, features = ["reqwest"] }
http = { workspace = true }
tokio = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
50 changes: 48 additions & 2 deletions opendut-util/opendut-auth/opendut-auth-tests/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use oauth2::{ClientId, ClientSecret, RedirectUrl};
use openidconnect::RegistrationUrl;
use pem::Pem;
use rstest::fixture;
use serde::Deserialize;
use url::Url;

use opendut_auth::confidential::client::{ConfidentialClient, ConfidentialClientRef};
Expand Down Expand Up @@ -60,6 +61,15 @@ pub async fn registration_client(#[future] confidential_carl_client: Confidentia
RegistrationClient::new(carl_idp_config, client)
}

#[derive(Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
struct Client {
id: String,
client_id: String,
name: Option<String>,
base_url: Option<String>,
}

#[cfg(test)]
mod auth_tests {
use googletest::assert_that;
Expand All @@ -73,7 +83,7 @@ mod auth_tests {
use opendut_auth::registration::client::{RegistrationClientError, RegistrationClientRef};
use opendut_types::resources::Id;

use crate::{issuer_certificate_authority, registration_client};
use crate::{Client, issuer_certificate_authority, registration_client};

async fn delete_client(client: RegistrationClientRef, delete_client_id: String, issuer_ca: Pem) -> Result<(), RegistrationClientError> {
let client_id = client.inner.config.client_id.clone().to_string();
Expand Down Expand Up @@ -106,6 +116,40 @@ mod auth_tests {
Ok(())
}

async fn list_clients_for_user(client: RegistrationClientRef, user_id: String, issuer_ca: Pem) -> Result<(), RegistrationClientError> {
let client_id = client.inner.config.client_id.clone().to_string();
let access_token = client.inner.get_token().await
.map_err(|error| RegistrationClientError::RequestError { error: format!("Could not fetch token to delete client {}!", client_id), cause: error.into() })?;
let list_client_url = client.inner.config.issuer_url.join("/admin/realms/opendut/clients").unwrap();

let mut headers = HeaderMap::new();
let bearer_header = format!("Bearer {}", access_token);
let access_token_value = HeaderValue::from_str(&bearer_header)
.map_err(|error| RegistrationClientError::InvalidConfiguration { error: error.to_string() })?;
headers.insert(http::header::AUTHORIZATION, access_token_value);

let request = HttpRequest {
method: http::Method::GET,
url: list_client_url,
headers,
body: vec![],
};

let reqwest_client = OidcReqwestClient::from_pem(issuer_ca)
.map_err(|error| RegistrationClientError::InvalidConfiguration { error: format!("Failed to load certificate authority. {}", error) })?;

let response = reqwest_client.async_http_client(request)
.await
.map_err(|error| RegistrationClientError::RequestError { error: "OIDC client list request failed!".to_string(), cause: Box::new(error) })?;

let clients: Vec<Client> = serde_json::from_slice(&response.body).unwrap();
let filtered_clients: Vec<Client> = clients.into_iter().filter(|client| client.base_url.clone().is_some_and(|url| url.contains(&user_id))).collect();
assert_eq!(response.status_code, 200, "Failed to list clients for user'{:?}'", user_id);
assert!(!filtered_clients.is_empty());

Ok(())
}

#[rstest]
#[tokio::test]
#[ignore]
Expand All @@ -118,9 +162,11 @@ mod auth_tests {
let pem = issuer_certificate_authority.await;
println!("{:?}", client);
let resource_id = Id::random();
let credentials = client.register_new_client(resource_id).await.unwrap();
let user_id = String::from("testUser");
let credentials = client.register_new_client_for_user(resource_id, user_id.clone()).await.unwrap();
let (client_id, client_secret) = (credentials.client_id.value(), credentials.client_secret.value());
println!("New client id: {}, secret: {}", client_id, client_secret);
list_clients_for_user(client.clone(), user_id, pem.clone()).await.unwrap();
delete_client(client, client_id.clone(), pem).await.unwrap();
assert_that!(client_id.len().gt(&10), eq(true));
}
Expand Down
1 change: 1 addition & 0 deletions opendut-util/opendut-auth/src/public/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ pub struct AuthData {
pub email: String,
pub groups: Vec<String>,
pub roles: Vec<String>,
pub subject: String,
}
4 changes: 2 additions & 2 deletions opendut-util/opendut-auth/src/registration/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ impl RegistrationClient {
})
}

pub async fn register_new_client(&self, resource_id: Id) -> Result<ClientCredentials, RegistrationClientError> {
pub async fn register_new_client_for_user(&self, resource_id: Id, user_id: String) -> Result<ClientCredentials, RegistrationClientError> {
match self.config.peer_credentials.clone() {
Some(peer_credentials) => {
Ok(peer_credentials)
Expand All @@ -86,7 +86,7 @@ impl RegistrationClient {
let registration_url = self.config.registration_url.clone();

let client_name: ClientName = ClientName::new(resource_id.to_string());
let resource_uri = self.config.client_home_base_url.resource_url(resource_id)
let resource_uri = self.config.client_home_base_url.resource_url(resource_id, user_id)
.map_err(|error| RegistrationClientError::ClientParameter {
message: format!("Failed to create resource url for client: {:?}", error),
cause: Box::new(error),
Expand Down
4 changes: 2 additions & 2 deletions opendut-util/opendut-auth/src/registration/resources.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ impl ResourceHomeUrl {
pub fn value(&self) -> Url {
self.0.clone()
}
pub fn resource_url(&self, resource_id: Id) -> Result<Url, ResourceHomeUrlError> {
let path = format!("/resources/{}", resource_id.value());
pub fn resource_url(&self, resource_id: Id, user_id: String) -> Result<Url, ResourceHomeUrlError> {
let path = format!("/{}/{}", user_id, resource_id.value());
self.0.join(&path)
.map_err(|error| ResourceHomeUrlError(format!("Failed to create resource URL for resource_id='{}': {}", resource_id.value(), error)))
}
Expand Down

0 comments on commit c161e51

Please sign in to comment.