mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Add basic cookie tracking to auth endpoint
This commit is contained in:
parent
63e600d49e
commit
0c9a77c39b
124
src/lib/core.rs
124
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<AppState>) -> HttpResponse {
|
||||
println!("{:?}", req);
|
||||
|
||||
HttpResponse::Ok().body("Hello\n")
|
||||
}
|
||||
|
||||
fn class_list((_name, state): (Path<String>, State<AppState>)) -> FutureResponse<HttpResponse> {
|
||||
// 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<AppState>, State<AppState>),
|
||||
) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
|
@ -134,6 +97,78 @@ fn search(
|
|||
json_event_decode!(req, state, SearchEvent, SearchResponse, SearchRequest)
|
||||
}
|
||||
|
||||
// delete, modify
|
||||
|
||||
fn auth(
|
||||
(req, state): (HttpRequest<AppState>, State<AppState>),
|
||||
) -> impl Future<Item = HttpResponse, Error = Error> {
|
||||
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<Future<Item = HttpResponse, Error = Error>> {
|
||||
let r_obj = serde_json::from_slice::<AuthRequest>(&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::<i32>("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<AppState>) -> 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()
|
||||
|
|
|
@ -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<AuthResult, OperationError>;
|
||||
}
|
||||
|
||||
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"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
src/lib/modify.rs
Normal file
29
src/lib/modify.rs
Normal file
|
@ -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<Modify>,
|
||||
}
|
||||
|
||||
impl ModifyList {
|
||||
pub fn new() -> Self {
|
||||
ModifyList { mods: Vec::new() }
|
||||
}
|
||||
|
||||
pub fn new_list(mods: Vec<Modify>) -> Self {
|
||||
ModifyList { mods: mods }
|
||||
}
|
||||
|
||||
pub fn push_mod(&mut self, modify: Modify) {
|
||||
self.mods.push(modify)
|
||||
}
|
||||
}
|
|
@ -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<String>),
|
||||
/*
|
||||
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 {}
|
||||
|
|
|
@ -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<CreateEvent> for QueryServer {
|
|||
}
|
||||
}
|
||||
|
||||
impl Handler<AuthEvent> for QueryServer {
|
||||
type Result = Result<AuthResult, OperationError>;
|
||||
|
||||
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)]
|
||||
|
|
Loading…
Reference in a new issue