mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
User feedback improvements, also handling a permissions issue (#424)
This commit is contained in:
parent
f9dd0a78dc
commit
77381c1a2a
13
.clippy.toml
Normal file
13
.clippy.toml
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
########################################################################
|
||||||
|
# complexity clippy settings
|
||||||
|
#
|
||||||
|
# because the clippy devs also acknowledge that complexity is hard to define
|
||||||
|
# https://github.com/rust-lang/rust-clippy/issues/5418#issuecomment-610054361
|
||||||
|
#
|
||||||
|
########################################################################
|
||||||
|
|
||||||
|
# default is 7, 8's ok. https://rust-lang.github.io/rust-clippy/master/index.html#too_many_arguments
|
||||||
|
too-many-arguments-threshold = 8
|
||||||
|
|
||||||
|
# default's 250
|
||||||
|
type-complexity-threshold = 300
|
|
@ -15,6 +15,7 @@ use serde_derive::Deserialize;
|
||||||
use serde_json::error::Error as SerdeJsonError;
|
use serde_json::error::Error as SerdeJsonError;
|
||||||
use std::collections::BTreeSet as Set;
|
use std::collections::BTreeSet as Set;
|
||||||
use std::fs::{metadata, File, Metadata};
|
use std::fs::{metadata, File, Metadata};
|
||||||
|
use std::io::ErrorKind;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::os::unix::fs::MetadataExt;
|
use std::os::unix::fs::MetadataExt;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
@ -151,10 +152,26 @@ impl KanidmClientBuilder {
|
||||||
let mut f = match File::open(&config_path) {
|
let mut f = match File::open(&config_path) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
debug!(
|
match e.kind() {
|
||||||
"Unable to open config file {:#?} [{:?}], skipping ...",
|
ErrorKind::NotFound => {
|
||||||
&config_path, e
|
debug!(
|
||||||
);
|
"Configuration file {:#?} not found, skipping ...",
|
||||||
|
&config_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
ErrorKind::PermissionDenied => {
|
||||||
|
warn!(
|
||||||
|
"Permission denied loading configuration file {:#?}, skipping ...",
|
||||||
|
&config_path
|
||||||
|
);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
debug!(
|
||||||
|
"Unable to open config file {:#?} [{:?}], skipping ...",
|
||||||
|
&config_path, e
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -81,7 +81,7 @@ impl CommonOpt {
|
||||||
#[allow(clippy::expect_used)]
|
#[allow(clippy::expect_used)]
|
||||||
let (f_uname, f_token) = tokens.iter().next().expect("Memory Corruption");
|
let (f_uname, f_token) = tokens.iter().next().expect("Memory Corruption");
|
||||||
// else pick the first token
|
// else pick the first token
|
||||||
info!("Authenticated as {}", f_uname);
|
info!("Using cached token for name {}", f_uname);
|
||||||
f_token.clone()
|
f_token.clone()
|
||||||
} else {
|
} else {
|
||||||
// Unable to select
|
// Unable to select
|
||||||
|
|
|
@ -36,13 +36,18 @@ impl SelfOpt {
|
||||||
let client = copt.to_client();
|
let client = copt.to_client();
|
||||||
|
|
||||||
match client.whoami() {
|
match client.whoami() {
|
||||||
Ok(o_ent) => match o_ent {
|
Ok(o_ent) => {
|
||||||
Some((ent, uat)) => {
|
match o_ent {
|
||||||
debug!("{:?}", ent);
|
Some((ent, uat)) => {
|
||||||
println!("{}", uat);
|
debug!("{:?}", ent);
|
||||||
|
println!("{}", uat);
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Authentication with cached token failed, can't query information.");
|
||||||
|
// TODO: remove token when we know it's not valid
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => println!("Unauthenticated"),
|
}
|
||||||
},
|
|
||||||
Err(e) => println!("Error: {:?}", e),
|
Err(e) => println!("Error: {:?}", e),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,13 +58,13 @@ impl SelfOpt {
|
||||||
let password = match rpassword::prompt_password_stderr("Enter new password: ") {
|
let password = match rpassword::prompt_password_stderr("Enter new password: ") {
|
||||||
Ok(p) => p,
|
Ok(p) => p,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("Error -> {:?}", e);
|
error!("Error -> {:?}", e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = client.idm_account_set_password(password) {
|
if let Err(e) = client.idm_account_set_password(password) {
|
||||||
eprintln!("Error -> {:?}", e);
|
error!("Error -> {:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +111,7 @@ pub(crate) fn password_prompt(prompt: &str) -> Option<String> {
|
||||||
if password == password_confirm {
|
if password == password_confirm {
|
||||||
return Some(password);
|
return Some(password);
|
||||||
} else {
|
} else {
|
||||||
eprintln!("Passwords do not match");
|
error!("Passwords do not match");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None
|
None
|
||||||
|
|
|
@ -4,6 +4,7 @@ use kanidm_proto::v1::{AuthAllowed, AuthResponse, AuthState};
|
||||||
use libc::umask;
|
use libc::umask;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::fs::{create_dir, File};
|
use std::fs::{create_dir, File};
|
||||||
|
use std::io::ErrorKind;
|
||||||
use std::io::{self, BufReader, BufWriter};
|
use std::io::{self, BufReader, BufWriter};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use webauthn_authenticator_rs::{u2fhid::U2FHid, RequestChallengeResponse, WebauthnAuthenticator};
|
use webauthn_authenticator_rs::{u2fhid::U2FHid, RequestChallengeResponse, WebauthnAuthenticator};
|
||||||
|
@ -15,7 +16,7 @@ pub fn read_tokens() -> Result<BTreeMap<String, String>, ()> {
|
||||||
let token_path = PathBuf::from(shellexpand::tilde(TOKEN_PATH).into_owned());
|
let token_path = PathBuf::from(shellexpand::tilde(TOKEN_PATH).into_owned());
|
||||||
if !token_path.exists() {
|
if !token_path.exists() {
|
||||||
debug!(
|
debug!(
|
||||||
"Token path {} does not exist, assuming empty ... ",
|
"Token cache file path {:?} does not exist, returning an empty token store.",
|
||||||
TOKEN_PATH
|
TOKEN_PATH
|
||||||
);
|
);
|
||||||
return Ok(BTreeMap::new());
|
return Ok(BTreeMap::new());
|
||||||
|
@ -26,11 +27,24 @@ pub fn read_tokens() -> Result<BTreeMap<String, String>, ()> {
|
||||||
let file = match File::open(&token_path) {
|
let file = match File::open(&token_path) {
|
||||||
Ok(f) => f,
|
Ok(f) => f,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!(
|
match e.kind() {
|
||||||
"Cannot read tokens from {} due to error: {:?} ... continuing.",
|
ErrorKind::PermissionDenied => {
|
||||||
TOKEN_PATH, e
|
// we bail here because you won't be able to write them back...
|
||||||
);
|
error!(
|
||||||
return Ok(BTreeMap::new());
|
"Permission denied reading token store file {:?}",
|
||||||
|
&token_path
|
||||||
|
);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
// other errors are OK to continue past
|
||||||
|
_ => {
|
||||||
|
warn!(
|
||||||
|
"Cannot read tokens from {} due to error: {:?} ... continuing.",
|
||||||
|
TOKEN_PATH, e
|
||||||
|
);
|
||||||
|
return Ok(BTreeMap::new());
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let reader = BufReader::new(file);
|
let reader = BufReader::new(file);
|
||||||
|
|
|
@ -153,6 +153,12 @@ pub(crate) async fn create_ldap_server(
|
||||||
opt_tls_params: Option<SslAcceptorBuilder>,
|
opt_tls_params: Option<SslAcceptorBuilder>,
|
||||||
qe_r_ref: &'static QueryServerReadV1,
|
qe_r_ref: &'static QueryServerReadV1,
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
|
if address.starts_with(":::") {
|
||||||
|
// takes :::xxxx to xxxx
|
||||||
|
let port = address.replacen(":::", "", 1);
|
||||||
|
eprintln!("Address '{}' looks like an attempt to wildcard bind with IPv6 on port {} - please try using ldapbindaddress = '[::]:{}'", address, port, port);
|
||||||
|
};
|
||||||
|
|
||||||
let addr = net::SocketAddr::from_str(address).map_err(|e| {
|
let addr = net::SocketAddr::from_str(address).map_err(|e| {
|
||||||
eprintln!("Could not parse ldap server address {} -> {:?}", address, e);
|
eprintln!("Could not parse ldap server address {} -> {:?}", address, e);
|
||||||
})?;
|
})?;
|
||||||
|
|
|
@ -2,23 +2,21 @@
|
||||||
//! concepts along with [`filter`]s and [`schema`] that everything else builds upon.
|
//! concepts along with [`filter`]s and [`schema`] that everything else builds upon.
|
||||||
//!
|
//!
|
||||||
//! An [`Entry`] is a collection of attribute-value sets. There are sometimes called attribute value
|
//! An [`Entry`] is a collection of attribute-value sets. There are sometimes called attribute value
|
||||||
//! assertions, or avas. The attribute is a "key" and it holds 1 to infinitite associtade values
|
//! assertions, or AVAs. The attribute is a "key" and it holds 1 to infinite associated values
|
||||||
//! with no ordering. An entry has many avas. A pseudo example, minus schema and typing:
|
//! with no ordering. An entry has many AVAs. A pseudo example, minus schema and typing:
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```text
|
||||||
//! /*
|
|
||||||
//! Entry {
|
//! Entry {
|
||||||
//! "name": ["william"],
|
//! "name": ["william"],
|
||||||
//! "uuid": ["..."],
|
//! "uuid": ["..."],
|
||||||
//! "mail": ["maila@example.com", "mailb@example.com"],
|
//! "mail": ["maila@example.com", "mailb@example.com"],
|
||||||
//! }
|
//! };
|
||||||
//! */
|
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! There are three rules for entries:
|
//! There are three rules for entries:
|
||||||
//! * Must have an ava for UUID containing a single value.
|
//! * Must have an AVA for UUID containing a single value.
|
||||||
//! * Any ava with zero values will be removed.
|
//! * Any AVA with zero values will be removed.
|
||||||
//! * Avas are stored with no sorting.
|
//! * AVAs are stored with no sorting.
|
||||||
//!
|
//!
|
||||||
//! For more, see the [`Entry`] type.
|
//! For more, see the [`Entry`] type.
|
||||||
//!
|
//!
|
||||||
|
@ -56,7 +54,7 @@ use uuid::Uuid;
|
||||||
// use std::str::FromStr;
|
// use std::str::FromStr;
|
||||||
|
|
||||||
// make a trait entry for everything to adhere to?
|
// make a trait entry for everything to adhere to?
|
||||||
// * How to get indexs out?
|
// * How to get indexes out?
|
||||||
// * How to track pending diffs?
|
// * How to track pending diffs?
|
||||||
|
|
||||||
// Entry is really similar to serde Value, but limits the possibility
|
// Entry is really similar to serde Value, but limits the possibility
|
||||||
|
@ -172,17 +170,17 @@ fn compare_attrs(left: &Map<AttrString, Set<Value>>, right: &Map<AttrString, Set
|
||||||
/// Entry is the core data storage type of the server. Almost every aspect of the server is
|
/// Entry is the core data storage type of the server. Almost every aspect of the server is
|
||||||
/// designed to read, handle and manipulate entries.
|
/// designed to read, handle and manipulate entries.
|
||||||
///
|
///
|
||||||
/// Entries store attribute value assertions, or ava. These are sets of key-values.
|
/// Entries store attribute value assertions, or AVA. These are sets of key-values.
|
||||||
///
|
///
|
||||||
/// Entries have a lifecycle within a single operation, and as part of replication.
|
/// Entries have a lifecycle within a single operation, and as part of replication.
|
||||||
/// The lifecycle for operations is defined through state and valid types. Each entry has a pair
|
/// The lifecycle for operations is defined through state and valid types. Each entry has a pair
|
||||||
/// Of these types at anytime. The first is the ava [`schema`] and [`access`] control assertion
|
/// Of these types at anytime. The first is the AVA [`schema`] and [`access`] control assertion
|
||||||
/// state. This is represented by the type `VALID` as one of `EntryValid`, `EntryInvalid` or
|
/// state. This is represented by the type `VALID` as one of `EntryValid`, `EntryInvalid` or
|
||||||
/// `EntryReduced`. Every entry starts as `EntryInvalid`, and when checked by the schema for
|
/// `EntryReduced`. Every entry starts as `EntryInvalid`, and when checked by the schema for
|
||||||
/// correctness, transitions to `EntryValid`. While an entry is `EntryValid` it can not be
|
/// correctness, transitions to `EntryValid`. While an entry is `EntryValid` it can not be
|
||||||
/// altered - you must invalidate it to `EntryInvalid`, then modify, then check again.
|
/// altered - you must invalidate it to `EntryInvalid`, then modify, then check again.
|
||||||
/// An entry that has had access controls applied moves from `EntryValid` to `EntryReduced`,
|
/// An entry that has had access controls applied moves from `EntryValid` to `EntryReduced`,
|
||||||
/// to show that the avas have reduced to the valid read set of the current [`event`] user.
|
/// to show that the AVAs have reduced to the valid read set of the current [`event`] user.
|
||||||
///
|
///
|
||||||
/// The second type of `STATE` represents the database commit state and internal db ID's. A
|
/// The second type of `STATE` represents the database commit state and internal db ID's. A
|
||||||
/// new entry that has never been committed is `EntryNew`, but an entry that has been retrieved
|
/// new entry that has never been committed is `EntryNew`, but an entry that has been retrieved
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
//! [`Filter`]s are one of the three foundational concepts of the design in kanidm.
|
//! [`Filter`]s are one of the three foundational concepts of the design in kanidm.
|
||||||
//! They are used in nearly every aspect ofthe server to provide searching of
|
//! They are used in nearly every aspect of the server to provide searching of
|
||||||
//! datasets, and assertion of entry properties.
|
//! datasets and assertion of entry properties.
|
||||||
//!
|
//!
|
||||||
//! A filter is a logical statement of properties that an [`Entry`] and it's
|
//! A filter is a logical statement of properties that an [`Entry`] and its
|
||||||
//! avas must uphold to be considered true.
|
//! AVAs must uphold to be considered true.
|
||||||
//!
|
//!
|
||||||
//! [`Filter`]: struct.Filter.html
|
//! [`Filter`]: struct.Filter.html
|
||||||
//! [`Entry`]: ../entry/struct.Entry.html
|
//! [`Entry`]: ../entry/struct.Entry.html
|
||||||
|
|
|
@ -431,8 +431,6 @@ fn ldap_domain_to_dc(input: &str) -> String {
|
||||||
output.push_str(dc);
|
output.push_str(dc);
|
||||||
#[allow(clippy::single_char_add_str)]
|
#[allow(clippy::single_char_add_str)]
|
||||||
output.push_str(",");
|
output.push_str(",");
|
||||||
// Can't use concat as it's evalled at compile, not run time.
|
|
||||||
// output.push_str(concat!("dc=", dc, ","));
|
|
||||||
});
|
});
|
||||||
// Remove the last ','
|
// Remove the last ','
|
||||||
output.pop();
|
output.pop();
|
||||||
|
|
Loading…
Reference in a new issue