use itertools::Itertools;
use oauth1_request::signature_method::HmacSha1;
use reqwest::{self, Url};
use reqwest_oauth1::{OAuthClientProvider, Secrets, SecretsProvider, TokenReaderFuture};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
const REQUEST_TOKEN_URL_SUFFIX: &str = "/services/oauth/request_token";
const AUTHORIZE_TOKEN_URL_SUFFIX: &str = "/services/oauth/authorize";
const ACCESS_TOKEN_URL_SUFFIX: &str = "/services/oauth/access_token";
type ReqwestOauth1Client<'a> =
reqwest_oauth1::Client<reqwest_oauth1::Signer<'a, reqwest_oauth1::Secrets<'a>, HmacSha1>>;
#[derive(Debug)]
pub struct UsosRs<'a> {
authorize_url: Url,
request_token_url: Url,
access_token_url: Url,
callback_url: Option<Url>,
secrets: Secrets<'a>,
reqwest_client: ReqwestOauth1Client<'a>,
api_url: Url,
}
impl<'a> UsosRs<'a> {
pub fn new(
api_url: Url,
consumer_key: String,
consumer_secret: String,
) -> Result<UsosRs<'a>, url::ParseError> {
let request_token_url = api_url.join(REQUEST_TOKEN_URL_SUFFIX)?;
let authorize_url = api_url.join(AUTHORIZE_TOKEN_URL_SUFFIX)?;
let access_token_url = api_url.join(ACCESS_TOKEN_URL_SUFFIX)?;
let secrets = reqwest_oauth1::Secrets::new(consumer_key, consumer_secret);
let client = reqwest::Client::new().oauth1(secrets.clone());
Ok(UsosRs {
access_token_url,
request_token_url,
authorize_url,
reqwest_client: client,
secrets,
callback_url: None,
api_url,
})
}
pub async fn get_unauthorized_user(
&'a self,
scopes: Option<&HashSet<Scope>>,
) -> Result<UnauthorizedUser<'a>, reqwest_oauth1::Error> {
let callback_url = self.callback_url.as_ref().map(|x| x.as_str());
let mut query = vec![("oauth_callback", callback_url.unwrap_or("oob"))];
let scopes_text: String;
if let Some(scopes) = scopes {
scopes_text =
Itertools::intersperse(scopes.iter().map(|x| x.api_name()), "|").collect();
query.push(("scopes", &scopes_text))
}
let resp = self
.reqwest_client
.post(self.request_token_url.clone())
.query(&query)
.send()
.parse_oauth_token()
.await?;
let mut authorize_url = self.authorize_url.clone();
authorize_url
.query_pairs_mut()
.append_pair("oauth_token", &resp.oauth_token);
let secrets = self
.secrets
.clone()
.token(resp.oauth_token, resp.oauth_token_secret);
Ok(UnauthorizedUser {
usos_rs: self,
authorize_url,
reqwest_client: reqwest::Client::new().oauth1(secrets),
})
}
pub async fn get_authorized_user(&'a self, access_token: AccessToken) -> User<'a> {
let secrets = self
.secrets
.clone()
.token(access_token.token, access_token.token_secret);
User {
secrets: secrets.clone(),
reqwest_client: reqwest::Client::new().oauth1(secrets),
usos_rs: self,
}
}
pub fn callback_url_mut(&'a mut self) -> &'a mut Option<Url> {
&mut self.callback_url
}
pub fn api_url(&'a self) -> &'a Url {
&self.api_url
}
pub fn reqwest_client(&'a self) -> &'a ReqwestOauth1Client {
&self.reqwest_client
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)]
pub enum Scope {
AdministrationDocuments,
Cards,
ChangeAllPreferences,
CourseTests,
DormitoryAdmin,
EditUserAttributes,
Email,
Events,
Grades,
WriteGrades,
MailClient,
PhoneNumbers,
OfflineAccess,
OtherEmails,
Payments,
PersonalData,
Photo,
PlacementTests,
SessionDebuggingPermissions,
Slips,
SlipsAdmin,
StaffPerspective,
StudentExams,
StudentExamsWrite,
Studies,
SurveysFilling,
SurveysReports,
ThesesProtocolsWrite,
}
impl Scope {
const fn api_name(&self) -> &'static str {
match self {
Scope::AdministrationDocuments => "adm_documents",
Scope::Cards => "cards",
Scope::ChangeAllPreferences => "change_all_preferences",
Scope::CourseTests => "crstests",
Scope::DormitoryAdmin => "dorm_admin",
Scope::EditUserAttributes => "edit_user_attrs",
Scope::Email => "email",
Scope::Events => "events",
Scope::Grades => "grades",
Scope::WriteGrades => "grades_write",
Scope::MailClient => "mailclient",
Scope::PhoneNumbers => "mobile_numbers",
Scope::OfflineAccess => "offline_access",
Scope::OtherEmails => "other_emails",
Scope::Payments => "payments",
Scope::PersonalData => "personal",
Scope::Photo => "photo",
Scope::PlacementTests => "placement_tests",
Scope::SessionDebuggingPermissions => "session_debugging_perm",
Scope::Slips => "slips",
Scope::SlipsAdmin => "slips_admin",
Scope::StaffPerspective => "staff_perspective",
Scope::StudentExams => "student_exams",
Scope::StudentExamsWrite => "student_exams_write",
Scope::Studies => "studies",
Scope::SurveysFilling => "surveys_filling",
Scope::SurveysReports => "surveys_reports",
Scope::ThesesProtocolsWrite => "theses_protocols_write",
}
}
}
#[derive(Debug)]
pub struct UnauthorizedUser<'a> {
usos_rs: &'a UsosRs<'a>,
authorize_url: Url,
reqwest_client: ReqwestOauth1Client<'a>,
}
impl<'a> UnauthorizedUser<'a> {
pub async fn authorize(self, pin: &str) -> Result<User<'a>, (Self, reqwest_oauth1::Error)> {
let resp = self
.reqwest_client
.get(self.usos_rs.access_token_url.clone())
.query(&[("oauth_verifier", pin)])
.send()
.parse_oauth_token()
.await;
let resp = match resp {
Ok(v) => v,
Err(e) => return Err((self, e)),
};
let secrets = self
.usos_rs
.secrets
.clone()
.token(resp.oauth_token, resp.oauth_token_secret);
Ok(User {
secrets: secrets.clone(),
reqwest_client: reqwest::Client::new().oauth1(secrets),
usos_rs: self.usos_rs,
})
}
pub fn authorize_url(&'a self) -> &'a Url {
&self.authorize_url
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct AccessToken {
pub token: String,
pub token_secret: String,
}
#[derive(Debug)]
pub struct User<'a> {
usos_rs: &'a UsosRs<'a>,
secrets: Secrets<'a>,
reqwest_client: ReqwestOauth1Client<'a>,
}
impl<'a> User<'a> {
pub fn access_token(&'a self) -> AccessToken {
let token_pair = self
.secrets
.get_token_pair_option()
.expect("Should have token pair");
AccessToken {
token: token_pair.0.to_owned(),
token_secret: token_pair.1.to_owned(),
}
}
pub fn reqwest_client(&'a self) -> &'a ReqwestOauth1Client {
&self.reqwest_client
}
pub fn api_url(&'a self) -> &'a Url {
&self.usos_rs.api_url
}
}