Add auth docs (#463)

This commit is contained in:
Firstyear 2021-06-02 09:42:40 +10:00 committed by GitHub
parent 807af81184
commit 2493dad4fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 302 additions and 108 deletions

View file

@ -1,19 +1,18 @@
// Access Control Profiles //! Access Control Profiles
// //!
// This is a pretty important and security sensitive part of the code - it's //! 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 //! responsible for making sure that who is allowed to do what is enforced, as
// well as who is *not* allowed to do what. //! well as who is *not* allowed to do what.
// //!
// A detailed design can be found in access-profiles-and-security. //! A detailed design can be found in access-profiles-and-security.
//!
// //! This component of the server really has a few parts
// This part of the server really has a few parts //! - the ability to parse access profile structures into real ACP structs
// - 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
// - the ability to apply sets of ACP's to entries for coarse actions (IE //! search.
// search. //! - the ability to turn an entry into a partial-entry for results send
// - the ability to turn an entry into a partial-entry for results send //! requirements (also search).
// requirements (also search). //!
//
// use concread::collections::bptree::*; // use concread::collections::bptree::*;
use concread::arcache::{ARCache, ARCacheReadTxn}; use concread::arcache::{ARCache, ARCacheReadTxn};
@ -65,7 +64,7 @@ impl AccessControlSearch {
qs: &mut QueryServerWriteTransaction, qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>, value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", &CLASS_ACS) { if !value.attribute_equality("class", &CLASS_ACS) {
ladmin_error!(audit, "class access_control_search not present."); ladmin_error!(audit, "class access_control_search not present.");
return Err(OperationError::InvalidAcpState( return Err(OperationError::InvalidAcpState(
"Missing access_control_search".to_string(), "Missing access_control_search".to_string(),
@ -120,7 +119,7 @@ impl AccessControlDelete {
qs: &mut QueryServerWriteTransaction, qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>, value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", &CLASS_ACD) { if !value.attribute_equality("class", &CLASS_ACD) {
ladmin_error!(audit, "class access_control_delete not present."); ladmin_error!(audit, "class access_control_delete not present.");
return Err(OperationError::InvalidAcpState( return Err(OperationError::InvalidAcpState(
"Missing access_control_delete".to_string(), "Missing access_control_delete".to_string(),
@ -163,7 +162,7 @@ impl AccessControlCreate {
qs: &mut QueryServerWriteTransaction, qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>, value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", &CLASS_ACC) { if !value.attribute_equality("class", &CLASS_ACC) {
ladmin_error!(audit, "class access_control_create not present."); ladmin_error!(audit, "class access_control_create not present.");
return Err(OperationError::InvalidAcpState( return Err(OperationError::InvalidAcpState(
"Missing access_control_create".to_string(), "Missing access_control_create".to_string(),
@ -223,7 +222,7 @@ impl AccessControlModify {
qs: &mut QueryServerWriteTransaction, qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>, value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", &CLASS_ACM) { if !value.attribute_equality("class", &CLASS_ACM) {
ladmin_error!(audit, "class access_control_modify not present."); ladmin_error!(audit, "class access_control_modify not present.");
return Err(OperationError::InvalidAcpState( return Err(OperationError::InvalidAcpState(
"Missing access_control_modify".to_string(), "Missing access_control_modify".to_string(),
@ -301,7 +300,7 @@ impl AccessControlProfile {
value: &Entry<EntrySealed, EntryCommitted>, value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
// Assert we have class access_control_profile // 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."); ladmin_error!(audit, "class access_control_profile not present.");
return Err(OperationError::InvalidAcpState( return Err(OperationError::InvalidAcpState(
"Missing access_control_profile".to_string(), "Missing access_control_profile".to_string(),

View file

@ -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_read;
pub mod v1_write; pub mod v1_write;

View file

@ -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 std::fs;
use crate::value::IndexType; use crate::value::IndexType;

View file

@ -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 rand::prelude::*;
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;

View file

@ -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 https;
mod ldaps; mod ldaps;
use libc::umask; use libc::umask;

View file

@ -235,6 +235,10 @@ pub struct Credential {
} }
#[derive(Clone, Debug)] #[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 { pub enum CredentialType {
// Anonymous, // Anonymous,
Password(Password), Password(Password),
@ -334,6 +338,7 @@ impl TryFrom<DbCredV1> for Credential {
} }
impl Credential { impl Credential {
/// Create a new credential that contains a CredentialType::Password
pub fn new_password_only( pub fn new_password_only(
policy: &CryptoPolicy, policy: &CryptoPolicy,
cleartext: &str, cleartext: &str,
@ -341,6 +346,7 @@ impl Credential {
Password::new(policy, cleartext).map(Self::new_from_password) Password::new(policy, cleartext).map(Self::new_from_password)
} }
/// Create a new credential that contains a CredentialType::GeneratedPassword
pub fn new_generatedpassword_only( pub fn new_generatedpassword_only(
policy: &CryptoPolicy, policy: &CryptoPolicy,
cleartext: &str, cleartext: &str,
@ -348,6 +354,7 @@ impl Credential {
Password::new(policy, cleartext).map(Self::new_from_generatedpassword) 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 { pub fn new_webauthn_only(label: String, cred: WebauthnCredential) -> Self {
let mut webauthn_map = Map::new(); let mut webauthn_map = Map::new();
webauthn_map.insert(label, cred); 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( pub fn set_password(
&self, &self,
policy: &CryptoPolicy, policy: &CryptoPolicy,
@ -366,6 +375,9 @@ impl Credential {
Password::new(policy, cleartext).map(|pw| self.update_password(pw)) 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( pub fn append_webauthn(
&self, &self,
label: String, 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<Self, OperationError> { pub fn remove_webauthn(&self, label: &str) -> Result<Self, OperationError> {
let type_ = match &self.type_ { let type_ = match &self.type_ {
CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => { CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => {
@ -459,6 +472,8 @@ impl Credential {
} }
#[allow(clippy::ptr_arg)] #[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( pub fn update_webauthn_counter(
&self, &self,
cid: &CredentialID, 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<String, WebauthnCredential>, OperationError> { pub fn webauthn_ref(&self) -> Result<&Map<String, WebauthnCredential>, OperationError> {
match &self.type_ { match &self.type_ {
CredentialType::Password(_) | CredentialType::GeneratedPassword(_) => Err( 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> { pub fn password_ref(&self) -> Result<&Password, OperationError> {
match &self.type_ { match &self.type_ {
CredentialType::Password(pw) CredentialType::Password(pw)
@ -539,6 +556,7 @@ impl Credential {
self.password_ref().and_then(|pw| pw.verify(cleartext)) 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 { pub fn to_db_valuev1(&self) -> DbCredV1 {
let claims = self.claims.clone(); let claims = self.claims.clone();
let uuid = self.uuid; let uuid = self.uuid;

View file

@ -1,7 +1,12 @@
//! This module contains cryptographic setup code, a long with what policy
//! and ciphers we accept.
use crate::config::Configuration; use crate::config::Configuration;
use openssl::error::ErrorStack; use openssl::error::ErrorStack;
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod}; 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<Option<SslAcceptorBuilder>, ErrorStack> { pub fn setup_tls(config: &Configuration) -> Result<Option<SslAcceptorBuilder>, ErrorStack> {
match &config.tls_config { match &config.tls_config {
Some(tls_config) => { Some(tls_config) => {

View file

@ -227,6 +227,7 @@ impl<STATE> std::fmt::Display for Entry<EntryInit, STATE> {
} }
impl<STATE> Entry<EntryInit, STATE> { impl<STATE> Entry<EntryInit, STATE> {
/// Get the uuid of this entry.
pub(crate) fn get_uuid(&self) -> Option<&Uuid> { pub(crate) fn get_uuid(&self) -> Option<&Uuid> {
match self.attrs.get("uuid") { match self.attrs.get("uuid") {
Some(vs) => match vs.iter().take(1).next() { Some(vs) => match vs.iter().take(1).next() {
@ -256,11 +257,8 @@ impl Entry<EntryInit, EntryNew> {
} }
} }
// Could we consume protoentry? /// Consume a Protocol Entry from JSON, and validate and process the data into an internal
// /// [`Entry`] type.
// 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.
pub fn from_proto_entry( pub fn from_proto_entry(
audit: &mut AuditScope, audit: &mut AuditScope,
e: &ProtoEntry, e: &ProtoEntry,
@ -297,6 +295,8 @@ impl Entry<EntryInit, EntryNew> {
}) })
} }
/// Given a proto entry in JSON formed as a serialised string, processed that string
/// into an Entry.
pub fn from_proto_entry_str( pub fn from_proto_entry_str(
audit: &mut AuditScope, audit: &mut AuditScope,
es: &str, es: &str,
@ -428,6 +428,8 @@ impl Entry<EntryInit, EntryNew> {
} }
} }
/// 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<EntryInvalid, EntryNew> { pub fn assign_cid(mut self, cid: Cid) -> Entry<EntryInvalid, EntryNew> {
/* setup our last changed time */ /* setup our last changed time */
self.set_last_changed(cid.clone()); self.set_last_changed(cid.clone());
@ -439,6 +441,7 @@ impl Entry<EntryInit, EntryNew> {
} }
} }
/// Compare this entry to another.
pub fn compare(&self, rhs: &Entry<EntrySealed, EntryCommitted>) -> bool { pub fn compare(&self, rhs: &Entry<EntrySealed, EntryCommitted>) -> bool {
compare_attrs(&self.attrs, &rhs.attrs) compare_attrs(&self.attrs, &rhs.attrs)
} }
@ -491,10 +494,12 @@ impl Entry<EntryInit, EntryNew> {
} }
} }
/// Add an attribute-value-assertion to this Entry.
pub fn add_ava(&mut self, attr: &str, value: Value) { pub fn add_ava(&mut self, attr: &str, value: Value) {
self.add_ava_int(attr, 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<Value>) { pub fn set_ava(&mut self, attr: &str, values: Set<Value>) {
self.set_ava_int(attr, values) self.set_ava_int(attr, values)
} }
@ -513,6 +518,8 @@ impl<STATE> Entry<EntryInvalid, STATE> {
} }
} }
/// Validate that this entry and it's attribute-value sets are conformant to the systems
/// schema and the releant syntaxes.
pub fn validate( pub fn validate(
self, self,
schema: &dyn SchemaTransaction, schema: &dyn SchemaTransaction,
@ -551,7 +558,7 @@ impl<STATE> Entry<EntryInvalid, STATE> {
} }
// Do we have extensible? // 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 entry_classes = ne.get_ava_set("class").ok_or(SchemaError::NoClassFound)?;
let mut invalid_classes = Vec::with_capacity(0); let mut invalid_classes = Vec::with_capacity(0);
@ -729,6 +736,7 @@ impl Entry<EntryInvalid, EntryCommitted> {
} }
} }
/// Convert this entry into a recycled entry, that is "in the recycle bin".
pub fn into_recycled(mut self) -> Self { pub fn into_recycled(mut self) -> Self {
self.add_ava("class", Value::new_class("recycled")); self.add_ava("class", Value::new_class("recycled"));
@ -828,6 +836,8 @@ impl Entry<EntrySealed, EntryNew> {
} }
} }
/// 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<EntrySealed, EntryCommitted> { pub fn into_sealed_committed_id(self, id: u64) -> Entry<EntrySealed, EntryCommitted> {
Entry { Entry {
valid: self.valid, valid: self.valid,
@ -845,6 +855,7 @@ type IdxDiff<'a> =
Vec<Result<(&'a AttrString, &'a IndexType, String), (&'a AttrString, &'a IndexType, String)>>; Vec<Result<(&'a AttrString, &'a IndexType, String), (&'a AttrString, &'a IndexType, String)>>;
impl<VALID> Entry<VALID, EntryCommitted> { impl<VALID> Entry<VALID, EntryCommitted> {
/// If this entry has ever been commited to disk, retrieve it's database id number.
pub fn get_id(&self) -> u64 { pub fn get_id(&self) -> u64 {
self.state.id self.state.id
} }
@ -867,6 +878,8 @@ impl Entry<EntrySealed, EntryCommitted> {
self 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) { pub fn insert_claim(&mut self, value: &str) {
self.add_ava_int("claim", Value::new_iutf8(value)); self.add_ava_int("claim", Value::new_iutf8(value));
} }
@ -875,6 +888,7 @@ impl Entry<EntrySealed, EntryCommitted> {
compare_attrs(&self.attrs, &rhs.attrs) compare_attrs(&self.attrs, &rhs.attrs)
} }
/// Serialise this entry to it's Database format ready for storage.
pub fn to_dbentry(&self) -> DbEntry { pub fn to_dbentry(&self) -> DbEntry {
// In the future this will do extra work to process uuid // In the future this will do extra work to process uuid
// into "attributes" suitable for dbentry storage. // into "attributes" suitable for dbentry storage.
@ -899,6 +913,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
#[inline] #[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<String> { fn get_name2uuid_cands(&self) -> Set<String> {
// The cands are: // The cands are:
// * spn // * spn
@ -918,6 +934,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
#[inline] #[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 { pub(crate) fn get_uuid2spn(&self) -> Value {
self.attrs self.attrs
.get("spn") .get("spn")
@ -931,6 +949,7 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
#[inline] #[inline]
/// Given this entry, determine it's relative distinguished named for LDAP compatability.
pub(crate) fn get_uuid2rdn(&self) -> String { pub(crate) fn get_uuid2rdn(&self) -> String {
self.attrs self.attrs
.get("spn") .get("spn")
@ -952,6 +971,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
#[inline] #[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> { pub(crate) fn mask_recycled_ts(&self) -> Option<&Self> {
// Only when cls has ts/rc then None, else lways Some(self). // Only when cls has ts/rc then None, else lways Some(self).
match self.attrs.get("class") { match self.attrs.get("class") {
@ -1007,6 +1028,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
} }
/// 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( pub(crate) fn idx_uuid2spn_diff(
pre: Option<&Self>, pre: Option<&Self>,
post: Option<&Self>, post: Option<&Self>,
@ -1038,6 +1061,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
} }
/// 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( pub(crate) fn idx_uuid2rdn_diff(
pre: Option<&Self>, pre: Option<&Self>,
post: Option<&Self>, post: Option<&Self>,
@ -1069,8 +1094,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
} }
// This is an associated method, not on & self so we can take options on /// Given the previous and current state of this entry, determine the indexing differential
// both sides. /// that needs to be applied. i.e. what indexes must be created, modified and removed.
pub(crate) fn idx_diff<'a>( pub(crate) fn idx_diff<'a>(
idxmeta: &'a HashSet<IdxKey>, idxmeta: &'a HashSet<IdxKey>,
pre: Option<&Self>, pre: Option<&Self>,
@ -1317,6 +1342,8 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
} }
/// 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( pub fn reduce_attributes(
self, self,
allowed_attrs: &BTreeSet<&str>, allowed_attrs: &BTreeSet<&str>,
@ -1347,6 +1374,7 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
} }
/// Convert this recycled entry, into a tombstone ready for reaping.
pub fn to_tombstone(&self, cid: Cid) -> Entry<EntryInvalid, EntryCommitted> { pub fn to_tombstone(&self, cid: Cid) -> Entry<EntryInvalid, EntryCommitted> {
// Duplicate this to a tombstone entry // Duplicate this to a tombstone entry
let class_ava = btreeset![Value::new_class("object"), Value::new_class("tombstone")]; let class_ava = btreeset![Value::new_class("object"), Value::new_class("tombstone")];
@ -1368,6 +1396,7 @@ impl Entry<EntrySealed, EntryCommitted> {
} }
} }
/// Given a current transaction change identifier, mark this entry as valid and committed.
pub fn into_valid(self, cid: Cid) -> Entry<EntryValid, EntryCommitted> { pub fn into_valid(self, cid: Cid) -> Entry<EntryValid, EntryCommitted> {
Entry { Entry {
valid: EntryValid { valid: EntryValid {
@ -1442,6 +1471,7 @@ impl Entry<EntryReduced, EntryCommitted> {
&self.valid.uuid &self.valid.uuid
} }
/// Transform this reduced entry into a JSON protocol form that can be sent to clients.
pub fn to_pe( pub fn to_pe(
&self, &self,
audit: &mut AuditScope, audit: &mut AuditScope,
@ -1461,6 +1491,7 @@ impl Entry<EntryReduced, EntryCommitted> {
Ok(ProtoEntry { attrs: attrs? }) Ok(ProtoEntry { attrs: attrs? })
} }
/// Transform this reduced entry into an LDAP form that can be sent to clients.
pub fn to_ldap( pub fn to_ldap(
&self, &self,
audit: &mut AuditScope, audit: &mut AuditScope,
@ -1538,6 +1569,7 @@ impl Entry<EntryReduced, EntryCommitted> {
// impl<STATE> Entry<EntryValid, STATE> { // impl<STATE> Entry<EntryValid, STATE> {
impl<VALID, STATE> Entry<VALID, STATE> { impl<VALID, STATE> Entry<VALID, STATE> {
/// This internally adds an AVA to the entry.
fn add_ava_int(&mut self, attr: &str, value: Value) { fn add_ava_int(&mut self, attr: &str, value: Value) {
// How do we make this turn into an ok / err? // How do we make this turn into an ok / err?
let v = self let v = self
@ -1549,38 +1581,45 @@ impl<VALID, STATE> Entry<VALID, STATE> {
// Doesn't matter if it already exists, equality will replace. // 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<Value>) { pub fn set_ava_int(&mut self, attr: &str, values: Set<Value>) {
// Overwrite the existing value, build a tree from the list. // Overwrite the existing value, build a tree from the list.
let _ = self.attrs.insert(AttrString::from(attr), values); 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) { fn set_last_changed(&mut self, cid: Cid) {
let cv = btreeset![Value::new_cid(cid)]; let cv = btreeset![Value::new_cid(cid)];
let _ = self.attrs.insert(AttrString::from("last_modified_cid"), cv); let _ = self.attrs.insert(AttrString::from("last_modified_cid"), cv);
} }
#[inline(always)] #[inline(always)]
/// Get an iterator over the current set of attribute names that this entry contains.
pub fn get_ava_names(&self) -> impl Iterator<Item = &str> { pub fn get_ava_names(&self) -> impl Iterator<Item = &str> {
// Get the set of all attribute names in the entry // Get the set of all attribute names in the entry
self.attrs.keys().map(|a| a.as_str()) self.attrs.keys().map(|a| a.as_str())
} }
#[inline(always)] #[inline(always)]
/// Get an iterator over the current set of values for an attribute name.
pub fn get_ava(&self, attr: &str) -> Option<impl Iterator<Item = &Value>> { pub fn get_ava(&self, attr: &str) -> Option<impl Iterator<Item = &Value>> {
self.attrs.get(attr).map(|vs| vs.iter()) self.attrs.get(attr).map(|vs| vs.iter())
} }
#[inline(always)] #[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<Value>> { pub fn get_ava_set(&self, attr: &str) -> Option<&Set<Value>> {
self.attrs.get(attr) self.attrs.get(attr)
} }
#[inline(always)] #[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<impl Iterator<Item = &str>> { pub fn get_ava_as_str(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
self.get_ava(attr).map(|i| i.filter_map(|s| s.to_str())) self.get_ava(attr).map(|i| i.filter_map(|s| s.to_str()))
} }
#[inline(always)] #[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<impl Iterator<Item = &Uuid>> { pub fn get_ava_as_refuuid(&self, attr: &str) -> Option<impl Iterator<Item = &Uuid>> {
// If any value is NOT a reference, it's filtered out. // If any value is NOT a reference, it's filtered out.
self.get_ava(attr) self.get_ava(attr)
@ -1588,6 +1627,7 @@ impl<VALID, STATE> Entry<VALID, STATE> {
} }
#[inline(always)] #[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<impl Iterator<Item = &str>> { pub fn get_ava_iter_sshpubkeys(&self, attr: &str) -> Option<impl Iterator<Item = &str>> {
self.get_ava(attr).map(|i| i.filter_map(|v| v.get_sshkey())) self.get_ava(attr).map(|i| i.filter_map(|v| v.get_sshkey()))
} }
@ -1613,7 +1653,8 @@ impl<VALID, STATE> Entry<VALID, STATE> {
} }
} }
/// 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)] #[inline(always)]
pub fn get_ava_single(&self, attr: &str) -> Option<&Value> { pub fn get_ava_single(&self, attr: &str) -> Option<&Value> {
match self.attrs.get(attr) { match self.attrs.get(attr) {
@ -1628,71 +1669,72 @@ impl<VALID, STATE> Entry<VALID, STATE> {
} }
} }
/// Get a bool from an ava
#[inline(always)] #[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<bool> { pub fn get_ava_single_bool(&self, attr: &str) -> Option<bool> {
self.get_ava_single(attr).and_then(|a| a.to_bool()) self.get_ava_single(attr).and_then(|a| a.to_bool())
} }
#[inline(always)] #[inline(always)]
/// Return a single uint32, if valid to transform this value.
pub fn get_ava_single_uint32(&self, attr: &str) -> Option<u32> { pub fn get_ava_single_uint32(&self, attr: &str) -> Option<u32> {
self.get_ava_single(attr).and_then(|a| a.to_uint32()) self.get_ava_single(attr).and_then(|a| a.to_uint32())
} }
#[inline(always)] #[inline(always)]
/// Return a single syntax type, if valid to transform this value.
pub fn get_ava_single_syntax(&self, attr: &str) -> Option<&SyntaxType> { pub fn get_ava_single_syntax(&self, attr: &str) -> Option<&SyntaxType> {
self.get_ava_single(attr).and_then(|a| a.to_syntaxtype()) self.get_ava_single(attr).and_then(|a| a.to_syntaxtype())
} }
#[inline(always)] #[inline(always)]
/// Return a single credential, if valid to transform this value.
pub fn get_ava_single_credential(&self, attr: &str) -> Option<&Credential> { pub fn get_ava_single_credential(&self, attr: &str) -> Option<&Credential> {
self.get_ava_single(attr).and_then(|a| a.to_credential()) self.get_ava_single(attr).and_then(|a| a.to_credential())
} }
#[inline(always)] #[inline(always)]
/// Return a single radius credential, if valid to transform this value.
pub fn get_ava_single_radiuscred(&self, attr: &str) -> Option<&str> { pub fn get_ava_single_radiuscred(&self, attr: &str) -> Option<&str> {
self.get_ava_single(attr) self.get_ava_single(attr)
.and_then(|a| a.get_radius_secret()) .and_then(|a| a.get_radius_secret())
} }
#[inline(always)] #[inline(always)]
/// Return a single datetime, if valid to transform this value.
pub fn get_ava_single_datetime(&self, attr: &str) -> Option<OffsetDateTime> { pub fn get_ava_single_datetime(&self, attr: &str) -> Option<OffsetDateTime> {
self.get_ava_single(attr).and_then(|a| a.to_datetime()) self.get_ava_single(attr).and_then(|a| a.to_datetime())
} }
#[inline(always)] #[inline(always)]
/// Return a single `&str`, if valid to transform this value.
pub fn get_ava_single_str(&self, attr: &str) -> Option<&str> { pub fn get_ava_single_str(&self, attr: &str) -> Option<&str> {
self.get_ava_single(attr).and_then(|v| v.to_str()) self.get_ava_single(attr).and_then(|v| v.to_str())
} }
#[inline(always)] #[inline(always)]
/// Return a single protocol filter, if valid to transform this value.
pub fn get_ava_single_protofilter(&self, attr: &str) -> Option<&ProtoFilter> { pub fn get_ava_single_protofilter(&self, attr: &str) -> Option<&ProtoFilter> {
self.get_ava_single(attr) self.get_ava_single(attr)
.and_then(|v: &Value| v.as_json_filter()) .and_then(|v: &Value| v.as_json_filter())
} }
#[inline(always)] #[inline(always)]
/// Return a single security principle name, if valid to transform this value.
pub(crate) fn generate_spn(&self, domain_name: &str) -> Option<Value> { pub(crate) fn generate_spn(&self, domain_name: &str) -> Option<Value> {
self.get_ava_single_str("name") self.get_ava_single_str("name")
.map(|name| Value::new_spn_str(name, domain_name)) .map(|name| Value::new_spn_str(name, domain_name))
} }
#[inline(always)] #[inline(always)]
/// Assert if an attribute of this name is present on this entry.
pub fn attribute_pres(&self, attr: &str) -> bool { 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) self.attrs.contains_key(attr)
} }
#[inline(always)] #[inline(always)]
pub fn attribute_value_pres(&self, attr: &str, value: &PartialValue) -> bool { /// Assert if an attribute of this name is present, and one of it's values contains
// Yeah, this is techdebt, but both names of this fn are valid - we are /// the an exact match of this partial value.
// 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)]
pub fn attribute_equality(&self, attr: &str, value: &PartialValue) -> bool { pub fn attribute_equality(&self, attr: &str, value: &PartialValue) -> bool {
// we assume based on schema normalisation on the way in // we assume based on schema normalisation on the way in
// that the equality here of the raw values MUST be correct. // that the equality here of the raw values MUST be correct.
@ -1705,6 +1747,8 @@ impl<VALID, STATE> Entry<VALID, STATE> {
} }
#[inline(always)] #[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 { pub fn attribute_substring(&self, attr: &str, subvalue: &PartialValue) -> bool {
match self.attrs.get(attr) { match self.attrs.get(attr) {
Some(v_list) => v_list Some(v_list) => v_list
@ -1714,8 +1758,9 @@ impl<VALID, STATE> Entry<VALID, STATE> {
} }
} }
/// Confirm if at least one value in the ava is less than subvalue.
#[inline(always)] #[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 { pub fn attribute_lessthan(&self, attr: &str, subvalue: &PartialValue) -> bool {
match self.attrs.get(attr) { match self.attrs.get(attr) {
Some(v_list) => v_list Some(v_list) => v_list
@ -1730,6 +1775,7 @@ impl<VALID, STATE> Entry<VALID, STATE> {
// valid, we still have strict typing checks between the filter -> entry to guarantee // 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. // they should be functional. We'll never match something that isn't syntactially valid.
#[inline(always)] #[inline(always)]
/// Test if the following filter applies to and matches this entry.
pub fn entry_match_no_index(&self, filter: &Filter<FilterValidResolved>) -> bool { pub fn entry_match_no_index(&self, filter: &Filter<FilterValidResolved>) -> bool {
self.entry_match_no_index_inner(filter.to_inner()) self.entry_match_no_index_inner(filter.to_inner())
} }
@ -1766,6 +1812,8 @@ impl<VALID, STATE> Entry<VALID, STATE> {
} }
} }
/// Given this entry, generate a filter containing the requested attributes strings as
/// equality components.
pub fn filter_from_attrs(&self, attrs: &[AttrString]) -> Option<Filter<FilterInvalid>> { pub fn filter_from_attrs(&self, attrs: &[AttrString]) -> Option<Filter<FilterInvalid>> {
// Because we are a valid entry, a filter we create still may not // Because we are a valid entry, a filter we create still may not
// be valid because the internal server entry templates are still // be valid because the internal server entry templates are still
@ -1802,6 +1850,8 @@ impl<VALID, STATE> Entry<VALID, STATE> {
))) )))
} }
/// Given this entry, generate a modification list that would "assert"
/// another entry is in the same/identical attribute state.
pub fn gen_modlist_assert( pub fn gen_modlist_assert(
&self, &self,
schema: &dyn SchemaTransaction, schema: &dyn SchemaTransaction,
@ -1866,6 +1916,7 @@ where
self.add_ava_int(attr, value) self.add_ava_int(attr, value)
} }
/// Remove an attribute-value pair from this entry.
fn remove_ava(&mut self, attr: &str, value: &PartialValue) { fn remove_ava(&mut self, attr: &str, value: &PartialValue) {
// It would be great to remove these extra allocations, but they // It would be great to remove these extra allocations, but they
// really don't cost much :( // 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) { pub fn purge_ava(&mut self, attr: &str) {
self.attrs.remove(attr); 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<Set<Value>> { pub fn pop_ava(&mut self, attr: &str) -> Option<Set<Value>> {
self.attrs.remove(attr) 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<Value>) { pub fn set_ava(&mut self, attr: &str, values: Set<Value>) {
self.set_ava_int(attr, values) 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? /// Apply the content of this modlist to this entry, enforcing the expressed state.
// YES. Makes it very cheap.
pub fn apply_modlist(&mut self, modlist: &ModifyList<ModifyValid>) { pub fn apply_modlist(&mut self, modlist: &ModifyList<ModifyValid>) {
// -> Result<Entry<EntryInvalid, STATE>, OperationError> { // -> Result<Entry<EntryInvalid, STATE>, OperationError> {
// Apply a modlist, generating a new entry that conforms to the changes. // Apply a modlist, generating a new entry that conforms to the changes.

View file

@ -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::entry::{Entry, EntryCommitted, EntryInit, EntryNew, EntryReduced};
use crate::filter::{Filter, FilterInvalid, FilterValid}; use crate::filter::{Filter, FilterInvalid, FilterValid};
use crate::identity::Limits; use crate::identity::Limits;

View file

@ -1,5 +1,7 @@
// Contains a structure representing the current authenticated //! Contains structures related to the Identity that initiated an `Event` in the
// identity (or anonymous, or admin, both of which are in mem). //! 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 crate::prelude::*;
use kanidm_proto::v1::UserAuthToken; use kanidm_proto::v1::UserAuthToken;
@ -37,6 +39,7 @@ impl Limits {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// Metadata and the entry of the current Identity which is an external account/user.
pub struct IdentUser { pub struct IdentUser {
pub entry: Entry<EntrySealed, EntryCommitted>, pub entry: Entry<EntrySealed, EntryCommitted>,
// IpAddr? // IpAddr?
@ -44,12 +47,15 @@ pub struct IdentUser {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// The type of Identity that is related to this session.
pub enum IdentType { pub enum IdentType {
User(IdentUser), User(IdentUser),
Internal, Internal,
} }
#[derive(Debug, Clone, PartialEq, Hash, Ord, PartialOrd, Eq)] #[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 { pub enum IdentityId {
// Time stamp of the originating event. // Time stamp of the originating event.
// The uuid of the originiating user // The uuid of the originiating user
@ -67,6 +73,7 @@ impl From<&IdentType> for IdentityId {
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
/// An identity that initiated an `Event`.
pub struct Identity { pub struct Identity {
pub origin: IdentType, pub origin: IdentType,
pub(crate) limits: Limits, pub(crate) limits: Limits,

View file

@ -26,7 +26,7 @@ lazy_static! {
macro_rules! try_from_entry { macro_rules! try_from_entry {
($value:expr, $groups:expr) => {{ ($value:expr, $groups:expr) => {{
// Check the classes // Check the classes
if !$value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { if !$value.attribute_equality("class", &PVCLASS_ACCOUNT) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: account".to_string(), "Missing class: account".to_string(),
)); ));

View file

@ -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::account::Account;
use crate::idm::AuthState; use crate::idm::AuthState;
use crate::prelude::*; use crate::prelude::*;
@ -33,6 +38,7 @@ const BAD_CREDENTIALS: &str = "invalid credential message";
const ACCOUNT_EXPIRED: &str = "account expired"; const ACCOUNT_EXPIRED: &str = "account expired";
const PW_BADLIST_MSG: &str = "password is in badlist"; 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 { enum CredState {
Success(AuthType), Success(AuthType),
Continue(Vec<AuthAllowed>), Continue(Vec<AuthAllowed>),
@ -40,6 +46,7 @@ enum CredState {
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
/// The state of verification of an individual credential during an authentication.
enum CredVerifyState { enum CredVerifyState {
Init, Init,
Success, Success,
@ -47,6 +54,7 @@ enum CredVerifyState {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// The state of a multifactor authenticator during authentication.
struct CredMfa { struct CredMfa {
pw: Password, pw: Password,
pw_state: CredVerifyState, pw_state: CredVerifyState,
@ -56,12 +64,16 @@ struct CredMfa {
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
/// The state of a webauthn credential during authentication
struct CredWebauthn { struct CredWebauthn {
chal: RequestChallengeResponse, chal: RequestChallengeResponse,
wan_state: AuthenticationState, wan_state: AuthenticationState,
state: CredVerifyState, 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)] #[derive(Clone, Debug)]
enum CredHandler { enum CredHandler {
Anonymous, Anonymous,
@ -73,7 +85,10 @@ enum CredHandler {
} }
impl 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( fn try_from(
au: &mut AuditScope, au: &mut AuditScope,
c: &Credential, c: &Credential,
@ -139,6 +154,9 @@ impl CredHandler {
} }
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( fn maybe_pw_upgrade(
au: &mut AuditScope, au: &mut AuditScope,
pw: &Password, 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 { fn validate_anonymous(au: &mut AuditScope, cred: &AuthCredential) -> CredState {
match cred { match cred {
AuthCredential::Anonymous => { AuthCredential::Anonymous => {
@ -173,6 +192,7 @@ impl CredHandler {
} }
} }
/// Validate a singule password credential of the account.
fn validate_password( fn validate_password(
au: &mut AuditScope, au: &mut AuditScope,
cred: &AuthCredential, 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( fn validate_password_mfa(
au: &mut AuditScope, au: &mut AuditScope,
cred: &AuthCredential, cred: &AuthCredential,
@ -345,6 +368,7 @@ impl CredHandler {
} }
} // end CredHandler::PasswordMfa } // end CredHandler::PasswordMfa
/// Validate a webauthn authentication attempt
pub fn validate_webauthn( pub fn validate_webauthn(
au: &mut AuditScope, au: &mut AuditScope,
cred: &AuthCredential, cred: &AuthCredential,
@ -399,6 +423,7 @@ impl CredHandler {
} }
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
/// Given the current handler, proceed to authenticate the attempted credential step.
pub fn validate( pub fn validate(
&mut self, &mut self,
au: &mut AuditScope, 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<AuthAllowed> { pub fn next_auth_allowed(&self) -> Vec<AuthAllowed> {
match &self { match &self {
CredHandler::Anonymous => vec![AuthAllowed::Anonymous], 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 { fn can_proceed(&self, mech: &AuthMech) -> bool {
match (self, mech) { match (self, mech) {
(CredHandler::Anonymous, AuthMech::Anonymous) (CredHandler::Anonymous, AuthMech::Anonymous)
@ -494,6 +522,7 @@ impl AuthSessionState {
} }
#[derive(Clone)] #[derive(Clone)]
/// The current state of an authentication session that is in progress.
pub(crate) struct AuthSession { pub(crate) struct AuthSession {
// Do we store a copy of the entry? // Do we store a copy of the entry?
// How do we know what claims to add? // How do we know what claims to add?
@ -507,6 +536,9 @@ pub(crate) struct AuthSession {
} }
impl 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( pub fn new(
au: &mut AuditScope, au: &mut AuditScope,
account: Account, account: Account,
@ -570,6 +602,9 @@ impl AuthSession {
&self.account &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( pub fn start_session(
&mut self, &mut self,
_au: &mut AuditScope, _au: &mut AuditScope,
@ -631,7 +666,9 @@ impl AuthSession {
response 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( pub fn validate_creds(
&mut self, &mut self,
au: &mut AuditScope, au: &mut AuditScope,
@ -710,6 +747,7 @@ impl AuthSession {
response response
} }
/// End the session, defaulting to a denied.
pub fn end_session(&mut self, reason: &'static str) -> Result<AuthState, OperationError> { pub fn end_session(&mut self, reason: &'static str) -> Result<AuthState, OperationError> {
let mut next_state = AuthSessionState::Denied(reason); let mut next_state = AuthSessionState::Denied(reason);
std::mem::swap(&mut self.state, &mut next_state); std::mem::swap(&mut self.state, &mut next_state);

View file

@ -88,7 +88,7 @@ impl Group {
pub fn try_from_entry( pub fn try_from_entry(
value: &Entry<EntrySealed, EntryCommitted>, value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", &PVCLASS_GROUP) { if !value.attribute_equality("class", &PVCLASS_GROUP) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: group".to_string(), "Missing class: group".to_string(),
)); ));

View file

@ -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 account;
pub(crate) mod authsession; pub(crate) mod authsession;
pub(crate) mod delayed; pub(crate) mod delayed;

View file

@ -31,7 +31,7 @@ impl RadiusAccount {
value: &Entry<EntryReduced, EntryCommitted>, value: &Entry<EntryReduced, EntryCommitted>,
qs: &mut QueryServerReadTransaction, qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> { ) -> Result<Self, OperationError> {
if !value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { if !value.attribute_equality("class", &PVCLASS_ACCOUNT) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: account".to_string(), "Missing class: account".to_string(),
)); ));

View file

@ -41,13 +41,13 @@ lazy_static! {
macro_rules! try_from_entry { macro_rules! try_from_entry {
($value:expr, $groups:expr) => {{ ($value:expr, $groups:expr) => {{
if !$value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { if !$value.attribute_equality("class", &PVCLASS_ACCOUNT) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: account".to_string(), "Missing class: account".to_string(),
)); ));
} }
if !$value.attribute_value_pres("class", &PVCLASS_POSIXACCOUNT) { if !$value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: posixaccount".to_string(), "Missing class: posixaccount".to_string(),
)); ));
@ -284,10 +284,10 @@ macro_rules! try_from_group_e {
($value:expr) => {{ ($value:expr) => {{
// We could be looking at a user for their UPG, OR a true group. // We could be looking at a user for their UPG, OR a true group.
if !(($value.attribute_value_pres("class", &PVCLASS_ACCOUNT) if !(($value.attribute_equality("class", &PVCLASS_ACCOUNT)
&& $value.attribute_value_pres("class", &PVCLASS_POSIXACCOUNT)) && $value.attribute_equality("class", &PVCLASS_POSIXACCOUNT))
|| ($value.attribute_value_pres("class", &PVCLASS_GROUP) || ($value.attribute_equality("class", &PVCLASS_GROUP)
&& $value.attribute_value_pres("class", &PVCLASS_POSIXGROUP))) && $value.attribute_equality("class", &PVCLASS_POSIXGROUP)))
{ {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: account && posixaccount OR group && posixgroup".to_string(), "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. // First synthesise the self-group from the account.
// We have already checked these, but paranoia is better than // We have already checked these, but paranoia is better than
// complacency. // complacency.
if !$value.attribute_value_pres("class", &PVCLASS_ACCOUNT) { if !$value.attribute_equality("class", &PVCLASS_ACCOUNT) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: account".to_string(), "Missing class: account".to_string(),
)); ));
} }
if !$value.attribute_value_pres("class", &PVCLASS_POSIXACCOUNT) { if !$value.attribute_equality("class", &PVCLASS_POSIXACCOUNT) {
return Err(OperationError::InvalidAccountState( return Err(OperationError::InvalidAccountState(
"Missing class: posixaccount".to_string(), "Missing class: posixaccount".to_string(),
)); ));

View file

@ -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::actors::v1_write::QueryServerWriteV1;
use crate::constants::PURGE_FREQUENCY; use crate::constants::PURGE_FREQUENCY;
use crate::event::{PurgeRecycledEvent, PurgeTombstoneEvent}; use crate::event::{PurgeRecycledEvent, PurgeTombstoneEvent};

View file

@ -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::event::SearchEvent;
use crate::idm::event::LdapAuthEvent; use crate::idm::event::LdapAuthEvent;
use crate::idm::server::{IdmServer, IdmServerTransaction}; use crate::idm::server::{IdmServer, IdmServerTransaction};

View file

@ -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)] #![deny(warnings)]
#![warn(unused_extern_crates)] #![warn(unused_extern_crates)]
#![deny(clippy::unwrap_used)] #![deny(clippy::unwrap_used)]
@ -56,6 +59,8 @@ mod status;
pub mod config; pub mod config;
pub mod core; 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 mod prelude {
pub use crate::utils::duration_from_epoch_now; pub use crate::utils::duration_from_epoch_now;
pub use kanidm_proto::v1::OperationError; pub use kanidm_proto::v1::OperationError;

View file

@ -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 crate::prelude::*;
use kanidm_proto::v1::Modify as ProtoModify; use kanidm_proto::v1::Modify as ProtoModify;
use kanidm_proto::v1::ModifyList as ProtoModifyList; use kanidm_proto::v1::ModifyList as ProtoModifyList;

View file

@ -30,8 +30,8 @@ impl Plugin for Domain {
) -> Result<(), OperationError> { ) -> Result<(), OperationError> {
ltrace!(au, "Entering plugin_domain pre_create_transform"); ltrace!(au, "Entering plugin_domain pre_create_transform");
cand.iter_mut().for_each(|e| { cand.iter_mut().for_each(|e| {
if e.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) if e.attribute_equality("class", &PVCLASS_DOMAIN_INFO)
&& e.attribute_value_pres("uuid", &PVUUID_DOMAIN_INFO) && e.attribute_equality("uuid", &PVUUID_DOMAIN_INFO)
{ {
// We always set this, because the DB uuid is authorative. // We always set this, because the DB uuid is authorative.
let u = Value::new_uuid(qs.get_domain_uuid()); let u = Value::new_uuid(qs.get_domain_uuid());
@ -66,7 +66,7 @@ mod tests {
let u_dom = server_txn.get_domain_uuid(); 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)));
}) })
} }
} }

View file

@ -25,8 +25,8 @@ fn apply_gidnumber<T: Clone>(
au: &mut AuditScope, au: &mut AuditScope,
e: &mut Entry<EntryInvalid, T>, e: &mut Entry<EntryInvalid, T>,
) -> Result<(), OperationError> { ) -> Result<(), OperationError> {
if (e.attribute_value_pres("class", &CLASS_POSIXGROUP) if (e.attribute_equality("class", &CLASS_POSIXGROUP)
|| e.attribute_value_pres("class", &CLASS_POSIXACCOUNT)) || e.attribute_equality("class", &CLASS_POSIXACCOUNT))
&& !e.attribute_pres("gidnumber") && !e.attribute_pres("gidnumber")
{ {
let u_ref = e let u_ref = e

View file

@ -127,7 +127,7 @@ fn apply_memberof(
while let Some((pre, mut tgte)) = work_set.pop() { while let Some((pre, mut tgte)) = work_set.pop() {
let guuid = *pre.get_uuid(); let guuid = *pre.get_uuid();
// load the entry from the db. // 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 // 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 // have seen this UUID before, as either we are on the first
// iteration OR the checks belowe should have filtered it out. // iteration OR the checks belowe should have filtered it out.
@ -184,7 +184,7 @@ fn apply_memberof(
.into_iter() .into_iter()
.try_for_each(|(auuid, (pre, mut tgte))| { .try_for_each(|(auuid, (pre, mut tgte))| {
ltrace!(au, "=> processing affected uuid {:?}", auuid); 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)?; do_memberof(au, qs, &auuid, &mut tgte)?;
// Only write if a change occured. // Only write if a change occured.
if pre.get_ava_set("memberof") != tgte.get_ava_set("memberof") if pre.get_ava_set("memberof") != tgte.get_ava_set("memberof")
@ -220,7 +220,7 @@ impl Plugin for MemberOf {
cand.iter() cand.iter()
.filter_map(|e| { .filter_map(|e| {
// Is it a group? // 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") e.get_ava_as_refuuid("member")
} else { } else {
None None
@ -249,7 +249,7 @@ impl Plugin for MemberOf {
pre_cand pre_cand
.iter() .iter()
.filter_map(|pre| { .filter_map(|pre| {
if pre.attribute_value_pres("class", &CLASS_GROUP) { if pre.attribute_equality("class", &CLASS_GROUP) {
pre.get_ava_as_refuuid("member") pre.get_ava_as_refuuid("member")
} else { } else {
None None
@ -260,7 +260,7 @@ impl Plugin for MemberOf {
.chain( .chain(
cand.iter() cand.iter()
.filter_map(|post| { .filter_map(|post| {
if post.attribute_value_pres("class", &CLASS_GROUP) { if post.attribute_equality("class", &CLASS_GROUP) {
post.get_ava_as_refuuid("member") post.get_ava_as_refuuid("member")
} else { } else {
None None
@ -306,7 +306,7 @@ impl Plugin for MemberOf {
.iter() .iter()
.filter_map(|e| { .filter_map(|e| {
// Is it a group? // 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") e.get_ava_as_refuuid("member")
} else { } else {
None None

View file

@ -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::entry::{Entry, EntryCommitted, EntryInvalid, EntryNew, EntrySealed};
use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent};
use crate::prelude::*; use crate::prelude::*;

View file

@ -63,12 +63,12 @@ impl Plugin for Protected {
cand.iter().fold(Ok(()), |acc, cand| match acc { cand.iter().fold(Ok(()), |acc, cand| match acc {
Err(_) => acc, Err(_) => acc,
Ok(_) => { Ok(_) => {
if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) if cand.attribute_equality("class", &PVCLASS_SYSTEM)
|| cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) || cand.attribute_equality("class", &PVCLASS_DOMAIN_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_INFO) || cand.attribute_equality("class", &PVCLASS_SYSTEM_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_CONFIG) || cand.attribute_equality("class", &PVCLASS_SYSTEM_CONFIG)
|| cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) || cand.attribute_equality("class", &PVCLASS_TOMBSTONE)
|| cand.attribute_value_pres("class", &PVCLASS_RECYCLED) || cand.attribute_equality("class", &PVCLASS_RECYCLED)
{ {
Err(OperationError::SystemProtectedObject) Err(OperationError::SystemProtectedObject)
} else { } else {
@ -123,8 +123,8 @@ impl Plugin for Protected {
cand.iter().fold(Ok(()), |acc, cand| match acc { cand.iter().fold(Ok(()), |acc, cand| match acc {
Err(_) => acc, Err(_) => acc,
Ok(_) => { Ok(_) => {
if cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) if cand.attribute_equality("class", &PVCLASS_TOMBSTONE)
|| cand.attribute_value_pres("class", &PVCLASS_RECYCLED) || cand.attribute_equality("class", &PVCLASS_RECYCLED)
{ {
Err(OperationError::SystemProtectedObject) Err(OperationError::SystemProtectedObject)
} else { } else {
@ -140,7 +140,7 @@ impl Plugin for Protected {
} else { } else {
// We don't need to check for domain info here because domain_info has a class // 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. // 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 { cand.iter().fold(Ok(()), |acc, cand| match acc {
Err(_) => acc, Err(_) => acc,
Ok(_) => { Ok(_) => {
if cand.attribute_value_pres("class", &PVCLASS_SYSTEM) if cand.attribute_equality("class", &PVCLASS_SYSTEM)
|| cand.attribute_value_pres("class", &PVCLASS_DOMAIN_INFO) || cand.attribute_equality("class", &PVCLASS_DOMAIN_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_INFO) || cand.attribute_equality("class", &PVCLASS_SYSTEM_INFO)
|| cand.attribute_value_pres("class", &PVCLASS_SYSTEM_CONFIG) || cand.attribute_equality("class", &PVCLASS_SYSTEM_CONFIG)
|| cand.attribute_value_pres("class", &PVCLASS_TOMBSTONE) || cand.attribute_equality("class", &PVCLASS_TOMBSTONE)
|| cand.attribute_value_pres("class", &PVCLASS_RECYCLED) || cand.attribute_equality("class", &PVCLASS_RECYCLED)
{ {
Err(OperationError::SystemProtectedObject) Err(OperationError::SystemProtectedObject)
} else { } else {

View file

@ -39,8 +39,8 @@ impl Plugin for Spn {
let mut domain_name: Option<String> = None; let mut domain_name: Option<String> = None;
for e in cand.iter_mut() { for e in cand.iter_mut() {
if e.attribute_value_pres("class", &CLASS_GROUP) if e.attribute_equality("class", &CLASS_GROUP)
|| e.attribute_value_pres("class", &CLASS_ACCOUNT) || e.attribute_equality("class", &CLASS_ACCOUNT)
{ {
// We do this in the loop so that we don't get it unless required. // We do this in the loop so that we don't get it unless required.
if domain_name.is_none() { if domain_name.is_none() {
@ -89,8 +89,8 @@ impl Plugin for Spn {
let mut domain_name: Option<String> = None; let mut domain_name: Option<String> = None;
for e in cand.iter_mut() { for e in cand.iter_mut() {
if e.attribute_value_pres("class", &CLASS_GROUP) if e.attribute_equality("class", &CLASS_GROUP)
|| e.attribute_value_pres("class", &CLASS_ACCOUNT) || e.attribute_equality("class", &CLASS_ACCOUNT)
{ {
if domain_name.is_none() { if domain_name.is_none() {
domain_name = Some(qs.get_domain_name(au)?); domain_name = Some(qs.get_domain_name(au)?);
@ -145,7 +145,7 @@ impl Plugin for Spn {
.fold(None, |acc, (post, pre)| { .fold(None, |acc, (post, pre)| {
if acc.is_some() { if acc.is_some() {
acc 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") != pre.get_ava_single("domain_name")
{ {
post.get_ava_single("domain_name") post.get_ava_single("domain_name")

View file

@ -113,7 +113,7 @@ impl SchemaAttribute {
let uuid = *value.get_uuid(); let uuid = *value.get_uuid();
// class // 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); ladmin_error!(audit, "class attribute type not present - {:?}", uuid);
return Err(OperationError::InvalidSchemaState( return Err(OperationError::InvalidSchemaState(
"missing attributetype".to_string(), "missing attributetype".to_string(),
@ -428,7 +428,7 @@ impl SchemaClass {
// uuid // uuid
let uuid = *value.get_uuid(); let uuid = *value.get_uuid();
// Convert entry to a schema class. // 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); ladmin_error!(audit, "class classtype not present - {:?}", uuid);
return Err(OperationError::InvalidSchemaState( return Err(OperationError::InvalidSchemaState(
"missing classtype".to_string(), "missing classtype".to_string(),

View file

@ -1123,15 +1123,15 @@ impl<'a> QueryServerWriteTransaction<'a> {
// schema or acp requires reload. // schema or acp requires reload.
if !self.changed_schema.get() { if !self.changed_schema.get() {
self.changed_schema.set(commit_cand.iter().any(|e| { self.changed_schema.set(commit_cand.iter().any(|e| {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) e.attribute_equality("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE)
})) }))
} }
if !self.changed_acp.get() { if !self.changed_acp.get() {
self.changed_acp.set( self.changed_acp.set(
commit_cand commit_cand
.iter() .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. // schema or acp requires reload.
if !self.changed_schema.get() { if !self.changed_schema.get() {
self.changed_schema.set(del_cand.iter().any(|e| { self.changed_schema.set(del_cand.iter().any(|e| {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) e.attribute_equality("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE)
})) }))
} }
if !self.changed_acp.get() { if !self.changed_acp.get() {
self.changed_acp.set( self.changed_acp.set(
del_cand del_cand
.iter() .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() { if !self.changed_schema.get() {
self.changed_schema self.changed_schema
.set(norm_cand.iter().chain(pre_candidates.iter()).any(|e| { .set(norm_cand.iter().chain(pre_candidates.iter()).any(|e| {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) e.attribute_equality("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE)
})) }))
} }
if !self.changed_acp.get() { if !self.changed_acp.get() {
@ -1635,7 +1635,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
norm_cand norm_cand
.iter() .iter()
.chain(pre_candidates.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(); let cu = self.changed_uuid.as_ptr();
@ -1764,8 +1764,8 @@ impl<'a> QueryServerWriteTransaction<'a> {
if !self.changed_schema.get() { if !self.changed_schema.get() {
self.changed_schema self.changed_schema
.set(norm_cand.iter().chain(pre_candidates.iter()).any(|e| { .set(norm_cand.iter().chain(pre_candidates.iter()).any(|e| {
e.attribute_value_pres("class", &PVCLASS_CLASSTYPE) e.attribute_equality("class", &PVCLASS_CLASSTYPE)
|| e.attribute_value_pres("class", &PVCLASS_ATTRIBUTETYPE) || e.attribute_equality("class", &PVCLASS_ATTRIBUTETYPE)
})) }))
} }
if !self.changed_acp.get() { if !self.changed_acp.get() {
@ -1773,7 +1773,7 @@ impl<'a> QueryServerWriteTransaction<'a> {
norm_cand norm_cand
.iter() .iter()
.chain(pre_candidates.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(); let cu = self.changed_uuid.as_ptr();
@ -3580,7 +3580,7 @@ mod tests {
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(), &Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
) )
.expect("failed"); .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 // Should still be good
server_txn.commit(audit).expect("should not fail"); server_txn.commit(audit).expect("should not fail");
@ -3667,7 +3667,7 @@ mod tests {
&Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(), &Uuid::parse_str("cc8e95b4-c24f-4d68-ba54-8bed76f63930").unwrap(),
) )
.expect("failed"); .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"); server_txn.commit(audit).expect("should not fail");
// Commit. // Commit.
@ -3768,7 +3768,7 @@ mod tests {
.pop() .pop()
.unwrap(); .unwrap();
e.attribute_value_pres("memberof", &PartialValue::new_refer_s(mo).unwrap()) e.attribute_equality("memberof", &PartialValue::new_refer_s(mo).unwrap())
} }
#[test] #[test]

View file

@ -1,3 +1,5 @@
//! An actor that shows the servers current status and statistics. (TODO).
use crate::audit::AuditScope; use crate::audit::AuditScope;
use tokio::sync::mpsc::UnboundedSender as Sender; use tokio::sync::mpsc::UnboundedSender as Sender;
use uuid::Uuid; use uuid::Uuid;

View file

@ -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::be::dbvalue::{DbCidV1, DbValueCredV1, DbValueTaggedStringV1, DbValueV1};
use crate::credential::Credential; use crate::credential::Credential;
use crate::repl::cid::Cid; use crate::repl::cid::Cid;