kanidm/server/lib/src/modify.rs
dependabot[bot] ed76bdbfb1
Bump the all group with 22 updates (#3376)
* Bump the all group with 22 updates

Bumps the all group with 22 updates:

| Package | From | To |
| --- | --- | --- |
| [async-trait](https://github.com/dtolnay/async-trait) | `0.1.83` | `0.1.85` |
| [bitflags](https://github.com/bitflags/bitflags) | `2.6.0` | `2.8.0` |
| [clap](https://github.com/clap-rs/clap) | `4.5.23` | `4.5.27` |
| [clap_complete](https://github.com/clap-rs/clap) | `4.5.40` | `4.5.42` |
| [lodepng](https://github.com/kornelski/lodepng-rust) | `3.10.7` | `3.11.0` |
| [openssl](https://github.com/sfackler/rust-openssl) | `0.10.68` | `0.10.69` |
| [proc-macro2](https://github.com/dtolnay/proc-macro2) | `1.0.92` | `1.0.93` |
| [reqwest](https://github.com/seanmonstar/reqwest) | `0.12.11` | `0.12.12` |
| [rustls](https://github.com/rustls/rustls) | `0.23.20` | `0.23.21` |
| [sd-notify](https://github.com/lnicola/sd-notify) | `0.4.4` | `0.4.5` |
| [serde_json](https://github.com/serde-rs/json) | `1.0.134` | `1.0.137` |
| [syn](https://github.com/dtolnay/syn) | `2.0.93` | `2.0.96` |
| [tempfile](https://github.com/Stebalien/tempfile) | `3.14.0` | `3.15.0` |
| [tokio](https://github.com/tokio-rs/tokio) | `1.42.0` | `1.43.0` |
| [uuid](https://github.com/uuid-rs/uuid) | `1.11.0` | `1.12.1` |
| [oauth2](https://github.com/ramosbugs/oauth2-rs) | `4.4.2` | `5.0.0` |
| [cc](https://github.com/rust-lang/cc-rs) | `1.2.6` | `1.2.10` |
| [axum-extra](https://github.com/tokio-rs/axum) | `0.9.6` | `0.10.0` |
| [axum-macros](https://github.com/tokio-rs/axum) | `0.4.2` | `0.5.0` |
| [fantoccini](https://github.com/jonhoo/fantoccini) | `0.21.3` | `0.21.4` |
| [petgraph](https://github.com/petgraph/petgraph) | `0.6.5` | `0.7.1` |
| [jsonschema](https://github.com/Stranger6667/jsonschema) | `0.28.0` | `0.28.3` |


Updates `async-trait` from 0.1.83 to 0.1.85
- [Release notes](https://github.com/dtolnay/async-trait/releases)
- [Commits](https://github.com/dtolnay/async-trait/compare/0.1.83...0.1.85)

Updates `bitflags` from 2.6.0 to 2.8.0
- [Release notes](https://github.com/bitflags/bitflags/releases)
- [Changelog](https://github.com/bitflags/bitflags/blob/main/CHANGELOG.md)
- [Commits](https://github.com/bitflags/bitflags/compare/2.6.0...2.8.0)

Updates `clap` from 4.5.23 to 4.5.27
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.23...clap_complete-v4.5.27)

Updates `clap_complete` from 4.5.40 to 4.5.42
- [Release notes](https://github.com/clap-rs/clap/releases)
- [Changelog](https://github.com/clap-rs/clap/blob/master/CHANGELOG.md)
- [Commits](https://github.com/clap-rs/clap/compare/clap_complete-v4.5.40...clap_complete-v4.5.42)

Updates `lodepng` from 3.10.7 to 3.11.0
- [Commits](https://github.com/kornelski/lodepng-rust/compare/v3.10.7...v3.11.0)

Updates `openssl` from 0.10.68 to 0.10.69
- [Release notes](https://github.com/sfackler/rust-openssl/releases)
- [Commits](https://github.com/sfackler/rust-openssl/compare/openssl-v0.10.68...openssl-v0.10.69)

Updates `proc-macro2` from 1.0.92 to 1.0.93
- [Release notes](https://github.com/dtolnay/proc-macro2/releases)
- [Commits](https://github.com/dtolnay/proc-macro2/compare/1.0.92...1.0.93)

Updates `reqwest` from 0.12.11 to 0.12.12
- [Release notes](https://github.com/seanmonstar/reqwest/releases)
- [Changelog](https://github.com/seanmonstar/reqwest/blob/master/CHANGELOG.md)
- [Commits](https://github.com/seanmonstar/reqwest/compare/v0.12.11...v0.12.12)

Updates `rustls` from 0.23.20 to 0.23.21
- [Release notes](https://github.com/rustls/rustls/releases)
- [Changelog](https://github.com/rustls/rustls/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rustls/rustls/compare/v/0.23.20...v/0.23.21)

Updates `sd-notify` from 0.4.4 to 0.4.5
- [Changelog](https://github.com/lnicola/sd-notify/blob/master/CHANGELOG.md)
- [Commits](https://github.com/lnicola/sd-notify/compare/v0.4.4...v0.4.5)

Updates `serde_json` from 1.0.134 to 1.0.137
- [Release notes](https://github.com/serde-rs/json/releases)
- [Commits](https://github.com/serde-rs/json/compare/v1.0.134...v1.0.137)

Updates `syn` from 2.0.93 to 2.0.96
- [Release notes](https://github.com/dtolnay/syn/releases)
- [Commits](https://github.com/dtolnay/syn/compare/2.0.93...2.0.96)

Updates `tempfile` from 3.14.0 to 3.15.0
- [Changelog](https://github.com/Stebalien/tempfile/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stebalien/tempfile/compare/v3.14.0...v3.15.0)

Updates `tokio` from 1.42.0 to 1.43.0
- [Release notes](https://github.com/tokio-rs/tokio/releases)
- [Commits](https://github.com/tokio-rs/tokio/compare/tokio-1.42.0...tokio-1.43.0)

Updates `uuid` from 1.11.0 to 1.12.1
- [Release notes](https://github.com/uuid-rs/uuid/releases)
- [Commits](https://github.com/uuid-rs/uuid/compare/1.11.0...1.12.1)

Updates `oauth2` from 4.4.2 to 5.0.0
- [Release notes](https://github.com/ramosbugs/oauth2-rs/releases)
- [Upgrade guide](https://github.com/ramosbugs/oauth2-rs/blob/main/UPGRADE.md)
- [Commits](https://github.com/ramosbugs/oauth2-rs/compare/4.4.2...5.0.0)

Updates `cc` from 1.2.6 to 1.2.10
- [Release notes](https://github.com/rust-lang/cc-rs/releases)
- [Changelog](https://github.com/rust-lang/cc-rs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rust-lang/cc-rs/compare/cc-v1.2.6...cc-v1.2.10)

Updates `axum-extra` from 0.9.6 to 0.10.0
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-extra-v0.9.6...axum-extra-v0.10.0)

Updates `axum-macros` from 0.4.2 to 0.5.0
- [Release notes](https://github.com/tokio-rs/axum/releases)
- [Changelog](https://github.com/tokio-rs/axum/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tokio-rs/axum/compare/axum-macros-v0.4.2...axum-macros-v0.5.0)

Updates `fantoccini` from 0.21.3 to 0.21.4
- [Commits](https://github.com/jonhoo/fantoccini/compare/v0.21.3...v0.21.4)

Updates `petgraph` from 0.6.5 to 0.7.1
- [Changelog](https://github.com/petgraph/petgraph/blob/master/RELEASES.rst)
- [Commits](https://github.com/petgraph/petgraph/compare/petgraph@v0.6.5...petgraph@v0.7.1)

Updates `jsonschema` from 0.28.0 to 0.28.3
- [Release notes](https://github.com/Stranger6667/jsonschema/releases)
- [Changelog](https://github.com/Stranger6667/jsonschema/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Stranger6667/jsonschema/compare/rust-v0.28.0...rust-v0.28.3)

---
updated-dependencies:
- dependency-name: async-trait
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: bitflags
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: clap
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: clap_complete
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: lodepng
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: openssl
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: proc-macro2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: reqwest
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: rustls
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: sd-notify
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: serde_json
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: syn
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: tempfile
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: tokio
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: uuid
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: oauth2
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: all
- dependency-name: cc
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: axum-extra
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: axum-macros
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: fantoccini
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
- dependency-name: petgraph
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: all
- dependency-name: jsonschema
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: all
...

Signed-off-by: dependabot[bot] <support@github.com>

* ok the otel stuff works now

* linting fixes

* fix: less parse more from_str, adding a todo

* fix: removing a TODO

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: James Hodgkinson <james@terminaloutcomes.com>
2025-01-29 13:57:38 +10:00

282 lines
8.9 KiB
Rust

//! 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 std::slice;
use kanidm_proto::internal::{
Modify as ProtoModify, ModifyList as ProtoModifyList, OperationError, SchemaError,
};
use kanidm_proto::v1::Entry as ProtoEntry;
// Should this be std?
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use crate::prelude::*;
use crate::schema::SchemaTransaction;
use crate::value::{PartialValue, Value};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModifyValid;
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ModifyInvalid;
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum Modify {
/// This value *should* exist for this attribute.
Present(Attribute, Value),
/// This value *should not* exist for this attribute.
Removed(Attribute, PartialValue),
/// This attr should not exist, and if it does exist, will have all content removed.
Purged(Attribute),
/// This attr and value must exist *in this state* for this change to proceed.
Assert(Attribute, PartialValue),
/// Set and replace the entire content of an attribute. This requires both presence
/// and removal access to the attribute to proceed.
Set(Attribute, ValueSet),
}
pub fn m_pres(attr: Attribute, v: &Value) -> Modify {
Modify::Present(attr, v.clone())
}
pub fn m_remove(attr: Attribute, v: &PartialValue) -> Modify {
Modify::Removed(attr, v.clone())
}
pub fn m_purge(attr: Attribute) -> Modify {
Modify::Purged(attr)
}
pub fn m_assert(attr: Attribute, v: &PartialValue) -> Modify {
Modify::Assert(attr, v.clone())
}
impl Modify {
pub fn from(
m: &ProtoModify,
qs: &mut QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
Ok(match m {
ProtoModify::Present(a, v) => {
let a = Attribute::from(a.as_str());
let v = qs.clone_value(&a, v)?;
Modify::Present(a, v)
}
ProtoModify::Removed(a, v) => {
let a = Attribute::from(a.as_str());
let v = qs.clone_partialvalue(&a, v)?;
Modify::Removed(a, v)
}
ProtoModify::Purged(a) => Modify::Purged(Attribute::from(a.as_str())),
})
}
}
#[derive(Clone, Debug, Default)]
pub struct ModifyList<VALID> {
// This is never read, it's just used for state machine enforcement.
#[allow(dead_code)]
valid: VALID,
// The order of this list matters. Each change must be done in order.
mods: Vec<Modify>,
}
impl<'a> IntoIterator for &'a ModifyList<ModifyValid> {
type IntoIter = slice::Iter<'a, Modify>;
type Item = &'a Modify;
fn into_iter(self) -> Self::IntoIter {
self.mods.iter()
}
}
impl ModifyList<ModifyInvalid> {
pub fn new() -> Self {
ModifyList {
valid: ModifyInvalid,
mods: Vec::with_capacity(0),
}
}
pub fn new_list(mods: Vec<Modify>) -> Self {
ModifyList {
valid: ModifyInvalid,
mods,
}
}
pub fn new_purge_and_set(attr: Attribute, v: Value) -> Self {
Self::new_list(vec![m_purge(attr.clone()), Modify::Present(attr, v)])
}
pub fn new_append(attr: Attribute, v: Value) -> Self {
Self::new_list(vec![Modify::Present(attr, v)])
}
pub fn new_remove(attr: Attribute, pv: PartialValue) -> Self {
Self::new_list(vec![Modify::Removed(attr, pv)])
}
pub fn new_purge(attr: Attribute) -> Self {
Self::new_list(vec![m_purge(attr)])
}
pub fn new_set(attr: Attribute, vs: ValueSet) -> Self {
Self::new_list(vec![Modify::Set(attr, vs)])
}
pub fn push_mod(&mut self, modify: Modify) {
self.mods.push(modify)
}
pub fn from(
ml: &ProtoModifyList,
qs: &mut QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
// For each ProtoModify, do a from.
let inner: Result<Vec<_>, _> = ml.mods.iter().map(|pm| Modify::from(pm, qs)).collect();
match inner {
Ok(m) => Ok(ModifyList {
valid: ModifyInvalid,
mods: m,
}),
Err(e) => Err(e),
}
}
pub fn from_patch(
pe: &ProtoEntry,
qs: &mut QueryServerWriteTransaction,
) -> Result<Self, OperationError> {
let mut mods = Vec::with_capacity(0);
pe.attrs.iter().try_for_each(|(attr, vals)| {
// Issue a purge to the attr.
let attr: Attribute = attr.as_str().into();
mods.push(m_purge(attr.clone()));
// Now if there are vals, push those too.
// For each value we want to now be present.
vals.iter().try_for_each(|val| {
qs.clone_value(&attr, val).map(|resolved_v| {
mods.push(Modify::Present(attr.clone(), resolved_v));
})
})
})?;
Ok(ModifyList {
valid: ModifyInvalid,
mods,
})
}
pub fn validate(
&self,
schema: &dyn SchemaTransaction,
) -> Result<ModifyList<ModifyValid>, SchemaError> {
let schema_attributes = schema.get_attributes();
/*
let schema_name = schema_attributes
.get(Attribute::Name.as_ref()")
.expect("Critical: Core schema corrupt or missing. To initiate a core transfer, please deposit substitute core in receptacle.");
*/
let res: Result<Vec<Modify>, _> = self
.mods
.iter()
.map(|m| match m {
Modify::Present(attr, value) => match schema_attributes.get(attr) {
Some(schema_a) => schema_a
.validate_value(attr, value)
.map(|_| Modify::Present(attr.clone(), value.clone())),
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
},
Modify::Removed(attr, value) => match schema_attributes.get(attr) {
Some(schema_a) => schema_a
.validate_partialvalue(attr, value)
.map(|_| Modify::Removed(attr.clone(), value.clone())),
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
},
Modify::Assert(attr, value) => match schema_attributes.get(attr) {
Some(schema_a) => schema_a
.validate_partialvalue(attr, value)
.map(|_| Modify::Assert(attr.clone(), value.clone())),
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
},
Modify::Purged(attr) => match schema_attributes.get(attr) {
Some(_attr_name) => Ok(Modify::Purged(attr.clone())),
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
},
Modify::Set(attr, valueset) => match schema_attributes.get(attr) {
Some(_attr_name) => Ok(Modify::Set(attr.clone(), valueset.clone())),
None => Err(SchemaError::InvalidAttribute(attr.to_string())),
},
})
.collect();
let valid_mods = res?;
// Return new ModifyList!
Ok(ModifyList {
valid: ModifyValid,
mods: valid_mods,
})
}
/// ⚠️ - Convert a modlist to be considered valid, bypassing schema.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub(crate) fn into_valid(self) -> ModifyList<ModifyValid> {
ModifyList {
valid: ModifyValid,
mods: self.mods,
}
}
}
impl From<BTreeMap<Attribute, Option<ValueSet>>> for ModifyList<ModifyInvalid> {
fn from(attrs: BTreeMap<Attribute, Option<ValueSet>>) -> Self {
let mods = attrs
.into_iter()
.map(|(attr, maybe_valueset)| {
if let Some(valueset) = maybe_valueset {
Modify::Set(attr, valueset)
} else {
Modify::Purged(attr)
}
})
.collect();
ModifyList {
valid: ModifyInvalid,
mods,
}
}
}
impl ModifyList<ModifyValid> {
/// ⚠️ - Create a new modlist that is considered valid, bypassing schema.
/// This is a TEST ONLY method and will never be exposed in production.
#[cfg(test)]
pub fn new_valid_list(mods: Vec<Modify>) -> Self {
ModifyList {
valid: ModifyValid,
mods,
}
}
pub fn iter(&self) -> slice::Iter<Modify> {
self.mods.iter()
}
}
impl<VALID> ModifyList<VALID> {
pub fn len(&self) -> usize {
self.mods.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}