diff options
Diffstat (limited to 'src/bin/sso/main.rs')
-rw-r--r-- | src/bin/sso/main.rs | 136 |
1 files changed, 83 insertions, 53 deletions
diff --git a/src/bin/sso/main.rs b/src/bin/sso/main.rs index b3ca612..0043a1d 100644 --- a/src/bin/sso/main.rs +++ b/src/bin/sso/main.rs @@ -10,20 +10,23 @@ //! login - default: get or renew an access token //! curl - pass the -use std::collections::{BTreeMap, HashSet}; -use std::{env, fs}; -use std::error::Error; -use std::path::{Path, PathBuf}; -use std::process::Command; use chrono::{DateTime, Duration, Utc}; use clap::{Parser, Subcommand}; -use serde::{Serialize, Deserialize}; -use oauth2::{AuthType, AuthUrl, ClientId, DeviceAuthorizationUrl, RefreshToken, Scope, TokenResponse, TokenUrl}; +use gethostname::gethostname; use oauth2::basic::BasicClient; use oauth2::devicecode::StandardDeviceAuthorizationResponse; use oauth2::reqwest::http_client; +use oauth2::{ + AuthType, AuthUrl, ClientId, DeviceAuthorizationUrl, RefreshToken, Scope, TokenResponse, + TokenUrl, +}; +use serde::{Deserialize, Serialize}; +use std::collections::{BTreeMap, HashSet}; +use std::error::Error; +use std::path::{Path, PathBuf}; +use std::process::Command; +use std::{env, fs}; use url::Url; -use gethostname::gethostname; #[derive(Parser)] #[clap(author, version, about, long_about = None)] @@ -52,7 +55,9 @@ struct Args { enum Commands { /// does testing things Login, - Curl { args: Vec<String> } + Curl { + args: Vec<String>, + }, } #[derive(Serialize, Deserialize, Clone)] @@ -80,8 +85,9 @@ impl Profile { /// Check if the access token should be valid. pub fn valid_access_token(&self) -> bool { - self.access_token.is_some() && - self.access_token_expiration + self.access_token.is_some() + && self + .access_token_expiration .map(|expiration| Utc::now() < expiration) .unwrap_or(true) } @@ -91,17 +97,18 @@ impl Profile { self.refresh_token.is_some() } - pub fn authorize(&mut self, ) -> Result<(), Box<dyn Error>> { - let client = BasicClient::new( - client_id(), - None, - self.auth_url(), - Some(self.token_url()), - ) - .set_auth_type(AuthType::RequestBody) - .set_device_authorization_url(self.device_url()); - - let scope = Scope::new(self.scopes.iter().map(|s| s.to_string()).collect::<Vec<String>>().join(" ")); + pub fn authorize(&mut self) -> Result<(), Box<dyn Error>> { + let client = BasicClient::new(client_id(), None, self.auth_url(), Some(self.token_url())) + .set_auth_type(AuthType::RequestBody) + .set_device_authorization_url(self.device_url()); + + let scope = Scope::new( + self.scopes + .iter() + .map(|s| s.to_string()) + .collect::<Vec<String>>() + .join(" "), + ); let details: StandardDeviceAuthorizationResponse = client .exchange_device_code()? @@ -114,34 +121,39 @@ impl Profile { details.user_code().secret().to_string() ); - let token_result = - client - .exchange_device_access_token(&details) - .request(http_client, std::thread::sleep, None)?; + let token_result = client.exchange_device_access_token(&details).request( + http_client, + std::thread::sleep, + None, + )?; self.access_token = Some(token_result.access_token().secret().to_string()); - self.access_token_expiration = token_result.expires_in().map(|d| Utc::now() + Duration::seconds(d.as_secs() as i64)); + self.access_token_expiration = token_result + .expires_in() + .map(|d| Utc::now() + Duration::seconds(d.as_secs() as i64)); self.refresh_token = token_result.refresh_token().map(|t| t.secret().to_string()); self.was_modified = true; Ok(()) } pub fn refresh(&mut self) -> Result<(), Box<dyn Error>> { - let client = - BasicClient::new( - client_id(), - None, - self.auth_url(), - Some(self.token_url()), - ) - .set_auth_type(AuthType::RequestBody); - - let refresh_token = RefreshToken::new(self.refresh_token.as_deref().map(|s| s.to_string()).expect("Missing refresh token")); - let token_result = client.exchange_refresh_token(&refresh_token) + let client = BasicClient::new(client_id(), None, self.auth_url(), Some(self.token_url())) + .set_auth_type(AuthType::RequestBody); + + let refresh_token = RefreshToken::new( + self.refresh_token + .as_deref() + .map(|s| s.to_string()) + .expect("Missing refresh token"), + ); + let token_result = client + .exchange_refresh_token(&refresh_token) .request(http_client)?; self.access_token = Some(token_result.access_token().secret().to_string()); - self.access_token_expiration = token_result.expires_in().map(|d| Utc::now() + Duration::seconds(d.as_secs() as i64)); + self.access_token_expiration = token_result + .expires_in() + .map(|d| Utc::now() + Duration::seconds(d.as_secs() as i64)); self.refresh_token = token_result.refresh_token().map(|t| t.secret().to_string()); self.was_modified = true; Ok(()) @@ -160,13 +172,11 @@ impl Profile { } fn auth_url(&self) -> AuthUrl { - AuthUrl::new(format!("{}/oauth/authorize", &self.endpoint)) - .expect("Bad endpoint url.") + AuthUrl::new(format!("{}/oauth/authorize", &self.endpoint)).expect("Bad endpoint url.") } fn token_url(&self) -> TokenUrl { - TokenUrl::new(format!("{}/oauth/token", &self.endpoint)) - .expect("Bad endpoint url.") + TokenUrl::new(format!("{}/oauth/token", &self.endpoint)).expect("Bad endpoint url.") } fn device_url(&self) -> DeviceAuthorizationUrl { @@ -197,13 +207,19 @@ fn load_profile(config_dir: &Path, profile_name: &str) -> Result<Profile, Box<dy if filename.exists() { let file = fs::File::open(filename)?; let mut profiles: BTreeMap<String, Profile> = serde_json::from_reader(file)?; - Ok(profiles.remove(profile_name).unwrap_or_else(Profile::default)) + Ok(profiles + .remove(profile_name) + .unwrap_or_else(Profile::default)) } else { Ok(Profile::default()) } } -fn save_profile(config_dir: &Path, profile_name: &str, profile: &Profile) -> Result<(), Box<dyn Error>> { +fn save_profile( + config_dir: &Path, + profile_name: &str, + profile: &Profile, +) -> Result<(), Box<dyn Error>> { let filename = config_dir.join("profiles.json"); let mut profiles: BTreeMap<String, Profile> = if filename.exists() { let file = fs::File::open(&filename)?; @@ -215,15 +231,24 @@ fn save_profile(config_dir: &Path, profile_name: &str, profile: &Profile) -> Res profiles.insert(profile_name.to_string(), profile.clone()); let file = fs::File::create(&filename)?; - serde_json::to_writer(file, &profiles) - .map_err(|e| e.into()) + serde_json::to_writer(file, &profiles).map_err(|e| e.into()) } fn do_curl(profile: &Profile, mut args: Vec<String>) -> Result<(), Box<dyn Error>> { args.push("-H".to_string()); - args.push(format!("Authorization: Bearer {}", profile.access_token.as_deref().expect("Must have valid access token"))); - Command::new("curl").args(args).spawn()?.wait().map(|_| ()).map_err(|e| e.into()) - + args.push(format!( + "Authorization: Bearer {}", + profile + .access_token + .as_deref() + .expect("Must have valid access token") + )); + Command::new("curl") + .args(args) + .spawn()? + .wait() + .map(|_| ()) + .map_err(|e| e.into()) } fn main() -> Result<(), Box<dyn Error>> { @@ -232,7 +257,10 @@ fn main() -> Result<(), Box<dyn Error>> { let command = args.command.unwrap_or(Commands::Login); // Find the config files. - let home: PathBuf = env::var("HOME").expect("No $HOME?").parse().expect("Bad $HOME?"); + let home: PathBuf = env::var("HOME") + .expect("No $HOME?") + .parse() + .expect("Bad $HOME?"); let config_dir = home.join(".config/jesterpm-sso"); if !config_dir.exists() { fs::create_dir(config_dir.as_path())?; @@ -270,7 +298,9 @@ fn main() -> Result<(), Box<dyn Error>> { } match command { - Commands::Login {} => { Ok(()) /* No-op, we already took care of it above */ }, + Commands::Login {} => { + Ok(()) /* No-op, we already took care of it above */ + } Commands::Curl { args } => do_curl(&profile, args), } -}
\ No newline at end of file +} |