use std::cmp::Ordering;
use std::collections::BTreeMap;
use std::fmt;
use uuid::Uuid;
use webauthn_rs::proto::{
CreationChallengeResponse, PublicKeyCredential, RegisterPublicKeyCredential,
RequestChallengeResponse,
};
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SchemaError {
NotImplemented,
NoClassFound,
InvalidClass(Vec<String>),
MissingMustAttribute(Vec<String>),
InvalidAttribute(String),
InvalidAttributeSyntax(String),
EmptyFilter,
Corrupted,
PhantomAttribute(String),
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PluginError {
AttrUnique(String),
Base(String),
ReferentialIntegrity(String),
PasswordImport(String),
Oauth2Secrets,
}
#[derive(Serialize, Deserialize, Debug, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum ConsistencyError {
Unknown,
SchemaClassMissingAttribute(String, String),
SchemaClassPhantomAttribute(String, String),
SchemaUuidNotUnique(Uuid),
QueryServerSearchFailure,
EntryUuidCorrupt(u64),
UuidIndexCorrupt(String),
UuidNotUnique(String),
RefintNotUpheld(u64),
MemberOfInvalid(u64),
InvalidAttributeType(String),
DuplicateUniqueAttribute(String),
InvalidSpn(u64),
SqliteIntegrityFailure,
BackendAllIdsSync,
BackendIndexSync,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "lowercase")]
pub enum OperationError {
SessionExpired,
EmptyRequest,
Backend,
NoMatchingEntries,
NoMatchingAttributes,
CorruptedEntry(u64),
CorruptedIndex(String),
ConsistencyError(Vec<Result<(), ConsistencyError>>),
SchemaViolation(SchemaError),
Plugin(PluginError),
FilterGeneration,
FilterUuidResolution,
InvalidAttributeName(String),
InvalidAttribute(String),
InvalidDbState,
InvalidCacheState,
InvalidValueState,
InvalidEntryId,
InvalidRequestState,
InvalidState,
InvalidEntryState,
InvalidUuid,
InvalidReplChangeId,
InvalidAcpState(String),
InvalidSchemaState(String),
InvalidAccountState(String),
BackendEngine,
SqliteError,
FsError,
SerdeJsonError,
SerdeCborError,
AccessDenied,
NotAuthenticated,
InvalidAuthState(String),
InvalidSessionState,
SystemProtectedObject,
SystemProtectedAttribute,
PasswordTooWeak,
PasswordTooShort(usize),
PasswordEmpty,
PasswordBadListed,
CryptographyError,
ResourceLimit,
QueueDisconnected,
Webauthn,
}
impl PartialEq for OperationError {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Group {
pub name: String,
pub uuid: String,
}
impl fmt::Display for Group {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ name: {}, ", self.name)?;
write!(f, "uuid: {} ]", self.uuid)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Claim {
pub name: String,
pub uuid: String,
}
#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum AuthType {
Anonymous,
UnixPassword,
Password,
GeneratedPassword,
Webauthn,
PasswordMfa,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub struct UserAuthToken {
pub session_id: Uuid,
pub auth_type: AuthType,
pub expiry: time::OffsetDateTime,
pub uuid: Uuid,
pub spn: String,
pub lim_uidx: bool,
pub lim_rmax: usize,
pub lim_pmax: usize,
pub lim_fmax: usize,
}
impl fmt::Display for UserAuthToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "spn: {}", self.spn)?;
writeln!(f, "uuid: {}", self.uuid)?;
writeln!(f, "token expiry: {}", self.expiry)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RadiusAuthToken {
pub name: String,
pub displayname: String,
pub uuid: String,
pub secret: String,
pub groups: Vec<Group>,
}
impl fmt::Display for RadiusAuthToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "name: {}", self.name)?;
writeln!(f, "displayname: {}", self.displayname)?;
writeln!(f, "uuid: {}", self.uuid)?;
writeln!(f, "secret: {}", self.secret)?;
self.groups
.iter()
.try_for_each(|g| writeln!(f, "group: {}", g))
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UnixGroupToken {
pub name: String,
pub spn: String,
pub uuid: String,
pub gidnumber: u32,
}
impl fmt::Display for UnixGroupToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[ spn: {}, ", self.spn)?;
write!(f, "gidnumber: {} ", self.gidnumber)?;
write!(f, "name: {}, ", self.name)?;
write!(f, "uuid: {} ]", self.uuid)
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct GroupUnixExtend {
pub gidnumber: Option<u32>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UnixUserToken {
pub name: String,
pub spn: String,
pub displayname: String,
pub gidnumber: u32,
pub uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub shell: Option<String>,
pub groups: Vec<UnixGroupToken>,
pub sshkeys: Vec<String>,
#[serde(default)]
pub valid: bool,
}
impl fmt::Display for UnixUserToken {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "---")?;
writeln!(f, "spn: {}", self.spn)?;
writeln!(f, "name: {}", self.name)?;
writeln!(f, "displayname: {}", self.displayname)?;
writeln!(f, "uuid: {}", self.uuid)?;
match &self.shell {
Some(s) => writeln!(f, "shell: {}", s)?,
None => writeln!(f, "shell: <none>")?,
}
self.sshkeys
.iter()
.try_for_each(|s| writeln!(f, "ssh_publickey: {}", s))?;
self.groups
.iter()
.try_for_each(|g| writeln!(f, "group: {}", g))
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AccountUnixExtend {
pub gidnumber: Option<u32>,
pub shell: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub enum CredentialDetailType {
Password,
GeneratedPassword,
Webauthn(Vec<String>),
PasswordMfa(bool, Vec<String>, usize),
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CredentialDetail {
pub uuid: Uuid,
pub claims: Vec<String>,
pub type_: CredentialDetailType,
}
impl fmt::Display for CredentialDetail {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "uuid: {}", self.uuid)?;
writeln!(f, "claims:")?;
for claim in &self.claims {
writeln!(f, " * {}", claim)?;
}
match &self.type_ {
CredentialDetailType::Password => writeln!(f, "password: set"),
CredentialDetailType::GeneratedPassword => writeln!(f, "generated password: set"),
CredentialDetailType::Webauthn(labels) => {
if labels.is_empty() {
writeln!(f, "webauthn: no authenticators")
} else {
writeln!(f, "webauthn:")?;
for label in labels {
writeln!(f, " * {}", label)?;
}
write!(f, "")
}
}
CredentialDetailType::PasswordMfa(totp, labels, backup_code) => {
writeln!(f, "password: set")?;
if *totp {
writeln!(f, "totp: enabled")?;
} else {
writeln!(f, "totp: disabled")?;
}
if *backup_code > 0 {
writeln!(f, "backup_code: enabled")?;
} else {
writeln!(f, "backup_code: disabled")?;
}
if labels.is_empty() {
writeln!(f, "webauthn: no authenticators")
} else {
writeln!(f, "webauthn:")?;
for label in labels {
writeln!(f, " * {}", label)?;
}
write!(f, "")
}
}
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CredentialStatus {
pub creds: Vec<CredentialDetail>,
}
impl fmt::Display for CredentialStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for cred in &self.creds {
writeln!(f, "---")?;
cred.fmt(f)?;
}
writeln!(f, "---")
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct BackupCodesView {
pub backup_codes: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Default)]
pub struct Entry {
pub attrs: BTreeMap<String, Vec<String>>,
}
impl fmt::Display for Entry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "---")?;
self.attrs
.iter()
.try_for_each(|(k, vs)| vs.iter().try_for_each(|v| writeln!(f, "{}: {}", k, v)))
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
#[serde(rename_all = "lowercase")]
pub enum Filter {
#[serde(alias = "Eq")]
Eq(String, String),
#[serde(alias = "Sub")]
Sub(String, String),
#[serde(alias = "Pres")]
Pres(String),
#[serde(alias = "Or")]
Or(Vec<Filter>),
#[serde(alias = "And")]
And(Vec<Filter>),
#[serde(alias = "AndNot")]
AndNot(Box<Filter>),
#[serde(rename = "self", alias = "Self")]
SelfUuid,
}
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "lowercase")]
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 }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SearchRequest {
pub filter: Filter,
}
impl SearchRequest {
pub fn new(filter: Filter) -> Self {
SearchRequest { filter }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SearchResponse {
pub entries: Vec<Entry>,
}
impl SearchResponse {
pub fn new(entries: Vec<Entry>) -> Self {
SearchResponse { entries }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct CreateRequest {
pub entries: Vec<Entry>,
}
impl CreateRequest {
pub fn new(entries: Vec<Entry>) -> Self {
CreateRequest { entries }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct DeleteRequest {
pub filter: Filter,
}
impl DeleteRequest {
pub fn new(filter: Filter) -> Self {
DeleteRequest { filter }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ModifyRequest {
pub filter: Filter,
pub modlist: ModifyList,
}
impl ModifyRequest {
pub fn new(filter: Filter, modlist: ModifyList) -> Self {
ModifyRequest { filter, modlist }
}
}
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthCredential {
Anonymous,
Password(String),
Totp(u32),
Webauthn(PublicKeyCredential),
BackupCode(String),
}
impl fmt::Debug for AuthCredential {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match self {
AuthCredential::Anonymous => write!(fmt, "Anonymous"),
AuthCredential::Password(_) => write!(fmt, "Password(_)"),
AuthCredential::Totp(_) => write!(fmt, "TOTP(_)"),
AuthCredential::Webauthn(_) => write!(fmt, "Webauthn(_)"),
AuthCredential::BackupCode(_) => write!(fmt, "BackupCode(_)"),
}
}
}
#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialOrd, Ord)]
#[serde(rename_all = "lowercase")]
pub enum AuthMech {
Anonymous,
Password,
PasswordMfa,
Webauthn,
}
impl PartialEq for AuthMech {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
impl fmt::Display for AuthMech {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthMech::Anonymous => write!(f, "Anonymous (no credentials)"),
AuthMech::Password => write!(f, "Passwold Only"),
AuthMech::PasswordMfa => write!(f, "TOTP or Token, and Password"),
AuthMech::Webauthn => write!(f, "Webauthn Token"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthStep {
Init(String),
Begin(AuthMech),
Cred(AuthCredential),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AuthRequest {
pub step: AuthStep,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum AuthAllowed {
Anonymous,
BackupCode,
Password,
Totp,
Webauthn(RequestChallengeResponse),
}
impl PartialEq for AuthAllowed {
fn eq(&self, other: &Self) -> bool {
std::mem::discriminant(self) == std::mem::discriminant(other)
}
}
impl Eq for AuthAllowed {}
impl Ord for AuthAllowed {
fn cmp(&self, other: &Self) -> Ordering {
if self.eq(other) {
Ordering::Equal
} else {
match (self, other) {
(AuthAllowed::Anonymous, _) => Ordering::Less,
(_, AuthAllowed::Anonymous) => Ordering::Greater,
(AuthAllowed::Password, _) => Ordering::Less,
(_, AuthAllowed::Password) => Ordering::Greater,
(AuthAllowed::BackupCode, _) => Ordering::Less,
(_, AuthAllowed::BackupCode) => Ordering::Greater,
(AuthAllowed::Totp, _) => Ordering::Less,
(_, AuthAllowed::Totp) => Ordering::Greater,
(AuthAllowed::Webauthn(_), _) => Ordering::Less,
}
}
}
}
impl PartialOrd for AuthAllowed {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl fmt::Display for AuthAllowed {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AuthAllowed::Anonymous => write!(f, "Anonymous (no credentials)"),
AuthAllowed::Password => write!(f, "Password"),
AuthAllowed::BackupCode => write!(f, "Backup Code"),
AuthAllowed::Totp => write!(f, "TOTP"),
AuthAllowed::Webauthn(_) => write!(f, "Webauthn Token"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum AuthState {
Choose(Vec<AuthMech>),
Continue(Vec<AuthAllowed>),
Denied(String),
Success(String),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct AuthResponse {
pub sessionid: Uuid,
pub state: AuthState,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SetCredentialRequest {
Password(String),
GeneratePassword,
TotpGenerate,
TotpVerify(Uuid, u32),
TotpAcceptSha1(Uuid),
TotpRemove,
WebauthnBegin(String),
WebauthnRegister(Uuid, RegisterPublicKeyCredential),
WebauthnRemove(String),
BackupCodeGenerate,
BackupCodeRemove,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum TotpAlgo {
Sha1,
Sha256,
Sha512,
}
impl fmt::Display for TotpAlgo {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
TotpAlgo::Sha1 => write!(f, "SHA1"),
TotpAlgo::Sha256 => write!(f, "SHA256"),
TotpAlgo::Sha512 => write!(f, "SHA512"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TotpSecret {
pub accountname: String,
pub issuer: String,
pub secret: Vec<u8>,
pub algo: TotpAlgo,
pub step: u64,
}
impl TotpSecret {
pub fn to_uri(&self) -> String {
let accountname = self
.accountname
.replace(":", "")
.replace("%3A", "")
.replace(" ", "%20");
let issuer = self
.issuer
.replace(":", "")
.replace("%3A", "")
.replace(" ", "%20");
let label = format!("{}:{}", issuer, accountname);
let algo = self.algo.to_string();
let secret = self.get_secret();
let period = self.step;
format!(
"otpauth://totp/{}?secret={}&issuer={}&algorithm={}&digits=6&period={}",
label, secret, issuer, algo, period
)
}
pub fn get_secret(&self) -> String {
base32::encode(base32::Alphabet::RFC4648 { padding: false }, &self.secret)
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum SetCredentialResponse {
Success,
Token(String),
TotpCheck(Uuid, TotpSecret),
TotpInvalidSha1(Uuid),
WebauthnCreateChallenge(Uuid, CreationChallengeResponse),
BackupCodes(Vec<String>),
}
#[derive(Debug, Serialize, Deserialize)]
pub struct WhoamiResponse {
pub youare: Entry,
pub uat: UserAuthToken,
}
impl WhoamiResponse {
pub fn new(e: Entry, uat: UserAuthToken) -> Self {
WhoamiResponse { youare: e, uat }
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct SingleStringRequest {
pub value: String,
}
impl SingleStringRequest {
pub fn new(s: String) -> Self {
SingleStringRequest { value: s }
}
}
#[cfg(test)]
mod tests {
use crate::v1::Filter as ProtoFilter;
use crate::v1::{TotpAlgo, TotpSecret};
#[test]
fn test_protofilter_simple() {
let pf: ProtoFilter = ProtoFilter::Pres("class".to_string());
println!("{:?}", serde_json::to_string(&pf).expect("JSON failure"));
}
#[test]
fn totp_to_string() {
let totp = TotpSecret {
accountname: "william".to_string(),
issuer: "blackhats".to_string(),
secret: vec![0xaa, 0xbb, 0xcc, 0xdd],
step: 30,
algo: TotpAlgo::Sha256,
};
let s = totp.to_uri();
assert!(s == "otpauth://totp/blackhats:william?secret=VK54ZXI&issuer=blackhats&algorithm=SHA256&digits=6&period=30");
let totp = TotpSecret {
accountname: "william:%3A".to_string(),
issuer: "blackhats australia".to_string(),
secret: vec![0xaa, 0xbb, 0xcc, 0xdd],
step: 30,
algo: TotpAlgo::Sha256,
};
let s = totp.to_uri();
assert!(s == "otpauth://totp/blackhats%20australia:william?secret=VK54ZXI&issuer=blackhats%20australia&algorithm=SHA256&digits=6&period=30");
}
}