use std::env::{self, VarError};
use std::io::Read;
use std::result::Result as StdResult;
use std::str;
use std::time::Duration;
use json;
use json::*;
use reqwest::header::*;
use reqwest::{Error as RError, StatusCode};
use reqwest::blocking::{Client, Response};
use url::form_urlencoded;
use self::credentials::{CredentialsProvider, RequestTokenCredentials};
pub use self::errors::*;
use super::*;
pub mod credentials;
mod errors;
pub type AccessTokenProviderResult =
StdResult<AuthorizationServerResponse, AccessTokenProviderError>;
pub struct AuthorizationServerResponse {
pub access_token: AccessToken,
pub expires_in: Duration,
pub refresh_token: Option<String>,
}
pub trait AccessTokenProvider {
fn request_access_token(&self, scopes: &[Scope]) -> AccessTokenProviderResult;
}
pub struct ResourceOwnerPasswordCredentialsGrantProvider {
full_endpoint_url: String,
client: Client,
credentials_provider: Box<dyn CredentialsProvider + Send + Sync + 'static>,
}
impl ResourceOwnerPasswordCredentialsGrantProvider {
pub fn new<U, C>(
endpoint_url: U,
credentials_provider: C,
realm: Option<&str>,
) -> InitializationResult<Self>
where
U: Into<String>,
C: CredentialsProvider + Send + Sync + 'static,
{
let client = Client::new();
let mut full_endpoint_url = endpoint_url.into();
if let Some(realm) = realm {
full_endpoint_url.push_str("?realm=");
full_endpoint_url.push_str(realm);
}
Ok(ResourceOwnerPasswordCredentialsGrantProvider {
full_endpoint_url,
client,
credentials_provider: Box::new(credentials_provider),
})
}
pub fn from_env_with_credentials_provider<C>(
credentials_provider: C,
) -> InitializationResult<Self>
where
C: CredentialsProvider + Send + Sync + 'static,
{
let endpoint_url: String = match env::var("TOKKIT_AUTHORIZATION_SERVER_URL") {
Ok(url) => url,
Err(VarError::NotPresent) => {
return Err(InitializationError(
"'TOKKIT_AUTHORIZATION_SERVER_URL' not found.".to_string(),
))
}
Err(err) => return Err(InitializationError(err.to_string())),
};
let realm: Option<String> = match env::var("TOKKIT_AUTHORIZATION_SERVER_REALM") {
Ok(realm) => Some(realm),
Err(VarError::NotPresent) => None,
Err(err) => return Err(InitializationError(err.to_string())),
};
ResourceOwnerPasswordCredentialsGrantProvider::new(
endpoint_url,
credentials_provider,
realm.as_ref().map(|x| &**x),
)
}
}
impl AccessTokenProvider for ResourceOwnerPasswordCredentialsGrantProvider {
fn request_access_token(&self, scopes: &[Scope]) -> AccessTokenProviderResult {
let credentials = self.credentials_provider.credentials()?;
match execute_access_token_request(
&self.client,
&self.full_endpoint_url,
scopes,
credentials,
) {
Ok(mut rsp) => evaluate_response(&mut rsp),
Err(err) => Err(AccessTokenProviderError::Connection(err.to_string())),
}
}
}
fn evaluate_response(rsp: &mut Response) -> AccessTokenProviderResult {
let status = rsp.status();
let mut body = Vec::new();
rsp.read_to_end(&mut body)?;
match status {
StatusCode::OK => parse_response(&body, None),
StatusCode::BAD_REQUEST => Err(AccessTokenProviderError::BadAuthorizationRequest(
parse_error(&body)?,
)),
_ if status.is_client_error() => {
let body = str::from_utf8(&body)?;
Err(AccessTokenProviderError::Server(format!(
"The request sent to the authorization server was faulty({}): {}",
status, body
)))
}
_ if status.is_server_error() => {
let body = str::from_utf8(&body)?;
Err(AccessTokenProviderError::Server(format!(
"The authorization server returned an error({}): {}",
status, body
)))
}
_ => {
let body = str::from_utf8(&body)?;
Err(AccessTokenProviderError::Client(format!(
"Received unexpected status code({}) from authorization server: {}",
status, body
)))
}
}
}
fn execute_access_token_request(
client: &Client,
full_url: &str,
scopes: &[Scope],
credentials: RequestTokenCredentials,
) -> StdResult<Response, RError> {
let request_builder = client
.post(full_url)
.header(
CONTENT_TYPE,
HeaderValue::from_static("application/x-www-form-urlencoded"),
).basic_auth(
credentials.client_credentials.client_id,
Some(credentials.client_credentials.client_secret),
);
let mut scope_vec = Vec::new();
for scope in scopes {
scope_vec.push(scope.0.clone());
}
let form_encoded = form_urlencoded::Serializer::new(String::new())
.append_pair("grant_type", "password")
.append_pair("username", &credentials.owner_credentials.username)
.append_pair("password", &credentials.owner_credentials.password)
.append_pair("scope", &scope_vec.join(" "))
.finish();
let rsp = request_builder.body(form_encoded).send()?;
Ok(rsp)
}
fn parse_response(bytes: &[u8], default_expires_in: Option<Duration>) -> AccessTokenProviderResult {
let json_utf8 =
str::from_utf8(bytes).map_err(|err| AccessTokenProviderError::Parse(err.to_string()))?;
let json =
json::parse(json_utf8).map_err(|err| AccessTokenProviderError::Parse(err.to_string()))?;
if let JsonValue::Object(data) = json {
let access_token = match data.get("access_token") {
Some(&JsonValue::Short(user_id)) => user_id.to_string(),
Some(&JsonValue::String(ref user_id)) => user_id.clone(),
_ => {
return Err(AccessTokenProviderError::Parse(
"Expected a string as the access token but found something else".to_string(),
))
}
};
let expires_in: Duration = match data.get("expires_in") {
Some(&JsonValue::Number(expires_in)) => {
if let Some(expires_in) = expires_in.as_fixed_point_u64(0) {
Duration::from_secs(expires_in)
} else {
return Err(AccessTokenProviderError::Parse(
"'expires in must fit into an u64'".to_string(),
));
}
}
None => {
if let Some(default_expires_in) = default_expires_in {
default_expires_in
} else {
return Err(AccessTokenProviderError::Parse(
"No field 'expires_in' found and no default".to_string(),
));
}
}
invalid => {
return Err(AccessTokenProviderError::Parse(format!(
"Expected a number as 'expires_in' but found a {:?}",
invalid
)))
}
};
let refresh_token = match data.get("refresh_token") {
Some(&JsonValue::Short(refresh_token)) => Some(refresh_token.to_string()),
Some(&JsonValue::String(ref refresh_token)) => Some(refresh_token.clone()),
None => None,
_ => {
return Err(AccessTokenProviderError::Parse(
"Expected a string as the refresh token but found something else".to_string(),
))
}
};
Ok(AuthorizationServerResponse {
access_token: AccessToken::new(access_token),
expires_in,
refresh_token,
})
} else {
Err(AccessTokenProviderError::Parse(
"Token service response is not a JSON object".to_string(),
))
}
}
fn parse_error(bytes: &[u8]) -> StdResult<AuthorizationRequestError, AccessTokenProviderError> {
let json_utf8 =
str::from_utf8(bytes).map_err(|err| AccessTokenProviderError::Parse(err.to_string()))?;
let json =
json::parse(json_utf8).map_err(|err| AccessTokenProviderError::Parse(err.to_string()))?;
if let JsonValue::Object(data) = json {
let error = match data.get("error") {
Some(&JsonValue::Short(kind)) => kind.parse()?,
Some(&JsonValue::String(ref kind)) => kind.parse()?,
_ => {
return Err(AccessTokenProviderError::Parse(
"Expected a string as the error but found something else".to_string(),
))
}
};
let error_description = match data.get("error_description") {
Some(&JsonValue::Short(error_description)) => Some(error_description.to_string()),
Some(&JsonValue::String(ref error_description)) => Some(error_description.clone()),
None => None,
_ => {
return Err(AccessTokenProviderError::Parse(
"Expected a string as the error_description but found something else"
.to_string(),
))
}
};
let error_uri = match data.get("error_uri") {
Some(&JsonValue::Short(error_uri)) => Some(error_uri.to_string()),
Some(&JsonValue::String(ref error_uri)) => Some(error_uri.clone()),
None => None,
_ => {
return Err(AccessTokenProviderError::Parse(
"Expected a string as the error_uri but found something else".to_string(),
))
}
};
Ok(AuthorizationRequestError {
error,
error_description,
error_uri,
})
} else {
Err(AccessTokenProviderError::Parse(
"The response is not a JSON object".to_string(),
))
}
}
pub struct EnvAccessTokenProvider {
env_var_name: String,
expires_in: Duration,
}
impl EnvAccessTokenProvider {
pub fn new<T: Into<String>>(
env_var_name: T,
expires_in: Duration,
) -> InitializationResult<Self> {
let env_var_name = env_var_name.into();
if env_var_name.is_empty() {
Err(InitializationError(
"'env_var_name' may not be empty".into(),
))
} else {
Ok(EnvAccessTokenProvider {
env_var_name,
expires_in,
})
}
}
}
impl AccessTokenProvider for EnvAccessTokenProvider {
fn request_access_token(&self, _scopes: &[Scope]) -> AccessTokenProviderResult {
let access_token = match env::var(&self.env_var_name) {
Ok(token) => AccessToken::new(token),
Err(err) => {
return Err(AccessTokenProviderError::Other(format!(
"Could not get token from env var '{}': {}",
self.env_var_name, err
)))
}
};
let response = AuthorizationServerResponse {
access_token,
expires_in: self.expires_in,
refresh_token: None,
};
Ok(response)
}
}