#![feature(proc_macro_hygiene, decl_macro)]
#![allow(
legacy_directory_ownership,
missing_copy_implementations,
missing_debug_implementations,
unknown_lints,
unsafe_code,
intra_doc_link_resolution_failure
)]
#![deny(
const_err,
dead_code,
deprecated,
exceeding_bitshifts,
improper_ctypes,
missing_docs,
mutable_transmutes,
no_mangle_const_items,
non_camel_case_types,
non_shorthand_field_patterns,
non_upper_case_globals,
overflowing_literals,
path_statements,
plugin_as_library,
stable_features,
trivial_casts,
trivial_numeric_casts,
unconditional_recursion,
unknown_crate_types,
unreachable_code,
unused_allocation,
unused_assignments,
unused_attributes,
unused_comparisons,
unused_extern_crates,
unused_features,
unused_imports,
unused_import_braces,
unused_qualifications,
unused_must_use,
unused_mut,
unused_parens,
unused_results,
unused_unsafe,
unused_variables,
variant_size_differences,
warnings,
while_true
)]
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
use biscuit as jwt;
use hyper;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate rocket;
use rocket_cors as cors;
#[macro_use]
extern crate serde_derive;
use serde_json;
#[cfg(test)]
extern crate serde_test;
#[macro_use]
mod macros;
#[cfg(test)]
#[macro_use]
mod test;
pub mod auth;
mod routes;
pub mod serde_custom;
pub mod token;
pub use self::routes::routes;
use std::error;
use std::fmt;
use std::io;
use std::ops::Deref;
use std::str::FromStr;
use ring::rand::SystemRandom;
use rocket::http::Status;
use rocket::response::{Responder, Response};
use rocket::Request;
use serde::de;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub use serde_json::Map as JsonMap;
pub use serde_json::Value as JsonValue;
#[derive(Debug)]
pub enum Error {
GenericError(String),
BadRequest(String),
Auth(auth::Error),
CORS(cors::Error),
Token(token::Error),
IOError(io::Error),
LaunchError(rocket::error::LaunchError),
UnsupportedOperation,
}
impl_from_error!(auth::Error, Error::Auth);
impl_from_error!(cors::Error, Error::CORS);
impl_from_error!(token::Error, Error::Token);
impl_from_error!(String, Error::GenericError);
impl_from_error!(io::Error, Error::IOError);
impl_from_error!(rocket::error::LaunchError, Error::LaunchError);
impl error::Error for Error {
fn description(&self) -> &str {
match *self {
Error::UnsupportedOperation => "This operation is not supported",
Error::Auth(ref e) => e.description(),
Error::CORS(ref e) => e.description(),
Error::Token(ref e) => e.description(),
Error::IOError(ref e) => e.description(),
Error::LaunchError(ref e) => e.description(),
Error::GenericError(ref e) | Error::BadRequest(ref e) => e,
}
}
fn cause(&self) -> Option<&dyn error::Error> {
match *self {
Error::Auth(ref e) => Some(e),
Error::CORS(ref e) => Some(e),
Error::Token(ref e) => Some(e),
Error::IOError(ref e) => Some(e),
Error::LaunchError(ref e) => Some(e),
Error::UnsupportedOperation | Error::GenericError(_) | Error::BadRequest(_) => {
Some(self)
}
}
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
Error::UnsupportedOperation => write!(f, "{}", error::Error::description(self)),
Error::Auth(ref e) => fmt::Display::fmt(e, f),
Error::CORS(ref e) => fmt::Display::fmt(e, f),
Error::Token(ref e) => fmt::Display::fmt(e, f),
Error::IOError(ref e) => fmt::Display::fmt(e, f),
Error::GenericError(ref e) => fmt::Display::fmt(e, f),
Error::LaunchError(ref e) => fmt::Display::fmt(e, f),
Error::BadRequest(ref e) => fmt::Display::fmt(e, f),
}
}
}
impl<'r> Responder<'r> for Error {
fn respond_to(self, request: &Request<'_>) -> Result<Response<'r>, Status> {
match self {
Error::Auth(e) => e.respond_to(request),
Error::CORS(e) => e.respond_to(request),
Error::Token(e) => e.respond_to(request),
Error::BadRequest(e) => {
error_!("{}", e);
Err(Status::BadRequest)
}
e => {
error_!("{}", e);
Err(Status::InternalServerError)
}
}
}
}
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
pub struct Url(hyper::Url);
impl_deref!(Url, hyper::Url);
impl FromStr for Url {
type Err = hyper::error::ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Url(hyper::Url::from_str(s)?))
}
}
impl fmt::Display for Url {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0.as_str())
}
}
impl Serialize for Url {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(self.0.as_str())
}
}
impl<'de> Deserialize<'de> for Url {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct UrlVisitor;
impl<'de> de::Visitor<'de> for UrlVisitor {
type Value = Url;
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("a valid URL string")
}
fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Url(
hyper::Url::from_str(&value).map_err(|e| E::custom(e.to_string()))?
))
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Url(
hyper::Url::from_str(value).map_err(|e| E::custom(e.to_string()))?
))
}
}
deserializer.deserialize_string(UrlVisitor)
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug)]
#[serde(untagged)]
pub enum ByteSequence {
String(String),
Bytes(Vec<u8>),
}
impl ByteSequence {
pub fn as_bytes(&self) -> Vec<u8> {
match *self {
ByteSequence::String(ref string) => string.to_string().into_bytes(),
ByteSequence::Bytes(ref bytes) => bytes.to_vec(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Configuration<B> {
pub token: token::Configuration,
pub basic_authenticator: B,
}
impl<B: auth::AuthenticatorConfiguration<auth::Basic>> Configuration<B> {
pub fn ignite(&self) -> Result<rocket::Rocket, Error> {
let token_getter_cors_options = self.token.cors_option();
let basic_authenticator = self.basic_authenticator.make_authenticator()?;
let basic_authenticator: Box<auth::BasicAuthenticator> = Box::new(basic_authenticator);
let keys = self.token.keys()?;
Ok(rocket::ignite()
.manage(self.token.clone())
.manage(basic_authenticator)
.manage(keys)
.attach(token_getter_cors_options))
}
}
pub fn launch<B: auth::AuthenticatorConfiguration<auth::Basic>>(
config: Configuration<B>,
) -> rocket::error::LaunchError {
let rocket = config.ignite().unwrap_or_else(|e| panic!("{}", e));
rocket.mount("/", routes()).launch()
}
pub(crate) fn rng() -> &'static SystemRandom {
use std::ops::Deref;
lazy_static! {
static ref RANDOM: SystemRandom = SystemRandom::new();
}
RANDOM.deref()
}
#[cfg(test)]
mod tests {
use std::str::FromStr;
use serde_test::{assert_tokens, Token};
use super::*;
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq)]
struct TestUrl {
url: Url,
}
#[derive(Clone, Serialize, Deserialize, Debug, Eq, PartialEq)]
struct TestClaims {
company: String,
department: String,
}
impl Default for TestClaims {
fn default() -> Self {
TestClaims {
company: "ACME".to_string(),
department: "Toilet Cleaning".to_string(),
}
}
}
#[test]
fn url_serialization_token_round_trip() {
let test = TestUrl {
url: not_err!(Url::from_str("https://www.example.com/")),
};
assert_tokens(
&test,
&[
Token::Struct {
name: "TestUrl",
len: 1,
},
Token::Str("url"),
Token::Str("https://www.example.com/"),
Token::StructEnd,
],
);
}
}