From 48867da5354959a8fc4ae3b5777be45641662e04 Mon Sep 17 00:00:00 2001 From: Dominic Date: Sat, 25 Jan 2020 15:59:37 +0100 Subject: [PATCH] add AuthResult resource result type --- gotham_restful/src/auth.rs | 39 ++++++++-- gotham_restful/src/lib.rs | 2 + gotham_restful/src/result.rs | 140 ++++++++++++++++++++++++++++++++++- 3 files changed, 175 insertions(+), 6 deletions(-) diff --git a/gotham_restful/src/auth.rs b/gotham_restful/src/auth.rs index 8da3d5e..cd24b86 100644 --- a/gotham_restful/src/auth.rs +++ b/gotham_restful/src/auth.rs @@ -308,6 +308,7 @@ mod test // some known tokens const VALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9.8h8Ax-nnykqEQ62t7CxmM3ja6NzUQ4L0MLOOzddjLKk"; const EXPIRED_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjE1Nzc4MzcxMDB9.eV1snaGLYrJ7qUoMk74OvBY3WUU9M0Je5HTU2xtX1v0"; + const INVALID_TOKEN : &'static str = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJtc3JkMCIsInN1YiI6ImdvdGhhbS1yZXN0ZnVsIiwiaWF0IjoxNTc3ODM2ODAwLCJleHAiOjQxMDI0NDQ4MDB9"; #[derive(Debug, Deserialize, PartialEq)] struct TestData @@ -332,15 +333,27 @@ mod test } #[derive(Default)] - struct TestHandler; - impl AuthHandler for TestHandler + struct NoneAuthHandler; + impl AuthHandler for NoneAuthHandler { fn jwt_secret Option>(&self, _state : &mut State, _decode_data : F) -> Option> { - Some(JWT_SECRET.to_vec()) + None } } + #[test] + fn test_auth_middleware_none_secret() + { + let middleware = >::from_source(AuthSource::AuthorizationHeader); + State::with_new(|mut state| { + let mut headers = HeaderMap::new(); + headers.insert(AUTHORIZATION, format!("Bearer {}", VALID_TOKEN).parse().unwrap()); + state.put(headers); + middleware.auth_status(&mut state); + }); + } + #[derive(Default)] struct TestAssertingHandler; impl AuthHandler for TestAssertingHandler @@ -365,10 +378,10 @@ mod test }); } - fn new_middleware(source : AuthSource) -> AuthMiddleware + fn new_middleware(source : AuthSource) -> AuthMiddleware where T : DeserializeOwned + Send { - AuthMiddleware::from_source(source) + AuthMiddleware::new(source, Default::default(), StaticAuthHandler::from_array(JWT_SECRET)) } #[test] @@ -400,6 +413,22 @@ mod test }); } + #[test] + fn test_auth_middleware_invalid_token() + { + let middleware = new_middleware::(AuthSource::AuthorizationHeader); + State::with_new(|mut state| { + let mut headers = HeaderMap::new(); + headers.insert(AUTHORIZATION, format!("Bearer {}", INVALID_TOKEN).parse().unwrap()); + state.put(headers); + let status = middleware.auth_status(&mut state); + match status { + AuthStatus::Invalid => {}, + _ => panic!("Expected AuthStatus::Invalid, got {:?}", status) + }; + }); + } + #[test] fn test_auth_middleware_auth_header_token() { diff --git a/gotham_restful/src/lib.rs b/gotham_restful/src/lib.rs index b46e7bd..1cc9420 100644 --- a/gotham_restful/src/lib.rs +++ b/gotham_restful/src/lib.rs @@ -170,6 +170,8 @@ pub use resource::{ mod result; pub use result::{ + AuthResult, + AuthResult::AuthErr, NoContent, Raw, ResourceResult, diff --git a/gotham_restful/src/result.rs b/gotham_restful/src/result.rs index 9cfe9c6..ef258a4 100644 --- a/gotham_restful/src/result.rs +++ b/gotham_restful/src/result.rs @@ -7,7 +7,10 @@ use mime::{Mime, APPLICATION_JSON, STAR_STAR}; use openapiv3::{SchemaKind, StringFormat, StringType, Type, VariantOrUnknownOrEmpty}; use serde::Serialize; use serde_json::error::Error as SerdeJsonError; -use std::error::Error; +use std::{ + error::Error, + fmt::Debug +}; /// A response, used to create the final gotham response from. pub struct Response @@ -49,6 +52,16 @@ impl Response } } + /// Create an empty _403 Forbidden_ `Response`. + pub fn forbidden() -> Self + { + Self { + status: StatusCode::FORBIDDEN, + body: Body::empty(), + mime: None + } + } + #[cfg(test)] fn full_body(self) -> Vec { @@ -170,6 +183,21 @@ impl From for Success } } +impl Clone for Success +{ + fn clone(&self) -> Self + { + Self(self.0.clone()) + } +} + +impl Debug for Success +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Success({:?})", self.0) + } +} + impl ResourceResult for Success { fn into_response(self) -> Result @@ -189,6 +217,98 @@ impl ResourceResult for Success } } +/** +This return type can be used to map another `ResourceResult` that can only be returned if the +client is authenticated. Otherwise, an empty _403 Forbidden_ response will be issued. Use can +look something like this (assuming the `auth` feature is enabled): + +``` +# #[macro_use] extern crate gotham_restful_derive; +# use gotham::state::State; +# use gotham_restful::*; +# use serde::Deserialize; +# +# #[derive(Resource)] +# struct MyResource; +# +# #[derive(Clone, Deserialize)] +# struct MyAuthData { exp : u64 } +# +#[rest_read_all(MyResource)] +fn read_all(auth : AuthStatus) -> AuthResult { + let auth_data = match auth { + AuthStatus::Authenticated(data) => data, + _ => return AuthErr + }; + // do something + NoContent::default().into() +} +``` +*/ +pub enum AuthResult +{ + Ok(T), + AuthErr +} + +impl From for AuthResult +{ + fn from(t : T) -> Self + { + Self::Ok(t) + } +} + +impl Clone for AuthResult +{ + fn clone(&self) -> Self + { + match self { + Self::Ok(t) => Self::Ok(t.clone()), + Self::AuthErr => Self::AuthErr + } + } +} + +impl Debug for AuthResult +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Ok(t) => write!(f, "Ok({:?})", t), + Self::AuthErr => write!(f, "AuthErr") + } + } +} + +impl ResourceResult for AuthResult +{ + fn into_response(self) -> Result + { + match self + { + Self::Ok(res) => res.into_response(), + Self::AuthErr => Ok(Response::forbidden()) + } + } + + fn accepted_types() -> Option> + { + T::accepted_types() + } + + #[cfg(feature = "openapi")] + fn schema() -> OpenapiSchema + { + T::schema() + } + + #[cfg(feature = "openapi")] + fn default_status() -> StatusCode + { + T::default_status() + } +} + /** This is the return type of a resource that doesn't actually return something. It will result in a _204 No Content_ answer by default. You don't need to use this type directly if using @@ -282,6 +402,24 @@ impl Raw } } +impl Clone for Raw +{ + fn clone(&self) -> Self + { + Self { + raw: self.raw.clone(), + mime: self.mime.clone() + } + } +} + +impl Debug for Raw +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Raw({:?}, {:?})", self.raw, self.mime) + } +} + impl> ResourceResult for Raw { fn into_response(self) -> Result