use std::str::FromStr; use kanidm_proto::constants::uri::{ OAUTH2_AUTHORISE, OAUTH2_AUTHORISE_DEVICE, OAUTH2_TOKEN_ENDPOINT, }; use oauth2::basic::BasicClient; use oauth2::devicecode::StandardDeviceAuthorizationResponse; use oauth2::http::StatusCode; use oauth2::{ AuthUrl, ClientId, DeviceAuthorizationUrl, HttpRequest, HttpResponse, Scope, TokenUrl, }; use reqwest::Client; use sketching::tracing_subscriber::layer::SubscriberExt; use sketching::tracing_subscriber::util::SubscriberInitExt; use sketching::tracing_subscriber::{fmt, EnvFilter}; use tracing::level_filters::LevelFilter; use tracing::{debug, error, info}; async fn http_client( request: HttpRequest, ) -> Result> { let client = Client::builder() .danger_accept_invalid_certs(true) // Following redirects opens the client up to SSRF vulnerabilities. .redirect(reqwest::redirect::Policy::none()) .build() .map_err(oauth2::reqwest::Error::Reqwest)?; let method = reqwest::Method::from_str(request.method.as_str()) .map_err(|err| oauth2::reqwest::Error::Other(err.to_string()))?; let mut request_builder = client .request(method, request.url.as_str()) .body(request.body); for (name, value) in &request.headers { request_builder = request_builder.header(name.as_str(), value.as_bytes()); } let response = client .execute(request_builder.build().map_err(|err| { error!("Failed to build request... {:?}", err); oauth2::reqwest::Error::Reqwest(err) })?) .await .map_err(|err| { error!("Failed to query url {} error={:?}", request.url, err); oauth2::reqwest::Error::Reqwest(err) })?; let status_code = StatusCode::from_u16(response.status().as_u16()) .map_err(|err| oauth2::reqwest::Error::Other(err.to_string()))?; let headers = response .headers() .into_iter() .map(|(k, v)| { debug!("header key={:?} value={:?}", k, v); ( oauth2::http::HeaderName::from_str(k.as_str()).expect("Failed to parse header"), oauth2::http::HeaderValue::from_str( v.to_str().expect("Failed to parse header value"), ) .expect("Failed to parse header value"), ) }) .collect(); let body = response.bytes().await.map_err(|err| { error!("Failed to parse body...? {:?}", err); oauth2::reqwest::Error::Reqwest(err) })?; info!("Response body: {:?}", String::from_utf8(body.to_vec())); Ok(HttpResponse { status_code, headers, body: body.to_vec(), }) } #[tokio::main] async fn main() -> anyhow::Result<()> { let fmt_layer = fmt::layer().with_writer(std::io::stderr); let filter_layer = EnvFilter::builder() .with_default_directive(LevelFilter::INFO.into()) .parse_lossy("info,kanidm_client=warn,kanidm_cli=info"); sketching::tracing_subscriber::registry() .with(filter_layer) .with(fmt_layer) .init(); info!("building client..."); // kanidm system oauth2 create-public device_flow device_flow 'https://deviceauth' let client = BasicClient::new( ClientId::new("device_code".to_string()), None, AuthUrl::new(format!("https://localhost:8443{}", OAUTH2_AUTHORISE))?, Some(TokenUrl::new(format!( "https://localhost:8443{}", OAUTH2_TOKEN_ENDPOINT ))?), ) .set_device_authorization_url(DeviceAuthorizationUrl::new(format!( "https://localhost:8443{}", OAUTH2_AUTHORISE_DEVICE ))?); info!("Getting details..."); let details: StandardDeviceAuthorizationResponse = client .exchange_device_code() .inspect_err(|err| error!("configuration error: {:?}", err))? .add_scope(Scope::new("read".to_string())) .request_async(http_client) .await?; println!( "Open this URL in your browser: {}", match details.verification_uri_complete() { Some(uri) => uri.secret().as_str(), None => details.verification_uri().as_str(), } ); println!("the code is {}", details.user_code().secret()); let token_result = client .exchange_device_access_token(&details) .request_async(http_client, tokio::time::sleep, None) .await?; println!("Result: {:?}", token_result); Ok(()) }