From 4795541719081c9e858f604db83c4905614aa216 Mon Sep 17 00:00:00 2001 From: Merlijn <32853531+ToxicMushroom@users.noreply.github.com> Date: Sat, 6 Jul 2024 04:25:55 +0200 Subject: [PATCH] Offer configuration of images for Oauth2 resources (#2665) --- Cargo.lock | 1 + Cargo.toml | 1 + proto/src/internal/mod.rs | 4 +- server/lib/src/idm/applinks.rs | 8 ++-- server/web_ui/user/src/views/apps.rs | 2 +- tools/cli/Cargo.toml | 1 + tools/cli/src/cli/oauth2.rs | 65 +++++++++++++++++++++++++++- tools/cli/src/opt/kanidm.rs | 14 +++++- 8 files changed, 87 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4828eb6ff..1cb166b7a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3316,6 +3316,7 @@ dependencies = [ name = "kanidm_tools" version = "1.3.0-dev" dependencies = [ + "anyhow", "async-recursion", "clap", "clap_complete", diff --git a/Cargo.toml b/Cargo.toml index c495cec94..912a7d015 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -140,6 +140,7 @@ kanidm_unix_common = { path = "./unix_integration/common", version = "=1.3.0-dev kanidm_utils_users = { path = "./libs/users", version = "=1.3.0-dev" } sketching = { path = "./libs/sketching", version = "=1.3.0-dev" } +anyhow = { version = "1.0.86" } argon2 = { version = "0.5.3", features = ["alloc"] } askama = { version = "0.12.1", features = ["serde"] } async-recursion = "1.1.0" diff --git a/proto/src/internal/mod.rs b/proto/src/internal/mod.rs index 90afd9b4e..72ad29db8 100644 --- a/proto/src/internal/mod.rs +++ b/proto/src/internal/mod.rs @@ -35,8 +35,8 @@ pub enum AppLink { name: String, display_name: String, redirect_url: Url, - // Where the icon can be retrieved from. - icon: Option, + // Whether this oauth2 resource has an image. + has_image: bool, }, } diff --git a/server/lib/src/idm/applinks.rs b/server/lib/src/idm/applinks.rs index 0eb9d4bdc..5944e6667 100644 --- a/server/lib/src/idm/applinks.rs +++ b/server/lib/src/idm/applinks.rs @@ -51,11 +51,13 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .get_ava_single_iname(Attribute::Name) .map(str::to_string)?; + let has_image = entry.get_ava_single_image(Attribute::Image).is_some(); + Some(AppLink::Oauth2 { name, display_name, redirect_url, - icon: None, + has_image, }) }) .collect::>(); @@ -181,14 +183,14 @@ mod tests { name, display_name, redirect_url, - icon, + has_image, } => { name == "test_resource_server" && display_name == "test_resource_server" && redirect_url == &Url::parse("https://demo.example.com/landing") .expect("Failed to parse URL") - && icon.is_none() + && !has_image } // _ => false, }) } diff --git a/server/web_ui/user/src/views/apps.rs b/server/web_ui/user/src/views/apps.rs index 014a7c54c..f7626146a 100644 --- a/server/web_ui/user/src/views/apps.rs +++ b/server/web_ui/user/src/views/apps.rs @@ -121,7 +121,7 @@ impl AppsApp { name, display_name, redirect_url, - icon: _, + has_image: _, } => { let redirect_url = redirect_url.to_string(); html!{ diff --git a/tools/cli/Cargo.toml b/tools/cli/Cargo.toml index eacdd83f4..2fa125ebd 100644 --- a/tools/cli/Cargo.toml +++ b/tools/cli/Cargo.toml @@ -35,6 +35,7 @@ test = true doctest = false [dependencies] +anyhow = { workspace = true } async-recursion = { workspace = true } clap = { workspace = true, features = ["derive", "env"] } compact_jwt = { workspace = true, features = ["openssl"] } diff --git a/tools/cli/src/cli/oauth2.rs b/tools/cli/src/cli/oauth2.rs index 37cb33b63..3130a8d69 100644 --- a/tools/cli/src/cli/oauth2.rs +++ b/tools/cli/src/cli/oauth2.rs @@ -1,10 +1,11 @@ +use anyhow::{Context, Error}; +use std::fs::read; use std::process::exit; - use crate::common::OpType; use crate::{handle_client_error, Oauth2Opt, OutputMode}; use crate::Oauth2ClaimMapJoin; -use kanidm_proto::internal::Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin; +use kanidm_proto::internal::{ImageValue, Oauth2ClaimMapJoin as ProtoOauth2ClaimMapJoin}; impl Oauth2Opt { pub fn debug(&self) -> bool { @@ -22,6 +23,8 @@ impl Oauth2Opt { Oauth2Opt::SetDisplayname(cbopt) => cbopt.nopt.copt.debug, Oauth2Opt::SetName { nopt, .. } => nopt.copt.debug, Oauth2Opt::SetLandingUrl { nopt, .. } => nopt.copt.debug, + Oauth2Opt::SetImage { nopt, .. } => nopt.copt.debug, + Oauth2Opt::RemoveImage(nopt) => nopt.copt.debug, Oauth2Opt::EnablePkce(nopt) => nopt.copt.debug, Oauth2Opt::DisablePkce(nopt) => nopt.copt.debug, Oauth2Opt::EnableLegacyCrypto(nopt) => nopt.copt.debug, @@ -248,6 +251,64 @@ impl Oauth2Opt { Err(e) => handle_client_error(e, nopt.copt.output_mode), } } + Oauth2Opt::SetImage { + nopt, + path, + image_type, + } => { + let client = nopt.copt.to_client(OpType::Write).await; + let img_res: Result = (move || { + let file_name = path + .file_name() + .context("Please pass a file")? + .to_str() + .context("Path contains non utf-8")? + .to_string(); + + let image_type = if let Some(image_type) = image_type { + image_type.as_str().try_into().map_err(Error::msg)? + } else { + path + .extension().context("Path has no extension so we can't infer the imageType, or you could pass the optional imageType argument yourself.")? + .to_str().context("Path contains invalid utf-8")? + .try_into() + .map_err(Error::msg)? + }; + + let read_res = read(path); + match read_res { + Ok(data) => Ok(ImageValue::new(file_name, image_type, data)), + Err(err) => Err(err).context("Reading error"), + } + })(); + + let img = match img_res { + Ok(img) => img, + Err(err) => { + eprintln!("{err}"); + return; + } + }; + + match client + .idm_oauth2_rs_update_image(nopt.name.as_str(), img) + .await + { + Ok(_) => println!("Success"), + Err(e) => handle_client_error(e, nopt.copt.output_mode), + } + } + Oauth2Opt::RemoveImage(nopt) => { + let client = nopt.copt.to_client(OpType::Write).await; + + match client + .idm_oauth2_rs_delete_image(nopt.name.as_str()) + .await + { + Ok(_) => println!("Success"), + Err(e) => handle_client_error(e, nopt.copt.output_mode), + } + } Oauth2Opt::EnablePkce(nopt) => { let client = nopt.copt.to_client(OpType::Write).await; match client.idm_oauth2_rs_enable_pkce(nopt.name.as_str()).await { diff --git a/tools/cli/src/opt/kanidm.rs b/tools/cli/src/opt/kanidm.rs index dddbeeecd..d159d3ff3 100644 --- a/tools/cli/src/opt/kanidm.rs +++ b/tools/cli/src/opt/kanidm.rs @@ -1048,6 +1048,19 @@ pub enum Oauth2Opt { #[clap(name = "landing-url")] url: Url, }, + /// The image presented on the Kanidm Apps Listing page for an oauth2 resource server. + #[clap(name="set-image")] + SetImage { + #[clap(flatten)] + nopt: Named, + #[clap(name = "file-path")] + path: PathBuf, + #[clap(name = "image-type")] + image_type: Option, + }, + /// Removes the custom image previously set. + #[clap(name="remove-image")] + RemoveImage(Named), /// Add a supplemental origin as a redirection target. For example a phone app /// may use a redirect URL such as `app://my-cool-app` to trigger a native @@ -1070,7 +1083,6 @@ pub enum Oauth2Opt { #[clap(flatten)] copt: CommonOpt, }, - #[clap(name = "enable-pkce")] /// Enable PKCE on this oauth2 client. This defaults to being enabled. EnablePkce(Named),