use std::{borrow::Cow, collections::HashMap};
use crate::{SecretsProvider, SignResult, SignerError};
use crate::{
OAUTH_CALLBACK_KEY, OAUTH_CONSUMER_KEY, OAUTH_KEY_PREFIX, OAUTH_NONCE_KEY,
OAUTH_SIGNATURE_METHOD_KEY, OAUTH_TIMESTAMP_KEY, OAUTH_TOKEN_KEY, OAUTH_VERIFIER_KEY,
OAUTH_VERSION_KEY, REALM_KEY,
};
use http::Method;
use oauth1_request::signature_method::SignatureMethod;
use oauth1_request::signer::Signer as OAuthSigner;
use oauth1_request::{HmacSha1, Options};
use url::Url;
#[derive(Debug, Clone)]
pub struct Signer<'a, TSecrets, TSM>
where
TSecrets: SecretsProvider + Clone,
TSM: SignatureMethod + Clone,
{
secrets: TSecrets,
parameters: Result<OAuthParameters<'a, TSM>, SignerError>,
}
impl<'a, TSecretsProvider, TSM> Signer<'a, TSecretsProvider, TSM>
where
TSecretsProvider: SecretsProvider + Clone,
TSM: SignatureMethod + Clone,
{
pub fn new(secrets: TSecretsProvider, parameters: OAuthParameters<'a, TSM>) -> Self {
Signer {
secrets,
parameters: Ok(parameters),
}
}
pub fn override_oauth_parameter(mut self, parameters: HashMap<String, String>) -> Self {
for (key, value) in parameters {
self.parameters = match self.parameters {
Ok(p) => match key.as_str() {
OAUTH_CALLBACK_KEY => Ok(p.callback(value)),
OAUTH_NONCE_KEY => Ok(p.nonce(value)),
OAUTH_VERIFIER_KEY => Ok(p.verifier(value)),
REALM_KEY => Ok(p.realm(value)),
OAUTH_TIMESTAMP_KEY => match value.parse::<u64>() {
Ok(v) => Ok(p.timestamp(v)),
Err(_) => Err(SignerError::InvalidTimestamp(value)),
},
OAUTH_VERSION_KEY => match value.as_str() {
"1.0" => Ok(p.version(true)),
"" => Ok(p.version(false)),
_ => Err(SignerError::InvalidVersion(value)),
},
OAUTH_SIGNATURE_METHOD_KEY | OAUTH_CONSUMER_KEY | OAUTH_TOKEN_KEY => {
Err(SignerError::UnconfigurableParameter(key))
}
_ => Err(SignerError::UnknownParameter(key)),
},
Err(e) => Err(e),
};
}
self
}
pub(crate) fn generate_signature(
self,
method: Method,
url: Url,
payload: &str,
is_url_query: bool,
) -> SignResult<String> {
let (consumer_key, consumer_secret) = self.secrets.get_consumer_key_pair();
let (token, token_secret) = self.secrets.get_token_option_pair();
let params = self.parameters?;
let options = params.build_options(token);
let parsed_payload: Vec<(Cow<str>, Cow<str>)> =
url::form_urlencoded::parse(payload.as_bytes())
.into_iter()
.collect();
let oauth_identifier = vec![(Cow::from(OAUTH_KEY_PREFIX), Cow::from(""))];
let mut sorted_query = [parsed_payload, oauth_identifier].concat();
sorted_query.sort();
let mut divided = sorted_query
.splitn(2, |(k, _)| k == &OAUTH_KEY_PREFIX)
.into_iter();
let query_before_oauth = divided.next().unwrap();
let query_after_oauth = divided.next().unwrap_or_default();
let sig_method = params.signature_method.clone();
let mut signer = generate_signer(
sig_method,
method.as_str(),
url,
consumer_secret,
token_secret,
is_url_query,
);
for (key, value) in query_before_oauth {
if !key.starts_with(OAUTH_KEY_PREFIX) {
signer.parameter(key, value);
}
}
let mut signer = signer.oauth_parameters(consumer_key, &options);
for (key, value) in query_after_oauth {
if !key.starts_with(OAUTH_KEY_PREFIX) {
signer.parameter(key, value);
}
}
let sign = signer.finish().authorization;
if let Some(realm) = params.realm {
Ok(format!("{},{}=\"{}\"", sign, REALM_KEY, realm.as_ref()))
} else {
Ok(sign)
}
}
}
fn generate_signer<TSM>(
signature_method: TSM,
method: &str,
url: Url,
consumer_secret: &str,
token_secret: Option<&str>,
is_url_query: bool,
) -> OAuthSigner<TSM>
where
TSM: SignatureMethod,
{
if is_url_query {
OAuthSigner::with_signature_method(
signature_method,
method,
url,
consumer_secret,
token_secret,
)
} else {
OAuthSigner::form_with_signature_method(
signature_method,
method,
url,
consumer_secret,
token_secret,
)
}
}
#[derive(Debug, Clone)]
pub struct OAuthParameters<'a, TSM>
where
TSM: SignatureMethod + Clone,
{
callback: Option<Cow<'a, str>>,
nonce: Option<Cow<'a, str>>,
realm: Option<Cow<'a, str>>,
signature_method: TSM,
timestamp: Option<u64>,
verifier: Option<Cow<'a, str>>,
version: bool,
}
impl Default for OAuthParameters<'static, HmacSha1> {
fn default() -> Self {
OAuthParameters {
callback: None,
nonce: None,
realm: None,
signature_method: HmacSha1,
timestamp: None,
verifier: None,
version: false,
}
}
}
impl OAuthParameters<'_, HmacSha1> {
pub fn new() -> Self {
Default::default()
}
}
impl<'a, TSM> OAuthParameters<'a, TSM>
where
TSM: SignatureMethod + Clone,
{
pub fn callback<T>(self, callback: T) -> Self
where
T: Into<Cow<'a, str>>,
{
OAuthParameters {
callback: Some(callback.into()),
..self
}
}
pub fn nonce<T>(self, nonce: T) -> Self
where
T: Into<Cow<'a, str>>,
{
OAuthParameters {
nonce: Some(nonce.into()),
..self
}
}
pub fn realm<T>(self, realm: T) -> Self
where
T: Into<Cow<'a, str>>,
{
OAuthParameters {
realm: Some(realm.into()),
..self
}
}
pub fn timestamp<T>(self, timestamp: T) -> Self
where
T: Into<u64>,
{
OAuthParameters {
timestamp: Some(timestamp.into()),
..self
}
}
pub fn verifier<T>(self, verifier: T) -> Self
where
T: Into<Cow<'a, str>>,
{
OAuthParameters {
verifier: Some(verifier.into()),
..self
}
}
pub fn version<T>(self, version: T) -> Self
where
T: Into<bool>,
{
OAuthParameters {
version: version.into(),
..self
}
}
pub fn signature_method<T>(self, signature_method: T) -> OAuthParameters<'a, T>
where
T: SignatureMethod + Clone,
{
OAuthParameters {
signature_method,
callback: None,
nonce: None,
realm: None,
timestamp: None,
verifier: None,
version: false,
}
}
}
impl<T> OAuthParameters<'_, T>
where
T: SignatureMethod + Clone,
{
fn build_options<'a>(&'a self, token: Option<&'a str>) -> Options<'a> {
let mut opt = Options::new();
if let Some(ref callback) = self.callback {
opt.callback(callback.as_ref());
}
if let Some(ref nonce) = self.nonce {
opt.nonce(nonce.as_ref());
}
if let Some(timestamp) = self.timestamp {
opt.timestamp(timestamp);
}
if let Some(token) = token {
opt.token(token);
}
if let Some(ref verifier) = self.verifier {
opt.verifier(verifier.as_ref());
}
opt.version(self.version);
if self.version {}
opt
}
}