use crate::{ClientError, KanidmClient}; use kanidm_proto::constants::{ ATTR_DISPLAYNAME, ATTR_ES256_PRIVATE_KEY_DER, ATTR_NAME, ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE, ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT, ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE, ATTR_OAUTH2_PREFER_SHORT_USERNAME, ATTR_OAUTH2_RS_BASIC_SECRET, ATTR_OAUTH2_RS_ORIGIN, ATTR_OAUTH2_RS_ORIGIN_LANDING, ATTR_OAUTH2_RS_TOKEN_KEY, ATTR_RS256_PRIVATE_KEY_DER, }; use kanidm_proto::internal::{ImageValue, Oauth2ClaimMapJoin}; use kanidm_proto::v1::Entry; use reqwest::multipart; use std::collections::BTreeMap; use url::Url; impl KanidmClient { // ==== Oauth2 resource server configuration #[instrument(level = "debug")] pub async fn idm_oauth2_rs_list(&self) -> Result, ClientError> { self.perform_get_request("/v1/oauth2").await } pub async fn idm_oauth2_rs_basic_create( &self, name: &str, displayname: &str, origin: &str, ) -> Result<(), ClientError> { let mut new_oauth2_rs = Entry::default(); new_oauth2_rs .attrs .insert(ATTR_NAME.to_string(), vec![name.to_string()]); new_oauth2_rs .attrs .insert(ATTR_DISPLAYNAME.to_string(), vec![displayname.to_string()]); new_oauth2_rs.attrs.insert( ATTR_OAUTH2_RS_ORIGIN_LANDING.to_string(), vec![origin.to_string()], ); self.perform_post_request("/v1/oauth2/_basic", new_oauth2_rs) .await } pub async fn idm_oauth2_rs_public_create( &self, name: &str, displayname: &str, origin: &str, ) -> Result<(), ClientError> { let mut new_oauth2_rs = Entry::default(); new_oauth2_rs .attrs .insert(ATTR_NAME.to_string(), vec![name.to_string()]); new_oauth2_rs .attrs .insert(ATTR_DISPLAYNAME.to_string(), vec![displayname.to_string()]); new_oauth2_rs.attrs.insert( ATTR_OAUTH2_RS_ORIGIN_LANDING.to_string(), vec![origin.to_string()], ); self.perform_post_request("/v1/oauth2/_public", new_oauth2_rs) .await } // TODO: the "id" here is actually the *name* not the uuid of the entry... pub async fn idm_oauth2_rs_get(&self, id: &str) -> Result, ClientError> { self.perform_get_request(format!("/v1/oauth2/{}", id).as_str()) .await } pub async fn idm_oauth2_rs_get_basic_secret( &self, id: &str, ) -> Result, ClientError> { self.perform_get_request(format!("/v1/oauth2/{}/_basic_secret", id).as_str()) .await } #[allow(clippy::too_many_arguments)] pub async fn idm_oauth2_rs_update( &self, id: &str, name: Option<&str>, displayname: Option<&str>, landing: Option<&str>, reset_secret: bool, reset_token_key: bool, reset_sign_key: bool, ) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; if let Some(newname) = name { update_oauth2_rs .attrs .insert(ATTR_NAME.to_string(), vec![newname.to_string()]); } if let Some(newdisplayname) = displayname { update_oauth2_rs.attrs.insert( ATTR_DISPLAYNAME.to_string(), vec![newdisplayname.to_string()], ); } if let Some(newlanding) = landing { update_oauth2_rs.attrs.insert( ATTR_OAUTH2_RS_ORIGIN_LANDING.to_string(), vec![newlanding.to_string()], ); } if reset_secret { update_oauth2_rs .attrs .insert(ATTR_OAUTH2_RS_BASIC_SECRET.to_string(), Vec::new()); } if reset_token_key { update_oauth2_rs .attrs .insert(ATTR_OAUTH2_RS_TOKEN_KEY.to_string(), Vec::new()); } if reset_sign_key { update_oauth2_rs .attrs .insert(ATTR_ES256_PRIVATE_KEY_DER.to_string(), Vec::new()); update_oauth2_rs .attrs .insert(ATTR_RS256_PRIVATE_KEY_DER.to_string(), Vec::new()); } self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_update_scope_map( &self, id: &str, group: &str, scopes: Vec<&str>, ) -> Result<(), ClientError> { let scopes: Vec = scopes.into_iter().map(str::to_string).collect(); self.perform_post_request( format!("/v1/oauth2/{}/_scopemap/{}", id, group).as_str(), scopes, ) .await } pub async fn idm_oauth2_rs_delete_scope_map( &self, id: &str, group: &str, ) -> Result<(), ClientError> { self.perform_delete_request(format!("/v1/oauth2/{}/_scopemap/{}", id, group).as_str()) .await } pub async fn idm_oauth2_rs_update_sup_scope_map( &self, id: &str, group: &str, scopes: Vec<&str>, ) -> Result<(), ClientError> { let scopes: Vec = scopes.into_iter().map(str::to_string).collect(); self.perform_post_request( format!("/v1/oauth2/{}/_sup_scopemap/{}", id, group).as_str(), scopes, ) .await } pub async fn idm_oauth2_rs_delete_sup_scope_map( &self, id: &str, group: &str, ) -> Result<(), ClientError> { self.perform_delete_request(format!("/v1/oauth2/{}/_sup_scopemap/{}", id, group).as_str()) .await } pub async fn idm_oauth2_rs_delete(&self, id: &str) -> Result<(), ClientError> { self.perform_delete_request(["/v1/oauth2/", id].concat().as_str()) .await } /// Want to delete the image associated with a resource server? Here's your thing! pub async fn idm_oauth2_rs_delete_image(&self, id: &str) -> Result<(), ClientError> { self.perform_delete_request(format!("/v1/oauth2/{}/_image", id).as_str()) .await } /// Want to add/update the image associated with a resource server? Here's your thing! pub async fn idm_oauth2_rs_update_image( &self, id: &str, image: ImageValue, ) -> Result<(), ClientError> { let file_content_type = image.filetype.as_content_type_str(); let file_data = match multipart::Part::bytes(image.contents.clone()) .file_name(image.filename) .mime_str(file_content_type) { Ok(part) => part, Err(err) => { error!( "Failed to generate multipart body from image data: {:}", err ); return Err(ClientError::SystemError); } }; let form = multipart::Form::new().part("image", file_data); // send it let response = self .client .post(self.make_url(&format!("/v1/oauth2/{}/_image", id))) .multipart(form); let response = { let tguard = self.bearer_token.read().await; if let Some(token) = &(*tguard) { response.bearer_auth(token) } else { response } }; let response = response .send() .await .map_err(|err| self.handle_response_error(err))?; self.expect_version(&response).await; let opid = self.get_kopid_from_response(&response); match response.status() { reqwest::StatusCode::OK => {} unexpect => { return Err(ClientError::Http( unexpect, response.json().await.ok(), opid, )) } } response .json() .await .map_err(|e| ClientError::JsonDecode(e, opid)) } pub async fn idm_oauth2_rs_enable_pkce(&self, id: &str) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE.to_string(), Vec::new(), ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_disable_pkce(&self, id: &str) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_ALLOW_INSECURE_CLIENT_DISABLE_PKCE.to_string(), vec!["true".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_enable_legacy_crypto(&self, id: &str) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.to_string(), vec!["true".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_disable_legacy_crypto(&self, id: &str) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_JWT_LEGACY_CRYPTO_ENABLE.to_string(), vec!["false".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_prefer_short_username(&self, id: &str) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_PREFER_SHORT_USERNAME.to_string(), vec!["true".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_prefer_spn_username(&self, id: &str) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_PREFER_SHORT_USERNAME.to_string(), vec!["false".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_enable_public_localhost_redirect( &self, id: &str, ) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT.to_string(), vec!["true".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_disable_public_localhost_redirect( &self, id: &str, ) -> Result<(), ClientError> { let mut update_oauth2_rs = Entry { attrs: BTreeMap::new(), }; update_oauth2_rs.attrs.insert( ATTR_OAUTH2_ALLOW_LOCALHOST_REDIRECT.to_string(), vec!["false".to_string()], ); self.perform_patch_request(format!("/v1/oauth2/{}", id).as_str(), update_oauth2_rs) .await } pub async fn idm_oauth2_rs_update_claim_map( &self, id: &str, claim_name: &str, group_id: &str, values: &[String], ) -> Result<(), ClientError> { let values: Vec = values.to_vec(); self.perform_post_request( format!("/v1/oauth2/{}/_claimmap/{}/{}", id, claim_name, group_id).as_str(), values, ) .await } pub async fn idm_oauth2_rs_update_claim_map_join( &self, id: &str, claim_name: &str, join: Oauth2ClaimMapJoin, ) -> Result<(), ClientError> { self.perform_post_request( format!("/v1/oauth2/{}/_claimmap/{}", id, claim_name).as_str(), join, ) .await } pub async fn idm_oauth2_rs_delete_claim_map( &self, id: &str, claim_name: &str, group_id: &str, ) -> Result<(), ClientError> { self.perform_delete_request( format!("/v1/oauth2/{}/_claimmap/{}/{}", id, claim_name, group_id).as_str(), ) .await } pub async fn idm_oauth2_client_add_origin( &self, id: &str, origin: &Url, ) -> Result<(), ClientError> { let url_to_add = &[origin.as_str()]; self.perform_post_request( format!("/v1/oauth2/{}/_attr/{}", id, ATTR_OAUTH2_RS_ORIGIN).as_str(), url_to_add, ) .await } pub async fn idm_oauth2_client_remove_origin( &self, id: &str, origin: &Url, ) -> Result<(), ClientError> { let url_to_remove = &[origin.as_str()]; self.perform_delete_request_with_body( format!("/v1/oauth2/{}/_attr/{}", id, ATTR_OAUTH2_RS_ORIGIN).as_str(), url_to_remove, ) .await } }