oxide_auth_actix/
lib.rs

1//! Bindings and utilities for creating an oauth endpoint with actix.
2//!
3//! Use the provided methods to use code grant methods in an asynchronous fashion, or use an
4//! `AsActor<_>` to create an actor implementing endpoint functionality via messages.
5#![warn(missing_docs)]
6
7use actix::{MailboxError, Message};
8use actix_web::{
9    body::BoxBody,
10    dev::Payload,
11    http::{
12        header::{self, HeaderMap, InvalidHeaderValue},
13        StatusCode,
14    },
15    web::Form,
16    web::Query,
17    FromRequest, HttpRequest, HttpResponse, HttpResponseBuilder, Responder, ResponseError,
18};
19use futures::future::{self, FutureExt, LocalBoxFuture, Ready};
20use oxide_auth::{
21    endpoint::{Endpoint, NormalizedParameter, OAuthError, QueryParameter, WebRequest, WebResponse},
22    frontends::simple::endpoint::Error,
23};
24use std::{borrow::Cow, convert::TryFrom, error, fmt};
25use url::Url;
26
27mod operations;
28
29pub use operations::{Authorize, Refresh, Resource, Token, ClientCredentials};
30
31/// Describes an operation that can be performed in the presence of an `Endpoint`
32///
33/// This trait can be implemented by any type, but is very useful in Actor scenarios, where an
34/// Actor can provide an endpoint to an operation sent as a message.
35///
36/// Here's how any Endpoint type can be turned into an Actor that responds to OAuthMessages:
37/// ```rust,ignore
38/// use actix::{Actor, Context, Handler};
39/// use oxide_auth::endpoint::Endpoint;
40/// use oxide_auth_actix::OAuthOperation;
41///
42/// pub struct MyEndpoint {
43///     // Define your endpoint...
44/// }
45///
46/// impl Endpoint<OAuthRequest> for MyEndpoint {
47///     // Implement your endpoint...
48/// }
49///
50/// // Implement Actor
51/// impl Actor for MyEndpoint {
52///     type Context = Context<Self>;
53/// }
54///
55/// // Handle incoming OAuthMessages
56/// impl<Op, Ext> Handler<OAuthMessage<Op, Ext>> for MyEndpoint
57/// where
58///     Op: OAuthOperation,
59/// {
60///     type Result = Result<Op::Item, Op::Error>;
61///
62///     fn handle(&mut self, msg: OAuthMessage<Op, Ext>, _: &mut Self::Context) -> Self::Result {
63///         let (op, _) = msg.into_inner();
64///
65///         op.run(self)
66///     }
67/// }
68/// ```
69///
70/// By additionally specifying a type for Extras, more advanced patterns can be used
71/// ```rust,ignore
72/// type Ext = Option<MyCustomSolicitor>;
73///
74/// // Handle incoming OAuthMessages
75/// impl<Op> Handler<OAuthMessage<Op, Ext>> for MyEndpoint
76/// where
77///     Op: OAuthOperation,
78/// {
79///     type Result = Result<Op::Item, Op::Error>;
80///
81///     fn handle(&mut self, msg: OAuthMessage<Op, Ext>, _: &mut Self::Context) -> Self::Result {
82///         let (op, ext) = msg.into_inner();
83///
84///         op.run(self.with_my_custom_solicitor(ext))
85///     }
86/// }
87/// ```
88pub trait OAuthOperation: Sized + 'static {
89    /// The success-type produced by an OAuthOperation
90    type Item: 'static;
91
92    /// The error type produced by an OAuthOperation
93    type Error: fmt::Debug + 'static;
94
95    /// Performs the oxide operation with the provided endpoint
96    fn run<E>(self, endpoint: E) -> Result<Self::Item, Self::Error>
97    where
98        E: Endpoint<OAuthRequest>,
99        WebError: From<E::Error>;
100
101    /// Turn an OAuthOperation into a Message to send to an actor
102    fn wrap<Extras>(self, extras: Extras) -> OAuthMessage<Self, Extras> {
103        OAuthMessage(self, extras)
104    }
105}
106
107/// A message type to easily send `OAuthOperation`s to an actor
108pub struct OAuthMessage<Operation, Extras>(Operation, Extras);
109
110#[derive(Clone, Debug)]
111/// Type implementing `WebRequest` as well as `FromRequest` for use in route handlers
112///
113/// This type consumes the body of the HttpRequest upon extraction, so be careful not to use it in
114/// places you also expect an application payload
115pub struct OAuthRequest {
116    auth: Option<String>,
117    query: Option<NormalizedParameter>,
118    body: Option<NormalizedParameter>,
119}
120
121impl OAuthResponse {
122    /// Get the headers from `OAuthResponse`
123    pub fn get_headers(&self) -> HeaderMap {
124        self.headers.clone()
125    }
126
127    /// Get the body from `OAuthResponse`
128    pub fn get_body(&self) -> Option<String> {
129        self.body.clone()
130    }
131}
132
133/// Type implementing `WebRequest` as well as `FromRequest` for use in guarding resources
134///
135/// This is useful over [OAuthRequest] since [OAuthResource] doesn't consume the body of the
136/// request upon extraction
137pub struct OAuthResource {
138    auth: Option<String>,
139}
140
141#[derive(Clone, Debug)]
142/// Type implementing `WebResponse` and `Responder` for use in route handlers
143pub struct OAuthResponse {
144    status: StatusCode,
145    headers: HeaderMap,
146    body: Option<String>,
147}
148
149#[derive(Debug)]
150/// The error type for Oxide Auth operations
151pub enum WebError {
152    /// Errors occuring in Endpoint operations
153    Endpoint(OAuthError),
154
155    /// Errors occuring when producing Headers
156    Header(InvalidHeaderValue),
157
158    /// Errors with the request encoding
159    Encoding,
160
161    /// Request body could not be parsed as a form
162    Form,
163
164    /// Request query was absent or could not be parsed
165    Query,
166
167    /// Request was missing a body
168    Body,
169
170    /// The Authorization header was invalid
171    Authorization,
172
173    /// Processing part of the request was canceled
174    Canceled,
175
176    /// An actor's mailbox was full
177    Mailbox,
178
179    /// General internal server error
180    InternalError(Option<String>),
181}
182
183impl OAuthRequest {
184    /// Create a new OAuthRequest from an HttpRequest and Payload
185    pub async fn new(req: HttpRequest, mut payload: Payload) -> Result<Self, WebError> {
186        let query = Query::extract(&req)
187            .await
188            .ok()
189            .map(|q: Query<NormalizedParameter>| q.into_inner());
190        let body = Form::from_request(&req, &mut payload)
191            .await
192            .ok()
193            .map(|b: Form<NormalizedParameter>| b.into_inner());
194
195        let mut all_auth = req.headers().get_all(header::AUTHORIZATION);
196        let optional = all_auth.next();
197
198        let auth = if all_auth.next().is_some() {
199            return Err(WebError::Authorization);
200        } else {
201            optional.and_then(|hv| hv.to_str().ok().map(str::to_owned))
202        };
203
204        Ok(OAuthRequest { auth, query, body })
205    }
206
207    /// Fetch the authorization header from the request
208    pub fn authorization_header(&self) -> Option<&str> {
209        self.auth.as_deref()
210    }
211
212    /// Fetch the query for this request
213    pub fn query(&self) -> Option<&NormalizedParameter> {
214        self.query.as_ref()
215    }
216
217    /// Fetch the query mutably
218    pub fn query_mut(&mut self) -> Option<&mut NormalizedParameter> {
219        self.query.as_mut()
220    }
221
222    /// Fetch the body of the request
223    pub fn body(&self) -> Option<&NormalizedParameter> {
224        self.body.as_ref()
225    }
226}
227
228impl OAuthResource {
229    /// Create a new OAuthResource from an HttpRequest
230    pub fn new(req: &HttpRequest) -> Result<Self, WebError> {
231        let mut all_auth = req.headers().get_all(header::AUTHORIZATION);
232        let optional = all_auth.next();
233
234        let auth = if all_auth.next().is_some() {
235            return Err(WebError::Authorization);
236        } else {
237            optional.and_then(|hv| hv.to_str().ok().map(str::to_owned))
238        };
239
240        Ok(OAuthResource { auth })
241    }
242
243    /// Turn this OAuthResource into an OAuthRequest for processing
244    pub fn into_request(self) -> OAuthRequest {
245        OAuthRequest {
246            query: None,
247            body: None,
248            auth: self.auth,
249        }
250    }
251}
252
253impl OAuthResponse {
254    /// Create a simple response with no body and a '200 OK' HTTP Status
255    pub fn ok() -> Self {
256        OAuthResponse {
257            status: StatusCode::OK,
258            headers: HeaderMap::new(),
259            body: None,
260        }
261    }
262
263    /// Set the `ContentType` header on a response
264    pub fn content_type(mut self, content_type: &str) -> Result<Self, WebError> {
265        self.headers
266            .insert(header::CONTENT_TYPE, TryFrom::try_from(content_type)?);
267        Ok(self)
268    }
269
270    /// Set the bodyfor the response
271    pub fn body(mut self, body: &str) -> Self {
272        self.body = Some(body.to_owned());
273        self
274    }
275}
276
277impl<Operation, Extras> OAuthMessage<Operation, Extras> {
278    /// Produce an OAuthOperation from a wrapping OAuthMessage
279    pub fn into_inner(self) -> (Operation, Extras) {
280        (self.0, self.1)
281    }
282}
283
284impl WebRequest for OAuthRequest {
285    type Error = WebError;
286    type Response = OAuthResponse;
287
288    fn query(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
289        self.query
290            .as_ref()
291            .map(|q| Cow::Borrowed(q as &dyn QueryParameter))
292            .ok_or(WebError::Query)
293    }
294
295    fn urlbody(&mut self) -> Result<Cow<dyn QueryParameter + 'static>, Self::Error> {
296        self.body
297            .as_ref()
298            .map(|b| Cow::Borrowed(b as &dyn QueryParameter))
299            .ok_or(WebError::Body)
300    }
301
302    fn authheader(&mut self) -> Result<Option<Cow<str>>, Self::Error> {
303        Ok(self.auth.as_deref().map(Cow::Borrowed))
304    }
305}
306
307impl WebResponse for OAuthResponse {
308    type Error = WebError;
309
310    fn ok(&mut self) -> Result<(), Self::Error> {
311        self.status = StatusCode::OK;
312        Ok(())
313    }
314
315    fn redirect(&mut self, url: Url) -> Result<(), Self::Error> {
316        self.status = StatusCode::FOUND;
317        let location = String::from(url);
318        self.headers
319            .insert(header::LOCATION, TryFrom::try_from(location)?);
320        Ok(())
321    }
322
323    fn client_error(&mut self) -> Result<(), Self::Error> {
324        self.status = StatusCode::BAD_REQUEST;
325        Ok(())
326    }
327
328    fn unauthorized(&mut self, kind: &str) -> Result<(), Self::Error> {
329        self.status = StatusCode::UNAUTHORIZED;
330        self.headers
331            .insert(header::WWW_AUTHENTICATE, TryFrom::try_from(kind)?);
332        Ok(())
333    }
334
335    fn body_text(&mut self, text: &str) -> Result<(), Self::Error> {
336        self.body = Some(text.to_owned());
337        self.headers
338            .insert(header::CONTENT_TYPE, TryFrom::try_from("text/plain")?);
339        Ok(())
340    }
341
342    fn body_json(&mut self, json: &str) -> Result<(), Self::Error> {
343        self.body = Some(json.to_owned());
344        self.headers
345            .insert(header::CONTENT_TYPE, TryFrom::try_from("application/json")?);
346        Ok(())
347    }
348}
349
350impl<Operation, Extras> Message for OAuthMessage<Operation, Extras>
351where
352    Operation: OAuthOperation + 'static,
353{
354    type Result = Result<Operation::Item, Operation::Error>;
355}
356
357impl FromRequest for OAuthRequest {
358    type Error = WebError;
359    type Future = LocalBoxFuture<'static, Result<Self, Self::Error>>;
360
361    fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
362        Self::new(req.clone(), payload.take()).boxed_local()
363    }
364}
365
366impl FromRequest for OAuthResource {
367    type Error = WebError;
368    type Future = Ready<Result<Self, Self::Error>>;
369
370    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
371        future::ready(Self::new(req))
372    }
373}
374
375impl Responder for OAuthResponse {
376    type Body = BoxBody;
377
378    fn respond_to(self, _: &HttpRequest) -> HttpResponse {
379        let mut builder = HttpResponseBuilder::new(self.status);
380        for (k, v) in self.headers.into_iter() {
381            builder.insert_header((k, v.to_owned()));
382        }
383
384        if let Some(body) = self.body {
385            builder.body(body)
386        } else {
387            builder.finish()
388        }
389    }
390}
391
392impl From<OAuthResource> for OAuthRequest {
393    fn from(o: OAuthResource) -> Self {
394        o.into_request()
395    }
396}
397
398impl Default for OAuthResponse {
399    fn default() -> Self {
400        OAuthResponse {
401            status: StatusCode::OK,
402            headers: HeaderMap::new(),
403            body: None,
404        }
405    }
406}
407
408impl From<Error<OAuthRequest>> for WebError {
409    fn from(e: Error<OAuthRequest>) -> Self {
410        match e {
411            Error::Web(e) => e,
412            Error::OAuth(e) => e.into(),
413        }
414    }
415}
416
417impl From<InvalidHeaderValue> for WebError {
418    fn from(e: InvalidHeaderValue) -> Self {
419        WebError::Header(e)
420    }
421}
422
423impl From<MailboxError> for WebError {
424    fn from(e: MailboxError) -> Self {
425        match e {
426            MailboxError::Closed => WebError::Mailbox,
427            MailboxError::Timeout => WebError::Canceled,
428        }
429    }
430}
431
432impl From<OAuthError> for WebError {
433    fn from(e: OAuthError) -> Self {
434        WebError::Endpoint(e)
435    }
436}
437
438impl fmt::Display for WebError {
439    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
440        match *self {
441            WebError::Endpoint(ref e) => write!(f, "Endpoint, {}", e),
442            WebError::Header(ref e) => write!(f, "Couldn't set header, {}", e),
443            WebError::Encoding => write!(f, "Error decoding request"),
444            WebError::Form => write!(f, "Request is not a form"),
445            WebError::Query => write!(f, "No query present"),
446            WebError::Body => write!(f, "No body present"),
447            WebError::Authorization => write!(f, "Request has invalid Authorization headers"),
448            WebError::Canceled => write!(f, "Operation canceled"),
449            WebError::Mailbox => write!(f, "An actor's mailbox was full"),
450            WebError::InternalError(None) => write!(f, "An internal server error occured"),
451            WebError::InternalError(Some(ref e)) => write!(f, "An internal server error occured: {}", e),
452        }
453    }
454}
455
456impl error::Error for WebError {
457    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
458        match *self {
459            WebError::Endpoint(ref e) => e.source(),
460            WebError::Header(ref e) => e.source(),
461            WebError::Encoding
462            | WebError::Form
463            | WebError::Authorization
464            | WebError::Query
465            | WebError::Body
466            | WebError::Canceled
467            | WebError::Mailbox
468            | WebError::InternalError(_) => None,
469        }
470    }
471}
472
473impl ResponseError for WebError {
474    // Default to 500 for now
475}