mirror of
https://github.com/kanidm/kanidm.git
synced 2025-05-23 17:33:57 +02:00
Partially Implements #6 - add ability for accounts to self set password. This is good for now, as I get closer to a trial radius deployment, but I think I'm finding the rest api probably needs a better plan at this point, as well as probably the way we do the proto and the communication needs some more thoughts too.
386 lines
9.5 KiB
Rust
386 lines
9.5 KiB
Rust
use std::collections::BTreeMap;
|
|
use std::fmt;
|
|
use uuid::Uuid;
|
|
|
|
// These proto implementations are here because they have public definitions
|
|
|
|
/* ===== errors ===== */
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
pub enum SchemaError {
|
|
NotImplemented,
|
|
InvalidClass,
|
|
MissingMustAttribute(String),
|
|
InvalidAttribute,
|
|
InvalidAttributeSyntax,
|
|
EmptyFilter,
|
|
Corrupted,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
pub enum OperationError {
|
|
EmptyRequest,
|
|
Backend,
|
|
NoMatchingEntries,
|
|
CorruptedEntry(u64),
|
|
ConsistencyError(Vec<Result<(), ConsistencyError>>),
|
|
SchemaViolation(SchemaError),
|
|
Plugin,
|
|
FilterGeneration,
|
|
FilterUUIDResolution,
|
|
InvalidAttributeName(String),
|
|
InvalidAttribute(&'static str),
|
|
InvalidDBState,
|
|
InvalidEntryID,
|
|
InvalidRequestState,
|
|
InvalidState,
|
|
InvalidEntryState,
|
|
InvalidUuid,
|
|
InvalidACPState(&'static str),
|
|
InvalidSchemaState(&'static str),
|
|
InvalidAccountState(&'static str),
|
|
BackendEngine,
|
|
SQLiteError, //(RusqliteError)
|
|
FsError,
|
|
SerdeJsonError,
|
|
SerdeCborError,
|
|
AccessDenied,
|
|
NotAuthenticated,
|
|
InvalidAuthState(&'static str),
|
|
InvalidSessionState,
|
|
SystemProtectedObject,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, PartialEq)]
|
|
pub enum ConsistencyError {
|
|
Unknown,
|
|
// Class, Attribute
|
|
SchemaClassMissingAttribute(String, String),
|
|
QueryServerSearchFailure,
|
|
EntryUuidCorrupt(u64),
|
|
UuidIndexCorrupt(String),
|
|
UuidNotUnique(String),
|
|
RefintNotUpheld(u64),
|
|
MemberOfInvalid(u64),
|
|
InvalidAttributeType(&'static str),
|
|
DuplicateUniqueAttribute(String),
|
|
}
|
|
|
|
/* ===== higher level types ===== */
|
|
// These are all types that are conceptually layers ontop of entry and
|
|
// friends. They allow us to process more complex requests and provide
|
|
// domain specific fields for the purposes of IDM, over the normal
|
|
// entry/ava/filter types. These related deeply to schema.
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Group {
|
|
pub name: String,
|
|
pub uuid: String,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Claim {
|
|
pub name: String,
|
|
pub uuid: String,
|
|
// These can be ephemeral, or shortlived in a session.
|
|
// some may even need requesting.
|
|
// pub expiry: DateTime
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Application {
|
|
pub name: String,
|
|
pub uuid: String,
|
|
}
|
|
|
|
// The currently authenticated user, and any required metadata for them
|
|
// to properly authorise them. This is similar in nature to oauth and the krb
|
|
// PAC/PAD structures. Currently we only use this internally, but we should
|
|
// consider making it "parseable" by the client so they can have per-session
|
|
// group/authorisation data.
|
|
//
|
|
// This structure and how it works will *very much* change over time from this
|
|
// point onward!
|
|
//
|
|
// It's likely that this must have a relationship to the server's user structure
|
|
// and to the Entry so that filters or access controls can be applied.
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct UserAuthToken {
|
|
// When this data should be considered invalid. Interpretation
|
|
// may depend on the client application.
|
|
// pub expiry: DateTime,
|
|
pub name: String,
|
|
pub displayname: String,
|
|
pub uuid: String,
|
|
pub application: Option<Application>,
|
|
pub groups: Vec<Group>,
|
|
pub claims: Vec<Claim>,
|
|
// Should we allow supplemental ava's to be added on request?
|
|
}
|
|
|
|
impl fmt::Display for UserAuthToken {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
writeln!(f, "name: {}", self.name)?;
|
|
writeln!(f, "display: {}", self.displayname)?;
|
|
writeln!(f, "uuid: {}", self.uuid)?;
|
|
writeln!(f, "groups: {:?}", self.groups)?;
|
|
writeln!(f, "claims: {:?}", self.claims)
|
|
}
|
|
}
|
|
|
|
// UAT will need a downcast to Entry, which adds in the claims to the entry
|
|
// for the purpose of filtering.
|
|
|
|
/* ===== low level proto types ===== */
|
|
|
|
// ProtoEntry vs Entry
|
|
// There is a good future reason for this seperation. It allows changing
|
|
// the in memory server core entry type, without affecting the protoEntry type
|
|
//
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
pub struct Entry {
|
|
pub attrs: BTreeMap<String, Vec<String>>,
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)]
|
|
pub enum Filter {
|
|
// This is attr - value
|
|
Eq(String, String),
|
|
Sub(String, String),
|
|
Pres(String),
|
|
Or(Vec<Filter>),
|
|
And(Vec<Filter>),
|
|
AndNot(Box<Filter>),
|
|
#[serde(rename = "Self")]
|
|
SelfUUID,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub enum Modify {
|
|
Present(String, String),
|
|
Removed(String, String),
|
|
Purged(String),
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
pub struct ModifyList {
|
|
pub mods: Vec<Modify>,
|
|
}
|
|
|
|
impl ModifyList {
|
|
pub fn new_list(mods: Vec<Modify>) -> Self {
|
|
ModifyList { mods: mods }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct OperationResponse {}
|
|
|
|
impl OperationResponse {
|
|
pub fn new(_: ()) -> Self {
|
|
OperationResponse {}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SearchRequest {
|
|
pub filter: Filter,
|
|
}
|
|
|
|
impl SearchRequest {
|
|
pub fn new(filter: Filter) -> Self {
|
|
SearchRequest { filter: filter }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SearchResponse {
|
|
pub entries: Vec<Entry>,
|
|
}
|
|
|
|
impl SearchResponse {
|
|
pub fn new(entries: Vec<Entry>) -> Self {
|
|
SearchResponse { entries: entries }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct CreateRequest {
|
|
pub entries: Vec<Entry>,
|
|
}
|
|
|
|
impl CreateRequest {
|
|
pub fn new(entries: Vec<Entry>) -> Self {
|
|
CreateRequest { entries: entries }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct DeleteRequest {
|
|
pub filter: Filter,
|
|
}
|
|
|
|
impl DeleteRequest {
|
|
pub fn new(filter: Filter) -> Self {
|
|
DeleteRequest { filter: filter }
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct ModifyRequest {
|
|
// Probably needs a modlist?
|
|
pub filter: Filter,
|
|
pub modlist: ModifyList,
|
|
}
|
|
|
|
impl ModifyRequest {
|
|
pub fn new(filter: Filter, modlist: ModifyList) -> Self {
|
|
ModifyRequest {
|
|
filter: filter,
|
|
modlist: modlist,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Login is a multi-step process potentially. First the client says who they
|
|
// want to request
|
|
//
|
|
// we respond with a set of possible authentications that can proceed, and perhaps
|
|
// we indicate which options must/may?
|
|
//
|
|
// The client can then step and negotiate each.
|
|
//
|
|
// This continues until a LoginSuccess, or LoginFailure is returned.
|
|
//
|
|
// On loginSuccess, we send a cookie, and that allows the token to be
|
|
// generated. The cookie can be shared between servers.
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum AuthCredential {
|
|
Anonymous,
|
|
Password(String),
|
|
// TOTP(String),
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum AuthStep {
|
|
// name, application id?
|
|
Init(String, Option<String>),
|
|
/*
|
|
Step(
|
|
Type(params ....)
|
|
),
|
|
*/
|
|
Creds(Vec<AuthCredential>),
|
|
// Should we have a "finalise" type to attempt to finish based on
|
|
// what we have given?
|
|
}
|
|
|
|
// Request auth for identity X with roles Y?
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct AuthRequest {
|
|
pub step: AuthStep,
|
|
}
|
|
|
|
// Respond with the list of auth types and nonce, etc.
|
|
// It can also contain a denied, or success.
|
|
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
|
pub enum AuthAllowed {
|
|
Anonymous,
|
|
Password,
|
|
// Webauthn(String),
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub enum AuthState {
|
|
// Everything is good, your cookie has been issued, and a token is set here
|
|
// for the client to view.
|
|
Success(UserAuthToken),
|
|
// Something was bad, your session is terminated and no cookie.
|
|
Denied(String),
|
|
// Continue to auth, allowed mechanisms listed.
|
|
Continue(Vec<AuthAllowed>),
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct AuthResponse {
|
|
pub sessionid: Uuid,
|
|
pub state: AuthState,
|
|
}
|
|
|
|
/* Recycle Requests area */
|
|
|
|
// Only two actions on recycled is possible. Search and Revive.
|
|
|
|
pub struct SearchRecycledRequest {
|
|
pub filter: Filter,
|
|
}
|
|
|
|
impl SearchRecycledRequest {
|
|
pub fn new(filter: Filter) -> Self {
|
|
SearchRecycledRequest { filter: filter }
|
|
}
|
|
}
|
|
|
|
// Need a search response here later.
|
|
|
|
pub struct ReviveRecycledRequest {
|
|
pub filter: Filter,
|
|
}
|
|
|
|
impl ReviveRecycledRequest {
|
|
pub fn new(filter: Filter) -> Self {
|
|
ReviveRecycledRequest { filter: filter }
|
|
}
|
|
}
|
|
|
|
// This doesn't need seralise because it's only accessed via a "get".
|
|
#[derive(Debug)]
|
|
pub struct WhoamiRequest {}
|
|
|
|
impl WhoamiRequest {
|
|
pub fn new() -> Self {
|
|
WhoamiRequest {}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct WhoamiResponse {
|
|
// Should we just embed the entry? Or destructure it?
|
|
pub youare: Entry,
|
|
pub uat: UserAuthToken,
|
|
}
|
|
|
|
impl WhoamiResponse {
|
|
pub fn new(e: Entry, uat: UserAuthToken) -> Self {
|
|
WhoamiResponse {
|
|
youare: e,
|
|
uat: uat,
|
|
}
|
|
}
|
|
}
|
|
|
|
// Simple string value provision.
|
|
#[derive(Debug, Serialize, Deserialize)]
|
|
pub struct SingleStringRequest {
|
|
pub value: String,
|
|
}
|
|
|
|
impl SingleStringRequest {
|
|
pub fn new(s: String) -> Self {
|
|
SingleStringRequest { value: s }
|
|
}
|
|
}
|
|
// Use OperationResponse here ...
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::v1::Filter as ProtoFilter;
|
|
#[test]
|
|
fn test_protofilter_simple() {
|
|
let pf: ProtoFilter = ProtoFilter::Pres("class".to_string());
|
|
|
|
println!("{:?}", serde_json::to_string(&pf).expect("JSON failure"));
|
|
}
|
|
}
|