extern crate actix;
extern crate actix_web;
extern crate base64;
extern crate chrono;
extern crate failure;
extern crate futures;
extern crate rand;
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate failure_derive;
#[macro_use]
extern crate log;
mod sql;
use chrono::prelude::Utc;
use chrono::NaiveDateTime;
use std::rc::Rc;
use failure::Error;
use actix::Addr;
use actix_web::error::{self, Error as ActixWebError};
use actix_web::http::header::HeaderValue;
use actix_web::middleware::identity::{Identity, IdentityPolicy};
use actix_web::middleware::Response as MiddlewareResponse;
use actix_web::{HttpMessage, HttpRequest, HttpResponse};
use futures::future::{err as FutErr, ok as FutOk};
use futures::Future;
use sql::{DeleteIdentity, FindIdentity, SqlActor, SqlIdentityModel, UpdateIdentity, Variant};
use rand::Rng;
const DEFAULT_RESPONSE_HDR: &'static str = "X-Actix-Auth";
const DEFAULT_POOL_SIZE: usize = 3;
#[derive(Debug, Fail)]
enum SqlIdentityError {
#[allow(dead_code)]
#[fail(display = "sql variant not supported")]
SqlVariantNotSupported,
#[fail(display = "token not found")]
TokenNotFound,
#[fail(display = "token failed to set in header")]
TokenNotSet,
#[fail(display = "token not provided but required, bad request")]
TokenRequired,
}
enum SqlIdentityState {
Created,
Updated,
Deleted,
Unchanged,
}
pub struct SqlIdentity {
id: i64,
state: SqlIdentityState,
identity: Option<String>,
token: Option<String>,
ip: Option<String>,
user_agent: Option<String>,
created: NaiveDateTime,
inner: Rc<SqlIdentityInner>,
}
impl Identity for SqlIdentity {
fn identity(&self) -> Option<&str> {
self.identity.as_ref().map(|s| s.as_ref())
}
fn remember(&mut self, value: String) {
self.identity = Some(value);
let mut arr = [0u8; 24];
rand::thread_rng().fill(&mut arr[..]);
self.token = Some(base64::encode(&arr));
self.state = SqlIdentityState::Created;
}
fn forget(&mut self) {
self.identity = None;
self.state = SqlIdentityState::Deleted;
}
fn write(&mut self, resp: HttpResponse) -> Result<MiddlewareResponse, ActixWebError> {
match self.state {
SqlIdentityState::Created => {
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Future(self.inner.create(self, resp)))
},
SqlIdentityState::Updated if self.token.is_some() && self.identity.is_some() => {
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Future(self.inner.save(self, resp)))
}
SqlIdentityState::Deleted if self.token.is_some() => {
let token = self.token.as_ref().expect("[SIS::Deleted] Token is None!");
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Future(self.inner.remove(token, resp)))
}
SqlIdentityState::Deleted | SqlIdentityState::Updated => {
Err(error::ErrorBadRequest(SqlIdentityError::TokenRequired))
}
_ => {
self.state = SqlIdentityState::Unchanged;
Ok(MiddlewareResponse::Done(resp))
}
}
}
}
struct SqlIdentityInner {
addr: Addr<SqlActor>,
hdr: &'static str,
}
impl SqlIdentityInner {
fn new(addr: Addr<SqlActor>, hdr: &'static str) -> SqlIdentityInner {
SqlIdentityInner { addr, hdr }
}
fn create(&self, identity: &SqlIdentity, mut resp: HttpResponse) -> Box<Future<Item = HttpResponse, Error = ActixWebError>> {
if let Some(ref token) = identity.token {
let headers = resp.headers_mut();
if let Ok(token) = token.parse() {
headers.append(self.hdr, token);
} else {
error!("Failed to parse token to place in header!");
return Box::new(FutErr(error::ErrorInternalServerError(
SqlIdentityError::TokenNotSet,
)));
}
} else {
error!("Identity token not set!");
return Box::new(FutErr(error::ErrorUnauthorized(
SqlIdentityError::TokenNotFound,
)));
}
Box::new(
self.addr
.send(UpdateIdentity::create(identity))
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(_) => Ok(resp),
Err(e) => {
error!("ERROR: {:?}", e);
Err(error::ErrorInternalServerError(e))
}
}),
)
}
fn save(
&self,
identity: &SqlIdentity,
resp: HttpResponse,
) -> Box<Future<Item = HttpResponse, Error = ActixWebError>> {
Box::new(
self.addr
.send(UpdateIdentity::update(identity))
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(_) => Ok(resp),
Err(e) => {
error!("ERROR: {:?}", e);
Err(error::ErrorInternalServerError(e))
}
}),
)
}
fn remove(
&self,
token: &str,
resp: HttpResponse,
) -> Box<Future<Item = HttpResponse, Error = ActixWebError>> {
Box::new(
self.addr
.send(DeleteIdentity {
token: token.to_string(),
})
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(_) => Ok(resp),
Err(e) => {
error!("ERROR: {:?}", e);
Err(error::ErrorInternalServerError(e))
}
}),
)
}
fn load<S>(
&self,
req: &HttpRequest<S>,
) -> Box<Future<Item = Option<SqlIdentityModel>, Error = ActixWebError>> {
let headers = req.headers();
let auth_header = headers.get("Authorization");
if let Some(auth_header) = auth_header {
if let Ok(auth_header) = auth_header.to_str() {
let mut iter = auth_header.split(' ');
let scheme = iter.next();
let token = iter.next();
if scheme.is_some() && token.is_some() {
let _scheme = scheme.expect("[SII::load] Scheme is None!");
let token = token.expect("[SII::load] Token is None!");
return Box::new(
self.addr
.send(FindIdentity {
token: token.to_string(),
})
.map_err(ActixWebError::from)
.and_then(move |res| match res {
Ok(val) => Ok(Some(val)),
Err(e) => {
warn!("WARN: {:?}", e);
Ok(None)
}
}),
);
}
}
}
Box::new(FutOk(None))
}
}
#[derive(Clone)]
pub struct SqlIdentityPolicy(Rc<SqlIdentityInner>);
#[derive(Clone)]
pub struct SqlIdentityBuilder {
pool: usize,
uri: String,
hdr: &'static str,
variant: Variant,
}
impl SqlIdentityBuilder {
pub fn new<T: Into<String>>(uri: T) -> SqlIdentityBuilder {
let uri: String = uri.into();
let variant = SqlIdentityBuilder::variant(&uri);
SqlIdentityBuilder {
pool: DEFAULT_POOL_SIZE,
uri: uri,
hdr: DEFAULT_RESPONSE_HDR,
variant: variant,
}
}
fn variant(uri: &str) -> Variant {
if uri.starts_with("mysql://") {
return Variant::Mysql;
} else if uri.starts_with("postgres://") || uri.starts_with("postgresql://") {
return Variant::Pg;
} else {
return Variant::Sqlite;
}
}
pub fn response_header(mut self, hdr: &'static str) -> SqlIdentityBuilder {
self.hdr = hdr;
self
}
pub fn pool_size(mut self, count: usize) -> SqlIdentityBuilder {
self.pool = count;
self
}
pub fn finish(self) -> Result<SqlIdentityPolicy, Error> {
info!("Registering identity provider: {:?}", self.variant);
Ok(SqlIdentityPolicy(Rc::new(SqlIdentityInner::new(
match self.variant {
Variant::Sqlite => SqlActor::sqlite(self.pool, &self.uri)?,
Variant::Mysql => SqlActor::mysql(self.pool, &self.uri)?,
Variant::Pg => SqlActor::pg(self.pool, &self.uri)?,
},
self.hdr,
))))
}
pub fn sqlite(mut self) -> SqlIdentityBuilder {
self.variant = Variant::Sqlite;
self
}
pub fn mysql(mut self) -> SqlIdentityBuilder {
self.variant = Variant::Mysql;
self
}
pub fn postgresql(mut self) -> SqlIdentityBuilder {
self.variant = Variant::Pg;
self
}
}
impl<S> IdentityPolicy<S> for SqlIdentityPolicy {
type Identity = SqlIdentity;
type Future = Box<Future<Item = SqlIdentity, Error = ActixWebError>>;
fn from_request(&self, req: &HttpRequest<S>) -> Self::Future {
let inner = Rc::clone(&self.0);
let ip = req.connection_info()
.remote()
.unwrap_or("0.0.0.0")
.to_owned();
let unk = HeaderValue::from_static("Unknown");
let ua = req.headers()
.get("user-agent")
.unwrap_or(&unk)
.to_str()
.unwrap_or("Unknown")
.to_owned();
Box::new(self.0.load(req).map(move |ident| {
if let Some(id) = ident {
let uip = match id.ip {
Some(ref nip) if &ip == nip => nip.clone(),
_ => ip,
};
SqlIdentity {
id: id.id,
identity: Some(id.userid),
token: Some(id.token),
ip: Some(uip),
user_agent: Some(ua),
created: id.created,
state: SqlIdentityState::Updated,
inner: inner,
}
} else {
SqlIdentity {
id: -1,
identity: None,
token: None,
ip: Some(ip),
user_agent: Some(ua),
created: Utc::now().naive_utc(),
state: SqlIdentityState::Unchanged,
inner: inner,
}
}
}))
}
}