mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 04:57:00 +01:00
Add auth docs (#463)
This commit is contained in:
parent
807af81184
commit
2493dad4fb
|
@ -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(),
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -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(),
|
||||||
));
|
));
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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};
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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)));
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
Loading…
Reference in a new issue