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