#![warn(missing_docs)]
use actix::{MailboxError, Message};
use actix_web::{
dev::{HttpResponseBuilder, Payload},
http::{
header::{self, HeaderMap, InvalidHeaderValue},
StatusCode,
},
web::Form,
web::Query,
FromRequest, HttpRequest, HttpResponse, Responder, ResponseError,
};
use futures::future::{self, FutureExt, LocalBoxFuture, Ready};
use oxide_auth::{
endpoint::{Endpoint, NormalizedParameter, OAuthError, QueryParameter, WebRequest, WebResponse},
frontends::simple::endpoint::Error,
};
use std::{borrow::Cow, error, fmt, convert::TryFrom};
use url::Url;
mod operations;
pub use operations::{Authorize, Refresh, Resource, Token};
pub trait OAuthOperation: Sized + 'static {
type Item: 'static;
type Error: fmt::Debug + 'static;
fn run<E>(self, endpoint: E) -> Result<Self::Item, Self::Error>
where
E: Endpoint<OAuthRequest>,
WebError: From<E::Error>;
fn wrap<Extras>(self, extras: Extras) -> OAuthMessage<Self, Extras> {
OAuthMessage(self, extras)
}
}
pub struct OAuthMessage<Operation, Extras>(Operation, Extras);
#[derive(Clone, Debug)]
pub struct OAuthRequest {
auth: Option<String>,
query: Option<NormalizedParameter>,
body: Option<NormalizedParameter>,
}
impl OAuthResponse {
pub fn get_headers(&self) -> HeaderMap {
self.headers.clone()
}
pub fn get_body(&self) -> Option<String> {
self.body.clone()
}
}
pub struct OAuthResource {
auth: Option<String>,
}
#[derive(Clone, Debug)]
pub struct OAuthResponse {
status: StatusCode,
headers: HeaderMap,
body: Option<String>,
}
#[derive(Debug)]
pub enum WebError {
Endpoint(OAuthError),
Header(InvalidHeaderValue),
Encoding,
Form,
Query,
Body,
Authorization,
Canceled,
Mailbox,
InternalError(Option<String>),
}
impl OAuthRequest {
pub async fn new(req: HttpRequest, mut payload: Payload) -> Result<Self, WebError> {
let query = Query::extract(&req)
.await
.ok()
.map(|q: Query<NormalizedParameter>| q.into_inner());
let body = Form::from_request(&req, &mut payload)
.await
.ok()
.map(|b: Form<NormalizedParameter>| b.into_inner());
let mut all_auth = req.headers().get_all(header::AUTHORIZATION);
let optional = all_auth.next();
let auth = if all_auth.next().is_some() {
return Err(WebError::Authorization);
} else {
optional.and_then(|hv| hv.to_str().ok().map(str::to_owned))
};
Ok(OAuthRequest { auth, query, body })
}
pub fn authorization_header(&self) -> Option<&str> {
self.auth.as_deref()
}
pub fn query(&self) -> Option<&NormalizedParameter> {
self.query.as_ref()
}
pub fn query_mut(&mut self) -> Option<&mut NormalizedParameter> {
self.query.as_mut()
}
pub fn body(&self) -> Option<&NormalizedParameter> {
self.body.as_ref()
}
}
impl OAuthResource {
pub fn new(req: &HttpRequest) -> Result<Self, WebError> {
let mut all_auth = req.headers().get_all(header::AUTHORIZATION);
let optional = all_auth.next();
let auth = if all_auth.next().is_some() {
return Err(WebError::Authorization);
} else {
optional.and_then(|hv| hv.to_str().ok().map(str::to_owned))
};
Ok(OAuthResource { auth })
}
pub fn into_request(self) -> OAuthRequest {
OAuthRequest {
query: None,
body: None,
auth: self.auth,
}
}
}
impl OAuthResponse {
pub fn ok() -> Self {
OAuthResponse {
status: StatusCode::OK,
headers: HeaderMap::new(),
body: None,
}
}
pub fn content_type(mut self, content_type: &str) -> Result<Self, WebError> {
self.headers
.insert(header::CONTENT_TYPE, TryFrom::try_from(content_type)?);
Ok(self)
}
pub fn body(mut self, body: &str) -> Self {
self.body = Some(body.to_owned());
self
}
}
impl<Operation, Extras> OAuthMessage<Operation, Extras> {
pub fn into_inner(self) -> (Operation, Extras) {
(self.0, self.1)
}
}
impl WebRequest for OAuthRequest {
type Error = WebError;
type Response = OAuthResponse;
fn query(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
self.query
.as_ref()
.map(|q| Cow::Borrowed(q as &dyn QueryParameter))
.ok_or(WebError::Query)
}
fn urlbody(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
self.body
.as_ref()
.map(|b| Cow::Borrowed(b as &dyn QueryParameter))
.ok_or(WebError::Body)
}
fn authheader(&mut self) -> Result<Option<Cow<str>>, Self::Error> {
Ok(self.auth.as_deref().map(Cow::Borrowed))
}
}
impl WebResponse for OAuthResponse {
type Error = WebError;
fn ok(&mut self) -> Result<(), Self::Error> {
self.status = StatusCode::OK;
Ok(())
}
fn redirect(&mut self, url: Url) -> Result<(), Self::Error> {
self.status = StatusCode::FOUND;
self.headers
.insert(header::LOCATION, TryFrom::try_from(url.into_string())?);
Ok(())
}
fn client_error(&mut self) -> Result<(), Self::Error> {
self.status = StatusCode::BAD_REQUEST;
Ok(())
}
fn unauthorized(&mut self, kind: &str) -> Result<(), Self::Error> {
self.status = StatusCode::UNAUTHORIZED;
self.headers
.insert(header::WWW_AUTHENTICATE, TryFrom::try_from(kind)?);
Ok(())
}
fn body_text(&mut self, text: &str) -> Result<(), Self::Error> {
self.body = Some(text.to_owned());
self.headers
.insert(header::CONTENT_TYPE, TryFrom::try_from("text/plain")?);
Ok(())
}
fn body_json(&mut self, json: &str) -> Result<(), Self::Error> {
self.body = Some(json.to_owned());
self.headers
.insert(header::CONTENT_TYPE, TryFrom::try_from("application/json")?);
Ok(())
}
}
impl<Operation, Extras> Message for OAuthMessage<Operation, Extras>
where
Operation: OAuthOperation + 'static,
{
type Result = Result<Operation::Item, Operation::Error>;
}
impl FromRequest for OAuthRequest {
type Error = WebError;
type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
Self::new(req.clone(), payload.take()).boxed_local()
}
}
impl FromRequest for OAuthResource {
type Error = WebError;
type Future = Ready<Result<Self, Self::Error>>;
type Config = ();
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
future::ready(Self::new(req))
}
}
impl Responder for OAuthResponse {
type Error = WebError;
type Future = Ready<Result<HttpResponse, Self::Error>>;
fn respond_to(self, _: &HttpRequest) -> Self::Future {
let mut builder = HttpResponseBuilder::new(self.status);
for (k, v) in self.headers.into_iter() {
builder.header(k, v.to_owned());
}
if let Some(body) = self.body {
future::ok(builder.body(body))
} else {
future::ok(builder.finish())
}
}
}
impl From<OAuthResource> for OAuthRequest {
fn from(o: OAuthResource) -> Self {
o.into_request()
}
}
impl Default for OAuthResponse {
fn default() -> Self {
OAuthResponse {
status: StatusCode::OK,
headers: HeaderMap::new(),
body: None,
}
}
}
impl From<Error<OAuthRequest>> for WebError {
fn from(e: Error<OAuthRequest>) -> Self {
match e {
Error::Web(e) => e,
Error::OAuth(e) => e.into(),
}
}
}
impl From<InvalidHeaderValue> for WebError {
fn from(e: InvalidHeaderValue) -> Self {
WebError::Header(e)
}
}
impl From<MailboxError> for WebError {
fn from(e: MailboxError) -> Self {
match e {
MailboxError::Closed => WebError::Mailbox,
MailboxError::Timeout => WebError::Canceled,
}
}
}
impl From<OAuthError> for WebError {
fn from(e: OAuthError) -> Self {
WebError::Endpoint(e)
}
}
impl fmt::Display for WebError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
WebError::Endpoint(ref e) => write!(f, "Endpoint, {}", e),
WebError::Header(ref e) => write!(f, "Couldn't set header, {}", e),
WebError::Encoding => write!(f, "Error decoding request"),
WebError::Form => write!(f, "Request is not a form"),
WebError::Query => write!(f, "No query present"),
WebError::Body => write!(f, "No body present"),
WebError::Authorization => write!(f, "Request has invalid Authorization headers"),
WebError::Canceled => write!(f, "Operation canceled"),
WebError::Mailbox => write!(f, "An actor's mailbox was full"),
WebError::InternalError(None) => write!(f, "An internal server error occured"),
WebError::InternalError(Some(ref e)) => write!(f, "An internal server error occured: {}", e),
}
}
}
impl error::Error for WebError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
WebError::Endpoint(ref e) => e.source(),
WebError::Header(ref e) => e.source(),
WebError::Encoding
| WebError::Form
| WebError::Authorization
| WebError::Query
| WebError::Body
| WebError::Canceled
| WebError::Mailbox
| WebError::InternalError(_) => None,
}
}
}
impl ResponseError for WebError {
}