Add auth session header type (#398)

This commit is contained in:
Firstyear 2021-04-01 07:14:15 +10:00 committed by GitHub
parent 8a2f3b65ec
commit 988944a085
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 1066 additions and 843 deletions

143
Cargo.lock generated
View file

@ -82,9 +82,9 @@ dependencies = [
[[package]]
name = "anyhow"
version = "1.0.39"
version = "1.0.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cddc5f91628367664cc7c69714ff08deee8a3efc54623011c772544d7b2767"
checksum = "28b2cd92db5cbd74e8e5028f7e27dd7aa3090e89e4f2a197cc7c8dfb69c7063b"
[[package]]
name = "anymap"
@ -273,7 +273,7 @@ dependencies = [
"async-io",
"async-lock",
"async-process",
"crossbeam-utils 0.8.3",
"crossbeam-utils",
"futures-channel",
"futures-core",
"futures-io",
@ -512,11 +512,11 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byte-pool"
version = "0.2.2"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38e98299d518ec351ca016363e0cbfc77059dcd08dfa9700d15e405536097a"
checksum = "f8c7230ddbb427b1094d477d821a99f3f54d36333178eeb806e279bcdcecf0ca"
dependencies = [
"crossbeam-queue 0.2.3",
"crossbeam-queue",
"stable_deref_trait",
]
@ -634,7 +634,7 @@ dependencies = [
"ahash 0.7.2",
"crossbeam",
"crossbeam-epoch",
"crossbeam-utils 0.8.3",
"crossbeam-utils",
"num",
"packed_simd_2",
"parking_lot",
@ -663,9 +663,9 @@ dependencies = [
[[package]]
name = "const_fn"
version = "0.4.5"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6"
checksum = "076a6803b0dacd6a88cfe64deba628b01533ff5ef265687e6938280c1afd0a28"
[[package]]
name = "constant_time_eq"
@ -780,8 +780,8 @@ dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue 0.3.1",
"crossbeam-utils 0.8.3",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
@ -791,7 +791,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.3",
"crossbeam-utils",
]
[[package]]
@ -802,7 +802,7 @@ checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-epoch",
"crossbeam-utils 0.8.3",
"crossbeam-utils",
]
[[package]]
@ -812,23 +812,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.3",
"crossbeam-utils",
"lazy_static",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-queue"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570"
dependencies = [
"cfg-if 0.1.10",
"crossbeam-utils 0.7.2",
"maybe-uninit",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.1"
@ -836,18 +825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756"
dependencies = [
"cfg-if 1.0.0",
"crossbeam-utils 0.8.3",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
"autocfg",
"cfg-if 0.1.10",
"lazy_static",
"crossbeam-utils",
]
[[package]]
@ -1414,9 +1392,9 @@ dependencies = [
[[package]]
name = "h2"
version = "0.3.1"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d832b01df74254fe364568d6ddc294443f61cbec82816b60904303af87efae78"
checksum = "fc018e188373e2777d0ef2467ebff62a08e66c3f5857b23c8fbec3018210dc00"
dependencies = [
"bytes",
"fnv",
@ -1571,9 +1549,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "hyper"
version = "0.14.4"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8e946c2b1349055e0b72ae281b238baf1a3ea7307c7e9f9d64673bdd9c26ac7"
checksum = "8bf09f61b52cfcf4c00de50df88ae423d6c02354e385a86341133b5338630ad1"
dependencies = [
"bytes",
"futures-channel",
@ -1586,7 +1564,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project",
"socket2 0.3.19",
"socket2",
"tokio",
"tower-service",
"tracing",
@ -1713,9 +1691,9 @@ dependencies = [
[[package]]
name = "js-sys"
version = "0.3.49"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc15e39392125075f60c95ba416f5381ff6c3a948ff02ab12464715adf56c821"
checksum = "2d99f9e3e84b8f67f846ef5b4cbbc3b1c29f6c759fcbce6f01aa0e73d932a24c"
dependencies = [
"wasm-bindgen",
]
@ -1923,9 +1901,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.91"
version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8916b1f6ca17130ec6568feccee27c156ad12037880833a3b842a823236502e7"
checksum = "56d855069fafbb9b344c0f962150cd2c1187975cb1c22c1522c240d8c4986714"
[[package]]
name = "libm"
@ -2023,12 +2001,6 @@ version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.3.4"
@ -2037,9 +2009,9 @@ checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
[[package]]
name = "memoffset"
version = "0.6.1"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87"
checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d"
dependencies = [
"autocfg",
]
@ -2097,7 +2069,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19900e7eee95eb2b3c2e26d12a874cc80aaf750e31be6fcbe743ead369fa45d"
dependencies = [
"libc",
"socket2 0.4.0",
"socket2",
]
[[package]]
@ -2384,18 +2356,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96fa8ebb90271c4477f144354485b8068bd8f6b78b428b01ba892ca26caf0b63"
checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.0.5"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "758669ae3558c6f74bd2a18b41f7ac0b5a195aea6639d6a9b5e5d1ad5ba24c0b"
checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5"
dependencies = [
"proc-macro2",
"quote",
@ -2691,7 +2663,7 @@ checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils 0.8.3",
"crossbeam-utils",
"lazy_static",
"num_cpus",
]
@ -2915,9 +2887,9 @@ dependencies = [
[[package]]
name = "security-framework"
version = "2.1.2"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d493c5f39e02dfb062cd8f33301f90f9b13b650e8c1b1d0fd75c19dd64bff69d"
checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84"
dependencies = [
"bitflags",
"core-foundation",
@ -2928,9 +2900,9 @@ dependencies = [
[[package]]
name = "security-framework-sys"
version = "2.1.1"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dee48cdde5ed250b0d3252818f646e174ab414036edb884dde62d80a3ac6082d"
checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339"
dependencies = [
"core-foundation-sys",
"libc",
@ -3118,17 +3090,6 @@ dependencies = [
"static_assertions",
]
[[package]]
name = "socket2"
version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "122e570113d28d773067fab24266b66753f6ea915758651696b6e35e49f88d6e"
dependencies = [
"cfg-if 1.0.0",
"libc",
"winapi",
]
[[package]]
name = "socket2"
version = "0.4.0"
@ -3270,9 +3231,9 @@ checksum = "45f6ee7c7b87caf59549e9fe45d6a69c75c8019e79e212a835c5da0e92f0ba08"
[[package]]
name = "syn"
version = "1.0.64"
version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fd9d1e9976102a03c542daa2eff1b43f9d72306342f3f8b3ed5fb8908195d6f"
checksum = "6498a9efc342871f91cc2d0d694c674368b4ceb40f62b65a7a08c3792935e702"
dependencies = [
"proc-macro2",
"quote",
@ -3734,9 +3695,9 @@ checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
[[package]]
name = "wasm-bindgen"
version = "0.2.72"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fe8f61dba8e5d645a4d8132dc7a0a66861ed5e1045d2c0ed940fab33bac0fbe"
checksum = "83240549659d187488f91f33c0f8547cbfef0b2088bc470c116d1d260ef623d9"
dependencies = [
"cfg-if 1.0.0",
"serde",
@ -3746,9 +3707,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.72"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "046ceba58ff062da072c7cb4ba5b22a37f00a302483f7e2a6cdc18fedbdc1fd3"
checksum = "ae70622411ca953215ca6d06d3ebeb1e915f0f6613e3b495122878d7ebec7dae"
dependencies = [
"bumpalo",
"lazy_static",
@ -3761,9 +3722,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-futures"
version = "0.4.22"
version = "0.4.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73157efb9af26fb564bb59a009afd1c7c334a44db171d280690d0c3faaec3468"
checksum = "81b8b767af23de6ac18bf2168b690bed2902743ddf0fb39252e36f9e2bfc63ea"
dependencies = [
"cfg-if 1.0.0",
"js-sys",
@ -3773,9 +3734,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.72"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ef9aa01d36cda046f797c57959ff5f3c615c9cc63997a8d545831ec7976819b"
checksum = "3e734d91443f177bfdb41969de821e15c516931c3c3db3d318fa1b68975d0f6f"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -3783,9 +3744,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.72"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96eb45c1b2ee33545a813a92dbb53856418bf7eb54ab34f7f7ff1448a5b3735d"
checksum = "d53739ff08c8a68b0fdbcd54c372b8ab800b1449ab3c9d706503bc7dd1621b2c"
dependencies = [
"proc-macro2",
"quote",
@ -3796,15 +3757,15 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.72"
version = "0.2.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7148f4696fb4960a346eaa60bbfb42a1ac4ebba21f750f75fc1375b098d5ffa"
checksum = "d9a543ae66aa233d14bb765ed9af4a33e81b8b58d1584cf1b47ff8cd0b9e4489"
[[package]]
name = "web-sys"
version = "0.3.49"
version = "0.3.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59fe19d70f5dacc03f6e46777213facae5ac3801575d56ca6cbd4c93dcd12310"
checksum = "a905d57e488fec8861446d3393670fb50d27a262344013181c2cdf9fff5481be"
dependencies = [
"js-sys",
"wasm-bindgen",

View file

@ -21,6 +21,7 @@ toml = "0.5"
uuid = { version = "0.8", features = ["serde", "v4"] }
url = "2.1.1"
webauthn-rs = "0.3.0-alpha.7"
tokio = { version = "1", features = ["rt", "net", "time", "macros", "sync", "signal"] }
[dev-dependencies]
tokio = { version = "1", features = ["rt", "net", "time", "macros", "sync", "signal"] }

View file

@ -1,8 +1,15 @@
use crate::{ClientError, KanidmClientBuilder, APPLICATION_JSON, KOPID};
use crate::{ClientError, KanidmClientBuilder, APPLICATION_JSON, KOPID, KSESSIONID};
use reqwest::header::CONTENT_TYPE;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::collections::BTreeMap;
use std::collections::BTreeSet as Set;
use uuid::Uuid;
use webauthn_rs::proto::{
CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential,
RequestChallengeResponse,
};
use kanidm_proto::v1::*;
@ -10,11 +17,107 @@ use kanidm_proto::v1::*;
pub struct KanidmAsyncClient {
pub(crate) client: reqwest::Client,
pub(crate) addr: String,
pub(crate) origin: String,
pub(crate) builder: KanidmClientBuilder,
pub(crate) bearer_token: Option<String>,
pub(crate) auth_session_id: Option<String>,
}
impl KanidmAsyncClient {
pub fn get_origin(&self) -> &str {
self.origin.as_str()
}
pub fn set_token(&mut self, new_token: String) {
let mut new_token = Some(new_token);
std::mem::swap(&mut self.bearer_token, &mut new_token);
}
pub fn get_token(&self) -> Option<&str> {
self.bearer_token.as_deref()
}
pub fn new_session(&self) -> Result<Self, reqwest::Error> {
// Copy our builder, and then just process it.
let builder = self.builder.clone();
builder.build_async()
}
pub fn logout(&mut self) -> Result<(), reqwest::Error> {
// hack - we have to replace our reqwest client because that's the only way
// to currently flush the cookie store. To achieve this we need to rebuild
// and then destructure.
let builder = self.builder.clone();
let KanidmAsyncClient { mut client, .. } = builder.build_async()?;
std::mem::swap(&mut self.client, &mut client);
Ok(())
}
async fn perform_auth_post_request<R: Serialize, T: DeserializeOwned>(
&mut self,
dest: &str,
request: R,
) -> Result<T, ClientError> {
let dest = [self.addr.as_str(), dest].concat();
debug!("{:?}", dest);
// format doesn't work in async ?!
// let dest = format!("{}{}", self.addr, dest);
let req_string = serde_json::to_string(&request).map_err(ClientError::JSONEncode)?;
let response = self
.client
.post(dest.as_str())
.body(req_string)
.header(CONTENT_TYPE, APPLICATION_JSON);
let response = if let Some(token) = &self.bearer_token {
response.bearer_auth(token)
} else {
response
};
// If we have a session header, set it now.
let response = if let Some(sessionid) = &self.auth_session_id {
response.header(KSESSIONID, sessionid)
} else {
response
};
let response = response.send().await.map_err(ClientError::Transport)?;
// If we have a sessionid header in the response, get it now.
let headers = response.headers();
self.auth_session_id = headers
.get(KSESSIONID)
.map(|hv| hv.to_str().ok().map(|s| s.to_string()))
.flatten();
let opid = headers
.get(KOPID)
.and_then(|hv| hv.to_str().ok().map(|s| s.to_string()))
.unwrap_or_else(|| "missing_kopid".to_string());
debug!("opid -> {:?}", opid);
match response.status() {
reqwest::StatusCode::OK => {}
unexpect => {
return Err(ClientError::Http(
unexpect,
response.json().await.ok(),
opid,
))
}
}
response
.json()
.await
.map_err(|e| ClientError::JSONDecode(e, opid))
}
async fn perform_post_request<R: Serialize, T: DeserializeOwned>(
&self,
dest: &str,
@ -190,14 +293,16 @@ impl KanidmAsyncClient {
.map_err(|e| ClientError::JSONDecode(e, opid))
}
pub async fn auth_step_init(&self, ident: &str) -> Result<Set<AuthMech>, ClientError> {
pub async fn auth_step_init(&mut self, ident: &str) -> Result<Set<AuthMech>, ClientError> {
let auth_init = AuthRequest {
step: AuthStep::Init(ident.to_string()),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_init).await;
let r: Result<AuthResponse, _> =
self.perform_auth_post_request("/v1/auth", auth_init).await;
r.map(|v| {
debug!("Authentication Session ID -> {:?}", v.sessionid);
// Stash the session ID header.
v.state
})
.and_then(|state| match state {
@ -207,12 +312,16 @@ impl KanidmAsyncClient {
.map(|mechs| mechs.into_iter().collect())
}
pub async fn auth_step_begin(&self, mech: AuthMech) -> Result<Vec<AuthAllowed>, ClientError> {
pub async fn auth_step_begin(
&mut self,
mech: AuthMech,
) -> Result<Vec<AuthAllowed>, ClientError> {
let auth_begin = AuthRequest {
step: AuthStep::Begin(mech),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_begin).await;
let r: Result<AuthResponse, _> =
self.perform_auth_post_request("/v1/auth", auth_begin).await;
r.map(|v| {
debug!("Authentication Session ID -> {:?}", v.sessionid);
v.state
@ -225,6 +334,96 @@ impl KanidmAsyncClient {
// .map(|allowed| allowed.into_iter().collect())
}
pub async fn auth_step_anonymous(&mut self) -> Result<AuthResponse, ClientError> {
let auth_anon = AuthRequest {
step: AuthStep::Cred(AuthCredential::Anonymous),
};
let r: Result<AuthResponse, _> =
self.perform_auth_post_request("/v1/auth", auth_anon).await;
r.map(|ar| {
if let AuthState::Success(token) = &ar.state {
self.bearer_token = Some(token.clone());
};
ar
})
}
pub async fn auth_step_password(
&mut self,
password: &str,
) -> Result<AuthResponse, ClientError> {
let auth_req = AuthRequest {
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
};
let r: Result<AuthResponse, _> = self.perform_auth_post_request("/v1/auth", auth_req).await;
r.map(|ar| {
if let AuthState::Success(token) = &ar.state {
self.bearer_token = Some(token.clone());
};
ar
})
}
pub async fn auth_step_totp(&mut self, totp: u32) -> Result<AuthResponse, ClientError> {
let auth_req = AuthRequest {
step: AuthStep::Cred(AuthCredential::TOTP(totp)),
};
let r: Result<AuthResponse, _> = self.perform_auth_post_request("/v1/auth", auth_req).await;
r.map(|ar| {
if let AuthState::Success(token) = &ar.state {
self.bearer_token = Some(token.clone());
};
ar
})
}
pub async fn auth_step_webauthn_complete(
&mut self,
pkc: PublicKeyCredential,
) -> Result<AuthResponse, ClientError> {
let auth_req = AuthRequest {
step: AuthStep::Cred(AuthCredential::Webauthn(pkc)),
};
let r: Result<AuthResponse, _> = self.perform_auth_post_request("/v1/auth", auth_req).await;
r.map(|ar| {
if let AuthState::Success(token) = &ar.state {
self.bearer_token = Some(token.clone());
};
ar
})
}
pub async fn auth_anonymous(&mut self) -> Result<(), ClientError> {
let mechs = match self.auth_step_init("anonymous").await {
Ok(s) => s,
Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Anonymous) {
debug!("Anonymous mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let _state = match self.auth_step_begin(AuthMech::Anonymous).await {
Ok(s) => s,
Err(e) => return Err(e),
};
let r = self.auth_step_anonymous().await?;
match r.state {
AuthState::Success(token) => {
self.bearer_token = Some(token);
Ok(())
}
_ => Err(ClientError::AuthenticationFailed),
}
}
pub async fn auth_simple_password(
&mut self,
ident: &str,
@ -245,50 +444,97 @@ impl KanidmAsyncClient {
Err(e) => return Err(e),
};
let auth_req = AuthRequest {
step: AuthStep::Cred(AuthCredential::Password(password.to_string())),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_req).await;
let r = r?;
let r = self.auth_step_password(password).await?;
match r.state {
AuthState::Success(token) => {
self.bearer_token = Some(token);
Ok(())
}
AuthState::Success(_) => Ok(()),
_ => Err(ClientError::AuthenticationFailed),
}
}
pub async fn auth_anonymous(&mut self) -> Result<(), ClientError> {
let mechs = match self.auth_step_init("anonymous").await {
pub async fn auth_password_totp(
&mut self,
ident: &str,
password: &str,
totp: u32,
) -> Result<(), ClientError> {
let mechs = match self.auth_step_init(ident).await {
Ok(s) => s,
Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Anonymous) {
debug!("Anonymous mech not presented");
if !mechs.contains(&AuthMech::PasswordMFA) {
debug!("PasswordMFA mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let _state = match self.auth_step_begin(AuthMech::Anonymous).await {
let state = match self.auth_step_begin(AuthMech::PasswordMFA).await {
Ok(s) => s,
Err(e) => return Err(e),
};
let auth_anon = AuthRequest {
step: AuthStep::Cred(AuthCredential::Anonymous),
};
let r: Result<AuthResponse, _> = self.perform_post_request("/v1/auth", auth_anon).await;
if !state.contains(&AuthAllowed::TOTP) {
debug!("TOTP step not offered.");
return Err(ClientError::AuthenticationFailed);
}
let r = r?;
let r = self.auth_step_totp(totp).await?;
// Should need to continue.
match r.state {
AuthState::Continue(allowed) => {
if !allowed.contains(&AuthAllowed::Password) {
debug!("Password step not offered.");
return Err(ClientError::AuthenticationFailed);
}
}
_ => {
debug!("Invalid AuthState presented.");
return Err(ClientError::AuthenticationFailed);
}
};
let r = self.auth_step_password(password).await?;
match r.state {
AuthState::Success(token) => {
self.bearer_token = Some(token);
Ok(())
}
AuthState::Success(_token) => Ok(()),
_ => Err(ClientError::AuthenticationFailed),
}
}
pub async fn auth_webauthn_begin(
&mut self,
ident: &str,
) -> Result<RequestChallengeResponse, ClientError> {
let mechs = match self.auth_step_init(ident).await {
Ok(s) => s,
Err(e) => return Err(e),
};
if !mechs.contains(&AuthMech::Webauthn) {
debug!("Webauthn mech not presented");
return Err(ClientError::AuthenticationFailed);
}
let mut state = match self.auth_step_begin(AuthMech::Webauthn).await {
Ok(s) => s,
Err(e) => return Err(e),
};
// State is now a set of auth continues.
match state.pop() {
Some(AuthAllowed::Webauthn(r)) => Ok(r),
_ => Err(ClientError::AuthenticationFailed),
}
}
pub async fn auth_webauthn_complete(
&mut self,
pkc: PublicKeyCredential,
) -> Result<(), ClientError> {
let r = self.auth_step_webauthn_complete(pkc).await?;
match r.state {
AuthState::Success(_token) => Ok(()),
_ => Err(ClientError::AuthenticationFailed),
}
}
@ -297,12 +543,15 @@ impl KanidmAsyncClient {
let whoami_dest = [self.addr.as_str(), "/v1/self"].concat();
// format!("{}/v1/self", self.addr);
debug!("{:?}", whoami_dest);
let response = self
.client
.get(whoami_dest.as_str())
.send()
.await
.map_err(ClientError::Transport)?;
let response = self.client.get(whoami_dest.as_str());
let response = if let Some(token) = &self.bearer_token {
response.bearer_auth(token)
} else {
response
};
let response = response.send().await.map_err(ClientError::Transport)?;
let opid = response
.headers()
@ -332,6 +581,170 @@ impl KanidmAsyncClient {
Ok(Some((r.youare, r.uat)))
}
// Raw DB actions
pub async fn search(&self, filter: Filter) -> Result<Vec<Entry>, ClientError> {
let sr = SearchRequest { filter };
let r: Result<SearchResponse, _> = self.perform_post_request("/v1/raw/search", sr).await;
r.map(|v| v.entries)
}
pub async fn create(&self, entries: Vec<Entry>) -> Result<bool, ClientError> {
let c = CreateRequest { entries };
let r: Result<OperationResponse, _> = self.perform_post_request("/v1/raw/create", c).await;
r.map(|_| true)
}
pub async fn modify(&self, filter: Filter, modlist: ModifyList) -> Result<bool, ClientError> {
let mr = ModifyRequest { filter, modlist };
let r: Result<OperationResponse, _> = self.perform_post_request("/v1/raw/modify", mr).await;
r.map(|_| true)
}
pub async fn delete(&self, filter: Filter) -> Result<bool, ClientError> {
let dr = DeleteRequest { filter };
let r: Result<OperationResponse, _> = self.perform_post_request("/v1/raw/delete", dr).await;
r.map(|_| true)
}
// === idm actions here ==
// ===== GROUPS
pub async fn idm_group_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/group").await
}
pub async fn idm_group_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
self.perform_get_request(format!("/v1/group/{}", id).as_str())
.await
}
pub async fn idm_group_get_members(
&self,
id: &str,
) -> Result<Option<Vec<String>>, ClientError> {
self.perform_get_request(format!("/v1/group/{}/_attr/member", id).as_str())
.await
}
pub async fn idm_group_create(&self, name: &str) -> Result<bool, ClientError> {
let mut new_group = Entry {
attrs: BTreeMap::new(),
};
new_group
.attrs
.insert("name".to_string(), vec![name.to_string()]);
self.perform_post_request("/v1/group", new_group)
.await
.map(|_: OperationResponse| true)
}
pub async fn idm_group_set_members(
&self,
id: &str,
members: &[&str],
) -> Result<bool, ClientError> {
let m: Vec<_> = members.iter().map(|v| (*v).to_string()).collect();
self.perform_put_request(format!("/v1/group/{}/_attr/member", id).as_str(), m)
.await
}
pub async fn idm_group_add_members(
&self,
id: &str,
members: &[&str],
) -> Result<bool, ClientError> {
let m: Vec<_> = members.iter().map(|v| (*v).to_string()).collect();
self.perform_post_request(["/v1/group/", id, "/_attr/member"].concat().as_str(), m)
.await
}
/*
pub fn idm_group_remove_member(&self, id: &str, member: &str) -> Result<(), ClientError> {
unimplemented!();
}
*/
pub async fn idm_group_purge_members(&self, id: &str) -> Result<bool, ClientError> {
self.perform_delete_request(format!("/v1/group/{}/_attr/member", id).as_str())
.await
}
pub async fn idm_group_unix_extend(
&self,
id: &str,
gidnumber: Option<u32>,
) -> Result<bool, ClientError> {
let gx = GroupUnixExtend { gidnumber };
self.perform_post_request(format!("/v1/group/{}/_unix", id).as_str(), gx)
.await
}
pub async fn idm_group_unix_token_get(&self, id: &str) -> Result<UnixGroupToken, ClientError> {
// Format doesn't work in async
// format!("/v1/account/{}/_unix/_token", id).as_str()
self.perform_get_request(["/v1/group/", id, "/_unix/_token"].concat().as_str())
.await
}
pub async fn idm_group_delete(&self, id: &str) -> Result<bool, ClientError> {
self.perform_delete_request(["/v1/group/", id].concat().as_str())
.await
}
// ==== ACCOUNTS
pub async fn idm_account_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/account").await
}
pub async fn idm_account_create(&self, name: &str, dn: &str) -> Result<bool, ClientError> {
let mut new_acct = Entry {
attrs: BTreeMap::new(),
};
new_acct
.attrs
.insert("name".to_string(), vec![name.to_string()]);
new_acct
.attrs
.insert("displayname".to_string(), vec![dn.to_string()]);
self.perform_post_request("/v1/account", new_acct)
.await
.map(|_: OperationResponse| true)
}
pub async fn idm_account_set_password(&self, cleartext: String) -> Result<bool, ClientError> {
let s = SingleStringRequest { value: cleartext };
let r: Result<OperationResponse, _> = self
.perform_post_request("/v1/self/_credential/primary/set_password", s)
.await;
r.map(|_| true)
}
pub async fn idm_account_set_displayname(
&self,
id: &str,
dn: &str,
) -> Result<bool, ClientError> {
self.idm_account_set_attr(id, "displayname", &[dn]).await
}
pub async fn idm_account_unix_token_get(&self, id: &str) -> Result<UnixUserToken, ClientError> {
// Format doesn't work in async
// format!("/v1/account/{}/_unix/_token", id).as_str()
self.perform_get_request(["/v1/account/", id, "/_unix/_token"].concat().as_str())
.await
}
pub async fn idm_account_delete(&self, id: &str) -> Result<bool, ClientError> {
self.perform_delete_request(["/v1/account/", id].concat().as_str())
.await
}
pub async fn idm_account_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
self.perform_get_request(format!("/v1/account/{}", id).as_str())
.await
}
pub async fn idm_account_set_attr(
&self,
id: &str,
@ -343,27 +756,240 @@ impl KanidmAsyncClient {
.await
}
pub async fn idm_account_unix_token_get(&self, id: &str) -> Result<UnixUserToken, ClientError> {
// Format doesn't work in async
// format!("/v1/account/{}/_unix/_token", id).as_str()
self.perform_get_request(["/v1/account/", id, "/_unix/_token"].concat().as_str())
pub async fn idm_account_get_attr(
&self,
id: &str,
attr: &str,
) -> Result<Option<Vec<String>>, ClientError> {
self.perform_get_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str())
.await
}
pub async fn idm_group_unix_token_get(&self, id: &str) -> Result<UnixGroupToken, ClientError> {
// Format doesn't work in async
// format!("/v1/account/{}/_unix/_token", id).as_str()
self.perform_get_request(["/v1/group/", id, "/_unix/_token"].concat().as_str())
pub async fn idm_account_purge_attr(&self, id: &str, attr: &str) -> Result<bool, ClientError> {
self.perform_delete_request(format!("/v1/account/{}/_attr/{}", id, attr).as_str())
.await
}
pub async fn idm_account_delete(&self, id: &str) -> Result<bool, ClientError> {
self.perform_delete_request(["/v1/account/", id].concat().as_str())
pub async fn idm_account_primary_credential_set_password(
&self,
id: &str,
pw: &str,
) -> Result<SetCredentialResponse, ClientError> {
let r = SetCredentialRequest::Password(pw.to_string());
self.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await
}
pub async fn idm_account_primary_credential_import_password(
&self,
id: &str,
pw: &str,
) -> Result<bool, ClientError> {
self.perform_put_request(
format!("/v1/account/{}/_attr/password_import", id).as_str(),
vec![pw.to_string()],
)
.await
}
pub async fn idm_account_primary_credential_set_generated(
&self,
id: &str,
) -> Result<String, ClientError> {
let r = SetCredentialRequest::GeneratePassword;
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::Token(p)) => Ok(p),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
// Reg intent for totp
pub async fn idm_account_primary_credential_generate_totp(
&self,
id: &str,
label: &str,
) -> Result<(Uuid, TOTPSecret), ClientError> {
let r = SetCredentialRequest::TOTPGenerate(label.to_string());
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::TOTPCheck(u, s)) => Ok((u, s)),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
// Verify the totp
pub async fn idm_account_primary_credential_verify_totp(
&self,
id: &str,
otp: u32,
session: Uuid,
) -> Result<bool, ClientError> {
let r = SetCredentialRequest::TOTPVerify(session, otp);
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::Success) => Ok(true),
Ok(SetCredentialResponse::TOTPCheck(u, s)) => Err(ClientError::TOTPVerifyFailed(u, s)),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
pub async fn idm_account_primary_credential_remove_totp(
&self,
id: &str,
) -> Result<bool, ClientError> {
let r = SetCredentialRequest::TOTPRemove;
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::Success) => Ok(true),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
pub async fn idm_account_primary_credential_register_webauthn(
&self,
id: &str,
label: &str,
) -> Result<(Uuid, CreationChallengeResponse), ClientError> {
let r = SetCredentialRequest::WebauthnBegin(label.to_string());
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::WebauthnCreateChallenge(u, s)) => Ok((u, s)),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
pub async fn idm_account_primary_credential_complete_webuthn_registration(
&self,
id: &str,
rego: RegisterPublicKeyCredential,
session: Uuid,
) -> Result<(), ClientError> {
let r = SetCredentialRequest::WebauthnRegister(session, rego);
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::Success) => Ok(()),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
pub async fn idm_account_primary_credential_remove_webauthn(
&self,
id: &str,
label: &str,
) -> Result<bool, ClientError> {
let r = SetCredentialRequest::WebauthnRemove(label.to_string());
let res: Result<SetCredentialResponse, ClientError> = self
.perform_put_request(
format!("/v1/account/{}/_credential/primary", id).as_str(),
r,
)
.await;
match res {
Ok(SetCredentialResponse::Success) => Ok(true),
Ok(_) => Err(ClientError::EmptyResponse),
Err(e) => Err(e),
}
}
pub async fn idm_account_get_credential_status(
&self,
id: &str,
) -> Result<CredentialStatus, ClientError> {
let res: Result<CredentialStatus, ClientError> = self
.perform_get_request(format!("/v1/account/{}/_credential/_status", id).as_str())
.await;
res.and_then(|cs| {
if cs.creds.is_empty() {
Err(ClientError::EmptyResponse)
} else {
Ok(cs)
}
})
}
pub async fn idm_account_radius_credential_get(
&self,
id: &str,
) -> Result<Option<String>, ClientError> {
self.perform_get_request(format!("/v1/account/{}/_radius", id).as_str())
.await
}
pub async fn idm_group_delete(&self, id: &str) -> Result<bool, ClientError> {
self.perform_delete_request(["/v1/group/", id].concat().as_str())
pub async fn idm_account_radius_credential_regenerate(
&self,
id: &str,
) -> Result<String, ClientError> {
self.perform_post_request(format!("/v1/account/{}/_radius", id).as_str(), ())
.await
}
pub async fn idm_account_radius_credential_delete(
&self,
id: &str,
) -> Result<bool, ClientError> {
self.perform_delete_request(format!("/v1/account/{}/_radius", id).as_str())
.await
}
pub async fn idm_account_radius_token_get(
&self,
id: &str,
) -> Result<RadiusAuthToken, ClientError> {
self.perform_get_request(format!("/v1/account/{}/_radius/_token", id).as_str())
.await
}
pub async fn idm_account_unix_extend(
&self,
id: &str,
gidnumber: Option<u32>,
shell: Option<&str>,
) -> Result<bool, ClientError> {
let ux = AccountUnixExtend {
shell: shell.map(|s| s.to_string()),
gidnumber,
};
self.perform_post_request(format!("/v1/account/{}/_unix", id).as_str(), ux)
.await
}
@ -399,13 +1025,114 @@ impl KanidmAsyncClient {
.await
}
pub async fn idm_group_add_members(
pub async fn idm_account_get_ssh_pubkeys(&self, id: &str) -> Result<Vec<String>, ClientError> {
self.perform_get_request(format!("/v1/account/{}/_ssh_pubkeys", id).as_str())
.await
}
pub async fn idm_account_post_ssh_pubkey(
&self,
id: &str,
members: Vec<&str>,
tag: &str,
pubkey: &str,
) -> Result<bool, ClientError> {
let m: Vec<_> = members.iter().map(|v| (*v).to_string()).collect();
self.perform_post_request(["/v1/group/", id, "/_attr/member"].concat().as_str(), m)
let sk = (tag.to_string(), pubkey.to_string());
self.perform_post_request(format!("/v1/account/{}/_ssh_pubkeys", id).as_str(), sk)
.await
}
pub async fn idm_account_person_extend(&self, id: &str) -> Result<bool, ClientError> {
self.perform_post_request(format!("/v1/account/{}/_person/_extend", id).as_str(), ())
.await
}
pub async fn idm_account_get_ssh_pubkey(
&self,
id: &str,
tag: &str,
) -> Result<Option<String>, ClientError> {
self.perform_get_request(format!("/v1/account/{}/_ssh_pubkeys/{}", id, tag).as_str())
.await
}
pub async fn idm_account_delete_ssh_pubkey(
&self,
id: &str,
tag: &str,
) -> Result<bool, ClientError> {
self.perform_delete_request(format!("/v1/account/{}/_ssh_pubkeys/{}", id, tag).as_str())
.await
}
// ==== domain_info (aka domain)
pub async fn idm_domain_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/domain").await
}
pub async fn idm_domain_get(&self, id: &str) -> Result<Entry, ClientError> {
self.perform_get_request(format!("/v1/domain/{}", id).as_str())
.await
}
// pub fn idm_domain_get_attr
pub async fn idm_domain_get_ssid(&self, id: &str) -> Result<String, ClientError> {
self.perform_get_request(format!("/v1/domain/{}/_attr/domain_ssid", id).as_str())
.await
.and_then(|mut r: Vec<String>|
// Get the first result
r.pop()
.ok_or(
ClientError::EmptyResponse
))
}
// pub fn idm_domain_put_attr
pub async fn idm_domain_set_ssid(&self, id: &str, ssid: &str) -> Result<bool, ClientError> {
self.perform_put_request(
format!("/v1/domain/{}/_attr/domain_ssid", id).as_str(),
vec![ssid.to_string()],
)
.await
}
// ==== schema
pub async fn idm_schema_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/schema").await
}
pub async fn idm_schema_attributetype_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/schema/attributetype").await
}
pub async fn idm_schema_attributetype_get(
&self,
id: &str,
) -> Result<Option<Entry>, ClientError> {
self.perform_get_request(format!("/v1/schema/attributetype/{}", id).as_str())
.await
}
pub async fn idm_schema_classtype_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/schema/classtype").await
}
pub async fn idm_schema_classtype_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
self.perform_get_request(format!("/v1/schema/classtype/{}", id).as_str())
.await
}
// ==== recycle bin
pub async fn recycle_bin_list(&self) -> Result<Vec<Entry>, ClientError> {
self.perform_get_request("/v1/recycle_bin").await
}
pub async fn recycle_bin_get(&self, id: &str) -> Result<Option<Entry>, ClientError> {
self.perform_get_request(format!("/v1/recycle_bin/{}", id).as_str())
.await
}
pub async fn recycle_bin_revive(&self, id: &str) -> Result<bool, ClientError> {
self.perform_post_request(format!("/v1/recycle_bin/{}/_revive", id).as_str(), ())
.await
}
}

File diff suppressed because it is too large Load diff

View file

@ -51,6 +51,9 @@ def _authenticate(s, acct, pw):
print(r.json())
raise Exception("AuthInitFailed")
session_id = r.headers["x-kanidm-auth-session-id"]
headers = {"X-KANIDM-AUTH-SESSION-ID": session_id}
# {'sessionid': '00000000-5fe5-46e1-06b6-b830dd035a10', 'state': {'choose': ['password']}}
if 'password' not in r.json().get('state', {'choose': None}).get('choose', None):
print("invalid auth mech presented %s" % r.json())
@ -58,13 +61,13 @@ def _authenticate(s, acct, pw):
begin_auth = {"step": {"begin": "password"}}
r = s.post(AUTH_URL, json=begin_auth, verify=CA, timeout=TIMEOUT)
r = s.post(AUTH_URL, json=begin_auth, verify=CA, timeout=TIMEOUT, headers=headers)
if r.status_code != 200:
print(r.json())
raise Exception("AuthBeginFailed")
cred_auth = {"step": { "cred": {"password": pw}}}
r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT)
r = s.post(AUTH_URL, json=cred_auth, verify=CA, timeout=TIMEOUT, headers=headers)
response = r.json()
if r.status_code != 200:
print(response)

View file

@ -526,7 +526,7 @@ fn test_cache_account_pam_allowed() {
.await
.expect("failed to auth as admin");
adminclient
.idm_group_add_members("allowed_group", vec!["testaccount1"])
.idm_group_add_members("allowed_group", &["testaccount1"])
.await
.unwrap();

View file

@ -4,5 +4,5 @@ db_path = "/tmp/kanidm.db"
db_fs_type = "zfs"
tls_chain = "../insecure/chain.pem"
tls_key = "../insecure/key.pem"
log_level = "quiet"
log_level = "verbose"
origin = "https://idm.example.com"

View file

@ -1085,15 +1085,17 @@ impl QueryServerWriteV1 {
"class".into(),
Value::new_class("posixaccount"),
)))
.chain(iter::once(gidnumber.as_ref().map(|_| {
Modify::Purged("gidnumber".into())
})))
.chain(iter::once(
gidnumber
.as_ref()
.map(|_| Modify::Purged("gidnumber".into())),
))
.chain(iter::once(gidnumber.map(|n| {
Modify::Present("gidnumber".into(), Value::new_uint32(n))
})))
.chain(iter::once(shell.as_ref().map(|_| {
Modify::Purged("loginshell".into())
})))
.chain(iter::once(
shell.as_ref().map(|_| Modify::Purged("loginshell".into())),
))
.chain(iter::once(shell.map(|s| {
Modify::Present("loginshell".into(), Value::new_iutf8(s.as_str()))
})))

View file

@ -456,7 +456,7 @@ impl IdlSqliteTransaction for IdlSqliteReadTransaction {
impl Drop for IdlSqliteReadTransaction {
// Abort - so far this has proven reliable to use drop here.
fn drop(self: &mut Self) {
fn drop(&mut self) {
if !self.committed {
#[allow(clippy::expect_used)]
self.conn
@ -496,7 +496,7 @@ impl IdlSqliteTransaction for IdlSqliteWriteTransaction {
impl Drop for IdlSqliteWriteTransaction {
// Abort
fn drop(self: &mut Self) {
fn drop(&mut self) {
if !self.committed {
#[allow(clippy::expect_used)]
self.conn

View file

@ -53,6 +53,8 @@ pub struct AppState {
pub trait RequestExtensions {
fn get_current_uat(&self) -> Option<UserAuthToken>;
fn get_current_auth_session_id(&self) -> Option<Uuid>;
fn get_url_param(&self, param: &str) -> Result<String, tide::Error>;
}
@ -81,6 +83,27 @@ impl RequestExtensions for tide::Request<AppState> {
})
}
fn get_current_auth_session_id(&self) -> Option<Uuid> {
// We see if there is a signed header copy first.
let kref = &self.state().fernet_handle;
self.header("X-KANIDM-AUTH-SESSION-ID")
.and_then(|hv| {
// Get the first header value.
hv.get(0)
})
.and_then(|h| {
// Take the token str and attempt to decrypt
// Attempt to re-inflate a uuid from bytes.
let uat: Option<Uuid> = kref
.decrypt_with_ttl(h.as_str(), 3600)
.ok()
.and_then(|b| serde_json::from_slice(&b).ok());
uat
})
// If not there, get from the cookie instead.
.or_else(|| self.session().get::<Uuid>("auth-session-id"))
}
fn get_url_param(&self, param: &str) -> Result<String, tide::Error> {
self.param(param)
.map(|s| s.to_string())
@ -1008,26 +1031,24 @@ pub async fn do_nothing(_req: tide::Request<AppState>) -> tide::Result {
Ok(res)
}
// We probably need an extract auth or similar to handle the different
// types (cookie, bearer), and to generic this over get/post.
pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
// AuthRequest
// First, deal with some state management.
// Do anything here first that's needed like getting the session details
// out of the req cookie.
let (eventid, hvalue) = new_eventid!();
let maybe_sessionid = req.session().get::<Uuid>("auth-session-id");
let maybe_sessionid = req.get_current_auth_session_id();
debug!("🍿 {:?}", maybe_sessionid);
let obj: AuthRequest = req.body_json().await
.map_err(|e| {debug!("wat? {:?}", e); e})
?;
let obj: AuthRequest = req.body_json().await.map_err(|e| {
debug!("wat? {:?}", e);
e
})?;
let auth_msg = AuthMessage::new(obj, maybe_sessionid, eventid);
let mut auth_session_id_tok = None;
// 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.
@ -1054,26 +1075,41 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
AuthState::Choose(allowed) => {
debug!("🧩 -> AuthState::Choose");
let msession = req.session_mut();
// Force a new cookie session.
// msession.regenerate();
// Ensure the auth-session-id is set
msession.remove("auth-session-id");
msession
.insert("auth-session-id", sessionid)
.map(|_| ProtoAuthState::Choose(allowed))
.map_err(|_| OperationError::InvalidSessionState)
.and_then(|_| {
let kref = &req.state().fernet_handle;
// Get the header token ready.
serde_json::to_vec(&sessionid)
.map(|data| {
auth_session_id_tok = Some(kref.encrypt(&data));
})
.map_err(|_| OperationError::InvalidSessionState)
})
.map(|_| ProtoAuthState::Choose(allowed))
}
AuthState::Continue(allowed) => {
debug!("🧩 -> AuthState::Continue");
let msession = req.session_mut();
// Force a new cookie session.
// msession.regenerate();
// Ensure the auth-session-id is set
msession.remove("auth-session-id");
msession
.insert("auth-session-id", sessionid)
.map(|_| ProtoAuthState::Continue(allowed))
.map_err(|_| OperationError::InvalidSessionState)
.and_then(|_| {
let kref = &req.state().fernet_handle;
// Get the header token ready.
serde_json::to_vec(&sessionid)
.map(|data| {
auth_session_id_tok = Some(kref.encrypt(&data));
})
.map_err(|_| OperationError::InvalidSessionState)
})
.map(|_| ProtoAuthState::Continue(allowed))
}
AuthState::Success(uat) => {
debug!("🧩 -> AuthState::Success");
@ -1102,7 +1138,14 @@ pub async fn auth(mut req: tide::Request<AppState>) -> tide::Result {
Err(e) => Err(e),
};
to_tide_response(res, hvalue)
to_tide_response(res, hvalue).map(|mut res| {
// if the sessionid was injected into our cookie, set it in the
// header too.
if let Some(tok) = auth_session_id_tok {
res.insert_header("X-KANIDM-AUTH-SESSION-ID", tok);
}
res
})
}
pub async fn idm_account_set_password(mut req: tide::Request<AppState>) -> tide::Result {

View file

@ -189,14 +189,10 @@ pub trait QueryServerTransaction {
// the QS wr/ro to the plugin trait. However, there shouldn't be a need for search
// plugis, because all data transforms should be in the write path.
let res = self
.get_be_txn()
.search(au, lims, &vfr)
.map(|r| r)
.map_err(|e| {
ladmin_error!(au, "backend failure -> {:?}", e);
OperationError::Backend
})?;
let res = self.get_be_txn().search(au, lims, &vfr).map_err(|e| {
ladmin_error!(au, "backend failure -> {:?}", e);
OperationError::Backend
})?;
// Apply ACP before we let the plugins "have at it".
// WARNING; for external searches this is NOT the only

View file

@ -1,4 +1,4 @@
#![recursion_limit="256"]
#![recursion_limit = "256"]
use wasm_bindgen::prelude::*;
use yew::prelude::*;

View file

@ -1,9 +1,9 @@
use wasm_bindgen::prelude::*;
use anyhow::Error;
use wasm_bindgen::prelude::*;
use yew::format::{Json, Nothing};
use yew::prelude::*;
use yew::services::{ConsoleService, StorageService};
use yew::services::fetch::{FetchService, FetchTask, Request, Response};
use yew::services::{ConsoleService, StorageService};
use kanidm_proto::v1::{AuthRequest, AuthState, AuthStep};
@ -24,32 +24,24 @@ pub enum LoginAppMsg {
impl LoginApp {
fn auth_begin(&mut self) {
let username_copy = self.username.clone();
let callback = self.link.callback(
move |response: Response<Json<Result<AuthState, Error>>>| {
let (parts, body) = response.into_parts();
match body {
Json(Ok(state)) => {
LoginAppMsg::Next(state)
let callback =
self.link
.callback(move |response: Response<Json<Result<AuthState, Error>>>| {
let (parts, body) = response.into_parts();
match body {
Json(Ok(state)) => LoginAppMsg::Next(state),
Json(Err(_)) => LoginAppMsg::DoNothing,
}
Json(Err(_)) => {
LoginAppMsg::DoNothing
}
}
}
);
});
let authreq = AuthRequest {
step: AuthStep::Init(self.username.clone())
step: AuthStep::Init(self.username.clone()),
};
// Setup the auth step::init(username);
self.ft = Request::post("/v1/auth")
.header("Content-Type", "application/json")
.body(Json(&authreq))
.map_err(|_| ())
.and_then(|request| {
FetchService::fetch_binary(request, callback)
.map_err(|_| ())
})
.and_then(|request| FetchService::fetch_binary(request, callback).map_err(|_| ()))
.map(|ft| Some(ft))
.unwrap_or_else(|_e| None);
}
@ -63,9 +55,7 @@ impl Component for LoginApp {
ConsoleService::log(format!("create").as_str());
// First we need to work out what state we are in.
let lstorage = StorageService::new(
yew::services::storage::Area::Local
).unwrap();
let lstorage = StorageService::new(yew::services::storage::Area::Local).unwrap();
// Get any previous sessions?
// Are they still valid?
@ -74,7 +64,7 @@ impl Component for LoginApp {
link,
username: "".to_string(),
lstorage,
ft: None
ft: None,
}
}
@ -98,9 +88,7 @@ impl Component for LoginApp {
ConsoleService::log(format!("next -> {:?}", state).as_str());
true
}
LoginAppMsg::DoNothing => {
false
}
LoginAppMsg::DoNothing => false,
}
}