mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-24 04:57:00 +01:00
* implementation of passkeys as an auth mech * listing the current passkeys when asking to remove one * tweaking insecure dev server config so passkeys will work * Fix domain rename Co-authored-by: James Hodgkinson <james@terminaloutcomes.com>
1002 lines
28 KiB
Rust
1002 lines
28 KiB
Rust
//! 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;
|
|
use crate::idm::AuthState;
|
|
use crate::modify::{ModifyInvalid, ModifyList, ModifyValid};
|
|
use crate::prelude::*;
|
|
use crate::schema::SchemaTransaction;
|
|
use crate::value::PartialValue;
|
|
use kanidm_proto::v1::Entry as ProtoEntry;
|
|
use kanidm_proto::v1::ModifyList as ProtoModifyList;
|
|
use kanidm_proto::v1::OperationError;
|
|
use kanidm_proto::v1::{
|
|
AuthCredential, AuthMech, AuthRequest, AuthStep, CreateRequest, DeleteRequest, ModifyRequest,
|
|
SearchRequest, SearchResponse, UserAuthToken, WhoamiResponse,
|
|
};
|
|
|
|
use ldap3_proto::simple::LdapFilter;
|
|
use std::collections::BTreeSet;
|
|
use std::time::Duration;
|
|
use uuid::Uuid;
|
|
|
|
#[cfg(test)]
|
|
use std::sync::Arc;
|
|
|
|
#[cfg(test)]
|
|
use webauthn_rs::prelude::PublicKeyCredential;
|
|
|
|
#[derive(Debug)]
|
|
pub struct SearchResult {
|
|
entries: Vec<ProtoEntry>,
|
|
}
|
|
|
|
impl SearchResult {
|
|
pub fn new(
|
|
qs: &QueryServerReadTransaction,
|
|
entries: &[Entry<EntryReduced, EntryCommitted>],
|
|
) -> Result<Self, OperationError> {
|
|
let entries: Result<_, _> = entries
|
|
.iter()
|
|
.map(|e| {
|
|
// All the needed transforms for this result are done
|
|
// in search_ext. This is just an entry -> protoentry
|
|
// step.
|
|
e.to_pe(qs)
|
|
})
|
|
.collect();
|
|
Ok(SearchResult { entries: entries? })
|
|
}
|
|
|
|
// Consume self into a search response
|
|
pub fn response(self) -> SearchResponse {
|
|
SearchResponse {
|
|
entries: self.entries,
|
|
}
|
|
}
|
|
|
|
// Consume into the array of entries, used in the json proto
|
|
pub fn into_proto_array(self) -> Vec<ProtoEntry> {
|
|
self.entries
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SearchEvent {
|
|
pub ident: Identity,
|
|
// This is the filter as we apply and process it.
|
|
pub filter: Filter<FilterValid>,
|
|
// This is the original filter, for the purpose of ACI checking.
|
|
pub filter_orig: Filter<FilterValid>,
|
|
pub attrs: Option<BTreeSet<AttrString>>,
|
|
}
|
|
|
|
impl SearchEvent {
|
|
pub fn from_message(
|
|
ident: Identity,
|
|
req: &SearchRequest,
|
|
qs: &QueryServerReadTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let f = Filter::from_ro(&ident, &req.filter, qs)?;
|
|
// We do need to do this twice to account for the ignore_hidden
|
|
// changes.
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
Ok(SearchEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
// We can't get this from the SearchMessage because it's annoying with the
|
|
// current macro design.
|
|
attrs: None,
|
|
})
|
|
}
|
|
|
|
pub fn from_internal_message(
|
|
ident: Identity,
|
|
filter: &Filter<FilterInvalid>,
|
|
attrs: Option<&[String]>,
|
|
qs: &QueryServerReadTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let r_attrs: Option<BTreeSet<AttrString>> = attrs.map(|vs| {
|
|
vs.iter()
|
|
.filter_map(|a| qs.get_schema().normalise_attr_if_exists(a.as_str()))
|
|
.collect()
|
|
});
|
|
|
|
if let Some(s) = &r_attrs {
|
|
if s.is_empty() {
|
|
request_error!("EmptyRequest for attributes");
|
|
return Err(OperationError::EmptyRequest);
|
|
}
|
|
}
|
|
|
|
let filter_orig = filter.validate(qs.get_schema()).map_err(|e| {
|
|
request_error!(?e, "filter schema violation");
|
|
OperationError::SchemaViolation(e)
|
|
})?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
|
|
Ok(SearchEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
attrs: r_attrs,
|
|
})
|
|
}
|
|
|
|
pub fn from_internal_recycle_message(
|
|
ident: Identity,
|
|
filter: &Filter<FilterInvalid>,
|
|
attrs: Option<&[String]>,
|
|
qs: &QueryServerReadTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let r_attrs: Option<BTreeSet<AttrString>> = attrs.map(|vs| {
|
|
vs.iter()
|
|
.filter_map(|a| qs.get_schema().normalise_attr_if_exists(a.as_str()))
|
|
.collect()
|
|
});
|
|
|
|
if let Some(s) = &r_attrs {
|
|
if s.is_empty() {
|
|
return Err(OperationError::EmptyRequest);
|
|
}
|
|
}
|
|
|
|
let filter_orig = filter
|
|
.validate(qs.get_schema())
|
|
.map(|f| f.into_recycled())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone();
|
|
|
|
Ok(SearchEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
attrs: r_attrs,
|
|
})
|
|
}
|
|
|
|
pub fn from_whoami_request(
|
|
ident: Identity,
|
|
qs: &QueryServerReadTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let filter_orig = filter_all!(f_self())
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
|
|
Ok(SearchEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
attrs: None,
|
|
})
|
|
}
|
|
|
|
pub fn from_target_uuid_request(
|
|
ident: Identity,
|
|
target_uuid: Uuid,
|
|
qs: &QueryServerReadTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let filter_orig = filter_all!(f_eq("uuid", PartialValue::new_uuid(target_uuid)))
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
Ok(SearchEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
attrs: None,
|
|
})
|
|
}
|
|
|
|
// Just impersonate the account with no filter changes.
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry_ser(e: &str, filter: Filter<FilterInvalid>) -> Self {
|
|
SearchEvent {
|
|
ident: Identity::from_impersonate_entry_ser(e),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry(
|
|
e: Arc<Entry<EntrySealed, EntryCommitted>>,
|
|
filter: Filter<FilterInvalid>,
|
|
) -> Self {
|
|
SearchEvent {
|
|
ident: Identity::from_impersonate_entry(e),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
pub fn new_impersonate(
|
|
ident: &Identity,
|
|
filter: Filter<FilterValid>,
|
|
filter_orig: Filter<FilterValid>,
|
|
) -> Self {
|
|
SearchEvent {
|
|
ident: Identity::from_impersonate(ident),
|
|
filter,
|
|
filter_orig,
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
/* Impersonate a request for recycled objects */
|
|
pub unsafe fn new_rec_impersonate_entry(
|
|
e: Arc<Entry<EntrySealed, EntryCommitted>>,
|
|
filter: Filter<FilterInvalid>,
|
|
) -> Self {
|
|
let filter_orig = filter.into_valid();
|
|
let filter = filter_orig.clone().into_recycled();
|
|
SearchEvent {
|
|
ident: Identity::from_impersonate_entry(e),
|
|
filter,
|
|
filter_orig,
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
/* Impersonate an external request AKA filter ts + recycle */
|
|
pub unsafe fn new_ext_impersonate_entry(
|
|
e: Arc<Entry<EntrySealed, EntryCommitted>>,
|
|
filter: Filter<FilterInvalid>,
|
|
) -> Self {
|
|
SearchEvent {
|
|
ident: Identity::from_impersonate_entry(e),
|
|
filter: filter.clone().into_valid().into_ignore_hidden(),
|
|
filter_orig: filter.into_valid(),
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn new_ext_impersonate_uuid(
|
|
qs: &QueryServerReadTransaction,
|
|
ident: Identity,
|
|
lf: &LdapFilter,
|
|
attrs: Option<BTreeSet<AttrString>>,
|
|
) -> Result<Self, OperationError> {
|
|
// Kanidm Filter from LdapFilter
|
|
let f = Filter::from_ldap_ro(&ident, lf, qs)?;
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
Ok(SearchEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
attrs,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_internal_invalid(filter: Filter<FilterInvalid>) -> Self {
|
|
SearchEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
pub fn new_internal(filter: Filter<FilterValid>) -> Self {
|
|
SearchEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone(),
|
|
filter_orig: filter,
|
|
attrs: None,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get_limits(&self) -> &Limits {
|
|
&self.ident.limits
|
|
}
|
|
}
|
|
|
|
// Represents the decoded entries from the protocol -> internal entry representation
|
|
// including information about the identity performing the request, and if the
|
|
// request is internal or not.
|
|
#[derive(Debug)]
|
|
pub struct CreateEvent {
|
|
pub ident: Identity,
|
|
// This may still actually change to handle the *raw* nature of the
|
|
// input that we plan to parse.
|
|
pub entries: Vec<Entry<EntryInit, EntryNew>>,
|
|
// Is the CreateEvent from an internal or external source?
|
|
// This may affect which plugins are run ...
|
|
}
|
|
|
|
impl CreateEvent {
|
|
pub fn from_message(
|
|
ident: Identity,
|
|
req: &CreateRequest,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let rentries: Result<Vec<_>, _> = req
|
|
.entries
|
|
.iter()
|
|
.map(|e| Entry::from_proto_entry(e, qs))
|
|
.collect();
|
|
// From ProtoEntry -> Entry
|
|
// What is the correct consuming iterator here? Can we
|
|
// even do that?
|
|
match rentries {
|
|
Ok(entries) => Ok(CreateEvent { ident, entries }),
|
|
Err(e) => Err(e),
|
|
}
|
|
}
|
|
|
|
// Is this an internal only function?
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry_ser(
|
|
e: &str,
|
|
entries: Vec<Entry<EntryInit, EntryNew>>,
|
|
) -> Self {
|
|
CreateEvent {
|
|
ident: Identity::from_impersonate_entry_ser(e),
|
|
entries,
|
|
}
|
|
}
|
|
|
|
pub fn new_internal(entries: Vec<Entry<EntryInit, EntryNew>>) -> Self {
|
|
CreateEvent {
|
|
ident: Identity::from_internal(),
|
|
entries,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ExistsEvent {
|
|
pub ident: Identity,
|
|
// This is the filter, as it will be processed.
|
|
pub filter: Filter<FilterValid>,
|
|
// This is the original filter, for the purpose of ACI checking.
|
|
pub filter_orig: Filter<FilterValid>,
|
|
}
|
|
|
|
impl ExistsEvent {
|
|
pub fn new_internal(filter: Filter<FilterValid>) -> Self {
|
|
ExistsEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone(),
|
|
filter_orig: filter,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[allow(dead_code)]
|
|
pub unsafe fn new_internal_invalid(filter: Filter<FilterInvalid>) -> Self {
|
|
ExistsEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get_limits(&self) -> &Limits {
|
|
&self.ident.limits
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct DeleteEvent {
|
|
pub ident: Identity,
|
|
// This is the filter, as it will be processed.
|
|
pub filter: Filter<FilterValid>,
|
|
// This is the original filter, for the purpose of ACI checking.
|
|
pub filter_orig: Filter<FilterValid>,
|
|
}
|
|
|
|
impl DeleteEvent {
|
|
pub fn from_message(
|
|
ident: Identity,
|
|
req: &DeleteRequest,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let f = Filter::from_rw(&ident, &req.filter, qs)?;
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
Ok(DeleteEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
})
|
|
}
|
|
|
|
pub fn from_parts(
|
|
ident: Identity,
|
|
f: &Filter<FilterInvalid>,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
Ok(DeleteEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry(
|
|
e: Arc<Entry<EntrySealed, EntryCommitted>>,
|
|
filter: Filter<FilterInvalid>,
|
|
) -> Self {
|
|
DeleteEvent {
|
|
ident: Identity::from_impersonate_entry(e),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry_ser(e: &str, filter: Filter<FilterInvalid>) -> Self {
|
|
DeleteEvent {
|
|
ident: Identity::from_impersonate_entry_ser(e),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_internal_invalid(filter: Filter<FilterInvalid>) -> Self {
|
|
DeleteEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
}
|
|
}
|
|
|
|
pub fn new_internal(filter: Filter<FilterValid>) -> Self {
|
|
DeleteEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone(),
|
|
filter_orig: filter,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ModifyEvent {
|
|
pub ident: Identity,
|
|
// This is the filter, as it will be processed.
|
|
pub filter: Filter<FilterValid>,
|
|
// This is the original filter, for the purpose of ACI checking.
|
|
pub filter_orig: Filter<FilterValid>,
|
|
pub modlist: ModifyList<ModifyValid>,
|
|
}
|
|
|
|
impl ModifyEvent {
|
|
pub fn from_message(
|
|
ident: Identity,
|
|
req: &ModifyRequest,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let f = Filter::from_rw(&ident, &req.filter, qs)?;
|
|
let m = ModifyList::from(&req.modlist, qs)?;
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
let modlist = m
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
Ok(ModifyEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
modlist,
|
|
})
|
|
}
|
|
|
|
pub fn from_parts(
|
|
ident: Identity,
|
|
target_uuid: Uuid,
|
|
proto_ml: &ProtoModifyList,
|
|
filter: Filter<FilterInvalid>,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let f_uuid = filter_all!(f_eq("uuid", PartialValue::new_uuid(target_uuid)));
|
|
// Add any supplemental conditions we have.
|
|
let f = Filter::join_parts_and(f_uuid, filter);
|
|
|
|
let m = ModifyList::from(proto_ml, qs)?;
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
let modlist = m
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
|
|
Ok(ModifyEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
modlist,
|
|
})
|
|
}
|
|
|
|
pub fn from_internal_parts(
|
|
ident: Identity,
|
|
ml: &ModifyList<ModifyInvalid>,
|
|
filter: &Filter<FilterInvalid>,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let filter_orig = filter
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
let modlist = ml
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
|
|
Ok(ModifyEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
modlist,
|
|
})
|
|
}
|
|
|
|
pub fn from_target_uuid_attr_purge(
|
|
ident: Identity,
|
|
target_uuid: Uuid,
|
|
attr: &str,
|
|
filter: Filter<FilterInvalid>,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let ml = ModifyList::new_purge(attr);
|
|
let f_uuid = filter_all!(f_eq("uuid", PartialValue::new_uuid(target_uuid)));
|
|
// Add any supplemental conditions we have.
|
|
let f = Filter::join_parts_and(f_uuid, filter);
|
|
|
|
let filter_orig = f
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
let filter = filter_orig.clone().into_ignore_hidden();
|
|
let modlist = ml
|
|
.validate(qs.get_schema())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
Ok(ModifyEvent {
|
|
ident,
|
|
filter,
|
|
filter_orig,
|
|
modlist,
|
|
})
|
|
}
|
|
|
|
pub fn new_internal(filter: Filter<FilterValid>, modlist: ModifyList<ModifyValid>) -> Self {
|
|
ModifyEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone(),
|
|
filter_orig: filter,
|
|
modlist,
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_internal_invalid(
|
|
filter: Filter<FilterInvalid>,
|
|
modlist: ModifyList<ModifyInvalid>,
|
|
) -> Self {
|
|
ModifyEvent {
|
|
ident: Identity::from_internal(),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
modlist: modlist.into_valid(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry_ser(
|
|
e: &str,
|
|
filter: Filter<FilterInvalid>,
|
|
modlist: ModifyList<ModifyInvalid>,
|
|
) -> Self {
|
|
ModifyEvent {
|
|
ident: Identity::from_impersonate_entry_ser(e),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
modlist: modlist.into_valid(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry(
|
|
e: Arc<Entry<EntrySealed, EntryCommitted>>,
|
|
filter: Filter<FilterInvalid>,
|
|
modlist: ModifyList<ModifyInvalid>,
|
|
) -> Self {
|
|
ModifyEvent {
|
|
ident: Identity::from_impersonate_entry(e),
|
|
filter: filter.clone().into_valid(),
|
|
filter_orig: filter.into_valid(),
|
|
modlist: modlist.into_valid(),
|
|
}
|
|
}
|
|
|
|
pub fn new_impersonate(
|
|
ident: &Identity,
|
|
filter: Filter<FilterValid>,
|
|
filter_orig: Filter<FilterValid>,
|
|
modlist: ModifyList<ModifyValid>,
|
|
) -> Self {
|
|
ModifyEvent {
|
|
ident: Identity::from_impersonate(ident),
|
|
filter,
|
|
filter_orig,
|
|
modlist,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AuthEventStepInit {
|
|
pub name: String,
|
|
pub appid: Option<String>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AuthEventStepCred {
|
|
pub sessionid: Uuid,
|
|
pub cred: AuthCredential,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AuthEventStepMech {
|
|
pub sessionid: Uuid,
|
|
pub mech: AuthMech,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub enum AuthEventStep {
|
|
Init(AuthEventStepInit),
|
|
Begin(AuthEventStepMech),
|
|
Cred(AuthEventStepCred),
|
|
}
|
|
|
|
impl AuthEventStep {
|
|
fn from_authstep(aus: AuthStep, sid: Option<Uuid>) -> Result<Self, OperationError> {
|
|
match aus {
|
|
AuthStep::Init(name) => {
|
|
Ok(AuthEventStep::Init(AuthEventStepInit { name, appid: None }))
|
|
}
|
|
AuthStep::Begin(mech) => match sid {
|
|
Some(ssid) => Ok(AuthEventStep::Begin(AuthEventStepMech {
|
|
sessionid: ssid,
|
|
mech,
|
|
})),
|
|
None => Err(OperationError::InvalidAuthState(
|
|
"session id not present in cred".to_string(),
|
|
)),
|
|
},
|
|
AuthStep::Cred(cred) => match sid {
|
|
Some(ssid) => Ok(AuthEventStep::Cred(AuthEventStepCred {
|
|
sessionid: ssid,
|
|
cred,
|
|
})),
|
|
None => Err(OperationError::InvalidAuthState(
|
|
"session id not present in cred".to_string(),
|
|
)),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn anonymous_init() -> Self {
|
|
AuthEventStep::Init(AuthEventStepInit {
|
|
name: "anonymous".to_string(),
|
|
appid: None,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn named_init(name: &str) -> Self {
|
|
AuthEventStep::Init(AuthEventStepInit {
|
|
name: name.to_string(),
|
|
appid: None,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn begin_mech(sessionid: Uuid, mech: AuthMech) -> Self {
|
|
AuthEventStep::Begin(AuthEventStepMech { sessionid, mech })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
|
AuthEventStep::Cred(AuthEventStepCred {
|
|
sessionid: sid,
|
|
cred: AuthCredential::Anonymous,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
|
|
AuthEventStep::Cred(AuthEventStepCred {
|
|
sessionid: sid,
|
|
cred: AuthCredential::Password(pw.to_string()),
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_totp(sid: Uuid, totp: u32) -> Self {
|
|
AuthEventStep::Cred(AuthEventStepCred {
|
|
sessionid: sid,
|
|
cred: AuthCredential::Totp(totp),
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_backup_code(sid: Uuid, code: &str) -> Self {
|
|
AuthEventStep::Cred(AuthEventStepCred {
|
|
sessionid: sid,
|
|
cred: AuthCredential::BackupCode(code.to_string()),
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_passkey(sid: Uuid, passkey_response: PublicKeyCredential) -> Self {
|
|
AuthEventStep::Cred(AuthEventStepCred {
|
|
sessionid: sid,
|
|
cred: AuthCredential::Passkey(passkey_response),
|
|
})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct AuthEvent {
|
|
pub ident: Option<Identity>,
|
|
pub step: AuthEventStep,
|
|
// pub sessionid: Option<Uuid>,
|
|
}
|
|
|
|
impl AuthEvent {
|
|
pub fn from_message(sessionid: Option<Uuid>, req: AuthRequest) -> Result<Self, OperationError> {
|
|
Ok(AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::from_authstep(req.step, sessionid)?,
|
|
})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn anonymous_init() -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::anonymous_init(),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn named_init(name: &str) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::named_init(name),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn begin_mech(sessionid: Uuid, mech: AuthMech) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::begin_mech(sessionid, mech),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_anonymous(sid: Uuid) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::cred_step_anonymous(sid),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_password(sid: Uuid, pw: &str) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::cred_step_password(sid, pw),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_totp(sid: Uuid, totp: u32) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::cred_step_totp(sid, totp),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_backup_code(sid: Uuid, code: &str) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::cred_step_backup_code(sid, code),
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub fn cred_step_passkey(sid: Uuid, passkey_response: PublicKeyCredential) -> Self {
|
|
AuthEvent {
|
|
ident: None,
|
|
step: AuthEventStep::cred_step_passkey(sid, passkey_response),
|
|
}
|
|
}
|
|
}
|
|
|
|
// Probably should be a struct with the session id present.
|
|
#[derive(Debug)]
|
|
pub struct AuthResult {
|
|
pub sessionid: Uuid,
|
|
pub state: AuthState,
|
|
pub delay: Option<Duration>,
|
|
}
|
|
|
|
/*
|
|
impl AuthResult {
|
|
pub fn response(self) -> AuthResponse {
|
|
AuthResponse {
|
|
sessionid: self.sessionid,
|
|
state: self.state,
|
|
}
|
|
}
|
|
}
|
|
*/
|
|
|
|
pub struct WhoamiResult {
|
|
youare: ProtoEntry,
|
|
uat: UserAuthToken,
|
|
}
|
|
|
|
impl WhoamiResult {
|
|
pub fn new(
|
|
qs: &QueryServerReadTransaction,
|
|
e: &Entry<EntryReduced, EntryCommitted>,
|
|
uat: UserAuthToken,
|
|
) -> Result<Self, OperationError> {
|
|
Ok(WhoamiResult {
|
|
youare: e.to_pe(qs)?,
|
|
uat,
|
|
})
|
|
}
|
|
|
|
pub fn response(self) -> WhoamiResponse {
|
|
WhoamiResponse {
|
|
youare: self.youare,
|
|
uat: self.uat,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PurgeTombstoneEvent {
|
|
pub ident: Identity,
|
|
pub eventid: Uuid,
|
|
}
|
|
|
|
impl Default for PurgeTombstoneEvent {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl PurgeTombstoneEvent {
|
|
pub fn new() -> Self {
|
|
PurgeTombstoneEvent {
|
|
ident: Identity::from_internal(),
|
|
eventid: Uuid::new_v4(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PurgeRecycledEvent {
|
|
pub ident: Identity,
|
|
pub eventid: Uuid,
|
|
}
|
|
|
|
impl Default for PurgeRecycledEvent {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl PurgeRecycledEvent {
|
|
pub fn new() -> Self {
|
|
PurgeRecycledEvent {
|
|
ident: Identity::from_internal(),
|
|
eventid: Uuid::new_v4(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct OnlineBackupEvent {
|
|
pub ident: Identity,
|
|
pub eventid: Uuid,
|
|
}
|
|
|
|
impl Default for OnlineBackupEvent {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl OnlineBackupEvent {
|
|
pub fn new() -> Self {
|
|
OnlineBackupEvent {
|
|
ident: Identity::from_internal(),
|
|
eventid: Uuid::new_v4(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ReviveRecycledEvent {
|
|
pub ident: Identity,
|
|
// This is the filter, as it will be processed.
|
|
pub filter: Filter<FilterValid>,
|
|
// Unlike the others, because of how this works, we don't need the orig filter
|
|
// to be retained, because the filter is the orig filter for this check.
|
|
//
|
|
// It will be duplicated into the modify ident as it exists.
|
|
}
|
|
|
|
impl ReviveRecycledEvent {
|
|
pub fn from_parts(
|
|
ident: Identity,
|
|
filter: &Filter<FilterInvalid>,
|
|
qs: &QueryServerWriteTransaction,
|
|
) -> Result<Self, OperationError> {
|
|
let filter = filter
|
|
.validate(qs.get_schema())
|
|
.map(|f| f.into_recycled())
|
|
.map_err(OperationError::SchemaViolation)?;
|
|
Ok(ReviveRecycledEvent { ident, filter })
|
|
}
|
|
|
|
#[cfg(test)]
|
|
pub unsafe fn new_impersonate_entry(
|
|
e: Arc<Entry<EntrySealed, EntryCommitted>>,
|
|
filter: Filter<FilterInvalid>,
|
|
) -> Self {
|
|
ReviveRecycledEvent {
|
|
ident: Identity::from_impersonate_entry(e),
|
|
filter: filter.into_valid(),
|
|
}
|
|
}
|
|
}
|