From 0c9a77c39b9baad823dcc5dece2f895302d56bbd Mon Sep 17 00:00:00 2001 From: William Brown Date: Thu, 31 Jan 2019 14:29:14 +1000 Subject: [PATCH] Add basic cookie tracking to auth endpoint --- src/lib/core.rs | 124 ++++++++++++++++++++++++++++++-------------- src/lib/event.rs | 28 +++++++++- src/lib/modify.rs | 29 +++++++++++ src/lib/proto_v1.rs | 49 +++++++++++------ src/lib/server.rs | 16 ++++++ 5 files changed, 190 insertions(+), 56 deletions(-) create mode 100644 src/lib/modify.rs diff --git a/src/lib/core.rs b/src/lib/core.rs index 147e1479d..51e0c076b 100644 --- a/src/lib/core.rs +++ b/src/lib/core.rs @@ -11,10 +11,10 @@ use futures::{future, Future, Stream}; use super::config::Configuration; // SearchResult -use super::event::{CreateEvent, SearchEvent}; +use super::event::{CreateEvent, SearchEvent, AuthEvent}; use super::filter::Filter; use super::log; -use super::proto_v1::{CreateRequest, SearchRequest}; +use super::proto_v1::{CreateRequest, SearchRequest, AuthRequest, AuthResponse}; use super::server; struct AppState { @@ -85,43 +85,6 @@ macro_rules! json_event_decode { // Handle the various end points we need to expose -/// simple handle -fn index(req: &HttpRequest) -> HttpResponse { - println!("{:?}", req); - - HttpResponse::Ok().body("Hello\n") -} - -fn class_list((_name, state): (Path, State)) -> FutureResponse { - // println!("request to class_list"); - let filt = Filter::Pres(String::from("objectclass")); - - state - .qe - .send( - // This is where we need to parse the request into an event - // LONG TERM - // Make a search REQUEST, and create the audit struct here, then - // pass it to the server - // - // FIXME: Don't use SEARCHEVENT here!!!! - // - SearchEvent::from_request(SearchRequest::new(filt)), - ) - // TODO: How to time this part of the code? - // What does this do? - .from_err() - .and_then(|res| match res { - // What type is entry? - Ok(search_result) => Ok(HttpResponse::Ok().json(search_result.response())), - // Ok(_) => Ok(HttpResponse::Ok().into()), - // Can we properly report this? - Err(_) => Ok(HttpResponse::InternalServerError().into()), - }) - // What does this do? - .responder() -} - fn create( (req, state): (HttpRequest, State), ) -> impl Future { @@ -134,6 +97,78 @@ fn search( json_event_decode!(req, state, SearchEvent, SearchResponse, SearchRequest) } +// delete, modify + +fn auth( + (req, state): (HttpRequest, State), +) -> impl Future { + let max_size = state.max_size; + + req.payload() + .from_err() + .fold(BytesMut::new(), move |mut body, chunk| { + // limit max size of in-memory payload + if (body.len() + chunk.len()) > max_size { + Err(error::ErrorBadRequest("overflow")) + } else { + body.extend_from_slice(&chunk); + Ok(body) + } + }) + .and_then( + move |body| -> Box> { + let r_obj = serde_json::from_slice::(&body); + + // Send to the db for action + match r_obj { + Ok(obj) => { + // First, deal with some state management. + // Do anything here first that's needed like getting the session details + // out of the req cookie. + let mut counter = 1; + + // TODO: Make this NOT UNWRAP. From the actix source unwrap here + // seems to be related to the serde_json deserialise of the cookie + // content, and because we control it's get/set it SHOULD be find + // provided we use secure cookies. But we can't always trust that ... + if let Some(count) = req.session().get::("counter").unwrap() { + println!("SESSION value: {}", count); + counter = count + 1; + req.session().set("counter", counter).unwrap(); + } else { + println!("INIT value: {}", counter); + req.session().set("counter", counter).unwrap(); + }; + + + // We probably need to know if we allocate the cookie, that this is a + // new session, and in that case, anything *except* authrequest init is + // invalid. + + let res = state + .qe + .send( + AuthEvent::from_request(obj), + ) + .from_err() + .and_then(|res| match res { + Ok(event_result) => { + Ok(HttpResponse::Ok().json(event_result.response())) + } + Err(e) => Ok(HttpResponse::InternalServerError().json(e)), + }); + + Box::new(res) + } + Err(e) => Box::new(future::err(error::ErrorBadRequest(format!( + "Json Decode Failed: {:?}", + e + )))), + } + }, + ) +} + fn whoami(req: &HttpRequest) -> Result<&'static str> { println!("{:?}", req); @@ -190,9 +225,10 @@ pub fn create_server_core(config: Configuration) { .http_only(true) .name("rsidm-session") // This forces https only + // TODO: Make this a config value .secure(false), )) - .resource("/", |r| r.f(index)) + // .resource("/", |r| r.f(index)) // curl --header ...? .resource("/v1/whoami", |r| r.f(whoami)) // .resource("/v1/login", ...) @@ -209,10 +245,18 @@ pub fn create_server_core(config: Configuration) { .resource("/v1/search", |r| { r.method(http::Method::POST).with_async(search) }) + + // This is one of the times we need cookies :) + // curl -b /tmp/cookie.jar -c /tmp/cookie.jar --header "Content-Type: application/json" --request POST --data '{ "state" : { "Init": ["Anonymous", []] }}' http://127.0.0.1:8080/v1/auth + .resource("/v1/auth", |r| { + r.method(http::Method::POST).with_async(auth) + }) // Add an ldap compat search function type? + /* .resource("/v1/list/{class_list}", |r| { r.method(http::Method::GET).with(class_list) }) + */ }) .bind(config.address) .unwrap() diff --git a/src/lib/event.rs b/src/lib/event.rs index f6c93c292..09bc70225 100644 --- a/src/lib/event.rs +++ b/src/lib/event.rs @@ -1,6 +1,6 @@ use super::filter::Filter; use super::proto_v1::Entry as ProtoEntry; -use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse}; +use super::proto_v1::{CreateRequest, Response, SearchRequest, SearchResponse, AuthRequest, AuthResponse, AuthStatus}; use actix::prelude::*; use entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; use error::OperationError; @@ -185,3 +185,29 @@ impl ModifyEvent { } } } + +#[derive(Debug)] +pub struct AuthEvent { +} + +impl Message for AuthEvent { + type Result = Result; +} + +impl AuthEvent { + pub fn from_request(request: AuthRequest) -> Self { + AuthEvent {} + } + +} + +pub struct AuthResult { +} + +impl AuthResult { + pub fn response(self) -> AuthResponse { + AuthResponse { + status: AuthStatus::Begin(String::from("hello")) + } + } +} diff --git a/src/lib/modify.rs b/src/lib/modify.rs new file mode 100644 index 000000000..24684eabf --- /dev/null +++ b/src/lib/modify.rs @@ -0,0 +1,29 @@ +#[derive(Serialize, Deserialize, Debug)] +pub enum Modify { + // This value *should* exist. + Present(String, String), + // This value *should not* exist. + Removed(String, String), + // This attr *should not* exist. + Purged(String), +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ModifyList { + // And ordered list of changes to apply. Should this be state based? + pub mods: Vec, +} + +impl ModifyList { + pub fn new() -> Self { + ModifyList { mods: Vec::new() } + } + + pub fn new_list(mods: Vec) -> Self { + ModifyList { mods: mods } + } + + pub fn push_mod(&mut self, modify: Modify) { + self.mods.push(modify) + } +} diff --git a/src/lib/proto_v1.rs b/src/lib/proto_v1.rs index 13e2b6031..dc1d767ea 100644 --- a/src/lib/proto_v1.rs +++ b/src/lib/proto_v1.rs @@ -76,22 +76,41 @@ impl CreateRequest { // On loginSuccess, we send a cookie, and that allows the token to be // generated. The cookie can be shared between servers. -// Request auth for identity X -pub struct AuthRequest {} + + +#[derive(Debug, Serialize, Deserialize)] +pub enum AuthState { + Init(String, Vec), + /* + Step( + Type(params ....) + ), + */ +} + +// Request auth for identity X with roles Y? +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthRequest { + pub state: AuthState +} // Respond with the list of auth types and nonce, etc. -pub struct AuthResponse {} +// It can also contain a denied, or success. +#[derive(Debug, Serialize, Deserialize)] +pub enum AuthStatus { + Begin(String), // uuid of this session. + // Continue, // Keep going, here are the things you could still provide ... + // Go away, you made a mistake somewhere. + // Provide reason? + // Denied(String), + // Welcome friend. + // On success provide entry "self", for group assertions? + // We also provide the "cookie"/token? + // Success(String, Entry), +} -// Provide responses -pub struct AuthProvide {} +#[derive(Debug, Serialize, Deserialize)] +pub struct AuthResponse { + pub status: AuthStatus, +} -// After authprovide, we can move to AuthResponse (for more) -// or below ... - -// Go away. -// Provide reason? -pub struct AuthDenied {} - -// Welcome friend. -// On success provide entry "self", for group assertions? -pub struct AuthSuccess {} diff --git a/src/lib/server.rs b/src/lib/server.rs index 847ebe111..3368ea2ef 100644 --- a/src/lib/server.rs +++ b/src/lib/server.rs @@ -14,6 +14,7 @@ use entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid}; use error::{OperationError, SchemaError}; use event::{ CreateEvent, DeleteEvent, ExistsEvent, ModifyEvent, OpResult, SearchEvent, SearchResult, + AuthEvent, AuthResult, }; use filter::Filter; use log::EventLog; @@ -695,6 +696,21 @@ impl Handler for QueryServer { } } +impl Handler for QueryServer { + type Result = Result; + + fn handle(&mut self, msg: AuthEvent, _: &mut Self::Context) -> Self::Result { + let mut audit = AuditScope::new("auth"); + let res = audit_segment!(&mut audit, || { + audit_log!(audit, "Begin auth event {:?}", msg); + Err(OperationError::InvalidState) + }); + // At the end of the event we send it for logging. + self.log.do_send(audit); + res + } +} + // Auth requests? How do we structure these ... #[cfg(test)]