summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/bin/sso/main.rs136
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
+}