mirror of
https://gitlab.com/msrd0/gotham-restful.git
synced 2025-02-22 20:52:27 +00:00
add AuthResult resource result type
This commit is contained in:
parent
756fd3a98a
commit
48867da535
3 changed files with 175 additions and 6 deletions
|
@ -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<T> AuthHandler<T> for TestHandler
|
||||
struct NoneAuthHandler;
|
||||
impl<T> AuthHandler<T> for NoneAuthHandler
|
||||
{
|
||||
fn jwt_secret<F : FnOnce() -> Option<T>>(&self, _state : &mut State, _decode_data : F) -> Option<Vec<u8>>
|
||||
{
|
||||
Some(JWT_SECRET.to_vec())
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_auth_middleware_none_secret()
|
||||
{
|
||||
let middleware = <AuthMiddleware<TestData, NoneAuthHandler>>::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<T> AuthHandler<T> for TestAssertingHandler
|
||||
|
@ -365,10 +378,10 @@ mod test
|
|||
});
|
||||
}
|
||||
|
||||
fn new_middleware<T>(source : AuthSource) -> AuthMiddleware<T, TestHandler>
|
||||
fn new_middleware<T>(source : AuthSource) -> AuthMiddleware<T, StaticAuthHandler>
|
||||
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::<TestData>(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()
|
||||
{
|
||||
|
|
|
@ -170,6 +170,8 @@ pub use resource::{
|
|||
|
||||
mod result;
|
||||
pub use result::{
|
||||
AuthResult,
|
||||
AuthResult::AuthErr,
|
||||
NoContent,
|
||||
Raw,
|
||||
ResourceResult,
|
||||
|
|
|
@ -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<u8>
|
||||
{
|
||||
|
@ -170,6 +183,21 @@ impl<T> From<T> for Success<T>
|
|||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for Success<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
Self(self.0.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for Success<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Success({:?})", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : ResponseBody> ResourceResult for Success<T>
|
||||
{
|
||||
fn into_response(self) -> Result<Response, SerdeJsonError>
|
||||
|
@ -189,6 +217,98 @@ impl<T : ResponseBody> ResourceResult for Success<T>
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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<MyAuthData>) -> AuthResult<NoContent> {
|
||||
let auth_data = match auth {
|
||||
AuthStatus::Authenticated(data) => data,
|
||||
_ => return AuthErr
|
||||
};
|
||||
// do something
|
||||
NoContent::default().into()
|
||||
}
|
||||
```
|
||||
*/
|
||||
pub enum AuthResult<T>
|
||||
{
|
||||
Ok(T),
|
||||
AuthErr
|
||||
}
|
||||
|
||||
impl<T> From<T> for AuthResult<T>
|
||||
{
|
||||
fn from(t : T) -> Self
|
||||
{
|
||||
Self::Ok(t)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for AuthResult<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
match self {
|
||||
Self::Ok(t) => Self::Ok(t.clone()),
|
||||
Self::AuthErr => Self::AuthErr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for AuthResult<T>
|
||||
{
|
||||
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<T : ResourceResult> ResourceResult for AuthResult<T>
|
||||
{
|
||||
fn into_response(self) -> Result<Response, SerdeJsonError>
|
||||
{
|
||||
match self
|
||||
{
|
||||
Self::Ok(res) => res.into_response(),
|
||||
Self::AuthErr => Ok(Response::forbidden())
|
||||
}
|
||||
}
|
||||
|
||||
fn accepted_types() -> Option<Vec<Mime>>
|
||||
{
|
||||
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<T> Raw<T>
|
|||
}
|
||||
}
|
||||
|
||||
impl<T : Clone> Clone for Raw<T>
|
||||
{
|
||||
fn clone(&self) -> Self
|
||||
{
|
||||
Self {
|
||||
raw: self.raw.clone(),
|
||||
mime: self.mime.clone()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Debug> Debug for Raw<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Raw({:?}, {:?})", self.raw, self.mime)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T : Into<Body>> ResourceResult for Raw<T>
|
||||
{
|
||||
fn into_response(self) -> Result<Response, SerdeJsonError>
|
||||
|
|
Loading…
Add table
Reference in a new issue