diff --git a/kanidmd/src/lib/access.rs b/kanidmd/src/lib/access.rs index f20227c1e..452b8c0bf 100644 --- a/kanidmd/src/lib/access.rs +++ b/kanidmd/src/lib/access.rs @@ -1,19 +1,18 @@ -// Access Control Profiles -// -// This is a pretty important and security sensitive part of the code - it's -// responsible for making sure that who is allowed to do what is enforced, as -// well as who is *not* allowed to do what. -// -// A detailed design can be found in access-profiles-and-security. - -// -// This part of the server really has a few parts -// - the ability to parse access profile structures into real ACP structs -// - the ability to apply sets of ACP's to entries for coarse actions (IE -// search. -// - the ability to turn an entry into a partial-entry for results send -// requirements (also search). -// +//! Access Control Profiles +//! +//! This is a pretty important and security sensitive part of the code - it's +//! responsible for making sure that who is allowed to do what is enforced, as +//! well as who is *not* allowed to do what. +//! +//! A detailed design can be found in access-profiles-and-security. +//! +//! This component of the server really has a few parts +//! - the ability to parse access profile structures into real ACP structs +//! - the ability to apply sets of ACP's to entries for coarse actions (IE +//! search. +//! - the ability to turn an entry into a partial-entry for results send +//! requirements (also search). +//! // use concread::collections::bptree::*; use concread::arcache::{ARCache, ARCacheReadTxn}; @@ -65,7 +64,7 @@ impl AccessControlSearch { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_value_pres("class", &CLASS_ACS) { + if !value.attribute_equality("class", &CLASS_ACS) { ladmin_error!(audit, "class access_control_search not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_search".to_string(), @@ -120,7 +119,7 @@ impl AccessControlDelete { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_value_pres("class", &CLASS_ACD) { + if !value.attribute_equality("class", &CLASS_ACD) { ladmin_error!(audit, "class access_control_delete not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_delete".to_string(), @@ -163,7 +162,7 @@ impl AccessControlCreate { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_value_pres("class", &CLASS_ACC) { + if !value.attribute_equality("class", &CLASS_ACC) { ladmin_error!(audit, "class access_control_create not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_create".to_string(), @@ -223,7 +222,7 @@ impl AccessControlModify { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_value_pres("class", &CLASS_ACM) { + if !value.attribute_equality("class", &CLASS_ACM) { ladmin_error!(audit, "class access_control_modify not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_modify".to_string(), @@ -301,7 +300,7 @@ impl AccessControlProfile { value: &Entry, ) -> Result { // Assert we have class access_control_profile - if !value.attribute_value_pres("class", &CLASS_ACP) { + if !value.attribute_equality("class", &CLASS_ACP) { ladmin_error!(audit, "class access_control_profile not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_profile".to_string(), diff --git a/kanidmd/src/lib/actors/mod.rs b/kanidmd/src/lib/actors/mod.rs index e38d2bcd7..b52b81738 100644 --- a/kanidmd/src/lib/actors/mod.rs +++ b/kanidmd/src/lib/actors/mod.rs @@ -1,2 +1,6 @@ +//! This module contains the server's async tasks that are called from the various frontend +//! components to conduct operations. These are seperated based on protocol versions and +//! if they are read or write transactions internally. + pub mod v1_read; pub mod v1_write; diff --git a/kanidmd/src/lib/be/mod.rs b/kanidmd/src/lib/be/mod.rs index 19b32e05f..63cc10213 100644 --- a/kanidmd/src/lib/be/mod.rs +++ b/kanidmd/src/lib/be/mod.rs @@ -1,3 +1,9 @@ +//! The backend. This contains the "low level" storage and query code, which is +//! implemented as a json-like kv document database. This has no rules about content +//! of the server, which are all enforced at higher levels. The role of the backend +//! is to persist content safely to disk, load that content, and execute queries +//! utilising indexes in the most effective way possible. + use std::fs; use crate::value::IndexType; diff --git a/kanidmd/src/lib/config.rs b/kanidmd/src/lib/config.rs index 14a446f08..c8e768de1 100644 --- a/kanidmd/src/lib/config.rs +++ b/kanidmd/src/lib/config.rs @@ -1,3 +1,9 @@ +//! The server configuration as processed from the startup wrapper. This controls a number of +//! variables that determine how our backends, query server, and frontends are configured. +//! +//! These components should be "per server". Any "per domain" config should be in the system +//! or domain entries that are able to be replicated. + use rand::prelude::*; use std::fmt; use std::str::FromStr; diff --git a/kanidmd/src/lib/core/mod.rs b/kanidmd/src/lib/core/mod.rs index 0643ea97d..c1adbb19e 100644 --- a/kanidmd/src/lib/core/mod.rs +++ b/kanidmd/src/lib/core/mod.rs @@ -1,3 +1,13 @@ +//! These contain the server "cores". These are able to startup the server +//! (bootstrap) to a running state and then execute tasks. This is where modules +//! are logically ordered based on their depenedncies for execution. Some of these +//! are task-only i.e. reindexing, and some of these launch the server into a +//! fully operational state (https, ldap, etc). +//! +//! Generally, this is the "entry point" where the server begins to run, and +//! the entry point for all client traffic which is then directed to the +//! varius [`actors`]. + mod https; mod ldaps; use libc::umask; diff --git a/kanidmd/src/lib/credential/mod.rs b/kanidmd/src/lib/credential/mod.rs index ca15f71a9..c34e56dd0 100644 --- a/kanidmd/src/lib/credential/mod.rs +++ b/kanidmd/src/lib/credential/mod.rs @@ -235,6 +235,10 @@ pub struct Credential { } #[derive(Clone, Debug)] +/// The typo of credential that is stored. Each of these represents a full set of 'what is required' +/// to complete an authentication session. The reason to have these typed like this is so we can +/// apply policy later to what classes or levels of credentials can be used. We use these types +/// to also know what type of auth session handler to initiate. pub enum CredentialType { // Anonymous, Password(Password), @@ -334,6 +338,7 @@ impl TryFrom for Credential { } impl Credential { + /// Create a new credential that contains a CredentialType::Password pub fn new_password_only( policy: &CryptoPolicy, cleartext: &str, @@ -341,6 +346,7 @@ impl Credential { Password::new(policy, cleartext).map(Self::new_from_password) } + /// Create a new credential that contains a CredentialType::GeneratedPassword pub fn new_generatedpassword_only( policy: &CryptoPolicy, cleartext: &str, @@ -348,6 +354,7 @@ impl Credential { Password::new(policy, cleartext).map(Self::new_from_generatedpassword) } + /// Create a new credential that contains a CredentialType::Webauthn pub fn new_webauthn_only(label: String, cred: WebauthnCredential) -> Self { let mut webauthn_map = Map::new(); webauthn_map.insert(label, cred); @@ -358,6 +365,8 @@ impl Credential { } } + /// Update the state of the Password on this credential, if a password is present. If possible + /// this will convert the credential to a PasswordMFA in some cases, or fail in others. pub fn set_password( &self, policy: &CryptoPolicy, @@ -366,6 +375,9 @@ impl Credential { Password::new(policy, cleartext).map(|pw| self.update_password(pw)) } + /// Extend this credential with another alternate webauthn credential. This is especially + /// useful for `PasswordMfa` where you can have many webauthn credentials and a password + /// generally so that one is a backup. pub fn append_webauthn( &self, label: String, @@ -407,6 +419,7 @@ impl Credential { }) } + /// Remove a webauthn token identified by `label` from this Credential. pub fn remove_webauthn(&self, label: &str) -> Result { let type_ = match &self.type_ { CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => { @@ -459,6 +472,8 @@ impl Credential { } #[allow(clippy::ptr_arg)] + /// After a successful authentication with Webauthn, we need to advance the credentials + /// counter value to prevent certain classes of replay attacks. pub fn update_webauthn_counter( &self, cid: &CredentialID, @@ -514,6 +529,7 @@ impl Credential { })) } + /// Get a reference to the contained webuthn credentials, if any. pub fn webauthn_ref(&self) -> Result<&Map, OperationError> { match &self.type_ { CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => Err( @@ -523,6 +539,7 @@ impl Credential { } } + /// Get a reference to the contained password, if any. pub fn password_ref(&self) -> Result<&Password, OperationError> { match &self.type_ { CredentialType::Password(pw) @@ -539,6 +556,7 @@ impl Credential { self.password_ref().and_then(|pw| pw.verify(cleartext)) } + /// Extract this credential into it's Serialisable Database form, ready for persistence. pub fn to_db_valuev1(&self) -> DbCredV1 { let claims = self.claims.clone(); let uuid = self.uuid; diff --git a/kanidmd/src/lib/crypto.rs b/kanidmd/src/lib/crypto.rs index fef0b843e..419d472af 100644 --- a/kanidmd/src/lib/crypto.rs +++ b/kanidmd/src/lib/crypto.rs @@ -1,7 +1,12 @@ +//! This module contains cryptographic setup code, a long with what policy +//! and ciphers we accept. + use crate::config::Configuration; use openssl::error::ErrorStack; use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; +/// From the server configuration, generate an OpenSSL acceptor that we can use +/// to build our sockets for https/ldaps. pub fn setup_tls(config: &Configuration) -> Result, ErrorStack> { match &config.tls_config { Some(tls_config) => { diff --git a/kanidmd/src/lib/entry.rs b/kanidmd/src/lib/entry.rs index 4d6c023d7..da51d3db1 100644 --- a/kanidmd/src/lib/entry.rs +++ b/kanidmd/src/lib/entry.rs @@ -227,6 +227,7 @@ impl std::fmt::Display for Entry { } impl Entry { + /// Get the uuid of this entry. pub(crate) fn get_uuid(&self) -> Option<&Uuid> { match self.attrs.get("uuid") { Some(vs) => match vs.iter().take(1).next() { @@ -256,11 +257,8 @@ impl Entry { } } - // Could we consume protoentry? - // - // I think we could, but that would limit us to how protoentry works, - // where we are likely to actually change the Entry type here and how - // we store and represent types and data. + /// Consume a Protocol Entry from JSON, and validate and process the data into an internal + /// [`Entry`] type. pub fn from_proto_entry( audit: &mut AuditScope, e: &ProtoEntry, @@ -297,6 +295,8 @@ impl Entry { }) } + /// Given a proto entry in JSON formed as a serialised string, processed that string + /// into an Entry. pub fn from_proto_entry_str( audit: &mut AuditScope, es: &str, @@ -428,6 +428,8 @@ impl Entry { } } + /// Assign the Change Identifier to this Entry, allowing it to be modified and then + /// written to the `Backend` pub fn assign_cid(mut self, cid: Cid) -> Entry { /* setup our last changed time */ self.set_last_changed(cid.clone()); @@ -439,6 +441,7 @@ impl Entry { } } + /// Compare this entry to another. pub fn compare(&self, rhs: &Entry) -> bool { compare_attrs(&self.attrs, &rhs.attrs) } @@ -491,10 +494,12 @@ impl Entry { } } + /// Add an attribute-value-assertion to this Entry. pub fn add_ava(&mut self, attr: &str, value: Value) { self.add_ava_int(attr, value) } + /// Replace the existing content of an attribute set of this Entry, with a new set of Values. pub fn set_ava(&mut self, attr: &str, values: Set) { self.set_ava_int(attr, values) } @@ -513,6 +518,8 @@ impl Entry { } } + /// Validate that this entry and it's attribute-value sets are conformant to the systems + /// schema and the releant syntaxes. pub fn validate( self, schema: &dyn SchemaTransaction, @@ -551,7 +558,7 @@ impl Entry { } // Do we have extensible? - let extensible = ne.attribute_value_pres("class", &CLASS_EXTENSIBLE); + let extensible = ne.attribute_equality("class", &CLASS_EXTENSIBLE); let entry_classes = ne.get_ava_set("class").ok_or(SchemaError::NoClassFound)?; let mut invalid_classes = Vec::with_capacity(0); @@ -729,6 +736,7 @@ impl Entry { } } + /// Convert this entry into a recycled entry, that is "in the recycle bin". pub fn into_recycled(mut self) -> Self { self.add_ava("class", Value::new_class("recycled")); @@ -828,6 +836,8 @@ impl Entry { } } + /// Given this validated and sealed entry, process it with a `Backend` ID number so that it + /// can be then serialised to the database. pub fn into_sealed_committed_id(self, id: u64) -> Entry { Entry { valid: self.valid, @@ -845,6 +855,7 @@ type IdxDiff<'a> = Vec>; impl Entry { + /// If this entry has ever been commited to disk, retrieve it's database id number. pub fn get_id(&self) -> u64 { self.state.id } @@ -867,6 +878,8 @@ impl Entry { self } + /// Insert a claim to this entry. This claim can NOT be persisted to disk, this is only + /// used during a single Event session. pub fn insert_claim(&mut self, value: &str) { self.add_ava_int("claim", Value::new_iutf8(value)); } @@ -875,6 +888,7 @@ impl Entry { compare_attrs(&self.attrs, &rhs.attrs) } + /// Serialise this entry to it's Database format ready for storage. pub fn to_dbentry(&self) -> DbEntry { // In the future this will do extra work to process uuid // into "attributes" suitable for dbentry storage. @@ -899,6 +913,8 @@ impl Entry { } #[inline] + /// Given this entry, extract the set of strings that can uniquely identify this for authentication + /// purposes. These strings are then indexed. fn get_name2uuid_cands(&self) -> Set { // The cands are: // * spn @@ -918,6 +934,8 @@ impl Entry { } #[inline] + /// Given this entry, extract it's primary security prinicple name, or if not present + /// extract it's name, and if that's not present, extract it's uuid. pub(crate) fn get_uuid2spn(&self) -> Value { self.attrs .get("spn") @@ -931,6 +949,7 @@ impl Entry { } #[inline] + /// Given this entry, determine it's relative distinguished named for LDAP compatability. pub(crate) fn get_uuid2rdn(&self) -> String { self.attrs .get("spn") @@ -952,6 +971,8 @@ impl Entry { } #[inline] + /// Determine if this entry is recycled or a tombstone, and map that to "None". This allows + /// filter_map to effectively remove entries that should not be considered as "alive". pub(crate) fn mask_recycled_ts(&self) -> Option<&Self> { // Only when cls has ts/rc then None, else lways Some(self). match self.attrs.get("class") { @@ -1007,6 +1028,8 @@ impl Entry { } } + /// Generate a differential between a previous and current entry state, and what changes this + /// means for the current set of spn's for this uuid. pub(crate) fn idx_uuid2spn_diff( pre: Option<&Self>, post: Option<&Self>, @@ -1038,6 +1061,8 @@ impl Entry { } } + /// Generate a differential between a previous and current entry state, and what changes this + /// means for the current set of LDAP relative distinguished names. pub(crate) fn idx_uuid2rdn_diff( pre: Option<&Self>, post: Option<&Self>, @@ -1069,8 +1094,8 @@ impl Entry { } } - // This is an associated method, not on & self so we can take options on - // both sides. + /// Given the previous and current state of this entry, determine the indexing differential + /// that needs to be applied. i.e. what indexes must be created, modified and removed. pub(crate) fn idx_diff<'a>( idxmeta: &'a HashSet, pre: Option<&Self>, @@ -1317,6 +1342,8 @@ impl Entry { } } + /// Given a set of attributes that are allowed to be seen on this entry, process and remove + /// all other values that are NOT allowed in this query. pub fn reduce_attributes( self, allowed_attrs: &BTreeSet<&str>, @@ -1347,6 +1374,7 @@ impl Entry { } } + /// Convert this recycled entry, into a tombstone ready for reaping. pub fn to_tombstone(&self, cid: Cid) -> Entry { // Duplicate this to a tombstone entry let class_ava = btreeset![Value::new_class("object"), Value::new_class("tombstone")]; @@ -1368,6 +1396,7 @@ impl Entry { } } + /// Given a current transaction change identifier, mark this entry as valid and committed. pub fn into_valid(self, cid: Cid) -> Entry { Entry { valid: EntryValid { @@ -1442,6 +1471,7 @@ impl Entry { &self.valid.uuid } + /// Transform this reduced entry into a JSON protocol form that can be sent to clients. pub fn to_pe( &self, audit: &mut AuditScope, @@ -1461,6 +1491,7 @@ impl Entry { Ok(ProtoEntry { attrs: attrs? }) } + /// Transform this reduced entry into an LDAP form that can be sent to clients. pub fn to_ldap( &self, audit: &mut AuditScope, @@ -1538,6 +1569,7 @@ impl Entry { // impl Entry { impl Entry { + /// This internally adds an AVA to the entry. fn add_ava_int(&mut self, attr: &str, value: Value) { // How do we make this turn into an ok / err? let v = self @@ -1549,38 +1581,45 @@ impl Entry { // Doesn't matter if it already exists, equality will replace. } + /// Overwrite the current set of values for an attribute, with this new set. pub fn set_ava_int(&mut self, attr: &str, values: Set) { // Overwrite the existing value, build a tree from the list. let _ = self.attrs.insert(AttrString::from(attr), values); } + /// Update the last_changed flag of this entry to the given change identifier. fn set_last_changed(&mut self, cid: Cid) { let cv = btreeset![Value::new_cid(cid)]; let _ = self.attrs.insert(AttrString::from("last_modified_cid"), cv); } #[inline(always)] + /// Get an iterator over the current set of attribute names that this entry contains. pub fn get_ava_names(&self) -> impl Iterator { // Get the set of all attribute names in the entry self.attrs.keys().map(|a| a.as_str()) } #[inline(always)] + /// Get an iterator over the current set of values for an attribute name. pub fn get_ava(&self, attr: &str) -> Option> { self.attrs.get(attr).map(|vs| vs.iter()) } #[inline(always)] + /// Return a reference to the current set of values that are associated to this attribute. pub fn get_ava_set(&self, attr: &str) -> Option<&Set> { self.attrs.get(attr) } #[inline(always)] + /// If possible, return an iterator over the set of values transformed into a `&str`. pub fn get_ava_as_str(&self, attr: &str) -> Option> { self.get_ava(attr).map(|i| i.filter_map(|s| s.to_str())) } #[inline(always)] + /// If possible, return an iterator over the set of values transformed into a `&Uuid`. pub fn get_ava_as_refuuid(&self, attr: &str) -> Option> { // If any value is NOT a reference, it's filtered out. self.get_ava(attr) @@ -1588,6 +1627,7 @@ impl Entry { } #[inline(always)] + /// If possible, return an iterator over the set of ssh key values transformed into a `&str`. pub fn get_ava_iter_sshpubkeys(&self, attr: &str) -> Option> { self.get_ava(attr).map(|i| i.filter_map(|v| v.get_sshkey())) } @@ -1613,7 +1653,8 @@ impl Entry { } } - /// Returns NONE if there is more than ONE!!!! + /// Return a single value of this attributes name, or `None` if it is NOT present, or + /// there are multiple values present (ambiguous). #[inline(always)] pub fn get_ava_single(&self, attr: &str) -> Option<&Value> { match self.attrs.get(attr) { @@ -1628,71 +1669,72 @@ impl Entry { } } - /// Get a bool from an ava #[inline(always)] + /// Return a single bool, if valid to transform this value into a boolean. pub fn get_ava_single_bool(&self, attr: &str) -> Option { self.get_ava_single(attr).and_then(|a| a.to_bool()) } #[inline(always)] + /// Return a single uint32, if valid to transform this value. pub fn get_ava_single_uint32(&self, attr: &str) -> Option { self.get_ava_single(attr).and_then(|a| a.to_uint32()) } #[inline(always)] + /// Return a single syntax type, if valid to transform this value. pub fn get_ava_single_syntax(&self, attr: &str) -> Option<&SyntaxType> { self.get_ava_single(attr).and_then(|a| a.to_syntaxtype()) } #[inline(always)] + /// Return a single credential, if valid to transform this value. pub fn get_ava_single_credential(&self, attr: &str) -> Option<&Credential> { self.get_ava_single(attr).and_then(|a| a.to_credential()) } #[inline(always)] + /// Return a single radius credential, if valid to transform this value. pub fn get_ava_single_radiuscred(&self, attr: &str) -> Option<&str> { self.get_ava_single(attr) .and_then(|a| a.get_radius_secret()) } #[inline(always)] + /// Return a single datetime, if valid to transform this value. pub fn get_ava_single_datetime(&self, attr: &str) -> Option { self.get_ava_single(attr).and_then(|a| a.to_datetime()) } #[inline(always)] + /// Return a single `&str`, if valid to transform this value. pub fn get_ava_single_str(&self, attr: &str) -> Option<&str> { self.get_ava_single(attr).and_then(|v| v.to_str()) } #[inline(always)] + /// Return a single protocol filter, if valid to transform this value. pub fn get_ava_single_protofilter(&self, attr: &str) -> Option<&ProtoFilter> { self.get_ava_single(attr) .and_then(|v: &Value| v.as_json_filter()) } #[inline(always)] + /// Return a single security principle name, if valid to transform this value. pub(crate) fn generate_spn(&self, domain_name: &str) -> Option { self.get_ava_single_str("name") .map(|name| Value::new_spn_str(name, domain_name)) } #[inline(always)] + /// Assert if an attribute of this name is present on this entry. pub fn attribute_pres(&self, attr: &str) -> bool { - // Note, we don't normalise attr name, but I think that's not - // something we should over-optimise on. self.attrs.contains_key(attr) } #[inline(always)] - pub fn attribute_value_pres(&self, attr: &str, value: &PartialValue) -> bool { - // Yeah, this is techdebt, but both names of this fn are valid - we are - // checking if an attribute-value is equal to, or asserting it's present - // as a pair. So I leave both, and let the compiler work it out. - self.attribute_equality(attr, value) - } - - #[inline(always)] + /// Assert if an attribute of this name is present, and one of it's values contains + /// the an exact match of this partial value. pub fn attribute_equality(&self, attr: &str, value: &PartialValue) -> bool { // we assume based on schema normalisation on the way in // that the equality here of the raw values MUST be correct. @@ -1705,6 +1747,8 @@ impl Entry { } #[inline(always)] + /// Assert if an attribute of this name is present, and one of it's values contains + /// the following substring, if possible to perform the substring comparison. pub fn attribute_substring(&self, attr: &str, subvalue: &PartialValue) -> bool { match self.attrs.get(attr) { Some(v_list) => v_list @@ -1714,8 +1758,9 @@ impl Entry { } } - /// Confirm if at least one value in the ava is less than subvalue. #[inline(always)] + /// Assert if an attribute of this name is present, and one of it's values is less than + /// the following partial value pub fn attribute_lessthan(&self, attr: &str, subvalue: &PartialValue) -> bool { match self.attrs.get(attr) { Some(v_list) => v_list @@ -1730,6 +1775,7 @@ impl Entry { // valid, we still have strict typing checks between the filter -> entry to guarantee // they should be functional. We'll never match something that isn't syntactially valid. #[inline(always)] + /// Test if the following filter applies to and matches this entry. pub fn entry_match_no_index(&self, filter: &Filter) -> bool { self.entry_match_no_index_inner(filter.to_inner()) } @@ -1766,6 +1812,8 @@ impl Entry { } } + /// Given this entry, generate a filter containing the requested attributes strings as + /// equality components. pub fn filter_from_attrs(&self, attrs: &[AttrString]) -> Option> { // Because we are a valid entry, a filter we create still may not // be valid because the internal server entry templates are still @@ -1802,6 +1850,8 @@ impl Entry { ))) } + /// Given this entry, generate a modification list that would "assert" + /// another entry is in the same/identical attribute state. pub fn gen_modlist_assert( &self, schema: &dyn SchemaTransaction, @@ -1866,6 +1916,7 @@ where self.add_ava_int(attr, value) } + /// Remove an attribute-value pair from this entry. fn remove_ava(&mut self, attr: &str, value: &PartialValue) { // It would be great to remove these extra allocations, but they // really don't cost much :( @@ -1884,15 +1935,17 @@ where } } + /// Remove all values of this attribute from the entry. pub fn purge_ava(&mut self, attr: &str) { self.attrs.remove(attr); } + /// Remove all values of this attribute from the entry, and return their content. pub fn pop_ava(&mut self, attr: &str) -> Option> { self.attrs.remove(attr) } - /// Provide a true ava set. + /// Replace the content of this attribute with a new value set. pub fn set_ava(&mut self, attr: &str, values: Set) { self.set_ava_int(attr, values) } @@ -1905,8 +1958,7 @@ where } */ - // Should this be schemaless, relying on checks of the modlist, and the entry validate after? - // YES. Makes it very cheap. + /// Apply the content of this modlist to this entry, enforcing the expressed state. pub fn apply_modlist(&mut self, modlist: &ModifyList) { // -> Result, OperationError> { // Apply a modlist, generating a new entry that conforms to the changes. diff --git a/kanidmd/src/lib/event.rs b/kanidmd/src/lib/event.rs index 90915c6dc..577387c04 100644 --- a/kanidmd/src/lib/event.rs +++ b/kanidmd/src/lib/event.rs @@ -1,3 +1,20 @@ +//! An `event` is a self contained module of data, that contains all of the +//! required information for any operation to proceed. While there are many +//! types of potential events, they all eventually lower to one of: +//! +//! * AuthEvent +//! * SearchEvent +//! * ExistsEvent +//! * ModifyEvent +//! * CreateEvent +//! * DeleteEvent +//! +//! An "event" is generally then passed to the `QueryServer` for processing. +//! By making these fully self contained units, it means that we can assert +//! at event creation time we have all the correct data requried to proceed +//! with the operation, and a clear path to know how to transform events between +//! various types. + use crate::entry::{Entry, EntryCommitted, EntryInit, EntryNew, EntryReduced}; use crate::filter::{Filter, FilterInvalid, FilterValid}; use crate::identity::Limits; diff --git a/kanidmd/src/lib/identity.rs b/kanidmd/src/lib/identity.rs index a8325809f..305644894 100644 --- a/kanidmd/src/lib/identity.rs +++ b/kanidmd/src/lib/identity.rs @@ -1,5 +1,7 @@ -// Contains a structure representing the current authenticated -// identity (or anonymous, or admin, both of which are in mem). +//! Contains structures related to the Identity that initiated an `Event` in the +//! server. Generally this Identity is what will have access controls applied to +//! and this provides the set of `Limits` to confine how many resources that the +//! identity may consume during operations to prevent denial-of-service. use crate::prelude::*; use kanidm_proto::v1::UserAuthToken; @@ -37,6 +39,7 @@ impl Limits { } #[derive(Debug, Clone)] +/// Metadata and the entry of the current Identity which is an external account/user. pub struct IdentUser { pub entry: Entry, // IpAddr? @@ -44,12 +47,15 @@ pub struct IdentUser { } #[derive(Debug, Clone)] +/// The type of Identity that is related to this session. pub enum IdentType { User(IdentUser), Internal, } #[derive(Debug, Clone, PartialEq, Hash, Ord, PartialOrd, Eq)] +/// A unique identifier of this Identity, that can be associated to various +/// caching components. pub enum IdentityId { // Time stamp of the originating event. // The uuid of the originiating user @@ -67,6 +73,7 @@ impl From<&IdentType> for IdentityId { } #[derive(Debug, Clone)] +/// An identity that initiated an `Event`. pub struct Identity { pub origin: IdentType, pub(crate) limits: Limits, diff --git a/kanidmd/src/lib/idm/account.rs b/kanidmd/src/lib/idm/account.rs index 6df982320..7a2facadc 100644 --- a/kanidmd/src/lib/idm/account.rs +++ b/kanidmd/src/lib/idm/account.rs @@ -26,7 +26,7 @@ lazy_static! { macro_rules! try_from_entry { ($value:expr, $groups:expr) => {{ // Check the classes - if !$value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { + if !$value.attribute_equality("class", &PVCLASS_ACCOUNT) { return Err(OperationError::InvalidAccountState( "Missing class: account".to_string(), )); diff --git a/kanidmd/src/lib/idm/authsession.rs b/kanidmd/src/lib/idm/authsession.rs index 33c9b5576..bff3eed36 100644 --- a/kanidmd/src/lib/idm/authsession.rs +++ b/kanidmd/src/lib/idm/authsession.rs @@ -1,3 +1,8 @@ +//! This module contains the logic to conduct an authentication of an account. +//! Generally this has to process an authentication attempt, and validate each +//! factor to assert that the user is legitimate. This also contains some +//! support code for asynchronous task execution. + use crate::idm::account::Account; use crate::idm::AuthState; use crate::prelude::*; @@ -33,6 +38,7 @@ const BAD_CREDENTIALS: &str = "invalid credential message"; const ACCOUNT_EXPIRED: &str = "account expired"; const PW_BADLIST_MSG: &str = "password is in badlist"; +/// A response type to indicate the progress and potential result of an authentication attempt. enum CredState { Success(AuthType), Continue(Vec), @@ -40,6 +46,7 @@ enum CredState { } #[derive(Clone, Debug, PartialEq)] +/// The state of verification of an individual credential during an authentication. enum CredVerifyState { Init, Success, @@ -47,6 +54,7 @@ enum CredVerifyState { } #[derive(Clone, Debug)] +/// The state of a multifactor authenticator during authentication. struct CredMfa { pw: Password, pw_state: CredVerifyState, @@ -56,12 +64,16 @@ struct CredMfa { } #[derive(Clone, Debug)] +/// The state of a webauthn credential during authentication struct CredWebauthn { chal: RequestChallengeResponse, wan_state: AuthenticationState, state: CredVerifyState, } +/// The current active handler for this authentication session. This is determined from what credentials +/// are possible from the account, and what the user selected as the preferred authentication +/// mechanism. #[derive(Clone, Debug)] enum CredHandler { Anonymous, @@ -73,7 +85,10 @@ enum CredHandler { } impl CredHandler { - // Is there a nicer implementation of this? + /// Given a credential and some external configuration, Generate the credential handler + /// that will be used for this session. This credential handler is a "self contained" + /// unit that defines what is possible to use during this authentication session to prevent + /// inconsistency. fn try_from( au: &mut AuditScope, c: &Credential, @@ -139,6 +154,9 @@ impl CredHandler { } impl CredHandler { + /// Determine if this password factor requires an upgrade of it's cryptographic type. If + /// so, send an asynchronous event into the queue that will allow the password to have it's + /// content upgraded later. fn maybe_pw_upgrade( au: &mut AuditScope, pw: &Password, @@ -156,6 +174,7 @@ impl CredHandler { } } + /// validate that the client wants to authenticate as the anonymous user. fn validate_anonymous(au: &mut AuditScope, cred: &AuthCredential) -> CredState { match cred { AuthCredential::Anonymous => { @@ -173,6 +192,7 @@ impl CredHandler { } } + /// Validate a singule password credential of the account. fn validate_password( au: &mut AuditScope, cred: &AuthCredential, @@ -222,6 +242,9 @@ impl CredHandler { } } + /// Proceed with the next step in a multifactor authentication, based on the current + /// verification results and state. If this logic of this statemachine is violated, the + /// authentication will fail. fn validate_password_mfa( au: &mut AuditScope, cred: &AuthCredential, @@ -345,6 +368,7 @@ impl CredHandler { } } // end CredHandler::PasswordMfa + /// Validate a webauthn authentication attempt pub fn validate_webauthn( au: &mut AuditScope, cred: &AuthCredential, @@ -399,6 +423,7 @@ impl CredHandler { } #[allow(clippy::too_many_arguments)] + /// Given the current handler, proceed to authenticate the attempted credential step. pub fn validate( &mut self, au: &mut AuditScope, @@ -430,6 +455,8 @@ impl CredHandler { } } + /// Determine based on the current status, what is the next allowed step that + /// can proceed. pub fn next_auth_allowed(&self) -> Vec { match &self { CredHandler::Anonymous => vec![AuthAllowed::Anonymous], @@ -449,6 +476,7 @@ impl CredHandler { } } + /// Determine which mechanismes can proceed given the requested mechanism. fn can_proceed(&self, mech: &AuthMech) -> bool { match (self, mech) { (CredHandler::Anonymous, AuthMech::Anonymous) @@ -494,6 +522,7 @@ impl AuthSessionState { } #[derive(Clone)] +/// The current state of an authentication session that is in progress. pub(crate) struct AuthSession { // Do we store a copy of the entry? // How do we know what claims to add? @@ -507,6 +536,9 @@ pub(crate) struct AuthSession { } impl AuthSession { + /// Create a new auth session, based on the available credential handlers of the account. + /// the session is a whole encapsulated unit of what we need to proceed, so that subsequent + /// or interleved write operations do not cause inconsistency in this process. pub fn new( au: &mut AuditScope, account: Account, @@ -570,6 +602,9 @@ impl AuthSession { &self.account } + /// Given the users indicated and preferred authentication mechanism that they want to proceed + /// with, select the credential handler and begin the process of stepping through the + /// authentication process. pub fn start_session( &mut self, _au: &mut AuditScope, @@ -631,7 +666,9 @@ impl AuthSession { response } - // This should return a AuthResult or similar state of checking? + /// Conduct a step of the authentication process. This validates the next credential factor + /// presented and returns a result of Success, Continue, or Denied. Only in the success + /// case is a UAT granted -- all others do not, including raised operation errors. pub fn validate_creds( &mut self, au: &mut AuditScope, @@ -710,6 +747,7 @@ impl AuthSession { response } + /// End the session, defaulting to a denied. pub fn end_session(&mut self, reason: &'static str) -> Result { let mut next_state = AuthSessionState::Denied(reason); std::mem::swap(&mut self.state, &mut next_state); diff --git a/kanidmd/src/lib/idm/group.rs b/kanidmd/src/lib/idm/group.rs index 64a9ed1d1..7709ca294 100644 --- a/kanidmd/src/lib/idm/group.rs +++ b/kanidmd/src/lib/idm/group.rs @@ -88,7 +88,7 @@ impl Group { pub fn try_from_entry( value: &Entry, ) -> Result { - if !value.attribute_value_pres("class", &PVCLASS_GROUP) { + if !value.attribute_equality("class", &PVCLASS_GROUP) { return Err(OperationError::InvalidAccountState( "Missing class: group".to_string(), )); diff --git a/kanidmd/src/lib/idm/mod.rs b/kanidmd/src/lib/idm/mod.rs index d7930d258..dd76f7ad9 100644 --- a/kanidmd/src/lib/idm/mod.rs +++ b/kanidmd/src/lib/idm/mod.rs @@ -1,3 +1,8 @@ +//! The Identity Management components that are layered ontop of the [`QueryServer`]. These allow +//! rich and expressive events and transformations that are lowered into the correct/relevant +//! actions in the [`QueryServer`]. Generally this is where "Identity Management" policy and code +//! is implemented. + pub(crate) mod account; pub(crate) mod authsession; pub(crate) mod delayed; diff --git a/kanidmd/src/lib/idm/radius.rs b/kanidmd/src/lib/idm/radius.rs index 56a9ae156..df818c739 100644 --- a/kanidmd/src/lib/idm/radius.rs +++ b/kanidmd/src/lib/idm/radius.rs @@ -31,7 +31,7 @@ impl RadiusAccount { value: &Entry, qs: &mut QueryServerReadTransaction, ) -> Result { - if !value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { + if !value.attribute_equality("class", &PVCLASS_ACCOUNT) { return Err(OperationError::InvalidAccountState( "Missing class: account".to_string(), )); diff --git a/kanidmd/src/lib/idm/unix.rs b/kanidmd/src/lib/idm/unix.rs index 21db59a98..3afa72906 100644 --- a/kanidmd/src/lib/idm/unix.rs +++ b/kanidmd/src/lib/idm/unix.rs @@ -41,13 +41,13 @@ lazy_static! { macro_rules! try_from_entry { ($value:expr, $groups:expr) => {{ - if !$value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { + if !$value.attribute_equality("class", &PVCLASS_ACCOUNT) { return Err(OperationError::InvalidAccountState( "Missing class: account".to_string(), )); } - if !$value.attribute_value_pres("class", &PVCLASS_POSIXACCOUNT) { + if !$value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) { return Err(OperationError::InvalidAccountState( "Missing class: posixaccount".to_string(), )); @@ -284,10 +284,10 @@ macro_rules! try_from_group_e { ($value:expr) => {{ // We could be looking at a user for their UPG, OR a true group. - if !(($value.attribute_value_pres("class", &PVCLASS_ACCOUNT) - && $value.attribute_value_pres("class", &PVCLASS_POSIXACCOUNT)) - || ($value.attribute_value_pres("class", &PVCLASS_GROUP) - && $value.attribute_value_pres("class", &PVCLASS_POSIXGROUP))) + if !(($value.attribute_equality("class", &PVCLASS_ACCOUNT) + && $value.attribute_equality("class", &PVCLASS_POSIXACCOUNT)) + || ($value.attribute_equality("class", &PVCLASS_GROUP) + && $value.attribute_equality("class", &PVCLASS_POSIXGROUP))) { return Err(OperationError::InvalidAccountState( "Missing class: account && posixaccount OR group && posixgroup".to_string(), @@ -328,13 +328,13 @@ macro_rules! try_from_account_group_e { // First synthesise the self-group from the account. // We have already checked these, but paranoia is better than // complacency. - if !$value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { + if !$value.attribute_equality("class", &PVCLASS_ACCOUNT) { return Err(OperationError::InvalidAccountState( "Missing class: account".to_string(), )); } - if !$value.attribute_value_pres("class", &PVCLASS_POSIXACCOUNT) { + if !$value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) { return Err(OperationError::InvalidAccountState( "Missing class: posixaccount".to_string(), )); diff --git a/kanidmd/src/lib/interval.rs b/kanidmd/src/lib/interval.rs index 1093fb00f..85c13a9a4 100644 --- a/kanidmd/src/lib/interval.rs +++ b/kanidmd/src/lib/interval.rs @@ -1,3 +1,6 @@ +//! This contains scheduled tasks/interval tasks that are run inside of the server on a schedule +//! as background operations. + use crate::actors::v1_write::QueryServerWriteV1; use crate::constants::PURGE_FREQUENCY; use crate::event::{PurgeRecycledEvent, PurgeTombstoneEvent}; diff --git a/kanidmd/src/lib/ldap.rs b/kanidmd/src/lib/ldap.rs index 6ef06cc85..8d8210fe0 100644 --- a/kanidmd/src/lib/ldap.rs +++ b/kanidmd/src/lib/ldap.rs @@ -1,3 +1,6 @@ +//! LDAP specific operations handling components. This is where LDAP operations +//! are sent to for processing. + use crate::event::SearchEvent; use crate::idm::event::LdapAuthEvent; use crate::idm::server::{IdmServer, IdmServerTransaction}; diff --git a/kanidmd/src/lib/lib.rs b/kanidmd/src/lib/lib.rs index 14c5419c9..27ddfb5e7 100644 --- a/kanidmd/src/lib/lib.rs +++ b/kanidmd/src/lib/lib.rs @@ -1,3 +1,6 @@ +//! The Kanidmd server library. This implements all of the internal components of the server +//! which is used to process authentication, store identities and enforce access controls. + #![deny(warnings)] #![warn(unused_extern_crates)] #![deny(clippy::unwrap_used)] @@ -56,6 +59,8 @@ mod status; pub mod config; pub mod core; +/// A prelude of imports that should be imported by all other Kanid modules to +/// help make imports cleaner. pub mod prelude { pub use crate::utils::duration_from_epoch_now; pub use kanidm_proto::v1::OperationError; diff --git a/kanidmd/src/lib/modify.rs b/kanidmd/src/lib/modify.rs index 28ae7603f..9f6e30c7c 100644 --- a/kanidmd/src/lib/modify.rs +++ b/kanidmd/src/lib/modify.rs @@ -1,3 +1,7 @@ +//! Modification expressions and validation. This is how `ModifyEvents` store and +//! express the series of Modifications that should be applied. These are expressed +//! as "states" on what attribute-values should appear as within the `Entry` + use crate::prelude::*; use kanidm_proto::v1::Modify as ProtoModify; use kanidm_proto::v1::ModifyList as ProtoModifyList; diff --git a/kanidmd/src/lib/plugins/domain.rs b/kanidmd/src/lib/plugins/domain.rs index 07b310d5a..46aa9dad2 100644 --- a/kanidmd/src/lib/plugins/domain.rs +++ b/kanidmd/src/lib/plugins/domain.rs @@ -30,8 +30,8 @@ impl Plugin for Domain { ) -> Result<(), OperationError> { ltrace!(au, "Entering plugin_domain pre_create_transform"); cand.iter_mut().for_each(|e| { - if e.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) - && e.attribute_value_pres("uuid", &PVUUID_DOMAIN_INFO) + if e.attribute_equality("class", &PVCLASS_DOMAIN_INFO) + && e.attribute_equality("uuid", &PVUUID_DOMAIN_INFO) { // We always set this, because the DB uuid is authorative. let u = Value::new_uuid(qs.get_domain_uuid()); @@ -66,7 +66,7 @@ mod tests { let u_dom = server_txn.get_domain_uuid(); - assert!(e_dom.attribute_value_pres("domain_uuid", &PartialValue::new_uuid(u_dom))); + assert!(e_dom.attribute_equality("domain_uuid", &PartialValue::new_uuid(u_dom))); }) } } diff --git a/kanidmd/src/lib/plugins/gidnumber.rs b/kanidmd/src/lib/plugins/gidnumber.rs index e92cdfd82..2a1bd0259 100644 --- a/kanidmd/src/lib/plugins/gidnumber.rs +++ b/kanidmd/src/lib/plugins/gidnumber.rs @@ -25,8 +25,8 @@ fn apply_gidnumber( au: &mut AuditScope, e: &mut Entry, ) -> Result<(), OperationError> { - if (e.attribute_value_pres("class", &CLASS_POSIXGROUP) - || e.attribute_value_pres("class", &CLASS_POSIXACCOUNT)) + if (e.attribute_equality("class", &CLASS_POSIXGROUP) + || e.attribute_equality("class", &CLASS_POSIXACCOUNT)) && !e.attribute_pres("gidnumber") { let u_ref = e diff --git a/kanidmd/src/lib/plugins/memberof.rs b/kanidmd/src/lib/plugins/memberof.rs index c97530d3b..a1c12745d 100644 --- a/kanidmd/src/lib/plugins/memberof.rs +++ b/kanidmd/src/lib/plugins/memberof.rs @@ -127,7 +127,7 @@ fn apply_memberof( while let Some((pre, mut tgte)) = work_set.pop() { let guuid = *pre.get_uuid(); // load the entry from the db. - if !tgte.attribute_value_pres("class", &CLASS_GROUP) { + if !tgte.attribute_equality("class", &CLASS_GROUP) { // It's not a group, we'll deal with you later. We should NOT // have seen this UUID before, as either we are on the first // iteration OR the checks belowe should have filtered it out. @@ -184,7 +184,7 @@ fn apply_memberof( .into_iter() .try_for_each(|(auuid, (pre, mut tgte))| { ltrace!(au, "=> processing affected uuid {:?}", auuid); - debug_assert!(!tgte.attribute_value_pres("class", &CLASS_GROUP)); + debug_assert!(!tgte.attribute_equality("class", &CLASS_GROUP)); do_memberof(au, qs, &auuid, &mut tgte)?; // Only write if a change occured. if pre.get_ava_set("memberof") != tgte.get_ava_set("memberof") @@ -220,7 +220,7 @@ impl Plugin for MemberOf { cand.iter() .filter_map(|e| { // Is it a group? - if e.attribute_value_pres("class", &CLASS_GROUP) { + if e.attribute_equality("class", &CLASS_GROUP) { e.get_ava_as_refuuid("member") } else { None @@ -249,7 +249,7 @@ impl Plugin for MemberOf { pre_cand .iter() .filter_map(|pre| { - if pre.attribute_value_pres("class", &CLASS_GROUP) { + if pre.attribute_equality("class", &CLASS_GROUP) { pre.get_ava_as_refuuid("member") } else { None @@ -260,7 +260,7 @@ impl Plugin for MemberOf { .chain( cand.iter() .filter_map(|post| { - if post.attribute_value_pres("class", &CLASS_GROUP) { + if post.attribute_equality("class", &CLASS_GROUP) { post.get_ava_as_refuuid("member") } else { None @@ -306,7 +306,7 @@ impl Plugin for MemberOf { .iter() .filter_map(|e| { // Is it a group? - if e.attribute_value_pres("class", &CLASS_GROUP) { + if e.attribute_equality("class", &CLASS_GROUP) { e.get_ava_as_refuuid("member") } else { None diff --git a/kanidmd/src/lib/plugins/mod.rs b/kanidmd/src/lib/plugins/mod.rs index 53cadb41a..747ac9726 100644 --- a/kanidmd/src/lib/plugins/mod.rs +++ b/kanidmd/src/lib/plugins/mod.rs @@ -1,3 +1,8 @@ +//! plugins allow an `Event` to be inspected and transformed during the write +//! paths of the server. This allows richer expression of some concepts and +//! helps to ensure that data is always in specific known states within the +//! `QueryServer` + use crate::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntrySealed}; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; use crate::prelude::*; diff --git a/kanidmd/src/lib/plugins/protected.rs b/kanidmd/src/lib/plugins/protected.rs index 65982cd4e..36bc7d40e 100644 --- a/kanidmd/src/lib/plugins/protected.rs +++ b/kanidmd/src/lib/plugins/protected.rs @@ -63,12 +63,12 @@ impl Plugin for Protected { cand.iter().fold(Ok(()), |acc, cand| match acc { Err(_) => acc, Ok(_) => { - if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) - || cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) - || cand.attribute_value_pres("class", &PVCLASS_SYSTEM_INFO) - || cand.attribute_value_pres("class", &PVCLASS_SYSTEM_CONFIG) - || cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) - || cand.attribute_value_pres("class", &PVCLASS_RECYCLED) + if cand.attribute_equality("class", &PVCLASS_SYSTEM) + || cand.attribute_equality("class", &PVCLASS_DOMAIN_INFO) + || cand.attribute_equality("class", &PVCLASS_SYSTEM_INFO) + || cand.attribute_equality("class", &PVCLASS_SYSTEM_CONFIG) + || cand.attribute_equality("class", &PVCLASS_TOMBSTONE) + || cand.attribute_equality("class", &PVCLASS_RECYCLED) { Err(OperationError::SystemProtectedObject) } else { @@ -123,8 +123,8 @@ impl Plugin for Protected { cand.iter().fold(Ok(()), |acc, cand| match acc { Err(_) => acc, Ok(_) => { - if cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) - || cand.attribute_value_pres("class", &PVCLASS_RECYCLED) + if cand.attribute_equality("class", &PVCLASS_TOMBSTONE) + || cand.attribute_equality("class", &PVCLASS_RECYCLED) { Err(OperationError::SystemProtectedObject) } else { @@ -140,7 +140,7 @@ impl Plugin for Protected { } else { // We don't need to check for domain info here because domain_info has a class // system also. We just need to block it from being created. - c.attribute_value_pres("class", &PVCLASS_SYSTEM) + c.attribute_equality("class", &PVCLASS_SYSTEM) } }); @@ -187,12 +187,12 @@ impl Plugin for Protected { cand.iter().fold(Ok(()), |acc, cand| match acc { Err(_) => acc, Ok(_) => { - if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) - || cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) - || cand.attribute_value_pres("class", &PVCLASS_SYSTEM_INFO) - || cand.attribute_value_pres("class", &PVCLASS_SYSTEM_CONFIG) - || cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) - || cand.attribute_value_pres("class", &PVCLASS_RECYCLED) + if cand.attribute_equality("class", &PVCLASS_SYSTEM) + || cand.attribute_equality("class", &PVCLASS_DOMAIN_INFO) + || cand.attribute_equality("class", &PVCLASS_SYSTEM_INFO) + || cand.attribute_equality("class", &PVCLASS_SYSTEM_CONFIG) + || cand.attribute_equality("class", &PVCLASS_TOMBSTONE) + || cand.attribute_equality("class", &PVCLASS_RECYCLED) { Err(OperationError::SystemProtectedObject) } else { diff --git a/kanidmd/src/lib/plugins/spn.rs b/kanidmd/src/lib/plugins/spn.rs index 96653b7cc..0d5a82da3 100644 --- a/kanidmd/src/lib/plugins/spn.rs +++ b/kanidmd/src/lib/plugins/spn.rs @@ -39,8 +39,8 @@ impl Plugin for Spn { let mut domain_name: Option = None; for e in cand.iter_mut() { - if e.attribute_value_pres("class", &CLASS_GROUP) - || e.attribute_value_pres("class", &CLASS_ACCOUNT) + if e.attribute_equality("class", &CLASS_GROUP) + || e.attribute_equality("class", &CLASS_ACCOUNT) { // We do this in the loop so that we don't get it unless required. if domain_name.is_none() { @@ -89,8 +89,8 @@ impl Plugin for Spn { let mut domain_name: Option = None; for e in cand.iter_mut() { - if e.attribute_value_pres("class", &CLASS_GROUP) - || e.attribute_value_pres("class", &CLASS_ACCOUNT) + if e.attribute_equality("class", &CLASS_GROUP) + || e.attribute_equality("class", &CLASS_ACCOUNT) { if domain_name.is_none() { domain_name = Some(qs.get_domain_name(au)?); @@ -145,7 +145,7 @@ impl Plugin for Spn { .fold(None, |acc, (post, pre)| { if acc.is_some() { acc - } else if post.attribute_value_pres("uuid", &PV_UUID_DOMAIN_INFO) + } else if post.attribute_equality("uuid", &PV_UUID_DOMAIN_INFO) && post.get_ava_single("domain_name") != pre.get_ava_single("domain_name") { post.get_ava_single("domain_name") diff --git a/kanidmd/src/lib/schema.rs b/kanidmd/src/lib/schema.rs index ff7460840..d54708270 100644 --- a/kanidmd/src/lib/schema.rs +++ b/kanidmd/src/lib/schema.rs @@ -113,7 +113,7 @@ impl SchemaAttribute { let uuid = *value.get_uuid(); // class - if !value.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) { + if !value.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE) { ladmin_error!(audit, "class attribute type not present - {:?}", uuid); return Err(OperationError::InvalidSchemaState( "missing attributetype".to_string(), @@ -428,7 +428,7 @@ impl SchemaClass { // uuid let uuid = *value.get_uuid(); // Convert entry to a schema class. - if !value.attribute_value_pres("class", &PVCLASS_CLASSTYPE) { + if !value.attribute_equality("class", &PVCLASS_CLASSTYPE) { ladmin_error!(audit, "class classtype not present - {:?}", uuid); return Err(OperationError::InvalidSchemaState( "missing classtype".to_string(), diff --git a/kanidmd/src/lib/server.rs b/kanidmd/src/lib/server.rs index 5c67fc9f7..3878a1654 100644 --- a/kanidmd/src/lib/server.rs +++ b/kanidmd/src/lib/server.rs @@ -1123,15 +1123,15 @@ impl<'a> QueryServerWriteTransaction<'a> { // schema or acp requires reload. if !self.changed_schema.get() { self.changed_schema.set(commit_cand.iter().any(|e| { - e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) - || e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) + e.attribute_equality("class", &PVCLASS_CLASSTYPE) + || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE) })) } if !self.changed_acp.get() { self.changed_acp.set( commit_cand .iter() - .any(|e| e.attribute_value_pres("class", &PVCLASS_ACP)), + .any(|e| e.attribute_equality("class", &PVCLASS_ACP)), ) } @@ -1257,15 +1257,15 @@ impl<'a> QueryServerWriteTransaction<'a> { // schema or acp requires reload. if !self.changed_schema.get() { self.changed_schema.set(del_cand.iter().any(|e| { - e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) - || e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) + e.attribute_equality("class", &PVCLASS_CLASSTYPE) + || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE) })) } if !self.changed_acp.get() { self.changed_acp.set( del_cand .iter() - .any(|e| e.attribute_value_pres("class", &PVCLASS_ACP)), + .any(|e| e.attribute_equality("class", &PVCLASS_ACP)), ) } @@ -1626,8 +1626,8 @@ impl<'a> QueryServerWriteTransaction<'a> { if !self.changed_schema.get() { self.changed_schema .set(norm_cand.iter().chain(pre_candidates.iter()).any(|e| { - e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) - || e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) + e.attribute_equality("class", &PVCLASS_CLASSTYPE) + || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE) })) } if !self.changed_acp.get() { @@ -1635,7 +1635,7 @@ impl<'a> QueryServerWriteTransaction<'a> { norm_cand .iter() .chain(pre_candidates.iter()) - .any(|e| e.attribute_value_pres("class", &PVCLASS_ACP)), + .any(|e| e.attribute_equality("class", &PVCLASS_ACP)), ) } let cu = self.changed_uuid.as_ptr(); @@ -1764,8 +1764,8 @@ impl<'a> QueryServerWriteTransaction<'a> { if !self.changed_schema.get() { self.changed_schema .set(norm_cand.iter().chain(pre_candidates.iter()).any(|e| { - e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) - || e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) + e.attribute_equality("class", &PVCLASS_CLASSTYPE) + || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE) })) } if !self.changed_acp.get() { @@ -1773,7 +1773,7 @@ impl<'a> QueryServerWriteTransaction<'a> { norm_cand .iter() .chain(pre_candidates.iter()) - .any(|e| e.attribute_value_pres("class", &PVCLASS_ACP)), + .any(|e| e.attribute_equality("class", &PVCLASS_ACP)), ) } let cu = self.changed_uuid.as_ptr(); @@ -3580,7 +3580,7 @@ mod tests { &Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(), ) .expect("failed"); - assert!(testobj1.attribute_value_pres("class", &PartialValue::new_class("testclass"))); + assert!(testobj1.attribute_equality("class", &PartialValue::new_class("testclass"))); // Should still be good server_txn.commit(audit).expect("should not fail"); @@ -3667,7 +3667,7 @@ mod tests { &Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(), ) .expect("failed"); - assert!(testobj1.attribute_value_pres("testattr", &PartialValue::new_utf8s("test"))); + assert!(testobj1.attribute_equality("testattr", &PartialValue::new_utf8s("test"))); server_txn.commit(audit).expect("should not fail"); // Commit. @@ -3768,7 +3768,7 @@ mod tests { .pop() .unwrap(); - e.attribute_value_pres("memberof", &PartialValue::new_refer_s(mo).unwrap()) + e.attribute_equality("memberof", &PartialValue::new_refer_s(mo).unwrap()) } #[test] diff --git a/kanidmd/src/lib/status.rs b/kanidmd/src/lib/status.rs index f57c2a36a..27b30148f 100644 --- a/kanidmd/src/lib/status.rs +++ b/kanidmd/src/lib/status.rs @@ -1,3 +1,5 @@ +//! An actor that shows the servers current status and statistics. (TODO). + use crate::audit::AuditScope; use tokio::sync::mpsc::UnboundedSender as Sender; use uuid::Uuid; diff --git a/kanidmd/src/lib/value.rs b/kanidmd/src/lib/value.rs index 40cd1d09b..ecdfe7ef6 100644 --- a/kanidmd/src/lib/value.rs +++ b/kanidmd/src/lib/value.rs @@ -1,3 +1,8 @@ +//! Inside an entry, the key-value pairs are stored in these [`Value`] types. The components of +//! the [`Value`] module allow storage and transformation of various types of input into strongly +//! typed values, allows their comparison, filtering and more. It also has the code for serialising +//! these into a form for the backend that can be persistent into the [`Backend`]. + use crate::be::dbvalue::{DbCidV1, DbValueCredV1, DbValueTaggedStringV1, DbValueV1}; use crate::credential::Credential; use crate::repl::cid::Cid;