mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
3 authentication (#79)
This adds support for authentication and credential storage to the server. It also adds account recovery and options for integration test fixtures, refactors to make the client library easier to manage, and support clean seperation of the proto vs lib.
This commit is contained in:
parent
d0e62ad85a
commit
da1af02f2b
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
.backup_test.db
|
||||||
/target
|
/target
|
||||||
**/*.rs.bk
|
**/*.rs.bk
|
||||||
test.db
|
test.db
|
||||||
|
|
65
Cargo.toml
65
Cargo.toml
|
@ -1,60 +1,9 @@
|
||||||
# cargo-features = ["default-run"]
|
|
||||||
|
|
||||||
[package]
|
|
||||||
name = "rsidm"
|
|
||||||
version = "0.1.0"
|
|
||||||
authors = ["William Brown <william@blackhats.net.au>"]
|
|
||||||
# default-run = "rsidm_core"
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
|
|
||||||
# We need three major binaries. The server itself, the unix client, and a cli
|
|
||||||
# mgmt tool.
|
|
||||||
|
|
||||||
[lib]
|
|
||||||
name = "rsidm"
|
|
||||||
path = "src/lib/lib.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "rsidmd"
|
|
||||||
path = "src/server/main.rs"
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "kanidm"
|
|
||||||
path = "src/clients/main.rs"
|
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
actix = "0.7"
|
|
||||||
actix-web = "0.7"
|
|
||||||
bytes = "0.4"
|
|
||||||
log = "0.4"
|
|
||||||
env_logger = "0.6"
|
|
||||||
reqwest = "0.9"
|
|
||||||
# reqwest = { path = "../reqwest" }
|
|
||||||
rand = "0.6"
|
|
||||||
|
|
||||||
chrono = "0.4"
|
|
||||||
cookie = "0.11"
|
|
||||||
regex = "1"
|
|
||||||
lazy_static = "1.2.0"
|
|
||||||
lru = "0.1"
|
|
||||||
|
|
||||||
tokio = "0.1"
|
|
||||||
futures = "0.1"
|
|
||||||
uuid = { version = "0.7", features = ["serde", "v4"] }
|
|
||||||
serde = "1.0"
|
|
||||||
serde_cbor = "0.10"
|
|
||||||
serde_json = "1.0"
|
|
||||||
serde_derive = "1.0"
|
|
||||||
|
|
||||||
rusqlite = { version = "0.15", features = ["backup"] }
|
|
||||||
r2d2 = "0.8"
|
|
||||||
r2d2_sqlite = "0.7"
|
|
||||||
|
|
||||||
structopt = { version = "0.2", default-features = false }
|
|
||||||
time = "0.1"
|
|
||||||
|
|
||||||
concread = "0.1"
|
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = [
|
||||||
|
"rsidm_proto",
|
||||||
|
"rsidmd",
|
||||||
|
"rsidm_client",
|
||||||
|
"rsidm_tools",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
About these artworks
|
About these artworks
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
These artworks were commissioned and produced by Jessie Irwin (tw: @wizardfortress). They are very
|
These artworks were commissioned and produced by Jesse Irwin (tw: @wizardfortress). They are very
|
||||||
much appreciated!
|
much appreciated!
|
||||||
|
|
||||||
Both artworks are licensed as CC-BY-NC-ND.
|
Both artworks are licensed as CC-BY-NC-ND.
|
||||||
|
|
18
rsidm_client/Cargo.toml
Normal file
18
rsidm_client/Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[package]
|
||||||
|
name = "rsidm_client"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.6"
|
||||||
|
reqwest = "0.9"
|
||||||
|
rsidm_proto = { path = "../rsidm_proto" }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
tokio = "0.1"
|
||||||
|
actix = "0.7"
|
||||||
|
rsidm = { path = "../rsidmd" }
|
||||||
|
futures = "0.1"
|
||||||
|
serde_json = "1.0"
|
29
rsidm_client/src/lib.rs
Normal file
29
rsidm_client/src/lib.rs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// #![deny(warnings)]
|
||||||
|
#![warn(unused_extern_crates)]
|
||||||
|
|
||||||
|
use reqwest;
|
||||||
|
|
||||||
|
pub struct RsidmClient {
|
||||||
|
client: reqwest::Client,
|
||||||
|
addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RsidmClient {
|
||||||
|
pub fn new(addr: &str) -> Self {
|
||||||
|
let client = reqwest::Client::builder()
|
||||||
|
.cookie_store(true)
|
||||||
|
.build()
|
||||||
|
.expect("Unexpected reqwest builder failure!");
|
||||||
|
RsidmClient {
|
||||||
|
client: client,
|
||||||
|
addr: addr.to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// auth
|
||||||
|
// whoami
|
||||||
|
// search
|
||||||
|
// create
|
||||||
|
// modify
|
||||||
|
//
|
||||||
|
}
|
|
@ -7,10 +7,13 @@ extern crate actix;
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
extern crate rsidm;
|
extern crate rsidm;
|
||||||
use rsidm::config::Configuration;
|
extern crate rsidm_proto;
|
||||||
|
extern crate serde_json;
|
||||||
|
|
||||||
|
use rsidm::config::{Configuration, IntegrationTestConfig};
|
||||||
use rsidm::constants::UUID_ADMIN;
|
use rsidm::constants::UUID_ADMIN;
|
||||||
use rsidm::core::create_server_core;
|
use rsidm::core::create_server_core;
|
||||||
use rsidm::proto::v1::{
|
use rsidm_proto::v1::{
|
||||||
AuthCredential, AuthRequest, AuthResponse, AuthState, AuthStep, CreateRequest, Entry,
|
AuthCredential, AuthRequest, AuthResponse, AuthState, AuthStep, CreateRequest, Entry,
|
||||||
OperationResponse,
|
OperationResponse,
|
||||||
};
|
};
|
||||||
|
@ -29,6 +32,7 @@ extern crate env_logger;
|
||||||
extern crate tokio;
|
extern crate tokio;
|
||||||
|
|
||||||
static PORT_ALLOC: AtomicUsize = AtomicUsize::new(8080);
|
static PORT_ALLOC: AtomicUsize = AtomicUsize::new(8080);
|
||||||
|
static ADMIN_TEST_PASSWORD: &'static str = "integration test admin password";
|
||||||
|
|
||||||
// Test external behaviorus of the service.
|
// Test external behaviorus of the service.
|
||||||
|
|
||||||
|
@ -36,8 +40,15 @@ fn run_test(test_fn: fn(reqwest::Client, &str) -> ()) {
|
||||||
let _ = env_logger::builder().is_test(true).try_init();
|
let _ = env_logger::builder().is_test(true).try_init();
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
let port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
let port = PORT_ALLOC.fetch_add(1, Ordering::SeqCst);
|
||||||
|
|
||||||
|
let int_config = Box::new(IntegrationTestConfig {
|
||||||
|
admin_password: ADMIN_TEST_PASSWORD.to_string(),
|
||||||
|
});
|
||||||
|
|
||||||
let mut config = Configuration::new();
|
let mut config = Configuration::new();
|
||||||
config.address = format!("127.0.0.1:{}", port);
|
config.address = format!("127.0.0.1:{}", port);
|
||||||
|
config.secure_cookies = false;
|
||||||
|
config.integration_test_config = Some(int_config);
|
||||||
// Setup the config ...
|
// Setup the config ...
|
||||||
|
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
|
@ -185,6 +196,73 @@ fn test_server_whoami_anonymous() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_server_whoami_admin_simple_password() {
|
||||||
|
run_test(|client: reqwest::Client, addr: &str| {
|
||||||
|
// First show we are un-authenticated.
|
||||||
|
let whoami_dest = format!("{}/v1/whoami", addr);
|
||||||
|
let auth_dest = format!("{}/v1/auth", addr);
|
||||||
|
// Now login as admin
|
||||||
|
|
||||||
|
// Setup the auth initialisation
|
||||||
|
let auth_init = AuthRequest {
|
||||||
|
step: AuthStep::Init("admin".to_string(), None),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.post(auth_dest.as_str())
|
||||||
|
.body(serde_json::to_string(&auth_init).unwrap())
|
||||||
|
.send()
|
||||||
|
.unwrap();
|
||||||
|
assert!(response.status() == reqwest::StatusCode::OK);
|
||||||
|
// Check that we got the next step
|
||||||
|
let r: AuthResponse = serde_json::from_str(response.text().unwrap().as_str()).unwrap();
|
||||||
|
println!("==> AUTHRESPONSE ==> {:?}", r);
|
||||||
|
|
||||||
|
assert!(match &r.state {
|
||||||
|
AuthState::Continue(_all_list) => {
|
||||||
|
// Check anonymous is present? It will fail on next step if not ...
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the credentials required now
|
||||||
|
let auth_admin = AuthRequest {
|
||||||
|
step: AuthStep::Creds(vec![AuthCredential::Password(
|
||||||
|
ADMIN_TEST_PASSWORD.to_string(),
|
||||||
|
)]),
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut response = client
|
||||||
|
.post(auth_dest.as_str())
|
||||||
|
.body(serde_json::to_string(&auth_admin).unwrap())
|
||||||
|
.send()
|
||||||
|
.unwrap();
|
||||||
|
debug!("{}", response.status());
|
||||||
|
assert!(response.status() == reqwest::StatusCode::OK);
|
||||||
|
// Check that we got the next step
|
||||||
|
let r: AuthResponse = serde_json::from_str(response.text().unwrap().as_str()).unwrap();
|
||||||
|
println!("==> AUTHRESPONSE ==> {:?}", r);
|
||||||
|
|
||||||
|
assert!(match &r.state {
|
||||||
|
AuthState::Success(uat) => {
|
||||||
|
println!("==> Authed as uat; {:?}", uat);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Now do a whoami.
|
||||||
|
let mut response = client.get(whoami_dest.as_str()).send().unwrap();
|
||||||
|
println!("WHOAMI -> {}", response.text().unwrap().as_str());
|
||||||
|
println!("WHOAMI STATUS -> {}", response.status());
|
||||||
|
assert!(response.status() == reqwest::StatusCode::OK);
|
||||||
|
|
||||||
|
// Check the json now ... response.json()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Test hitting all auth-required endpoints and assert they give unauthorized.
|
// Test hitting all auth-required endpoints and assert they give unauthorized.
|
||||||
|
|
||||||
/*
|
/*
|
17
rsidm_proto/Cargo.toml
Normal file
17
rsidm_proto/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "rsidm_proto"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
rsidm_internal = ["actix"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
uuid = { version = "0.7", features = ["serde", "v4"] }
|
||||||
|
actix = { version = "0.7", optional = true }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
serde_json = "1.0"
|
7
rsidm_proto/src/lib.rs
Normal file
7
rsidm_proto/src/lib.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![warn(unused_extern_crates)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
pub mod v1;
|
|
@ -1,16 +1,72 @@
|
||||||
// use super::entry::Entry;
|
|
||||||
// use super::filter::Filter;
|
|
||||||
use crate::error::OperationError;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub(crate) mod actors;
|
#[cfg(feature = "rsidm_internal")]
|
||||||
pub mod client;
|
use actix::prelude::*;
|
||||||
pub(crate) mod messages;
|
|
||||||
|
|
||||||
// These proto implementations are here because they have public definitions
|
// These proto implementations are here because they have public definitions
|
||||||
|
|
||||||
|
/* ===== errors ===== */
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub enum SchemaError {
|
||||||
|
NotImplemented,
|
||||||
|
InvalidClass,
|
||||||
|
MissingMustAttribute(String),
|
||||||
|
InvalidAttribute,
|
||||||
|
InvalidAttributeSyntax,
|
||||||
|
EmptyFilter,
|
||||||
|
Corrupted,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub enum OperationError {
|
||||||
|
EmptyRequest,
|
||||||
|
Backend,
|
||||||
|
NoMatchingEntries,
|
||||||
|
CorruptedEntry(u64),
|
||||||
|
ConsistencyError(Vec<Result<(), ConsistencyError>>),
|
||||||
|
SchemaViolation(SchemaError),
|
||||||
|
Plugin,
|
||||||
|
FilterGeneration,
|
||||||
|
FilterUUIDResolution,
|
||||||
|
InvalidAttributeName(String),
|
||||||
|
InvalidAttribute(&'static str),
|
||||||
|
InvalidDBState,
|
||||||
|
InvalidEntryID,
|
||||||
|
InvalidRequestState,
|
||||||
|
InvalidState,
|
||||||
|
InvalidEntryState,
|
||||||
|
InvalidUuid,
|
||||||
|
InvalidACPState(&'static str),
|
||||||
|
InvalidSchemaState(&'static str),
|
||||||
|
InvalidAccountState(&'static str),
|
||||||
|
BackendEngine,
|
||||||
|
SQLiteError, //(RusqliteError)
|
||||||
|
FsError,
|
||||||
|
SerdeJsonError,
|
||||||
|
SerdeCborError,
|
||||||
|
AccessDenied,
|
||||||
|
NotAuthenticated,
|
||||||
|
InvalidAuthState(&'static str),
|
||||||
|
InvalidSessionState,
|
||||||
|
SystemProtectedObject,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
||||||
|
pub enum ConsistencyError {
|
||||||
|
Unknown,
|
||||||
|
// Class, Attribute
|
||||||
|
SchemaClassMissingAttribute(String, String),
|
||||||
|
QueryServerSearchFailure,
|
||||||
|
EntryUuidCorrupt(u64),
|
||||||
|
UuidIndexCorrupt(String),
|
||||||
|
UuidNotUnique(String),
|
||||||
|
RefintNotUpheld(u64),
|
||||||
|
MemberOfInvalid(u64),
|
||||||
|
InvalidAttributeType(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== higher level types ===== */
|
/* ===== higher level types ===== */
|
||||||
// These are all types that are conceptually layers ontop of entry and
|
// These are all types that are conceptually layers ontop of entry and
|
||||||
// friends. They allow us to process more complex requests and provide
|
// friends. They allow us to process more complex requests and provide
|
||||||
|
@ -133,6 +189,7 @@ impl SearchRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rsidm_internal")]
|
||||||
impl Message for SearchRequest {
|
impl Message for SearchRequest {
|
||||||
type Result = Result<SearchResponse, OperationError>;
|
type Result = Result<SearchResponse, OperationError>;
|
||||||
}
|
}
|
||||||
|
@ -163,6 +220,7 @@ impl CreateRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rsidm_internal")]
|
||||||
impl Message for CreateRequest {
|
impl Message for CreateRequest {
|
||||||
type Result = Result<OperationResponse, OperationError>;
|
type Result = Result<OperationResponse, OperationError>;
|
||||||
}
|
}
|
||||||
|
@ -182,6 +240,7 @@ impl DeleteRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rsidm_internal")]
|
||||||
impl Message for DeleteRequest {
|
impl Message for DeleteRequest {
|
||||||
type Result = Result<OperationResponse, OperationError>;
|
type Result = Result<OperationResponse, OperationError>;
|
||||||
}
|
}
|
||||||
|
@ -204,6 +263,7 @@ impl ModifyRequest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "rsidm_internal")]
|
||||||
impl Message for ModifyRequest {
|
impl Message for ModifyRequest {
|
||||||
type Result = Result<OperationResponse, OperationError>;
|
type Result = Result<OperationResponse, OperationError>;
|
||||||
}
|
}
|
||||||
|
@ -253,7 +313,6 @@ pub struct AuthRequest {
|
||||||
pub enum AuthAllowed {
|
pub enum AuthAllowed {
|
||||||
Anonymous,
|
Anonymous,
|
||||||
Password,
|
Password,
|
||||||
// TOTP,
|
|
||||||
// Webauthn(String),
|
// Webauthn(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,7 +391,7 @@ impl WhoamiResponse {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::proto::v1::Filter as ProtoFilter;
|
use crate::v1::Filter as ProtoFilter;
|
||||||
#[test]
|
#[test]
|
||||||
fn test_protofilter_simple() {
|
fn test_protofilter_simple() {
|
||||||
let pf: ProtoFilter = ProtoFilter::Pres("class".to_string());
|
let pf: ProtoFilter = ProtoFilter::Pres("class".to_string());
|
13
rsidm_tools/Cargo.toml
Normal file
13
rsidm_tools/Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
[package]
|
||||||
|
name = "rsidm_tools"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "kanidm"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rsidm_client = { path = "../rsidm_client" }
|
||||||
|
|
55
rsidmd/Cargo.toml
Normal file
55
rsidmd/Cargo.toml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
# cargo-features = ["default-run"]
|
||||||
|
|
||||||
|
[package]
|
||||||
|
name = "rsidm"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["William Brown <william@blackhats.net.au>"]
|
||||||
|
# default-run = "rsidm_core"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "rsidm"
|
||||||
|
path = "src/lib/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rsidmd"
|
||||||
|
path = "src/server/main.rs"
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
rsidm_proto = { path = "../rsidm_proto", features = ["rsidm_internal"] }
|
||||||
|
|
||||||
|
actix = "0.7"
|
||||||
|
actix-web = "0.7"
|
||||||
|
bytes = "0.4"
|
||||||
|
log = "0.4"
|
||||||
|
env_logger = "0.6"
|
||||||
|
rand = "0.6"
|
||||||
|
|
||||||
|
chrono = "0.4"
|
||||||
|
cookie = "0.11"
|
||||||
|
regex = "1"
|
||||||
|
lazy_static = "1.2.0"
|
||||||
|
lru = "0.1"
|
||||||
|
|
||||||
|
tokio = "0.1"
|
||||||
|
futures = "0.1"
|
||||||
|
uuid = { version = "0.7", features = ["serde", "v4"] }
|
||||||
|
serde = "1.0"
|
||||||
|
serde_cbor = "0.10"
|
||||||
|
serde_json = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
|
||||||
|
rusqlite = { version = "0.15", features = ["backup"] }
|
||||||
|
r2d2 = "0.8"
|
||||||
|
r2d2_sqlite = "0.7"
|
||||||
|
|
||||||
|
structopt = { version = "0.2", default-features = false }
|
||||||
|
time = "0.1"
|
||||||
|
|
||||||
|
concread = "0.1"
|
||||||
|
|
||||||
|
openssl = "0.10"
|
||||||
|
|
||||||
|
rpassword = "0.4"
|
||||||
|
|
|
@ -16,15 +16,15 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
|
use concread::cowcell::{CowCell, CowCellReadTxn, CowCellWriteTxn};
|
||||||
|
use rsidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
use std::collections::{BTreeMap, BTreeSet};
|
use std::collections::{BTreeMap, BTreeSet};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::filter::{Filter, FilterValid};
|
use crate::filter::{Filter, FilterValid};
|
||||||
use crate::modify::Modify;
|
use crate::modify::Modify;
|
||||||
use crate::proto::v1::Filter as ProtoFilter;
|
|
||||||
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::PartialValue;
|
use crate::value::PartialValue;
|
||||||
|
|
|
@ -1,26 +1,64 @@
|
||||||
use actix::prelude::*;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::be::Backend;
|
|
||||||
|
|
||||||
use crate::async_log::EventLog;
|
use crate::async_log::EventLog;
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::event::{
|
use crate::event::{
|
||||||
AuthEvent, CreateEvent, DeleteEvent, ModifyEvent, PurgeRecycledEvent, PurgeTombstoneEvent,
|
AuthEvent, CreateEvent, DeleteEvent, ModifyEvent, PurgeRecycledEvent, PurgeTombstoneEvent,
|
||||||
SearchEvent, SearchResult, WhoamiResult,
|
SearchEvent, SearchResult, WhoamiResult,
|
||||||
};
|
};
|
||||||
use crate::schema::Schema;
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
use crate::idm::server::IdmServer;
|
use crate::idm::server::IdmServer;
|
||||||
use crate::server::{QueryServer, QueryServerTransaction};
|
use crate::server::{QueryServer, QueryServerTransaction};
|
||||||
|
|
||||||
use crate::proto::v1::{
|
use rsidm_proto::v1::{
|
||||||
AuthResponse, CreateRequest, DeleteRequest, ModifyRequest, OperationResponse, SearchRequest,
|
AuthRequest, AuthResponse, CreateRequest, DeleteRequest, ModifyRequest, OperationResponse,
|
||||||
SearchResponse, WhoamiResponse,
|
SearchRequest, SearchResponse, UserAuthToken, WhoamiResponse,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::proto::v1::messages::{AuthMessage, WhoamiMessage};
|
use actix::prelude::*;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
// These are used when the request (IE Get) has no intrising request
|
||||||
|
// type. Additionally, they are used in some requests where we need
|
||||||
|
// to supplement extra server state (IE userauthtokens) to a request.
|
||||||
|
//
|
||||||
|
// Generally we don't need to have the responses here because they are
|
||||||
|
// part of the protocol.
|
||||||
|
|
||||||
|
pub struct WhoamiMessage {
|
||||||
|
pub uat: Option<UserAuthToken>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WhoamiMessage {
|
||||||
|
pub fn new(uat: Option<UserAuthToken>) -> Self {
|
||||||
|
WhoamiMessage { uat: uat }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for WhoamiMessage {
|
||||||
|
type Result = Result<WhoamiResponse, OperationError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AuthMessage {
|
||||||
|
pub sessionid: Option<Uuid>,
|
||||||
|
pub req: AuthRequest,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AuthMessage {
|
||||||
|
pub fn new(req: AuthRequest, sessionid: Option<Uuid>) -> Self {
|
||||||
|
AuthMessage {
|
||||||
|
sessionid: sessionid,
|
||||||
|
req: req,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Message for AuthMessage {
|
||||||
|
type Result = Result<AuthResponse, OperationError>;
|
||||||
|
}
|
||||||
|
|
||||||
pub struct QueryServerV1 {
|
pub struct QueryServerV1 {
|
||||||
log: actix::Addr<EventLog>,
|
log: actix::Addr<EventLog>,
|
||||||
|
@ -46,54 +84,16 @@ impl QueryServerV1 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO #54: We could move most of the be/schema/qs setup and startup
|
|
||||||
// outside of this call, then pass in "what we need" in a cloneable
|
|
||||||
// form, this way we could have seperate Idm vs Qs threads, and dedicated
|
|
||||||
// threads for write vs read
|
|
||||||
pub fn start(
|
pub fn start(
|
||||||
log: actix::Addr<EventLog>,
|
log: actix::Addr<EventLog>,
|
||||||
be: Backend,
|
query_server: QueryServer,
|
||||||
|
idms: IdmServer,
|
||||||
threads: usize,
|
threads: usize,
|
||||||
) -> Result<actix::Addr<QueryServerV1>, OperationError> {
|
) -> actix::Addr<QueryServerV1> {
|
||||||
let mut audit = AuditScope::new("server_start");
|
let idms_arc = Arc::new(idms);
|
||||||
let log_inner = log.clone();
|
SyncArbiter::start(threads, move || {
|
||||||
|
QueryServerV1::new(log.clone(), query_server.clone(), idms_arc.clone())
|
||||||
let qs_addr: Result<actix::Addr<QueryServerV1>, _> = audit_segment!(audit, || {
|
})
|
||||||
// Create "just enough" schema for us to be able to load from
|
|
||||||
// disk ... Schema loading is one time where we validate the
|
|
||||||
// entries as we read them, so we need this here.
|
|
||||||
let schema = match Schema::new(&mut audit) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(e) => return Err(e),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a query_server implementation
|
|
||||||
let query_server = QueryServer::new(be, schema);
|
|
||||||
|
|
||||||
let mut audit_qsc = AuditScope::new("query_server_init");
|
|
||||||
// TODO #62: Should the IDM parts be broken out to the IdmServer?
|
|
||||||
// What's important about this initial setup here is that it also triggers
|
|
||||||
// the schema and acp reload, so they are now configured correctly!
|
|
||||||
// Initialise the schema core.
|
|
||||||
//
|
|
||||||
// Now search for the schema itself, and validate that the system
|
|
||||||
// in memory matches the BE on disk, and that it's syntactically correct.
|
|
||||||
// Write it out if changes are needed.
|
|
||||||
query_server.initialise_helper(&mut audit_qsc)?;
|
|
||||||
|
|
||||||
// We generate a SINGLE idms only!
|
|
||||||
|
|
||||||
let idms = Arc::new(IdmServer::new(query_server.clone()));
|
|
||||||
|
|
||||||
audit.append_scope(audit_qsc);
|
|
||||||
|
|
||||||
let x = SyncArbiter::start(threads, move || {
|
|
||||||
QueryServerV1::new(log_inner.clone(), query_server.clone(), idms.clone())
|
|
||||||
});
|
|
||||||
Ok(x)
|
|
||||||
});
|
|
||||||
log.do_send(audit);
|
|
||||||
qs_addr
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
32
rsidmd/src/lib/be/dbvalue.rs
Normal file
32
rsidmd/src/lib/be/dbvalue.rs
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum DbPasswordV1 {
|
||||||
|
PBKDF2(usize, Vec<u8>, Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DbCredV1 {
|
||||||
|
pub password: Option<DbPasswordV1>,
|
||||||
|
pub claims: Vec<String>,
|
||||||
|
pub uuid: Uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct DbValueCredV1 {
|
||||||
|
pub t: String,
|
||||||
|
pub d: DbCredV1,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub enum DbValueV1 {
|
||||||
|
U8(String),
|
||||||
|
I8(String),
|
||||||
|
UU(Uuid),
|
||||||
|
BO(bool),
|
||||||
|
SY(usize),
|
||||||
|
IN(usize),
|
||||||
|
RF(Uuid),
|
||||||
|
JF(String),
|
||||||
|
CR(DbValueCredV1),
|
||||||
|
}
|
|
@ -12,8 +12,8 @@ use std::fs;
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::be::dbentry::DbEntry;
|
use crate::be::dbentry::DbEntry;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
|
||||||
use crate::error::{ConsistencyError, OperationError};
|
|
||||||
use crate::filter::{Filter, FilterValidResolved};
|
use crate::filter::{Filter, FilterValidResolved};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError};
|
||||||
|
|
||||||
pub mod dbentry;
|
pub mod dbentry;
|
||||||
pub mod dbvalue;
|
pub mod dbvalue;
|
|
@ -1,6 +1,11 @@
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
|
pub struct IntegrationTestConfig {
|
||||||
|
pub admin_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct Configuration {
|
pub struct Configuration {
|
||||||
pub address: String,
|
pub address: String,
|
||||||
|
@ -11,6 +16,7 @@ pub struct Configuration {
|
||||||
pub maximum_request: usize,
|
pub maximum_request: usize,
|
||||||
pub secure_cookies: bool,
|
pub secure_cookies: bool,
|
||||||
pub cookie_key: [u8; 32],
|
pub cookie_key: [u8; 32],
|
||||||
|
pub integration_test_config: Option<Box<IntegrationTestConfig>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Configuration {
|
impl Configuration {
|
||||||
|
@ -24,8 +30,9 @@ impl Configuration {
|
||||||
// log type
|
// log type
|
||||||
// log path
|
// log path
|
||||||
// TODO #63: default true in prd
|
// TODO #63: default true in prd
|
||||||
secure_cookies: false,
|
secure_cookies: if cfg!(test) { false } else { true },
|
||||||
cookie_key: [0; 32],
|
cookie_key: [0; 32],
|
||||||
|
integration_test_config: None,
|
||||||
};
|
};
|
||||||
let mut rng = StdRng::from_entropy();
|
let mut rng = StdRng::from_entropy();
|
||||||
rng.fill(&mut c.cookie_key);
|
rng.fill(&mut c.cookie_key);
|
|
@ -293,8 +293,9 @@ pub static JSON_SCHEMA_ATTR_SSH_PUBLICKEY: &'static str = r#"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"#;
|
"#;
|
||||||
pub static UUID_SCHEMA_ATTR_PASSWORD: &'static str = "00000000-0000-0000-0000-ffff00000043";
|
pub static UUID_SCHEMA_ATTR_PRIMARY_CREDENTIAL: &'static str =
|
||||||
pub static JSON_SCHEMA_ATTR_PASSWORD: &'static str = r#"
|
"00000000-0000-0000-0000-ffff00000043";
|
||||||
|
pub static JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL: &'static str = r#"
|
||||||
{
|
{
|
||||||
"valid": {
|
"valid": {
|
||||||
"uuid": "00000000-0000-0000-0000-ffff00000043"
|
"uuid": "00000000-0000-0000-0000-ffff00000043"
|
||||||
|
@ -307,17 +308,17 @@ pub static JSON_SCHEMA_ATTR_PASSWORD: &'static str = r#"
|
||||||
"attributetype"
|
"attributetype"
|
||||||
],
|
],
|
||||||
"description": [
|
"description": [
|
||||||
"password hash material of the object for authentication"
|
"Primary credential material of the account for authentication interactively."
|
||||||
],
|
],
|
||||||
"index": [],
|
"index": [],
|
||||||
"multivalue": [
|
"multivalue": [
|
||||||
"true"
|
"false"
|
||||||
],
|
],
|
||||||
"name": [
|
"name": [
|
||||||
"password"
|
"primary_credential"
|
||||||
],
|
],
|
||||||
"syntax": [
|
"syntax": [
|
||||||
"UTF8STRING"
|
"CREDENTIAL"
|
||||||
],
|
],
|
||||||
"uuid": [
|
"uuid": [
|
||||||
"00000000-0000-0000-0000-ffff00000043"
|
"00000000-0000-0000-0000-ffff00000043"
|
||||||
|
@ -411,7 +412,7 @@ pub static JSON_SCHEMA_CLASS_ACCOUNT: &'static str = r#"
|
||||||
"account"
|
"account"
|
||||||
],
|
],
|
||||||
"systemmay": [
|
"systemmay": [
|
||||||
"password",
|
"primary_credential",
|
||||||
"ssh_publickey"
|
"ssh_publickey"
|
||||||
],
|
],
|
||||||
"systemmust": [
|
"systemmust": [
|
|
@ -12,19 +12,20 @@ use time::Duration;
|
||||||
use crate::config::Configuration;
|
use crate::config::Configuration;
|
||||||
|
|
||||||
// SearchResult
|
// SearchResult
|
||||||
|
use crate::actors::v1::QueryServerV1;
|
||||||
|
use crate::actors::v1::{AuthMessage, WhoamiMessage};
|
||||||
use crate::async_log;
|
use crate::async_log;
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::be::{Backend, BackendTransaction};
|
use crate::be::{Backend, BackendTransaction};
|
||||||
use crate::error::OperationError;
|
use crate::idm::server::IdmServer;
|
||||||
use crate::interval::IntervalActor;
|
use crate::interval::IntervalActor;
|
||||||
use crate::proto::v1::actors::QueryServerV1;
|
use crate::schema::Schema;
|
||||||
use crate::proto::v1::messages::{AuthMessage, WhoamiMessage};
|
use crate::server::QueryServer;
|
||||||
use crate::proto::v1::{
|
use rsidm_proto::v1::OperationError;
|
||||||
|
use rsidm_proto::v1::{
|
||||||
AuthRequest, AuthState, CreateRequest, DeleteRequest, ModifyRequest, SearchRequest,
|
AuthRequest, AuthState, CreateRequest, DeleteRequest, ModifyRequest, SearchRequest,
|
||||||
UserAuthToken,
|
UserAuthToken,
|
||||||
};
|
};
|
||||||
use crate::schema::Schema;
|
|
||||||
use crate::server::QueryServer;
|
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -266,6 +267,45 @@ fn setup_backend(config: &Configuration) -> Result<Backend, OperationError> {
|
||||||
be
|
be
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO #54: We could move most of the be/schema/qs setup and startup
|
||||||
|
// outside of this call, then pass in "what we need" in a cloneable
|
||||||
|
// form, this way we could have seperate Idm vs Qs threads, and dedicated
|
||||||
|
// threads for write vs read
|
||||||
|
fn setup_qs_idms(
|
||||||
|
audit: &mut AuditScope,
|
||||||
|
be: Backend,
|
||||||
|
) -> Result<(QueryServer, IdmServer), OperationError> {
|
||||||
|
// Create "just enough" schema for us to be able to load from
|
||||||
|
// disk ... Schema loading is one time where we validate the
|
||||||
|
// entries as we read them, so we need this here.
|
||||||
|
let schema = match Schema::new(audit) {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to setup in memory schema: {:?}", e);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create a query_server implementation
|
||||||
|
let query_server = QueryServer::new(be, schema);
|
||||||
|
|
||||||
|
// TODO #62: Should the IDM parts be broken out to the IdmServer?
|
||||||
|
// What's important about this initial setup here is that it also triggers
|
||||||
|
// the schema and acp reload, so they are now configured correctly!
|
||||||
|
// Initialise the schema core.
|
||||||
|
//
|
||||||
|
// Now search for the schema itself, and validate that the system
|
||||||
|
// in memory matches the BE on disk, and that it's syntactically correct.
|
||||||
|
// Write it out if changes are needed.
|
||||||
|
query_server.initialise_helper(audit)?;
|
||||||
|
|
||||||
|
// We generate a SINGLE idms only!
|
||||||
|
|
||||||
|
let idms = IdmServer::new(query_server.clone());
|
||||||
|
|
||||||
|
Ok((query_server, idms))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn backup_server_core(config: Configuration, dst_path: &str) {
|
pub fn backup_server_core(config: Configuration, dst_path: &str) {
|
||||||
let be = match setup_backend(&config) {
|
let be = match setup_backend(&config) {
|
||||||
Ok(be) => be,
|
Ok(be) => be,
|
||||||
|
@ -351,13 +391,59 @@ pub fn verify_server_core(config: Configuration) {
|
||||||
// Now add IDM server verifications?
|
// Now add IDM server verifications?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn recover_account_core(config: Configuration, name: String, password: String) {
|
||||||
|
let mut audit = AuditScope::new("recover_account");
|
||||||
|
|
||||||
|
// Start the backend.
|
||||||
|
let be = match setup_backend(&config) {
|
||||||
|
Ok(be) => be,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Failed to setup BE: {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// setup the qs - *with* init of the migrations and schema.
|
||||||
|
let (_qs, idms) = match setup_qs_idms(&mut audit, be) {
|
||||||
|
Ok(t) => t,
|
||||||
|
Err(e) => {
|
||||||
|
debug!("{}", audit);
|
||||||
|
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the password change.
|
||||||
|
let mut idms_prox_write = idms.proxy_write();
|
||||||
|
match idms_prox_write.recover_account(&mut audit, name, password) {
|
||||||
|
Ok(_) => {
|
||||||
|
idms_prox_write
|
||||||
|
.commit(&mut audit)
|
||||||
|
.expect("A critical error during commit occured.");
|
||||||
|
debug!("{}", audit);
|
||||||
|
info!("Password reset!");
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("Error during password reset -> {:?}", e);
|
||||||
|
debug!("{}", audit);
|
||||||
|
// abort the txn
|
||||||
|
std::mem::drop(idms_prox_write);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_server_core(config: Configuration) {
|
pub fn create_server_core(config: Configuration) {
|
||||||
// Until this point, we probably want to write to the log macro fns.
|
// Until this point, we probably want to write to the log macro fns.
|
||||||
|
|
||||||
|
if config.integration_test_config.is_some() {
|
||||||
|
warn!("RUNNING IN INTEGRATION TEST MODE.");
|
||||||
|
warn!("IF YOU SEE THIS IN PRODUCTION YOU MUST CONTACT SUPPORT IMMEDIATELY.");
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Starting rsidm with configuration: {:?}", config);
|
||||||
// The log server is started on it's own thread, and is contacted
|
// The log server is started on it's own thread, and is contacted
|
||||||
// asynchronously.
|
// asynchronously.
|
||||||
let log_addr = async_log::start();
|
let log_addr = async_log::start();
|
||||||
log_event!(log_addr, "Starting rsidm with configuration: {:?}", config);
|
|
||||||
|
|
||||||
// Similar, create a stats thread which aggregates statistics from the
|
// Similar, create a stats thread which aggregates statistics from the
|
||||||
// server as they come in.
|
// server as they come in.
|
||||||
|
@ -366,22 +452,56 @@ pub fn create_server_core(config: Configuration) {
|
||||||
let be = match setup_backend(&config) {
|
let be = match setup_backend(&config) {
|
||||||
Ok(be) => be,
|
Ok(be) => be,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to setup BE: {:?}", e);
|
error!("Failed to setup BE -> {:?}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Start the query server with the given be path: future config
|
let mut audit = AuditScope::new("setup_qs_idms");
|
||||||
let server_addr = match QueryServerV1::start(log_addr.clone(), be, config.threads) {
|
// Start the IDM server.
|
||||||
Ok(addr) => addr,
|
let (qs, idms) = match setup_qs_idms(&mut audit, be) {
|
||||||
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
println!(
|
debug!("{}", audit);
|
||||||
"An unknown failure in startup has occured - exiting -> {:?}",
|
error!("Unable to setup query server or idm server -> {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// Any pre-start tasks here.
|
||||||
|
match &config.integration_test_config {
|
||||||
|
Some(itc) => {
|
||||||
|
let mut idms_prox_write = idms.proxy_write();
|
||||||
|
match idms_prox_write.recover_account(
|
||||||
|
&mut audit,
|
||||||
|
"admin".to_string(),
|
||||||
|
itc.admin_password.clone(),
|
||||||
|
) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("{}", audit);
|
||||||
|
error!(
|
||||||
|
"Unable to configure INTERGATION TEST admin account -> {:?}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
match idms_prox_write.commit(&mut audit) {
|
||||||
|
Ok(_) => {}
|
||||||
|
Err(e) => {
|
||||||
|
debug!("{}", audit);
|
||||||
|
error!("Unable to commit INTERGATION TEST setup -> {:?}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
}
|
||||||
|
log_addr.do_send(audit);
|
||||||
|
|
||||||
|
// Pass it to the actor for threading.
|
||||||
|
// Start the query server with the given be path: future config
|
||||||
|
let server_addr = QueryServerV1::start(log_addr.clone(), qs, idms, config.threads);
|
||||||
|
|
||||||
// Setup timed events
|
// Setup timed events
|
||||||
let _int_addr = IntervalActor::new(server_addr.clone()).start();
|
let _int_addr = IntervalActor::new(server_addr.clone()).start();
|
222
rsidmd/src/lib/credential.rs
Normal file
222
rsidmd/src/lib/credential.rs
Normal file
|
@ -0,0 +1,222 @@
|
||||||
|
use crate::be::dbvalue::{DbCredV1, DbPasswordV1};
|
||||||
|
use openssl::hash::MessageDigest;
|
||||||
|
use openssl::pkcs5::pbkdf2_hmac;
|
||||||
|
use rand::prelude::*;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
// These are in order of "relative" strength.
|
||||||
|
/*
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub enum Policy {
|
||||||
|
PasswordOnly,
|
||||||
|
WebauthnOnly,
|
||||||
|
GeneratedPassword,
|
||||||
|
PasswordAndWebauthn,
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: Determine this at startup based on a time factor
|
||||||
|
const PBKDF2_COST: usize = 10000;
|
||||||
|
// NIST 800-63.b salt should be 112 bits -> 14 8u8.
|
||||||
|
// I choose tinfoil hat though ...
|
||||||
|
const PBKDF2_SALT_LEN: usize = 24;
|
||||||
|
// 64 * u8 -> 512 bits of out.
|
||||||
|
const PBKDF2_KEY_LEN: usize = 64;
|
||||||
|
|
||||||
|
// Why PBKDF2? Rust's bcrypt has a number of hardcodings like max pw len of 72
|
||||||
|
// I don't really feel like adding in so many restrictions, so I'll use
|
||||||
|
// pbkdf2 in openssl because it doesn't have the same limits.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum KDF {
|
||||||
|
// cost, salt, hash
|
||||||
|
PBKDF2(usize, Vec<u8>, Vec<u8>),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Password {
|
||||||
|
material: KDF,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DbPasswordV1> for Password {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: DbPasswordV1) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
|
DbPasswordV1::PBKDF2(c, s, h) => Ok(Password {
|
||||||
|
material: KDF::PBKDF2(c, s, h),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Password {
|
||||||
|
fn new_pbkdf2(cleartext: &str) -> KDF {
|
||||||
|
let mut rng = rand::thread_rng();
|
||||||
|
let salt: Vec<u8> = (0..PBKDF2_SALT_LEN).map(|_| rng.gen()).collect();
|
||||||
|
// This is 512 bits of output
|
||||||
|
let mut key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
|
||||||
|
|
||||||
|
pbkdf2_hmac(
|
||||||
|
cleartext.as_bytes(),
|
||||||
|
salt.as_slice(),
|
||||||
|
PBKDF2_COST,
|
||||||
|
MessageDigest::sha256(),
|
||||||
|
key.as_mut_slice(),
|
||||||
|
)
|
||||||
|
.expect("PBKDF2 failure");
|
||||||
|
// Turn key to a vec.
|
||||||
|
KDF::PBKDF2(PBKDF2_COST, salt, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(cleartext: &str) -> Self {
|
||||||
|
Password {
|
||||||
|
material: Self::new_pbkdf2(cleartext),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn verify(&self, cleartext: &str) -> bool {
|
||||||
|
match &self.material {
|
||||||
|
KDF::PBKDF2(cost, salt, key) => {
|
||||||
|
let mut chal_key: Vec<u8> = (0..PBKDF2_KEY_LEN).map(|_| 0).collect();
|
||||||
|
pbkdf2_hmac(
|
||||||
|
cleartext.as_bytes(),
|
||||||
|
salt.as_slice(),
|
||||||
|
*cost,
|
||||||
|
MessageDigest::sha256(),
|
||||||
|
chal_key.as_mut_slice(),
|
||||||
|
)
|
||||||
|
.expect("PBKDF2 failure");
|
||||||
|
// Actually compare the outputs.
|
||||||
|
&chal_key == key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// This is how we store credentials in the server. An account can have many credentials, and
|
||||||
|
/// a credential can have many factors. Only successful auth to a credential as a whole unit
|
||||||
|
/// will succeed. For example:
|
||||||
|
/// A: Credential { password: aaa }
|
||||||
|
/// B: Credential { password: bbb, otp: ... }
|
||||||
|
/// In this case, if we selected credential B, and then provided password "aaa" we would deny
|
||||||
|
/// the auth as the password of B was incorrect. Additionally, while A only needs the "password",
|
||||||
|
/// B requires both the password and otp to be valid.
|
||||||
|
///
|
||||||
|
/// In this way, each Credential provides it's own password requirements and policy, and requires
|
||||||
|
/// some metadata to support this such as it's source and strength etc. Some of these details are
|
||||||
|
/// to be resolved ...
|
||||||
|
pub struct Credential {
|
||||||
|
// Source (machine, user, ....). Strength?
|
||||||
|
// policy: Policy,
|
||||||
|
pub(crate) password: Option<Password>,
|
||||||
|
// webauthn: Option<NonEmptyVec<Webauthn>>
|
||||||
|
// totp: Option<NonEmptyVec<TOTP>>
|
||||||
|
pub(crate) claims: Vec<String>,
|
||||||
|
// Uuid of Credential, used by auth session to lock this specific credential
|
||||||
|
// if required.
|
||||||
|
pub(crate) uuid: Uuid,
|
||||||
|
// TODO: Add auth policy IE validUntil, lock state ...
|
||||||
|
// locked: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<DbCredV1> for Credential {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn try_from(value: DbCredV1) -> Result<Self, Self::Error> {
|
||||||
|
// Work out what the policy is?
|
||||||
|
let DbCredV1 {
|
||||||
|
password,
|
||||||
|
claims,
|
||||||
|
uuid,
|
||||||
|
} = value;
|
||||||
|
|
||||||
|
let v_password = match password {
|
||||||
|
Some(dbp) => Some(Password::try_from(dbp)?),
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(Credential {
|
||||||
|
password: v_password,
|
||||||
|
claims: claims,
|
||||||
|
uuid: uuid,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Credential {
|
||||||
|
pub fn new_password_only(cleartext: &str) -> Self {
|
||||||
|
Credential {
|
||||||
|
password: Some(Password::new(cleartext)),
|
||||||
|
claims: Vec::new(),
|
||||||
|
uuid: Uuid::new_v4(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_password(&self, cleartext: &str) -> Self {
|
||||||
|
Credential {
|
||||||
|
password: Some(Password::new(cleartext)),
|
||||||
|
claims: self.claims.clone(),
|
||||||
|
uuid: self.uuid.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn verify_password(&self, cleartext: &str) -> bool {
|
||||||
|
match &self.password {
|
||||||
|
Some(pw) => pw.verify(cleartext),
|
||||||
|
None => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_db_valuev1(&self) -> DbCredV1 {
|
||||||
|
DbCredV1 {
|
||||||
|
password: match &self.password {
|
||||||
|
Some(pw) => match &pw.material {
|
||||||
|
KDF::PBKDF2(cost, salt, hash) => {
|
||||||
|
Some(DbPasswordV1::PBKDF2(*cost, salt.clone(), hash.clone()))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
claims: self.claims.clone(),
|
||||||
|
uuid: self.uuid.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub fn add_claim(&mut self) {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_claim(&mut self) {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub fn modify_password(&mut self) {
|
||||||
|
// Change the password
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_webauthn_token() {
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_webauthn_token() {
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::credential::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_credential_simple() {
|
||||||
|
let c = Credential::new_password_only("password");
|
||||||
|
assert!(c.verify_password("password"));
|
||||||
|
assert!(!c.verify_password("password1"));
|
||||||
|
assert!(!c.verify_password("Password1"));
|
||||||
|
assert!(!c.verify_password("It Works!"));
|
||||||
|
assert!(!c.verify_password("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"));
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,15 @@
|
||||||
// use serde_json::{Error, Value};
|
// use serde_json::{Error, Value};
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::error::{OperationError, SchemaError};
|
use crate::credential::Credential;
|
||||||
use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved};
|
use crate::filter::{Filter, FilterInvalid, FilterResolved, FilterValidResolved};
|
||||||
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
|
use crate::modify::{Modify, ModifyInvalid, ModifyList, ModifyValid};
|
||||||
use crate::proto::v1::Entry as ProtoEntry;
|
|
||||||
use crate::proto::v1::Filter as ProtoFilter;
|
|
||||||
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction};
|
use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction};
|
||||||
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::{IndexType, SyntaxType};
|
use crate::value::{IndexType, SyntaxType};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::Entry as ProtoEntry;
|
||||||
|
use rsidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
use rsidm_proto::v1::{OperationError, SchemaError};
|
||||||
|
|
||||||
use crate::be::dbentry::{DbEntry, DbEntryV1, DbEntryVers};
|
use crate::be::dbentry::{DbEntry, DbEntryV1, DbEntryVers};
|
||||||
|
|
||||||
|
@ -831,6 +832,13 @@ impl Entry<EntryValid, EntryCommitted> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_ava_single_credential(&self, attr: &str) -> Option<&Credential> {
|
||||||
|
match self.get_ava_single(attr) {
|
||||||
|
Some(a) => a.to_credential(),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_ava_reference_uuid(&self, attr: &str) -> Option<Vec<&Uuid>> {
|
pub fn get_ava_reference_uuid(&self, attr: &str) -> Option<Vec<&Uuid>> {
|
||||||
// If any value is NOT a reference, return none!
|
// If any value is NOT a reference, return none!
|
||||||
match self.attrs.get(attr) {
|
match self.attrs.get(attr) {
|
|
@ -1,19 +1,19 @@
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
|
||||||
use crate::filter::{Filter, FilterValid};
|
use crate::filter::{Filter, FilterValid};
|
||||||
use crate::proto::v1::Entry as ProtoEntry;
|
use rsidm_proto::v1::Entry as ProtoEntry;
|
||||||
use crate::proto::v1::{
|
use rsidm_proto::v1::{
|
||||||
AuthCredential, AuthResponse, AuthState, AuthStep, CreateRequest, DeleteRequest, ModifyRequest,
|
AuthCredential, AuthResponse, AuthState, AuthStep, CreateRequest, DeleteRequest, ModifyRequest,
|
||||||
ReviveRecycledRequest, SearchRequest, SearchResponse, UserAuthToken, WhoamiResponse,
|
ReviveRecycledRequest, SearchRequest, SearchResponse, UserAuthToken, WhoamiResponse,
|
||||||
};
|
};
|
||||||
// use error::OperationError;
|
// use error::OperationError;
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::modify::{ModifyList, ModifyValid};
|
use crate::modify::{ModifyList, ModifyValid};
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
||||||
};
|
};
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
use crate::proto::v1::messages::AuthMessage;
|
use crate::actors::v1::AuthMessage;
|
||||||
// Bring in schematransaction trait for validate
|
// Bring in schematransaction trait for validate
|
||||||
// use crate::schema::SchemaTransaction;
|
// use crate::schema::SchemaTransaction;
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ use crate::filter::FilterInvalid;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::modify::ModifyInvalid;
|
use crate::modify::ModifyInvalid;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use crate::proto::v1::SearchRecycledRequest;
|
use rsidm_proto::v1::SearchRecycledRequest;
|
||||||
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
@ -613,12 +613,28 @@ impl AuthEventStep {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn anonymous_cred_step(sid: Uuid) -> Self {
|
pub fn named_init(name: &str) -> Self {
|
||||||
|
AuthEventStep::Init(AuthEventStepInit {
|
||||||
|
name: name.to_string(),
|
||||||
|
appid: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
||||||
AuthEventStep::Creds(AuthEventStepCreds {
|
AuthEventStep::Creds(AuthEventStepCreds {
|
||||||
sessionid: sid,
|
sessionid: sid,
|
||||||
creds: vec![AuthCredential::Anonymous],
|
creds: vec![AuthCredential::Anonymous],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
|
||||||
|
AuthEventStep::Creds(AuthEventStepCreds {
|
||||||
|
sessionid: sid,
|
||||||
|
creds: vec![AuthCredential::Password(pw.to_string())],
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -645,10 +661,26 @@ impl AuthEvent {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub fn anonymous_cred_step(sid: Uuid) -> Self {
|
pub fn named_init(name: &str) -> Self {
|
||||||
AuthEvent {
|
AuthEvent {
|
||||||
event: None,
|
event: None,
|
||||||
step: AuthEventStep::anonymous_cred_step(sid),
|
step: AuthEventStep::named_init(name),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
||||||
|
AuthEvent {
|
||||||
|
event: None,
|
||||||
|
step: AuthEventStep::cred_step_anonymous(sid),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
|
||||||
|
AuthEvent {
|
||||||
|
event: None,
|
||||||
|
step: AuthEventStep::cred_step_password(sid, pw),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -3,14 +3,14 @@
|
||||||
// entry to assert it matches.
|
// entry to assert it matches.
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::error::{OperationError, SchemaError};
|
|
||||||
use crate::event::{Event, EventOrigin};
|
use crate::event::{Event, EventOrigin};
|
||||||
use crate::proto::v1::Filter as ProtoFilter;
|
|
||||||
use crate::schema::SchemaTransaction;
|
use crate::schema::SchemaTransaction;
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
||||||
};
|
};
|
||||||
use crate::value::PartialValue;
|
use crate::value::PartialValue;
|
||||||
|
use rsidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
use rsidm_proto::v1::{OperationError, SchemaError};
|
||||||
use std::cmp::{Ordering, PartialOrd};
|
use std::cmp::{Ordering, PartialOrd};
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryValid};
|
||||||
use crate::error::OperationError;
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
use crate::proto::v1::UserAuthToken;
|
use rsidm_proto::v1::UserAuthToken;
|
||||||
|
|
||||||
|
use crate::credential::Credential;
|
||||||
use crate::idm::claim::Claim;
|
use crate::idm::claim::Claim;
|
||||||
use crate::idm::group::Group;
|
use crate::idm::group::Group;
|
||||||
use crate::value::PartialValue;
|
use crate::modify::{ModifyInvalid, ModifyList};
|
||||||
|
use crate::value::{PartialValue, Value};
|
||||||
|
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
@ -25,10 +27,10 @@ pub(crate) struct Account {
|
||||||
pub displayname: String,
|
pub displayname: String,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub groups: Vec<Group>,
|
pub groups: Vec<Group>,
|
||||||
// creds (various types)
|
pub primary: Option<Credential>,
|
||||||
// groups?
|
// primary: Credential
|
||||||
// claims?
|
// app_creds: Vec<Credential>
|
||||||
// account expiry?
|
// account expiry? (as opposed to cred expiry)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
|
@ -55,6 +57,10 @@ impl Account {
|
||||||
OperationError::InvalidAccountState("Missing attribute: displayname"),
|
OperationError::InvalidAccountState("Missing attribute: displayname"),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let primary = value
|
||||||
|
.get_ava_single_credential("primary_credential")
|
||||||
|
.map(|v| v.clone());
|
||||||
|
|
||||||
// TODO #71: Resolve groups!!!!
|
// TODO #71: Resolve groups!!!!
|
||||||
let groups = Vec::new();
|
let groups = Vec::new();
|
||||||
|
|
||||||
|
@ -65,6 +71,7 @@ impl Account {
|
||||||
name: name,
|
name: name,
|
||||||
displayname: displayname,
|
displayname: displayname,
|
||||||
groups: groups,
|
groups: groups,
|
||||||
|
primary: primary,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -85,6 +92,36 @@ impl Account {
|
||||||
claims: claims.iter().map(|c| c.into_proto()).collect(),
|
claims: claims.iter().map(|c| c.into_proto()).collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn gen_password_mod(
|
||||||
|
&self,
|
||||||
|
cleartext: &str,
|
||||||
|
appid: &Option<String>,
|
||||||
|
) -> Result<ModifyList<ModifyInvalid>, OperationError> {
|
||||||
|
// What should this look like? Probablf an appid + stuff -> modify?
|
||||||
|
// then the caller has to apply the modify under the requests event
|
||||||
|
// for proper auth checks.
|
||||||
|
match appid {
|
||||||
|
Some(_) => Err(OperationError::InvalidState),
|
||||||
|
None => {
|
||||||
|
// TODO: Enforce PW policy. Can we allow this change?
|
||||||
|
match &self.primary {
|
||||||
|
// Change the cred
|
||||||
|
Some(primary) => {
|
||||||
|
let ncred = primary.set_password(cleartext);
|
||||||
|
let vcred = Value::new_credential("primary", ncred);
|
||||||
|
Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
|
||||||
|
}
|
||||||
|
// Make a new credential instead
|
||||||
|
None => {
|
||||||
|
let ncred = Credential::new_password_only(cleartext);
|
||||||
|
let vcred = Value::new_credential("primary", ncred);
|
||||||
|
Ok(ModifyList::new_purge_and_set("primary_credential", vcred))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // no appid
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to also add a "to UserAuthToken" ...
|
// Need to also add a "to UserAuthToken" ...
|
||||||
|
@ -113,4 +150,10 @@ mod tests {
|
||||||
// For now, nothing, but later, we'll test different types of cred
|
// For now, nothing, but later, we'll test different types of cred
|
||||||
// passing.
|
// passing.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_account_set_credential() {
|
||||||
|
// Using a real entry, set a credential back to it's entry.
|
||||||
|
// In the end, this boils down to a modify operation on the Value
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,13 @@
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::constants::UUID_ANONYMOUS;
|
use crate::constants::UUID_ANONYMOUS;
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::idm::account::Account;
|
use crate::idm::account::Account;
|
||||||
use crate::idm::claim::Claim;
|
use crate::idm::claim::Claim;
|
||||||
use crate::proto::v1::{AuthAllowed, AuthCredential, AuthState};
|
use rsidm_proto::v1::OperationError;
|
||||||
|
use rsidm_proto::v1::{AuthAllowed, AuthCredential, AuthState};
|
||||||
|
|
||||||
|
use crate::credential::{Credential, Password};
|
||||||
|
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
// Each CredHandler takes one or more credentials and determines if the
|
// Each CredHandler takes one or more credentials and determines if the
|
||||||
// handlers requirements can be 100% fufilled. This is where MFA or other
|
// handlers requirements can be 100% fufilled. This is where MFA or other
|
||||||
|
@ -18,11 +22,13 @@ enum CredState {
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
enum CredHandler {
|
enum CredHandler {
|
||||||
|
Denied,
|
||||||
|
// The bool is a flag if the cred has been authed against.
|
||||||
Anonymous,
|
Anonymous,
|
||||||
// AppPassword
|
// AppPassword
|
||||||
// {
|
// {
|
||||||
// Password
|
// Password
|
||||||
// Webauthn
|
Password(Password), // Webauthn
|
||||||
// Webauthn + Password
|
// Webauthn + Password
|
||||||
// TOTP
|
// TOTP
|
||||||
// TOTP + Password
|
// TOTP + Password
|
||||||
|
@ -31,9 +37,25 @@ enum CredHandler {
|
||||||
// Verification Link?
|
// Verification Link?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TryFrom<&Credential> for CredHandler {
|
||||||
|
type Error = ();
|
||||||
|
// Is there a nicer implementation of this?
|
||||||
|
fn try_from(c: &Credential) -> Result<Self, Self::Error> {
|
||||||
|
if c.password.is_some() {
|
||||||
|
Ok(CredHandler::Password(c.password.clone().unwrap()))
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl CredHandler {
|
impl CredHandler {
|
||||||
pub fn validate(&mut self, creds: &Vec<AuthCredential>) -> CredState {
|
pub fn validate(&mut self, creds: &Vec<AuthCredential>) -> CredState {
|
||||||
match self {
|
match self {
|
||||||
|
CredHandler::Denied => {
|
||||||
|
// Sad trombone.
|
||||||
|
CredState::Denied("authentication denied")
|
||||||
|
}
|
||||||
CredHandler::Anonymous => {
|
CredHandler::Anonymous => {
|
||||||
creds.iter().fold(
|
creds.iter().fold(
|
||||||
CredState::Continue(vec![AuthAllowed::Anonymous]),
|
CredState::Continue(vec![AuthAllowed::Anonymous]),
|
||||||
|
@ -62,12 +84,48 @@ impl CredHandler {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
} // end credhandler::anonymous
|
} // end credhandler::anonymous
|
||||||
|
CredHandler::Password(pw) => {
|
||||||
|
creds.iter().fold(
|
||||||
|
// If no creds, remind that we want pw ...
|
||||||
|
CredState::Continue(vec![AuthAllowed::Password]),
|
||||||
|
|acc, cred| {
|
||||||
|
match acc {
|
||||||
|
// If failed, continue to fail.
|
||||||
|
CredState::Denied(_) => acc,
|
||||||
|
_ => {
|
||||||
|
match cred {
|
||||||
|
AuthCredential::Password(cleartext) => {
|
||||||
|
if pw.verify(cleartext.as_str()) {
|
||||||
|
CredState::Success(Vec::new())
|
||||||
|
} else {
|
||||||
|
CredState::Denied("incorrect password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// All other cases fail.
|
||||||
|
_ => CredState::Denied("pw authentication denied"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // end match acc
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} // end credhandler::password
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn valid_auth_mechs(&self) -> Vec<AuthAllowed> {
|
pub fn valid_auth_mechs(&self) -> Vec<AuthAllowed> {
|
||||||
match &self {
|
match &self {
|
||||||
|
CredHandler::Denied => Vec::new(),
|
||||||
CredHandler::Anonymous => vec![AuthAllowed::Anonymous],
|
CredHandler::Anonymous => vec![AuthAllowed::Anonymous],
|
||||||
|
CredHandler::Password(_) => vec![AuthAllowed::Password],
|
||||||
|
// webauth
|
||||||
|
// mfa
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_denied(&self) -> bool {
|
||||||
|
match &self {
|
||||||
|
CredHandler::Denied => true,
|
||||||
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +143,8 @@ pub(crate) struct AuthSession {
|
||||||
handler: CredHandler,
|
handler: CredHandler,
|
||||||
// Store any related appid we are processing for.
|
// Store any related appid we are processing for.
|
||||||
appid: Option<String>,
|
appid: Option<String>,
|
||||||
|
// Store claims related to the handler
|
||||||
|
// need to store state somehow?
|
||||||
finished: bool,
|
finished: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -94,9 +154,7 @@ impl AuthSession {
|
||||||
// for this session. This is currently based on presentation of an application
|
// for this session. This is currently based on presentation of an application
|
||||||
// id.
|
// id.
|
||||||
let handler = match appid {
|
let handler = match appid {
|
||||||
Some(_) => {
|
Some(_) => CredHandler::Denied,
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
None => {
|
None => {
|
||||||
// We want the primary handler - this is where we make a decision
|
// We want the primary handler - this is where we make a decision
|
||||||
// based on the anonymous ... in theory this could be cleaner
|
// based on the anonymous ... in theory this could be cleaner
|
||||||
|
@ -104,7 +162,15 @@ impl AuthSession {
|
||||||
if account.uuid == UUID_ANONYMOUS.clone() {
|
if account.uuid == UUID_ANONYMOUS.clone() {
|
||||||
CredHandler::Anonymous
|
CredHandler::Anonymous
|
||||||
} else {
|
} else {
|
||||||
unimplemented!();
|
// Now we see if they have one ...
|
||||||
|
match &account.primary {
|
||||||
|
Some(cred) => {
|
||||||
|
// TODO: Log this corruption better ... :(
|
||||||
|
// Probably means new authsession has to be failable
|
||||||
|
CredHandler::try_from(cred).unwrap_or_else(|_| CredHandler::Denied)
|
||||||
|
}
|
||||||
|
None => CredHandler::Denied,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -115,11 +181,14 @@ impl AuthSession {
|
||||||
// we store in the account somehow?
|
// we store in the account somehow?
|
||||||
// TODO #59: Implement handler locking!
|
// TODO #59: Implement handler locking!
|
||||||
|
|
||||||
|
// if credhandler == deny, finish = true.
|
||||||
|
let finished: bool = handler.is_denied();
|
||||||
|
|
||||||
AuthSession {
|
AuthSession {
|
||||||
account: account,
|
account: account,
|
||||||
handler: handler,
|
handler: handler,
|
||||||
appid: appid,
|
appid: appid,
|
||||||
finished: false,
|
finished: finished,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,12 +248,13 @@ impl AuthSession {
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::constants::JSON_ANONYMOUS_V1;
|
use crate::constants::{JSON_ADMIN_V1, JSON_ANONYMOUS_V1};
|
||||||
|
use crate::credential::Credential;
|
||||||
use crate::idm::authsession::AuthSession;
|
use crate::idm::authsession::AuthSession;
|
||||||
use crate::proto::v1::AuthAllowed;
|
use rsidm_proto::v1::AuthAllowed;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_idm_account_anonymous_auth_mech() {
|
fn test_idm_authsession_anonymous_auth_mech() {
|
||||||
let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1);
|
let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1);
|
||||||
|
|
||||||
let session = AuthSession::new(anon_account, None);
|
let session = AuthSession::new(anon_account, None);
|
||||||
|
@ -198,4 +268,36 @@ mod tests {
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_authsession_missing_appid() {
|
||||||
|
let anon_account = entry_str_to_account!(JSON_ANONYMOUS_V1);
|
||||||
|
|
||||||
|
let session = AuthSession::new(anon_account, Some("NonExistantAppID".to_string()));
|
||||||
|
|
||||||
|
let auth_mechs = session.valid_auth_mechs();
|
||||||
|
|
||||||
|
// Will always move to denied.
|
||||||
|
assert!(auth_mechs == Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_authsession_simple_password_mech() {
|
||||||
|
// create the ent
|
||||||
|
let mut account = entry_str_to_account!(JSON_ADMIN_V1);
|
||||||
|
// manually load in a cred
|
||||||
|
let cred = Credential::new_password_only("test_password");
|
||||||
|
account.primary = Some(cred);
|
||||||
|
|
||||||
|
// now check
|
||||||
|
let session = AuthSession::new(account, None);
|
||||||
|
let auth_mechs = session.valid_auth_mechs();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
true == auth_mechs.iter().fold(false, |acc, x| match x {
|
||||||
|
AuthAllowed::Password => true,
|
||||||
|
_ => acc,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::proto::v1::Claim as ProtoClaim;
|
use rsidm_proto::v1::Claim as ProtoClaim;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Claim {
|
pub struct Claim {
|
21
rsidmd/src/lib/idm/event.rs
Normal file
21
rsidmd/src/lib/idm/event.rs
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
use crate::event::Event;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PasswordChangeEvent {
|
||||||
|
pub event: Event,
|
||||||
|
pub target: Uuid,
|
||||||
|
pub cleartext: String,
|
||||||
|
pub appid: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PasswordChangeEvent {
|
||||||
|
pub fn new_internal(target: &Uuid, cleartext: &str, appid: Option<&str>) -> Self {
|
||||||
|
PasswordChangeEvent {
|
||||||
|
event: Event::from_internal(),
|
||||||
|
target: target.clone(),
|
||||||
|
cleartext: cleartext.to_string(),
|
||||||
|
appid: appid.map(|v| v.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::proto::v1::Group as ProtoGroup;
|
use rsidm_proto::v1::Group as ProtoGroup;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Group {
|
pub struct Group {
|
|
@ -1,5 +1,6 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
mod event;
|
||||||
|
|
||||||
pub(crate) mod account;
|
pub(crate) mod account;
|
||||||
pub(crate) mod authsession;
|
pub(crate) mod authsession;
|
|
@ -1,16 +1,17 @@
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::event::{AuthEvent, AuthEventStep, AuthResult};
|
use crate::event::{AuthEvent, AuthEventStep, AuthResult};
|
||||||
use crate::idm::account::Account;
|
use crate::idm::account::Account;
|
||||||
use crate::idm::authsession::AuthSession;
|
use crate::idm::authsession::AuthSession;
|
||||||
use crate::proto::v1::AuthState;
|
use crate::idm::event::PasswordChangeEvent;
|
||||||
use crate::server::{QueryServer, QueryServerTransaction};
|
use crate::server::{QueryServer, QueryServerTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::PartialValue;
|
use crate::value::PartialValue;
|
||||||
|
|
||||||
|
use rsidm_proto::v1::AuthState;
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
use concread::cowcell::{CowCell, CowCellWriteTxn};
|
use concread::cowcell::{CowCell, CowCellWriteTxn};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
// use lru::LruCache;
|
|
||||||
|
|
||||||
pub struct IdmServer {
|
pub struct IdmServer {
|
||||||
// There is a good reason to keep this single thread - it
|
// There is a good reason to keep this single thread - it
|
||||||
|
@ -19,6 +20,7 @@ pub struct IdmServer {
|
||||||
// in memory caches related to locking.
|
// in memory caches related to locking.
|
||||||
//
|
//
|
||||||
// TODO #60: This needs a mark-and-sweep gc to be added.
|
// TODO #60: This needs a mark-and-sweep gc to be added.
|
||||||
|
// use split_off()
|
||||||
sessions: CowCell<BTreeMap<Uuid, AuthSession>>,
|
sessions: CowCell<BTreeMap<Uuid, AuthSession>>,
|
||||||
// Need a reference to the query server.
|
// Need a reference to the query server.
|
||||||
qs: QueryServer,
|
qs: QueryServer,
|
||||||
|
@ -40,6 +42,12 @@ pub struct IdmServerReadTransaction<'a> {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
pub struct IdmServerProxyWriteTransaction<'a> {
|
||||||
|
// This does NOT take any read to the memory content, allowing safe
|
||||||
|
// qs operations to occur through this interface.
|
||||||
|
qs_write: QueryServerWriteTransaction<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
impl IdmServer {
|
impl IdmServer {
|
||||||
// TODO #59: Make number of authsessions configurable!!!
|
// TODO #59: Make number of authsessions configurable!!!
|
||||||
pub fn new(qs: QueryServer) -> IdmServer {
|
pub fn new(qs: QueryServer) -> IdmServer {
|
||||||
|
@ -61,6 +69,12 @@ impl IdmServer {
|
||||||
IdmServerReadTransaction { qs: &self.qs }
|
IdmServerReadTransaction { qs: &self.qs }
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
pub fn proxy_write(&self) -> IdmServerProxyWriteTransaction {
|
||||||
|
IdmServerProxyWriteTransaction {
|
||||||
|
qs_write: self.qs.write(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IdmServerWriteTransaction<'a> {
|
impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
|
@ -76,6 +90,7 @@ impl<'a> IdmServerWriteTransaction<'a> {
|
||||||
match &ae.step {
|
match &ae.step {
|
||||||
AuthEventStep::Init(init) => {
|
AuthEventStep::Init(init) => {
|
||||||
// Allocate a session id.
|
// Allocate a session id.
|
||||||
|
// TODO: #60 - make this new_v1 and use the tstamp.
|
||||||
let sessionid = Uuid::new_v4();
|
let sessionid = Uuid::new_v4();
|
||||||
|
|
||||||
// Begin the auth procedure!
|
// Begin the auth procedure!
|
||||||
|
@ -180,12 +195,84 @@ impl<'a> IdmServerReadTransaction<'a> {
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
impl<'a> IdmServerProxyWriteTransaction<'a> {
|
||||||
|
pub fn set_account_password(
|
||||||
|
&mut self,
|
||||||
|
au: &mut AuditScope,
|
||||||
|
pce: &PasswordChangeEvent,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
// TODO: Is it a security issue to reveal pw policy checks BEFORE permission is
|
||||||
|
// determined over the credential modification?
|
||||||
|
//
|
||||||
|
// I don't think so - because we should only be showing how STRONG the pw is ...
|
||||||
|
|
||||||
|
// Get the account
|
||||||
|
let account_entry = try_audit!(au, self.qs_write.internal_search_uuid(au, &pce.target));
|
||||||
|
let account = try_audit!(au, Account::try_from_entry(account_entry));
|
||||||
|
// Ask if tis all good - this step checks pwpolicy and such
|
||||||
|
// it returns a modify
|
||||||
|
let modlist = try_audit!(
|
||||||
|
au,
|
||||||
|
account.gen_password_mod(pce.cleartext.as_str(), &pce.appid)
|
||||||
|
);
|
||||||
|
audit_log!(au, "processing change {:?}", modlist);
|
||||||
|
// given the new credential generate a modify
|
||||||
|
// We use impersonate here to get the event from ae
|
||||||
|
try_audit!(
|
||||||
|
au,
|
||||||
|
self.qs_write.impersonate_modify(
|
||||||
|
au,
|
||||||
|
// Filter as executed
|
||||||
|
filter!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
|
||||||
|
// Filter as intended (acp)
|
||||||
|
filter_all!(f_eq("uuid", PartialValue::new_uuidr(&pce.target))),
|
||||||
|
modlist,
|
||||||
|
&pce.event,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn recover_account(
|
||||||
|
&mut self,
|
||||||
|
au: &mut AuditScope,
|
||||||
|
name: String,
|
||||||
|
cleartext: String,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
// name to uuid
|
||||||
|
let target = try_audit!(au, self.qs_write.name_to_uuid(au, name.as_str()));
|
||||||
|
// internal pce.
|
||||||
|
let pce = PasswordChangeEvent::new_internal(&target, cleartext.as_str(), None);
|
||||||
|
// now set_account_password.
|
||||||
|
self.set_account_password(au, &pce)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(self, au: &mut AuditScope) -> Result<(), OperationError> {
|
||||||
|
self.qs_write.commit(au)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Need tests of the sessions and the auth ...
|
// Need tests of the sessions and the auth ...
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::event::{AuthEvent, AuthResult};
|
use crate::constants::UUID_ADMIN;
|
||||||
use crate::proto::v1::{AuthAllowed, AuthState};
|
use crate::credential::Credential;
|
||||||
|
use crate::event::{AuthEvent, AuthResult, ModifyEvent};
|
||||||
|
use crate::idm::event::PasswordChangeEvent;
|
||||||
|
use crate::modify::{Modify, ModifyList};
|
||||||
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
|
use rsidm_proto::v1::{AuthAllowed, AuthState};
|
||||||
|
|
||||||
|
use crate::audit::AuditScope;
|
||||||
|
use crate::idm::server::IdmServer;
|
||||||
|
use crate::server::QueryServer;
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
static TEST_PASSWORD: &'static str = "ntaoeuntnaoeuhraohuercahu😍";
|
||||||
|
static TEST_PASSWORD_INC: &'static str = "ntaoentu nkrcgaeunhibwmwmqj;k wqjbkx ";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_idm_anonymous_auth() {
|
fn test_idm_anonymous_auth() {
|
||||||
|
@ -237,7 +324,7 @@ mod tests {
|
||||||
{
|
{
|
||||||
let mut idms_write = idms.write();
|
let mut idms_write = idms.write();
|
||||||
// Now send the anonymous request, given the session id.
|
// Now send the anonymous request, given the session id.
|
||||||
let anon_step = AuthEvent::anonymous_cred_step(sid);
|
let anon_step = AuthEvent::cred_step_anonymous(sid);
|
||||||
|
|
||||||
// Expect success
|
// Expect success
|
||||||
let r2 = idms_write.auth(au, &anon_step);
|
let r2 = idms_write.auth(au, &anon_step);
|
||||||
|
@ -274,4 +361,166 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Test sending anonymous but with no session init.
|
// Test sending anonymous but with no session init.
|
||||||
|
#[test]
|
||||||
|
fn test_idm_anonymous_auth_invalid_states() {
|
||||||
|
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
|
||||||
|
{
|
||||||
|
let mut idms_write = idms.write();
|
||||||
|
let sid = Uuid::new_v4();
|
||||||
|
let anon_step = AuthEvent::cred_step_anonymous(sid);
|
||||||
|
|
||||||
|
// Expect failure
|
||||||
|
let r2 = idms_write.auth(au, &anon_step);
|
||||||
|
println!("r2 ==> {:?}", r2);
|
||||||
|
|
||||||
|
match r2 {
|
||||||
|
Ok(_) => {
|
||||||
|
error!("Auth state machine not correctly enforced!");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
Err(e) => match e {
|
||||||
|
OperationError::InvalidSessionState => {}
|
||||||
|
_ => panic!(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_admin_w_password(
|
||||||
|
au: &mut AuditScope,
|
||||||
|
qs: &QueryServer,
|
||||||
|
pw: &str,
|
||||||
|
) -> Result<(), OperationError> {
|
||||||
|
let cred = Credential::new_password_only(pw);
|
||||||
|
let v_cred = Value::new_credential("primary", cred);
|
||||||
|
let mut qs_write = qs.write();
|
||||||
|
|
||||||
|
// now modify and provide a primary credential.
|
||||||
|
let me_inv_m = unsafe {
|
||||||
|
ModifyEvent::new_internal_invalid(
|
||||||
|
filter!(f_eq("name", PartialValue::new_iutf8s("admin"))),
|
||||||
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
|
"primary_credential".to_string(),
|
||||||
|
v_cred,
|
||||||
|
)]),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
// go!
|
||||||
|
assert!(qs_write.modify(au, &me_inv_m).is_ok());
|
||||||
|
|
||||||
|
qs_write.commit(au)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn init_admin_authsession_sid(idms: &IdmServer, au: &mut AuditScope) -> Uuid {
|
||||||
|
let mut idms_write = idms.write();
|
||||||
|
let admin_init = AuthEvent::named_init("admin");
|
||||||
|
|
||||||
|
let r1 = idms_write.auth(au, &admin_init);
|
||||||
|
let ar = r1.unwrap();
|
||||||
|
let AuthResult { sessionid, state } = ar;
|
||||||
|
|
||||||
|
match state {
|
||||||
|
AuthState::Continue(_) => {}
|
||||||
|
_ => {
|
||||||
|
error!("Sessions was not initialised");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
idms_write.commit().expect("Must not fail");
|
||||||
|
|
||||||
|
sessionid
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_simple_password_auth() {
|
||||||
|
run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
|
||||||
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
|
let sid = init_admin_authsession_sid(idms, au);
|
||||||
|
|
||||||
|
let mut idms_write = idms.write();
|
||||||
|
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD);
|
||||||
|
|
||||||
|
// Expect success
|
||||||
|
let r2 = idms_write.auth(au, &anon_step);
|
||||||
|
println!("r2 ==> {:?}", r2);
|
||||||
|
|
||||||
|
match r2 {
|
||||||
|
Ok(ar) => {
|
||||||
|
let AuthResult {
|
||||||
|
sessionid: _,
|
||||||
|
state,
|
||||||
|
} = ar;
|
||||||
|
match state {
|
||||||
|
AuthState::Success(_uat) => {
|
||||||
|
// Check the uat.
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("A critical error has occured! We have a non-succcess result!");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("A critical error has occured! {:?}", e);
|
||||||
|
// Should not occur!
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
idms_write.commit().expect("Must not fail");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_simple_password_invalid() {
|
||||||
|
run_idm_test!(|qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
|
||||||
|
init_admin_w_password(au, qs, TEST_PASSWORD).expect("Failed to setup admin account");
|
||||||
|
let sid = init_admin_authsession_sid(idms, au);
|
||||||
|
let mut idms_write = idms.write();
|
||||||
|
let anon_step = AuthEvent::cred_step_password(sid, TEST_PASSWORD_INC);
|
||||||
|
|
||||||
|
// Expect success
|
||||||
|
let r2 = idms_write.auth(au, &anon_step);
|
||||||
|
println!("r2 ==> {:?}", r2);
|
||||||
|
|
||||||
|
match r2 {
|
||||||
|
Ok(ar) => {
|
||||||
|
let AuthResult {
|
||||||
|
sessionid: _,
|
||||||
|
state,
|
||||||
|
} = ar;
|
||||||
|
match state {
|
||||||
|
AuthState::Denied(_reason) => {
|
||||||
|
// Check the uat.
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
error!("A critical error has occured! We have a non-denied result!");
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("A critical error has occured! {:?}", e);
|
||||||
|
// Should not occur!
|
||||||
|
panic!();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
idms_write.commit().expect("Must not fail");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_idm_simple_password_reset() {
|
||||||
|
run_idm_test!(|_qs: &QueryServer, idms: &IdmServer, au: &mut AuditScope| {
|
||||||
|
let pce = PasswordChangeEvent::new_internal(&UUID_ADMIN, TEST_PASSWORD, None);
|
||||||
|
|
||||||
|
let mut idms_prox_write = idms.proxy_write();
|
||||||
|
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
|
||||||
|
assert!(idms_prox_write.set_account_password(au, &pce).is_ok());
|
||||||
|
assert!(idms_prox_write.commit(au).is_ok());
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,9 +1,9 @@
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::actors::v1::QueryServerV1;
|
||||||
use crate::constants::PURGE_TIMEOUT;
|
use crate::constants::PURGE_TIMEOUT;
|
||||||
use crate::event::{PurgeRecycledEvent, PurgeTombstoneEvent};
|
use crate::event::{PurgeRecycledEvent, PurgeTombstoneEvent};
|
||||||
use crate::proto::v1::actors::QueryServerV1;
|
|
||||||
|
|
||||||
pub struct IntervalActor {
|
pub struct IntervalActor {
|
||||||
// Store any addresses we require
|
// Store any addresses we require
|
37
rsidmd/src/lib/lib.rs
Normal file
37
rsidmd/src/lib/lib.rs
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#![deny(warnings)]
|
||||||
|
#![warn(unused_extern_crates)]
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate log;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
#[macro_use]
|
||||||
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
// This has to be before be so the import order works
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
#[macro_use]
|
||||||
|
mod async_log;
|
||||||
|
#[macro_use]
|
||||||
|
mod audit;
|
||||||
|
mod be;
|
||||||
|
pub mod constants;
|
||||||
|
mod credential;
|
||||||
|
mod entry;
|
||||||
|
mod event;
|
||||||
|
mod filter;
|
||||||
|
mod interval;
|
||||||
|
mod modify;
|
||||||
|
mod value;
|
||||||
|
#[macro_use]
|
||||||
|
mod plugins;
|
||||||
|
mod access;
|
||||||
|
mod actors;
|
||||||
|
mod idm;
|
||||||
|
mod schema;
|
||||||
|
mod server;
|
||||||
|
|
||||||
|
pub mod config;
|
||||||
|
pub mod core;
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::proto::v1::Modify as ProtoModify;
|
use rsidm_proto::v1::Modify as ProtoModify;
|
||||||
use crate::proto::v1::ModifyList as ProtoModifyList;
|
use rsidm_proto::v1::ModifyList as ProtoModifyList;
|
||||||
|
|
||||||
use crate::error::{OperationError, SchemaError};
|
|
||||||
use crate::schema::SchemaTransaction;
|
use crate::schema::SchemaTransaction;
|
||||||
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::{OperationError, SchemaError};
|
||||||
|
|
||||||
// Should this be std?
|
// Should this be std?
|
||||||
use std::slice;
|
use std::slice;
|
||||||
|
@ -15,7 +15,7 @@ pub struct ModifyValid;
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct ModifyInvalid;
|
pub struct ModifyInvalid;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Modify {
|
pub enum Modify {
|
||||||
// This value *should* exist.
|
// This value *should* exist.
|
||||||
Present(String, Value),
|
Present(String, Value),
|
||||||
|
@ -56,7 +56,7 @@ impl Modify {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ModifyList<VALID> {
|
pub struct ModifyList<VALID> {
|
||||||
valid: VALID,
|
valid: VALID,
|
||||||
// The order of this list matters. Each change must be done in order.
|
// The order of this list matters. Each change must be done in order.
|
||||||
|
@ -87,6 +87,10 @@ impl ModifyList<ModifyInvalid> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_purge_and_set(attr: &str, v: Value) -> Self {
|
||||||
|
Self::new_list(vec![m_purge(attr), Modify::Present(attr.to_string(), v)])
|
||||||
|
}
|
||||||
|
|
||||||
pub fn push_mod(&mut self, modify: Modify) {
|
pub fn push_mod(&mut self, modify: Modify) {
|
||||||
self.mods.push(modify)
|
self.mods.push(modify)
|
||||||
}
|
}
|
|
@ -7,13 +7,13 @@ use crate::audit::AuditScope;
|
||||||
// use crate::constants::{STR_UUID_ADMIN, STR_UUID_ANONYMOUS, STR_UUID_DOES_NOT_EXIST};
|
// use crate::constants::{STR_UUID_ADMIN, STR_UUID_ANONYMOUS, STR_UUID_DOES_NOT_EXIST};
|
||||||
use crate::constants::{UUID_ADMIN, UUID_ANONYMOUS, UUID_DOES_NOT_EXIST};
|
use crate::constants::{UUID_ADMIN, UUID_ANONYMOUS, UUID_DOES_NOT_EXIST};
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew};
|
||||||
use crate::error::{ConsistencyError, OperationError};
|
|
||||||
use crate::event::{CreateEvent, ModifyEvent};
|
use crate::event::{CreateEvent, ModifyEvent};
|
||||||
use crate::modify::Modify;
|
use crate::modify::Modify;
|
||||||
use crate::server::{
|
use crate::server::{
|
||||||
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
QueryServerReadTransaction, QueryServerTransaction, QueryServerWriteTransaction,
|
||||||
};
|
};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref CLASS_OBJECT: Value = Value::new_class("object");
|
static ref CLASS_OBJECT: Value = Value::new_class("object");
|
||||||
|
@ -273,11 +273,11 @@ mod tests {
|
||||||
// use crate::plugins::Plugin;
|
// use crate::plugins::Plugin;
|
||||||
use crate::constants::JSON_ADMIN_V1;
|
use crate::constants::JSON_ADMIN_V1;
|
||||||
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::modify::{Modify, ModifyList};
|
use crate::modify::{Modify, ModifyList};
|
||||||
use crate::server::QueryServerTransaction;
|
use crate::server::QueryServerTransaction;
|
||||||
use crate::server::QueryServerWriteTransaction;
|
use crate::server::QueryServerWriteTransaction;
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
|
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
|
||||||
"valid": null,
|
"valid": null,
|
|
@ -12,13 +12,13 @@
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
|
||||||
use crate::error::{ConsistencyError, OperationError};
|
|
||||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||||
use crate::modify::{Modify, ModifyList};
|
use crate::modify::{Modify, ModifyList};
|
||||||
use crate::plugins::Plugin;
|
use crate::plugins::Plugin;
|
||||||
use crate::server::QueryServerTransaction;
|
use crate::server::QueryServerTransaction;
|
||||||
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError};
|
||||||
|
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
|
||||||
use crate::error::{ConsistencyError, OperationError};
|
|
||||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||||
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError};
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
|
@ -4,11 +4,11 @@ use crate::plugins::Plugin;
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryValid};
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||||
use crate::modify::Modify;
|
use crate::modify::Modify;
|
||||||
use crate::server::QueryServerWriteTransaction;
|
use crate::server::QueryServerWriteTransaction;
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
pub struct Protected {}
|
pub struct Protected {}
|
||||||
|
@ -159,8 +159,8 @@ impl Plugin for Protected {
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::constants::JSON_ADMIN_V1;
|
use crate::constants::JSON_ADMIN_V1;
|
||||||
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
|
static JSON_ADMIN_ALLOW_ALL: &'static str = r#"{
|
||||||
"valid": null,
|
"valid": null,
|
|
@ -13,7 +13,6 @@ use std::collections::BTreeSet;
|
||||||
|
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
|
||||||
use crate::error::{ConsistencyError, OperationError};
|
|
||||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
|
||||||
use crate::modify::{Modify, ModifyInvalid, ModifyList};
|
use crate::modify::{Modify, ModifyInvalid, ModifyList};
|
||||||
use crate::plugins::Plugin;
|
use crate::plugins::Plugin;
|
||||||
|
@ -21,6 +20,7 @@ use crate::schema::SchemaTransaction;
|
||||||
use crate::server::QueryServerTransaction;
|
use crate::server::QueryServerTransaction;
|
||||||
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerReadTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// NOTE: This *must* be after base.rs!!!
|
// NOTE: This *must* be after base.rs!!!
|
||||||
|
@ -244,10 +244,10 @@ mod tests {
|
||||||
// #[macro_use]
|
// #[macro_use]
|
||||||
// use crate::plugins::Plugin;
|
// use crate::plugins::Plugin;
|
||||||
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
||||||
use crate::error::OperationError;
|
|
||||||
use crate::modify::{Modify, ModifyList};
|
use crate::modify::{Modify, ModifyList};
|
||||||
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
use crate::server::{QueryServerTransaction, QueryServerWriteTransaction};
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::OperationError;
|
||||||
|
|
||||||
// The create references a uuid that doesn't exist - reject
|
// The create references a uuid that doesn't exist - reject
|
||||||
#[test]
|
#[test]
|
|
@ -1,8 +1,8 @@
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
use crate::constants::*;
|
use crate::constants::*;
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryNew, EntryValid};
|
||||||
use crate::error::{ConsistencyError, OperationError, SchemaError};
|
|
||||||
use crate::value::{IndexType, PartialValue, SyntaxType, Value};
|
use crate::value::{IndexType, PartialValue, SyntaxType, Value};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError, SchemaError};
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
@ -171,6 +171,14 @@ impl SchemaAttribute {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_credential(&self, v: &Value) -> Result<(), SchemaError> {
|
||||||
|
if v.is_credential() {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(SchemaError::InvalidAttributeSyntax)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn validate_utf8string_insensitive(&self, v: &Value) -> Result<(), SchemaError> {
|
fn validate_utf8string_insensitive(&self, v: &Value) -> Result<(), SchemaError> {
|
||||||
if v.is_insensitive_utf8() {
|
if v.is_insensitive_utf8() {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -200,6 +208,7 @@ impl SchemaAttribute {
|
||||||
SyntaxType::UTF8STRING_INSENSITIVE => v.is_iutf8(),
|
SyntaxType::UTF8STRING_INSENSITIVE => v.is_iutf8(),
|
||||||
SyntaxType::UTF8STRING => v.is_utf8(),
|
SyntaxType::UTF8STRING => v.is_utf8(),
|
||||||
SyntaxType::JSON_FILTER => v.is_json_filter(),
|
SyntaxType::JSON_FILTER => v.is_json_filter(),
|
||||||
|
SyntaxType::CREDENTIAL => v.is_credential(),
|
||||||
};
|
};
|
||||||
if r {
|
if r {
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -287,6 +296,13 @@ impl SchemaAttribute {
|
||||||
acc
|
acc
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
SyntaxType::CREDENTIAL => ava.iter().fold(Ok(()), |acc, v| {
|
||||||
|
if acc.is_ok() {
|
||||||
|
self.validate_credential(v)
|
||||||
|
} else {
|
||||||
|
acc
|
||||||
|
}
|
||||||
|
}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1062,13 +1078,6 @@ impl SchemaInner {
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normalise *does not* validate.
|
|
||||||
// Normalise just fixes some possible common issues, but it
|
|
||||||
// can't fix *everything* possibly wrong ...
|
|
||||||
pub fn normalise_filter(&mut self) {
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_multivalue(&self, attr_name: &str) -> Result<bool, SchemaError> {
|
fn is_multivalue(&self, attr_name: &str) -> Result<bool, SchemaError> {
|
||||||
match self.attributes.get(attr_name) {
|
match self.attributes.get(attr_name) {
|
||||||
Some(a_schema) => Ok(a_schema.multivalue),
|
Some(a_schema) => Ok(a_schema.multivalue),
|
||||||
|
@ -1189,7 +1198,7 @@ mod tests {
|
||||||
use crate::audit::AuditScope;
|
use crate::audit::AuditScope;
|
||||||
// use crate::constants::*;
|
// use crate::constants::*;
|
||||||
use crate::entry::{Entry, EntryInvalid, EntryNew, EntryValid};
|
use crate::entry::{Entry, EntryInvalid, EntryNew, EntryValid};
|
||||||
use crate::error::{ConsistencyError, SchemaError};
|
use rsidm_proto::v1::{ConsistencyError, SchemaError};
|
||||||
// use crate::filter::{Filter, FilterValid};
|
// use crate::filter::{Filter, FilterValid};
|
||||||
use crate::schema::SchemaTransaction;
|
use crate::schema::SchemaTransaction;
|
||||||
use crate::schema::{IndexType, Schema, SchemaAttribute, SchemaClass, SyntaxType};
|
use crate::schema::{IndexType, Schema, SchemaAttribute, SchemaClass, SyntaxType};
|
|
@ -15,12 +15,11 @@ use crate::access::{
|
||||||
use crate::constants::{
|
use crate::constants::{
|
||||||
JSON_ADMIN_V1, JSON_ANONYMOUS_V1, JSON_IDM_ADMINS_ACP_REVIVE_V1, JSON_IDM_ADMINS_ACP_SEARCH_V1,
|
JSON_ADMIN_V1, JSON_ANONYMOUS_V1, JSON_IDM_ADMINS_ACP_REVIVE_V1, JSON_IDM_ADMINS_ACP_SEARCH_V1,
|
||||||
JSON_IDM_ADMINS_V1, JSON_IDM_SELF_ACP_READ_V1, JSON_SCHEMA_ATTR_DISPLAYNAME,
|
JSON_IDM_ADMINS_V1, JSON_IDM_SELF_ACP_READ_V1, JSON_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
JSON_SCHEMA_ATTR_MAIL, JSON_SCHEMA_ATTR_PASSWORD, JSON_SCHEMA_ATTR_SSH_PUBLICKEY,
|
JSON_SCHEMA_ATTR_MAIL, JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL, JSON_SCHEMA_ATTR_SSH_PUBLICKEY,
|
||||||
JSON_SCHEMA_CLASS_ACCOUNT, JSON_SCHEMA_CLASS_GROUP, JSON_SCHEMA_CLASS_PERSON,
|
JSON_SCHEMA_CLASS_ACCOUNT, JSON_SCHEMA_CLASS_GROUP, JSON_SCHEMA_CLASS_PERSON,
|
||||||
JSON_SYSTEM_INFO_V1, UUID_DOES_NOT_EXIST,
|
JSON_SYSTEM_INFO_V1, UUID_DOES_NOT_EXIST,
|
||||||
};
|
};
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
|
use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntryReduced, EntryValid};
|
||||||
use crate::error::{ConsistencyError, OperationError, SchemaError};
|
|
||||||
use crate::event::{
|
use crate::event::{
|
||||||
CreateEvent, DeleteEvent, Event, EventOrigin, ExistsEvent, ModifyEvent, ReviveRecycledEvent,
|
CreateEvent, DeleteEvent, Event, EventOrigin, ExistsEvent, ModifyEvent, ReviveRecycledEvent,
|
||||||
SearchEvent,
|
SearchEvent,
|
||||||
|
@ -33,6 +32,7 @@ use crate::schema::{
|
||||||
SchemaWriteTransaction,
|
SchemaWriteTransaction,
|
||||||
};
|
};
|
||||||
use crate::value::{PartialValue, SyntaxType, Value};
|
use crate::value::{PartialValue, SyntaxType, Value};
|
||||||
|
use rsidm_proto::v1::{ConsistencyError, OperationError, SchemaError};
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PVCLASS_ATTRIBUTETYPE: PartialValue = PartialValue::new_class("attributetype");
|
static ref PVCLASS_ATTRIBUTETYPE: PartialValue = PartialValue::new_class("attributetype");
|
||||||
|
@ -393,6 +393,7 @@ pub trait QueryServerTransaction {
|
||||||
}
|
}
|
||||||
SyntaxType::JSON_FILTER => Value::new_json_filter(value)
|
SyntaxType::JSON_FILTER => Value::new_json_filter(value)
|
||||||
.ok_or(OperationError::InvalidAttribute("Invalid Filter syntax")),
|
.ok_or(OperationError::InvalidAttribute("Invalid Filter syntax")),
|
||||||
|
SyntaxType::CREDENTIAL => Err(OperationError::InvalidAttribute("Credentials can not be supplied through modification - please use the IDM api")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -456,6 +457,7 @@ pub trait QueryServerTransaction {
|
||||||
}
|
}
|
||||||
SyntaxType::JSON_FILTER => PartialValue::new_json_filter(value)
|
SyntaxType::JSON_FILTER => PartialValue::new_json_filter(value)
|
||||||
.ok_or(OperationError::InvalidAttribute("Invalid Filter syntax")),
|
.ok_or(OperationError::InvalidAttribute("Invalid Filter syntax")),
|
||||||
|
SyntaxType::CREDENTIAL => Ok(PartialValue::new_credential_tag(value.as_str())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
|
@ -467,9 +469,9 @@ pub trait QueryServerTransaction {
|
||||||
}
|
}
|
||||||
|
|
||||||
// In the opposite direction, we can resolve values for presentation
|
// In the opposite direction, we can resolve values for presentation
|
||||||
fn resolve_value(&self, _attr: &String, _value: &Value) -> Result<String, OperationError> {
|
fn resolve_value(&self, _value: &Value) -> Result<String, OperationError> {
|
||||||
|
// Ok(value.to_proto_string_clone())
|
||||||
unimplemented!();
|
unimplemented!();
|
||||||
// Ok(value.clone())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1274,15 +1276,24 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
modlist: ModifyList<ModifyInvalid>,
|
modlist: ModifyList<ModifyInvalid>,
|
||||||
event: &Event,
|
event: &Event,
|
||||||
) -> Result<(), OperationError> {
|
) -> Result<(), OperationError> {
|
||||||
let f_valid = filter
|
let f_valid = try_audit!(
|
||||||
|
audit,
|
||||||
|
filter
|
||||||
.validate(self.get_schema())
|
.validate(self.get_schema())
|
||||||
.map_err(|e| OperationError::SchemaViolation(e))?;
|
.map_err(|e| OperationError::SchemaViolation(e))
|
||||||
let f_intent_valid = filter_intent
|
);
|
||||||
|
let f_intent_valid = try_audit!(
|
||||||
|
audit,
|
||||||
|
filter_intent
|
||||||
.validate(self.get_schema())
|
.validate(self.get_schema())
|
||||||
.map_err(|e| OperationError::SchemaViolation(e))?;
|
.map_err(|e| OperationError::SchemaViolation(e))
|
||||||
let m_valid = modlist
|
);
|
||||||
|
let m_valid = try_audit!(
|
||||||
|
audit,
|
||||||
|
modlist
|
||||||
.validate(self.get_schema())
|
.validate(self.get_schema())
|
||||||
.map_err(|e| OperationError::SchemaViolation(e))?;
|
.map_err(|e| OperationError::SchemaViolation(e))
|
||||||
|
);
|
||||||
self.impersonate_modify_valid(audit, f_valid, f_intent_valid, m_valid, event)
|
self.impersonate_modify_valid(audit, f_valid, f_intent_valid, m_valid, event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1352,10 +1363,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
audit_log!(audit, "Generated modlist -> {:?}", modlist);
|
audit_log!(audit, "Generated modlist -> {:?}", modlist);
|
||||||
self.internal_modify(audit, filt, modlist)
|
self.internal_modify(audit, filt, modlist)
|
||||||
}
|
}
|
||||||
Err(_e) => {
|
Err(e) => Err(OperationError::SchemaViolation(e)),
|
||||||
unimplemented!()
|
|
||||||
// No action required.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Err(OperationError::InvalidDBState)
|
Err(OperationError::InvalidDBState)
|
||||||
|
@ -1453,7 +1461,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
JSON_SCHEMA_ATTR_DISPLAYNAME,
|
JSON_SCHEMA_ATTR_DISPLAYNAME,
|
||||||
JSON_SCHEMA_ATTR_MAIL,
|
JSON_SCHEMA_ATTR_MAIL,
|
||||||
JSON_SCHEMA_ATTR_SSH_PUBLICKEY,
|
JSON_SCHEMA_ATTR_SSH_PUBLICKEY,
|
||||||
JSON_SCHEMA_ATTR_PASSWORD,
|
JSON_SCHEMA_ATTR_PRIMARY_CREDENTIAL,
|
||||||
JSON_SCHEMA_CLASS_PERSON,
|
JSON_SCHEMA_CLASS_PERSON,
|
||||||
JSON_SCHEMA_CLASS_GROUP,
|
JSON_SCHEMA_CLASS_GROUP,
|
||||||
JSON_SCHEMA_CLASS_ACCOUNT,
|
JSON_SCHEMA_CLASS_ACCOUNT,
|
||||||
|
@ -1683,16 +1691,17 @@ impl<'a> QueryServerWriteTransaction<'a> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::constants::{JSON_ADMIN_V1, STR_UUID_ADMIN, UUID_ADMIN};
|
use crate::constants::{JSON_ADMIN_V1, STR_UUID_ADMIN, UUID_ADMIN};
|
||||||
|
use crate::credential::Credential;
|
||||||
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
use crate::entry::{Entry, EntryInvalid, EntryNew};
|
||||||
use crate::error::{OperationError, SchemaError};
|
|
||||||
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, ReviveRecycledEvent, SearchEvent};
|
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent, ReviveRecycledEvent, SearchEvent};
|
||||||
use crate::modify::{Modify, ModifyList};
|
use crate::modify::{Modify, ModifyList};
|
||||||
use crate::proto::v1::Filter as ProtoFilter;
|
|
||||||
use crate::proto::v1::Modify as ProtoModify;
|
|
||||||
use crate::proto::v1::ModifyList as ProtoModifyList;
|
|
||||||
use crate::proto::v1::{DeleteRequest, ModifyRequest, ReviveRecycledRequest};
|
|
||||||
use crate::server::QueryServerTransaction;
|
use crate::server::QueryServerTransaction;
|
||||||
use crate::value::{PartialValue, Value};
|
use crate::value::{PartialValue, Value};
|
||||||
|
use rsidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
use rsidm_proto::v1::Modify as ProtoModify;
|
||||||
|
use rsidm_proto::v1::ModifyList as ProtoModifyList;
|
||||||
|
use rsidm_proto::v1::{DeleteRequest, ModifyRequest, ReviveRecycledRequest};
|
||||||
|
use rsidm_proto::v1::{OperationError, SchemaError};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -2636,4 +2645,61 @@ mod tests {
|
||||||
// Commit.
|
// Commit.
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_qs_modify_password_only() {
|
||||||
|
run_test!(|server: &QueryServer, audit: &mut AuditScope| {
|
||||||
|
let e1: Entry<EntryInvalid, EntryNew> = Entry::unsafe_from_entry_str(
|
||||||
|
r#"{
|
||||||
|
"valid": null,
|
||||||
|
"state": null,
|
||||||
|
"attrs": {
|
||||||
|
"class": ["object", "person", "account"],
|
||||||
|
"name": ["testperson1"],
|
||||||
|
"uuid": ["cc8e95b4-c24f-4d68-ba54-8bed76f63930"],
|
||||||
|
"description": ["testperson"],
|
||||||
|
"displayname": ["testperson1"]
|
||||||
|
}
|
||||||
|
}"#,
|
||||||
|
);
|
||||||
|
let mut server_txn = server.write();
|
||||||
|
// Add the entry. Today we have no syntax to take simple str to a credential
|
||||||
|
// but honestly, that's probably okay :)
|
||||||
|
let ce = CreateEvent::new_internal(vec![e1]);
|
||||||
|
let cr = server_txn.create(audit, &ce);
|
||||||
|
assert!(cr.is_ok());
|
||||||
|
|
||||||
|
// Build the credential.
|
||||||
|
let cred = Credential::new_password_only("test_password");
|
||||||
|
let v_cred = Value::new_credential("primary", cred);
|
||||||
|
assert!(v_cred.validate());
|
||||||
|
|
||||||
|
// now modify and provide a primary credential.
|
||||||
|
let me_inv_m = unsafe {
|
||||||
|
ModifyEvent::new_internal_invalid(
|
||||||
|
filter!(f_eq("name", PartialValue::new_iutf8s("testperson1"))),
|
||||||
|
ModifyList::new_list(vec![Modify::Present(
|
||||||
|
"primary_credential".to_string(),
|
||||||
|
v_cred,
|
||||||
|
)]),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
// go!
|
||||||
|
assert!(server_txn.modify(audit, &me_inv_m).is_ok());
|
||||||
|
|
||||||
|
// assert it exists and the password checks out
|
||||||
|
let test_ent = server_txn
|
||||||
|
.internal_search_uuid(
|
||||||
|
audit,
|
||||||
|
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
|
||||||
|
)
|
||||||
|
.expect("failed");
|
||||||
|
// get the primary ava
|
||||||
|
let cred_ref = test_ent
|
||||||
|
.get_ava_single_credential("primary_credential")
|
||||||
|
.expect("Failed");
|
||||||
|
// do a pw check.
|
||||||
|
assert!(cred_ref.verify_password("test_password"));
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
use crate::be::dbvalue::DbValueV1;
|
use crate::be::dbvalue::{DbValueCredV1, DbValueV1};
|
||||||
use crate::proto::v1::Filter as ProtoFilter;
|
use crate::credential::Credential;
|
||||||
|
use rsidm_proto::v1::Filter as ProtoFilter;
|
||||||
|
|
||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[allow(non_camel_case_types)]
|
#[allow(non_camel_case_types)]
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
||||||
pub enum IndexType {
|
pub enum IndexType {
|
||||||
|
@ -72,6 +75,7 @@ pub enum SyntaxType {
|
||||||
INDEX_ID,
|
INDEX_ID,
|
||||||
REFERENCE_UUID,
|
REFERENCE_UUID,
|
||||||
JSON_FILTER,
|
JSON_FILTER,
|
||||||
|
CREDENTIAL,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for SyntaxType {
|
impl TryFrom<&str> for SyntaxType {
|
||||||
|
@ -88,6 +92,7 @@ impl TryFrom<&str> for SyntaxType {
|
||||||
"INDEX_ID" => Ok(SyntaxType::INDEX_ID),
|
"INDEX_ID" => Ok(SyntaxType::INDEX_ID),
|
||||||
"REFERENCE_UUID" => Ok(SyntaxType::REFERENCE_UUID),
|
"REFERENCE_UUID" => Ok(SyntaxType::REFERENCE_UUID),
|
||||||
"JSON_FILTER" => Ok(SyntaxType::JSON_FILTER),
|
"JSON_FILTER" => Ok(SyntaxType::JSON_FILTER),
|
||||||
|
"CREDENTIAL" => Ok(SyntaxType::CREDENTIAL),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +111,7 @@ impl TryFrom<usize> for SyntaxType {
|
||||||
5 => Ok(SyntaxType::INDEX_ID),
|
5 => Ok(SyntaxType::INDEX_ID),
|
||||||
6 => Ok(SyntaxType::REFERENCE_UUID),
|
6 => Ok(SyntaxType::REFERENCE_UUID),
|
||||||
7 => Ok(SyntaxType::JSON_FILTER),
|
7 => Ok(SyntaxType::JSON_FILTER),
|
||||||
|
8 => Ok(SyntaxType::CREDENTIAL),
|
||||||
_ => Err(()),
|
_ => Err(()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -122,6 +128,7 @@ impl SyntaxType {
|
||||||
SyntaxType::INDEX_ID => "INDEX_ID",
|
SyntaxType::INDEX_ID => "INDEX_ID",
|
||||||
SyntaxType::REFERENCE_UUID => "REFERENCE_UUID",
|
SyntaxType::REFERENCE_UUID => "REFERENCE_UUID",
|
||||||
SyntaxType::JSON_FILTER => "JSON_FILTER",
|
SyntaxType::JSON_FILTER => "JSON_FILTER",
|
||||||
|
SyntaxType::CREDENTIAL => "CREDENTIAL",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,10 +142,18 @@ impl SyntaxType {
|
||||||
SyntaxType::INDEX_ID => 5,
|
SyntaxType::INDEX_ID => 5,
|
||||||
SyntaxType::REFERENCE_UUID => 6,
|
SyntaxType::REFERENCE_UUID => 6,
|
||||||
SyntaxType::JSON_FILTER => 7,
|
SyntaxType::JSON_FILTER => 7,
|
||||||
|
SyntaxType::CREDENTIAL => 8,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum DataValue {
|
||||||
|
Cred(Credential),
|
||||||
|
// SshKey(String),
|
||||||
|
// RadiusCred(String),
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Deserialize, Serialize)]
|
||||||
pub enum PartialValue {
|
pub enum PartialValue {
|
||||||
Utf8(String),
|
Utf8(String),
|
||||||
|
@ -151,6 +166,10 @@ pub enum PartialValue {
|
||||||
// Does this make sense?
|
// Does this make sense?
|
||||||
// TODO: We'll probably add tagging to this type for the partial matching
|
// TODO: We'll probably add tagging to this type for the partial matching
|
||||||
JsonFilt(ProtoFilter),
|
JsonFilt(ProtoFilter),
|
||||||
|
// Tag, matches to a DataValue.
|
||||||
|
Cred(String),
|
||||||
|
// SshKey(String),
|
||||||
|
// RadiusCred(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialValue {
|
impl PartialValue {
|
||||||
|
@ -299,6 +318,17 @@ impl PartialValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_credential_tag(s: &str) -> Self {
|
||||||
|
PartialValue::Cred(s.to_lowercase())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_credential(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
PartialValue::Cred(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn to_str(&self) -> Option<&str> {
|
pub fn to_str(&self) -> Option<&str> {
|
||||||
match self {
|
match self {
|
||||||
PartialValue::Utf8(s) => Some(s.as_str()),
|
PartialValue::Utf8(s) => Some(s.as_str()),
|
||||||
|
@ -320,15 +350,36 @@ impl PartialValue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Value {
|
pub struct Value {
|
||||||
pv: PartialValue,
|
pv: PartialValue,
|
||||||
// Later we'll add extra data fields for different v types. They'll have to switch on
|
// Later we'll add extra data fields for different v types. They'll have to switch on
|
||||||
// pv somehow, so probably need optional or union?
|
// pv somehow, so probably need optional or union?
|
||||||
|
data: Option<DataValue>,
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Impl display
|
// TODO: Impl display
|
||||||
|
|
||||||
|
impl PartialEq for Value {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.pv.eq(&other.pv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for Value {}
|
||||||
|
|
||||||
|
impl PartialOrd for Value {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||||
|
Some(self.pv.cmp(&other.pv))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for Value {
|
||||||
|
fn cmp(&self, other: &Self) -> Ordering {
|
||||||
|
self.pv.cmp(&other.pv)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Need new_<type> -> Result<_, _>
|
// Need new_<type> -> Result<_, _>
|
||||||
// Need from_db_value
|
// Need from_db_value
|
||||||
// Need to_db_value
|
// Need to_db_value
|
||||||
|
@ -338,6 +389,7 @@ impl From<bool> for Value {
|
||||||
fn from(b: bool) -> Self {
|
fn from(b: bool) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::Bool(b),
|
pv: PartialValue::Bool(b),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,6 +398,7 @@ impl From<&bool> for Value {
|
||||||
fn from(b: &bool) -> Self {
|
fn from(b: &bool) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::Bool(*b),
|
pv: PartialValue::Bool(*b),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,6 +407,7 @@ impl From<SyntaxType> for Value {
|
||||||
fn from(s: SyntaxType) -> Self {
|
fn from(s: SyntaxType) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::Syntax(s),
|
pv: PartialValue::Syntax(s),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -362,6 +416,7 @@ impl From<IndexType> for Value {
|
||||||
fn from(i: IndexType) -> Self {
|
fn from(i: IndexType) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::Index(i),
|
pv: PartialValue::Index(i),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -376,9 +431,11 @@ impl From<&str> for Value {
|
||||||
match Uuid::parse_str(s) {
|
match Uuid::parse_str(s) {
|
||||||
Ok(u) => Value {
|
Ok(u) => Value {
|
||||||
pv: PartialValue::Uuid(u),
|
pv: PartialValue::Uuid(u),
|
||||||
|
data: None,
|
||||||
},
|
},
|
||||||
Err(_) => Value {
|
Err(_) => Value {
|
||||||
pv: PartialValue::Utf8(s.to_string()),
|
pv: PartialValue::Utf8(s.to_string()),
|
||||||
|
data: None,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -389,6 +446,7 @@ impl From<&Uuid> for Value {
|
||||||
fn from(u: &Uuid) -> Self {
|
fn from(u: &Uuid) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::Uuid(u.clone()),
|
pv: PartialValue::Uuid(u.clone()),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -398,6 +456,7 @@ impl From<Uuid> for Value {
|
||||||
fn from(u: Uuid) -> Self {
|
fn from(u: Uuid) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::Uuid(u),
|
pv: PartialValue::Uuid(u),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -407,12 +466,14 @@ impl Value {
|
||||||
pub fn new_utf8(s: String) -> Self {
|
pub fn new_utf8(s: String) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_utf8(s),
|
pv: PartialValue::new_utf8(s),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_utf8s(s: &str) -> Self {
|
pub fn new_utf8s(s: &str) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_utf8s(s),
|
pv: PartialValue::new_utf8s(s),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -426,12 +487,14 @@ impl Value {
|
||||||
pub fn new_iutf8(s: String) -> Self {
|
pub fn new_iutf8(s: String) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_iutf8s(s.as_str()),
|
pv: PartialValue::new_iutf8s(s.as_str()),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_iutf8s(s: &str) -> Self {
|
pub fn new_iutf8s(s: &str) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_iutf8s(s),
|
pv: PartialValue::new_iutf8s(s),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,18 +508,21 @@ impl Value {
|
||||||
pub fn new_uuid(u: Uuid) -> Self {
|
pub fn new_uuid(u: Uuid) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_uuid(u),
|
pv: PartialValue::new_uuid(u),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_uuids(s: &str) -> Option<Self> {
|
pub fn new_uuids(s: &str) -> Option<Self> {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
pv: PartialValue::new_uuids(s)?,
|
pv: PartialValue::new_uuids(s)?,
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_uuidr(u: &Uuid) -> Self {
|
pub fn new_uuidr(u: &Uuid) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_uuidr(u),
|
pv: PartialValue::new_uuidr(u),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -471,24 +537,28 @@ impl Value {
|
||||||
pub fn new_class(s: &str) -> Self {
|
pub fn new_class(s: &str) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_iutf8s(s),
|
pv: PartialValue::new_iutf8s(s),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_attr(s: &str) -> Self {
|
pub fn new_attr(s: &str) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_iutf8s(s),
|
pv: PartialValue::new_iutf8s(s),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_bool(b: bool) -> Self {
|
pub fn new_bool(b: bool) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_bool(b),
|
pv: PartialValue::new_bool(b),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_bools(s: &str) -> Option<Self> {
|
pub fn new_bools(s: &str) -> Option<Self> {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
pv: PartialValue::new_bools(s)?,
|
pv: PartialValue::new_bools(s)?,
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -503,6 +573,7 @@ impl Value {
|
||||||
pub fn new_syntaxs(s: &str) -> Option<Self> {
|
pub fn new_syntaxs(s: &str) -> Option<Self> {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
pv: PartialValue::new_syntaxs(s)?,
|
pv: PartialValue::new_syntaxs(s)?,
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -516,6 +587,7 @@ impl Value {
|
||||||
pub fn new_indexs(s: &str) -> Option<Self> {
|
pub fn new_indexs(s: &str) -> Option<Self> {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
pv: PartialValue::new_indexs(s)?,
|
pv: PartialValue::new_indexs(s)?,
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,18 +601,21 @@ impl Value {
|
||||||
pub fn new_refer(u: Uuid) -> Self {
|
pub fn new_refer(u: Uuid) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_refer(u),
|
pv: PartialValue::new_refer(u),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_refer_r(u: &Uuid) -> Self {
|
pub fn new_refer_r(u: &Uuid) -> Self {
|
||||||
Value {
|
Value {
|
||||||
pv: PartialValue::new_refer_r(u),
|
pv: PartialValue::new_refer_r(u),
|
||||||
|
data: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new_refer_s(us: &str) -> Option<Self> {
|
pub fn new_refer_s(us: &str) -> Option<Self> {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
pv: PartialValue::new_refer_s(us)?,
|
pv: PartialValue::new_refer_s(us)?,
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -554,6 +629,7 @@ impl Value {
|
||||||
pub fn new_json_filter(s: &str) -> Option<Self> {
|
pub fn new_json_filter(s: &str) -> Option<Self> {
|
||||||
Some(Value {
|
Some(Value {
|
||||||
pv: PartialValue::new_json_filter(s)?,
|
pv: PartialValue::new_json_filter(s)?,
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,6 +647,32 @@ impl Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_credential(tag: &str, cred: Credential) -> Self {
|
||||||
|
Value {
|
||||||
|
pv: PartialValue::new_credential_tag(tag),
|
||||||
|
data: Some(DataValue::Cred(cred)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_credential(&self) -> bool {
|
||||||
|
match &self.pv {
|
||||||
|
PartialValue::Cred(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_credential(&self) -> Option<&Credential> {
|
||||||
|
match &self.pv {
|
||||||
|
PartialValue::Cred(_) => match &self.data {
|
||||||
|
Some(dv) => match dv {
|
||||||
|
DataValue::Cred(c) => Some(&c),
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn contains(&self, s: &PartialValue) -> bool {
|
pub fn contains(&self, s: &PartialValue) -> bool {
|
||||||
self.pv.contains(s)
|
self.pv.contains(s)
|
||||||
}
|
}
|
||||||
|
@ -584,35 +686,50 @@ impl Value {
|
||||||
match v {
|
match v {
|
||||||
DbValueV1::U8(s) => Ok(Value {
|
DbValueV1::U8(s) => Ok(Value {
|
||||||
pv: PartialValue::Utf8(s),
|
pv: PartialValue::Utf8(s),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
DbValueV1::I8(s) => {
|
DbValueV1::I8(s) => {
|
||||||
Ok(Value {
|
Ok(Value {
|
||||||
// TODO: Should we be lowercasing here? The dbv should be normalised
|
// TODO: Should we be lowercasing here? The dbv should be normalised
|
||||||
// already, but is there a risk of corruption/tampering if we don't touch this?
|
// already, but is there a risk of corruption/tampering if we don't touch this?
|
||||||
pv: PartialValue::Iutf8(s.to_lowercase()),
|
pv: PartialValue::Iutf8(s.to_lowercase()),
|
||||||
|
data: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
DbValueV1::UU(u) => Ok(Value {
|
DbValueV1::UU(u) => Ok(Value {
|
||||||
pv: PartialValue::Uuid(u),
|
pv: PartialValue::Uuid(u),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
DbValueV1::BO(b) => Ok(Value {
|
DbValueV1::BO(b) => Ok(Value {
|
||||||
pv: PartialValue::Bool(b),
|
pv: PartialValue::Bool(b),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
DbValueV1::SY(us) => Ok(Value {
|
DbValueV1::SY(us) => Ok(Value {
|
||||||
pv: PartialValue::Syntax(SyntaxType::try_from(us)?),
|
pv: PartialValue::Syntax(SyntaxType::try_from(us)?),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
DbValueV1::IN(us) => Ok(Value {
|
DbValueV1::IN(us) => Ok(Value {
|
||||||
pv: PartialValue::Index(IndexType::try_from(us)?),
|
pv: PartialValue::Index(IndexType::try_from(us)?),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
DbValueV1::RF(u) => Ok(Value {
|
DbValueV1::RF(u) => Ok(Value {
|
||||||
pv: PartialValue::Refer(u),
|
pv: PartialValue::Refer(u),
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
DbValueV1::JF(s) => Ok(Value {
|
DbValueV1::JF(s) => Ok(Value {
|
||||||
pv: match PartialValue::new_json_filter(s.as_str()) {
|
pv: match PartialValue::new_json_filter(s.as_str()) {
|
||||||
Some(pv) => pv,
|
Some(pv) => pv,
|
||||||
None => return Err(()),
|
None => return Err(()),
|
||||||
},
|
},
|
||||||
|
data: None,
|
||||||
}),
|
}),
|
||||||
|
DbValueV1::CR(dvc) => {
|
||||||
|
// Deserialise the db cred here.
|
||||||
|
Ok(Value {
|
||||||
|
pv: PartialValue::Cred(dvc.t.to_lowercase()),
|
||||||
|
data: Some(DataValue::Cred(Credential::try_from(dvc.d)?)),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,6 +747,24 @@ impl Value {
|
||||||
serde_json::to_string(s)
|
serde_json::to_string(s)
|
||||||
.expect("A json filter value was corrupted during run-time"),
|
.expect("A json filter value was corrupted during run-time"),
|
||||||
),
|
),
|
||||||
|
PartialValue::Cred(tag) => {
|
||||||
|
// Get the credential out and make sure it matches the type we expect.
|
||||||
|
let c = match &self.data {
|
||||||
|
Some(v) => {
|
||||||
|
match &v {
|
||||||
|
DataValue::Cred(c) => c,
|
||||||
|
// _ => panic!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => panic!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Save the tag AND the dataValue here!
|
||||||
|
DbValueV1::CR(DbValueCredV1 {
|
||||||
|
t: tag.clone(),
|
||||||
|
d: c.to_db_valuev1(),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -647,6 +782,11 @@ impl Value {
|
||||||
PartialValue::JsonFilt(s) => {
|
PartialValue::JsonFilt(s) => {
|
||||||
serde_json::to_string(s).expect("A json filter value was corrupted during run-time")
|
serde_json::to_string(s).expect("A json filter value was corrupted during run-time")
|
||||||
}
|
}
|
||||||
|
PartialValue::Cred(tag) => {
|
||||||
|
// You can't actually read the credential values because we only display the
|
||||||
|
// tag to the proto side. The credentials private data is stored seperately.
|
||||||
|
tag.to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -717,7 +857,18 @@ impl Value {
|
||||||
// Validate that extra-data constraints on the type exist and are
|
// Validate that extra-data constraints on the type exist and are
|
||||||
// valid. IE json filter is really a filter, or cred types have supplemental
|
// valid. IE json filter is really a filter, or cred types have supplemental
|
||||||
// data.
|
// data.
|
||||||
true
|
match &self.pv {
|
||||||
|
PartialValue::Cred(_) => match &self.data {
|
||||||
|
Some(v) => {
|
||||||
|
match &v {
|
||||||
|
DataValue::Cred(_) => true,
|
||||||
|
// _ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => false,
|
||||||
|
},
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
extern crate actix;
|
extern crate actix;
|
||||||
extern crate env_logger;
|
extern crate env_logger;
|
||||||
|
extern crate rpassword;
|
||||||
|
|
||||||
extern crate rsidm;
|
extern crate rsidm;
|
||||||
extern crate structopt;
|
extern crate structopt;
|
||||||
|
@ -10,7 +11,8 @@ extern crate log;
|
||||||
|
|
||||||
use rsidm::config::Configuration;
|
use rsidm::config::Configuration;
|
||||||
use rsidm::core::{
|
use rsidm::core::{
|
||||||
backup_server_core, create_server_core, restore_server_core, verify_server_core,
|
backup_server_core, create_server_core, recover_account_core, restore_server_core,
|
||||||
|
verify_server_core,
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -40,6 +42,14 @@ struct RestoreOpt {
|
||||||
serveropts: ServerOpt,
|
serveropts: ServerOpt,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, StructOpt)]
|
||||||
|
struct RecoverAccountOpt {
|
||||||
|
#[structopt(short)]
|
||||||
|
name: String,
|
||||||
|
#[structopt(flatten)]
|
||||||
|
serveropts: ServerOpt,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
enum Opt {
|
enum Opt {
|
||||||
#[structopt(name = "server")]
|
#[structopt(name = "server")]
|
||||||
|
@ -50,6 +60,8 @@ enum Opt {
|
||||||
Restore(RestoreOpt),
|
Restore(RestoreOpt),
|
||||||
#[structopt(name = "verify")]
|
#[structopt(name = "verify")]
|
||||||
Verify(ServerOpt),
|
Verify(ServerOpt),
|
||||||
|
#[structopt(name = "recover_account")]
|
||||||
|
RecoverAccount(RecoverAccountOpt),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
|
@ -62,7 +74,7 @@ fn main() {
|
||||||
|
|
||||||
// Configure the server logger. This could be adjusted based on what config
|
// Configure the server logger. This could be adjusted based on what config
|
||||||
// says.
|
// says.
|
||||||
::std::env::set_var("RUST_LOG", "actix_web=info,rsidm=info");
|
// ::std::env::set_var("RUST_LOG", "actix_web=info,rsidm=info");
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
match opt {
|
match opt {
|
||||||
|
@ -109,5 +121,13 @@ fn main() {
|
||||||
config.update_db_path(&vopt.db_path);
|
config.update_db_path(&vopt.db_path);
|
||||||
verify_server_core(config);
|
verify_server_core(config);
|
||||||
}
|
}
|
||||||
|
Opt::RecoverAccount(raopt) => {
|
||||||
|
info!("Running account recovery ...");
|
||||||
|
|
||||||
|
let password = rpassword::prompt_password_stderr("new password: ").unwrap();
|
||||||
|
config.update_db_path(&raopt.serveropts.db_path);
|
||||||
|
|
||||||
|
recover_account_core(config, raopt.name, password);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,13 +0,0 @@
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
|
||||||
pub enum DbValueV1 {
|
|
||||||
U8(String),
|
|
||||||
I8(String),
|
|
||||||
UU(Uuid),
|
|
||||||
BO(bool),
|
|
||||||
SY(usize),
|
|
||||||
IN(usize),
|
|
||||||
RF(Uuid),
|
|
||||||
JF(String),
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
//use rusqlite::Error as RusqliteError;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
||||||
pub enum SchemaError {
|
|
||||||
NotImplemented,
|
|
||||||
InvalidClass,
|
|
||||||
MissingMustAttribute(String),
|
|
||||||
InvalidAttribute,
|
|
||||||
InvalidAttributeSyntax,
|
|
||||||
EmptyFilter,
|
|
||||||
Corrupted,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
||||||
pub enum OperationError {
|
|
||||||
EmptyRequest,
|
|
||||||
Backend,
|
|
||||||
NoMatchingEntries,
|
|
||||||
CorruptedEntry(u64),
|
|
||||||
ConsistencyError(Vec<Result<(), ConsistencyError>>),
|
|
||||||
SchemaViolation(SchemaError),
|
|
||||||
Plugin,
|
|
||||||
FilterGeneration,
|
|
||||||
FilterUUIDResolution,
|
|
||||||
InvalidAttributeName(String),
|
|
||||||
InvalidAttribute(&'static str),
|
|
||||||
InvalidDBState,
|
|
||||||
InvalidEntryID,
|
|
||||||
InvalidRequestState,
|
|
||||||
InvalidState,
|
|
||||||
InvalidEntryState,
|
|
||||||
InvalidUuid,
|
|
||||||
InvalidACPState(&'static str),
|
|
||||||
InvalidSchemaState(&'static str),
|
|
||||||
InvalidAccountState(&'static str),
|
|
||||||
BackendEngine,
|
|
||||||
SQLiteError, //(RusqliteError)
|
|
||||||
FsError,
|
|
||||||
SerdeJsonError,
|
|
||||||
SerdeCborError,
|
|
||||||
AccessDenied,
|
|
||||||
NotAuthenticated,
|
|
||||||
InvalidAuthState(&'static str),
|
|
||||||
InvalidSessionState,
|
|
||||||
SystemProtectedObject,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
||||||
pub enum ConsistencyError {
|
|
||||||
Unknown,
|
|
||||||
// Class, Attribute
|
|
||||||
SchemaClassMissingAttribute(String, String),
|
|
||||||
QueryServerSearchFailure,
|
|
||||||
EntryUuidCorrupt(u64),
|
|
||||||
UuidIndexCorrupt(String),
|
|
||||||
UuidNotUnique(String),
|
|
||||||
RefintNotUpheld(u64),
|
|
||||||
MemberOfInvalid(u64),
|
|
||||||
InvalidAttributeType(&'static str),
|
|
||||||
}
|
|
|
@ -1,63 +0,0 @@
|
||||||
#![deny(warnings)]
|
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate log;
|
|
||||||
extern crate serde;
|
|
||||||
extern crate serde_cbor;
|
|
||||||
extern crate serde_json;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
extern crate actix;
|
|
||||||
extern crate actix_web;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate r2d2;
|
|
||||||
extern crate r2d2_sqlite;
|
|
||||||
extern crate rand;
|
|
||||||
extern crate rusqlite;
|
|
||||||
extern crate time;
|
|
||||||
extern crate uuid;
|
|
||||||
|
|
||||||
extern crate bytes;
|
|
||||||
extern crate chrono;
|
|
||||||
extern crate cookie;
|
|
||||||
extern crate env_logger;
|
|
||||||
|
|
||||||
extern crate regex;
|
|
||||||
#[macro_use]
|
|
||||||
extern crate lazy_static;
|
|
||||||
|
|
||||||
extern crate concread;
|
|
||||||
|
|
||||||
// use actix::prelude::*;
|
|
||||||
// use actix_web::{
|
|
||||||
// http, middleware, App, AsyncResponder, FutureResponse, HttpRequest, HttpResponse, Path, State,
|
|
||||||
// };
|
|
||||||
|
|
||||||
// use futures::Future;
|
|
||||||
|
|
||||||
// This has to be before be so the import order works
|
|
||||||
#[macro_use]
|
|
||||||
mod macros;
|
|
||||||
#[macro_use]
|
|
||||||
mod async_log;
|
|
||||||
#[macro_use]
|
|
||||||
mod audit;
|
|
||||||
mod be;
|
|
||||||
pub mod constants;
|
|
||||||
mod entry;
|
|
||||||
mod event;
|
|
||||||
mod filter;
|
|
||||||
mod interval;
|
|
||||||
mod modify;
|
|
||||||
mod value;
|
|
||||||
#[macro_use]
|
|
||||||
mod plugins;
|
|
||||||
mod access;
|
|
||||||
mod idm;
|
|
||||||
mod schema;
|
|
||||||
mod server;
|
|
||||||
|
|
||||||
pub mod config;
|
|
||||||
pub mod core;
|
|
||||||
pub mod error;
|
|
||||||
pub mod proto;
|
|
|
@ -1,28 +0,0 @@
|
||||||
// Implement a client library that use used to interact with
|
|
||||||
// a kanidm server and perform operations - it is expected that
|
|
||||||
// this client can store some internal state, and will generally
|
|
||||||
// attempt to reflect and map to a simple representation of
|
|
||||||
// the protocol, which was intended to be easy-to-use and accessible.
|
|
||||||
|
|
||||||
/*
|
|
||||||
struct ClientV1 {}
|
|
||||||
|
|
||||||
impl ClientV1 {
|
|
||||||
fn auth_anonymous() -> () {}
|
|
||||||
|
|
||||||
fn auth_password() -> () {}
|
|
||||||
|
|
||||||
fn auth_application_password() -> () {}
|
|
||||||
|
|
||||||
fn whoami() -> () {}
|
|
||||||
|
|
||||||
// The four raw operations.
|
|
||||||
fn search() -> () {}
|
|
||||||
|
|
||||||
fn modify() -> () {}
|
|
||||||
|
|
||||||
fn create() -> () {}
|
|
||||||
|
|
||||||
fn delete() -> () {}
|
|
||||||
}
|
|
||||||
*/
|
|
|
@ -1,45 +0,0 @@
|
||||||
use crate::error::OperationError;
|
|
||||||
use actix::prelude::*;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
use crate::proto::v1::{AuthRequest, AuthResponse, UserAuthToken, WhoamiResponse};
|
|
||||||
|
|
||||||
// These are used when the request (IE Get) has no intrising request
|
|
||||||
// type. Additionally, they are used in some requests where we need
|
|
||||||
// to supplement extra server state (IE userauthtokens) to a request.
|
|
||||||
//
|
|
||||||
// Generally we don't need to have the responses here because they are
|
|
||||||
// part of the protocol.
|
|
||||||
|
|
||||||
pub struct WhoamiMessage {
|
|
||||||
pub uat: Option<UserAuthToken>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl WhoamiMessage {
|
|
||||||
pub fn new(uat: Option<UserAuthToken>) -> Self {
|
|
||||||
WhoamiMessage { uat: uat }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for WhoamiMessage {
|
|
||||||
type Result = Result<WhoamiResponse, OperationError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct AuthMessage {
|
|
||||||
pub sessionid: Option<Uuid>,
|
|
||||||
pub req: AuthRequest,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl AuthMessage {
|
|
||||||
pub fn new(req: AuthRequest, sessionid: Option<Uuid>) -> Self {
|
|
||||||
AuthMessage {
|
|
||||||
sessionid: sessionid,
|
|
||||||
req: req,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for AuthMessage {
|
|
||||||
type Result = Result<AuthResponse, OperationError>;
|
|
||||||
}
|
|
Loading…
Reference in a new issue