Files
oidc_client_flow_login/src/main.rs
2022-01-11 01:06:36 +08:00

180 lines
7.8 KiB
Rust

use std::collections::HashMap;
use std::env;
use std::process::exit;
use std::time::Duration;
use clap::{App, Arg};
use reqwest::{ClientBuilder, Proxy};
use rust_util::{debugging, failure, information, opt_result, opt_value_result, success, XResult};
use rust_util::util_file::read_file_content;
use rust_util::util_msg::{clear_lastline, MessageType, print_lastline};
use tokio::time::sleep;
use crate::oidc::{DeviceAuthorizationResponse, ErrorResponse, OpenIdClientConfiguration, OpenIdConfiguration, TokenResponse};
mod oidc;
const NAME: &str = env!("CARGO_PKG_NAME");
const VERSION: &str = env!("CARGO_PKG_VERSION");
const AUTHORS: &str = env!("CARGO_PKG_AUTHORS");
const DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
#[tokio::main]
async fn main() -> XResult<()> {
let matches = App::new(NAME)
.version(VERSION)
.about(DESCRIPTION)
.author(AUTHORS)
.arg(Arg::new("version").short('V').long("version").help("Print version"))
.arg(Arg::new("verbose").short('v').long("verbose").help("Verbose"))
.arg(Arg::new("proxy").short('p').long("proxy").takes_value(true).help("Proxy"))
.arg(Arg::new("hide-qr").long("hide-qr").help("Hide QR"))
.arg(Arg::new("skip-certificate-check").short('k').long("skip-certificate-check").help("Skip certificate check"))
.arg(Arg::new("config").short('c').long("config").takes_value(true).help("Config file"))
.get_matches();
if matches.is_present("verbose") {
env::set_var("LOGGER_LEVEL", "*");
}
if matches.is_present("version") {
information!("{} v{}", NAME, VERSION);
exit(1);
}
let config = opt_value_result!(matches.value_of("config"), "Config file is required");
let config_content = read_file_content(config)?;
let openid_client_configuration: OpenIdClientConfiguration =
opt_result!( serde_json::from_str(&config_content), "Parse config failed: {}");
let issuer = &openid_client_configuration.issuer;
let openid_configuration_url = format!("{}/.well-known/openid-configuration", issuer);
debugging!("Open id configuration url: {}", openid_configuration_url);
let mut client_builder = ClientBuilder::new()
.timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(3));
if let Some(proxy) = matches.value_of("proxy") {
let proxy = opt_result!( Proxy::all(proxy), "Parse proxy: {} failed: {}", proxy);
client_builder = client_builder.proxy(proxy);
}
if matches.is_present("skip-certificate-check") {
client_builder = client_builder.danger_accept_invalid_certs(true);
}
let client = opt_result!( client_builder.build(), "Build reqwest client failed: {}");
debugging!("Sending request...");
let get_openid_configuration_result = opt_result!(
client.get(&openid_configuration_url).send().await, "Get url: {}, failed: {}", openid_configuration_url);
let openid_configuration = opt_result!(
get_openid_configuration_result.json::<OpenIdConfiguration>().await, "Parse open id configuration failed: {}");
rust_util::util_msg::when(MessageType::DEBUG, || {
debugging!("Return open id configuration: {}", serde_json::to_string_pretty(&openid_configuration).unwrap());
});
let device_authorization_endpoint = opt_value_result!(
openid_configuration.device_authorization_endpoint, "Open id configuration has not configured device authorization endpoint");
let token_endpoint = openid_configuration.token_endpoint;
let userinfo_endpoint = openid_configuration.userinfo_endpoint;
information!("device_authorization_endpoint: {}", &device_authorization_endpoint);
information!("token_endpoint: {}", &token_endpoint);
information!("userinfo_endpoint: {}", &userinfo_endpoint);
// xh POST https://oauth2.googleapis.com/device/code
// client_id==364023986785-o5mqsqd1ej2d1vmvcqai108b7m7v7vc9.apps.googleusercontent.com scope=openid
//{
// "device_code": "***",
// "user_code": "STLB-RNKS",
// "expires_in": 1800,
// "interval": 5,
// "verification_url": "https://www.google.com/device"
// }
debugging!("Get {}", device_authorization_endpoint);
let mut params = HashMap::new();
params.insert("client_id", &openid_client_configuration.client_id);
params.insert("scope", &openid_client_configuration.scope);
let device_authorization_result = opt_result!(client.post(&device_authorization_endpoint)
.form(&params).send().await, "Get device authorization: {}, failed: {}", device_authorization_endpoint);
let device_authorization = opt_result!(
device_authorization_result.json::<DeviceAuthorizationResponse>().await, "Parse device authorization failed: {}");
let hide_qr = matches.is_present("hide-qr");
let input = &device_authorization.get_complete_url();
if !hide_qr {
let qr_url = format!("{}{}", "https://hatter.ink/qr/qr_terminal.action?qr=", urlencoding::encode(input));
let qr_text = ClientBuilder::new().timeout(Duration::from_secs(10))
.connect_timeout(Duration::from_secs(6))
.danger_accept_invalid_certs(true)
.build()?
.get(qr_url)
.send().await?
.text().await?;
print!("{}", qr_text);
}
success!("User code url: {}", input);
success!("Get device authorization success: {}", serde_json::to_string_pretty(&device_authorization).unwrap());
// xh POST https://oauth2.googleapis.com/token
// client_id==364023986785-o5mqsqd1ej2d1vmvcqai108b7m7v7vc9.apps.googleusercontent.com
// client_secret==***
// device_code==***
// grant_type==urn:ietf:params:oauth:grant-type:device_code
// {
// "access_token": "***",
// "expires_in": 3599,
// "refresh_token": "***",
// "scope": "openid",
// "token_type": "Bearer",
// "id_token": "***"
// }
let grant_type_device_code = "urn:ietf:params:oauth:grant-type:device_code".to_string();
debugging!("Get {}", token_endpoint);
for i in 0..1000 {
print_lastline(&format!("@{}", i));
sleep(Duration::from_secs(device_authorization.interval as u64)).await;
let mut params = HashMap::new();
params.insert("grant_type", &grant_type_device_code);
params.insert("client_id", &openid_client_configuration.client_id);
params.insert("client_secret", &openid_client_configuration.client_secret);
params.insert("device_code", &device_authorization.device_code);
let token_response_result = opt_result!(
client.post(&token_endpoint).form(&params).send().await, "Post url: {}, failed: {}", token_endpoint);
let token_response = opt_result!(token_response_result.text().await, "Get token failed: {}");
debugging!("Get token response: {}", token_response);
let try_parse_error: serde_json::error::Result<ErrorResponse> = serde_json::from_str(&token_response);
if let Ok(error) = try_parse_error {
let err = &error.error;
if err == "authorization_pending" {
// JUST FINE
} else if err == "slow_down" {
sleep(Duration::from_secs(5)).await;
} else {
clear_lastline();
failure!("Unknown error: {}, description: {}",
err, error.error_description.unwrap_or_else(|| "<none>".to_string()));
break;
}
continue;
}
let try_parse_token: serde_json::error::Result<TokenResponse> = serde_json::from_str(&token_response);
if let Ok(token) = try_parse_token {
clear_lastline();
success!("Get token: {}", serde_json::to_string_pretty(&token).unwrap());
break;
}
clear_lastline();
failure!("Get token failed, raw output: {}", token_response);
break;
}
Ok(())
}