1use crate::prelude::{OperationError, Url};
2use crate::server::identity::Source;
3use compact_jwt::JwsCompact;
4use crypto_glue::s256::Sha256Output;
5use kanidm_lib_crypto::x509_cert::Certificate;
6use kanidm_proto::{
7    internal::UserAuthToken,
8    oauth2::{AccessTokenRequest, AccessTokenResponse, AuthorisationRequest},
9    v1::{
10        AuthAllowed, AuthCredential as ProtoAuthCredential, AuthIssueSession, AuthMech,
11        AuthStep as ProtoAuthStep,
12    },
13};
14use std::fmt;
15use webauthn_rs::prelude::PublicKeyCredential;
16
17#[derive(Debug)]
18pub enum AuthStep {
19    Init(String),
20    Init2 {
21        username: String,
22        issue: AuthIssueSession,
23        privileged: bool,
24    },
25    Begin(AuthMech),
26    Cred(AuthCredential),
27}
28
29impl From<ProtoAuthStep> for AuthStep {
30    fn from(proto: ProtoAuthStep) -> Self {
31        match proto {
32            ProtoAuthStep::Init(name) => Self::Init(name),
33            ProtoAuthStep::Init2 {
34                username,
35                issue,
36                privileged,
37            } => Self::Init2 {
38                username,
39                issue,
40                privileged,
41            },
42            ProtoAuthStep::Begin(mech) => Self::Begin(mech),
43            ProtoAuthStep::Cred(proto_cred) => Self::Cred(AuthCredential::from(proto_cred)),
44        }
45    }
46}
47
48pub enum AuthExternal {
49    OAuth2AuthorisationRequest {
50        authorisation_url: Url,
51        request: AuthorisationRequest,
52    },
53    OAuth2AccessTokenRequest {
54        token_url: Url,
55        client_id: String,
56        client_secret: String,
57        request: AccessTokenRequest,
58    },
59}
60
61impl fmt::Debug for AuthExternal {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        match self {
64            Self::OAuth2AuthorisationRequest { .. } => write!(f, "OAuth2AuthorisationRequest"),
65            Self::OAuth2AccessTokenRequest { .. } => write!(f, "OAuth2AccessTokenRequest"),
66        }
67    }
68}
69
70#[allow(clippy::large_enum_variant)]
73pub enum AuthState {
74    Choose(Vec<AuthMech>),
75    Continue(Vec<AuthAllowed>),
76
77    External(AuthExternal),
81
82    Denied(String),
83    Success(Box<JwsCompact>, AuthIssueSession),
84}
85
86impl fmt::Debug for AuthState {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        match self {
89            AuthState::Choose(mechs) => write!(f, "AuthState::Choose({mechs:?})"),
90            AuthState::Continue(allow) => write!(f, "AuthState::Continue({allow:?})"),
91            AuthState::External(allow) => write!(f, "AuthState::External({allow:?})"),
92            AuthState::Denied(reason) => write!(f, "AuthState::Denied({reason:?})"),
93            AuthState::Success(_token, issue) => write!(f, "AuthState::Success({issue:?})"),
94        }
95    }
96}
97
98pub enum AuthCredential {
99    Anonymous,
100    Password(String),
101    Totp(u32),
102    SecurityKey(Box<PublicKeyCredential>),
103    BackupCode(String),
104    Passkey(Box<PublicKeyCredential>),
105
106    OAuth2AuthorisationResponse { code: String, state: Option<String> },
108    OAuth2AccessTokenResponse { response: AccessTokenResponse },
109}
110
111impl From<ProtoAuthCredential> for AuthCredential {
112    fn from(proto: ProtoAuthCredential) -> Self {
113        match proto {
114            ProtoAuthCredential::Anonymous => AuthCredential::Anonymous,
115            ProtoAuthCredential::Password(p) => AuthCredential::Password(p),
116            ProtoAuthCredential::Totp(t) => AuthCredential::Totp(t),
117            ProtoAuthCredential::SecurityKey(sk) => AuthCredential::SecurityKey(sk),
118            ProtoAuthCredential::BackupCode(bc) => AuthCredential::BackupCode(bc),
119            ProtoAuthCredential::Passkey(pkc) => AuthCredential::Passkey(pkc),
120        }
121    }
122}
123
124impl fmt::Debug for AuthCredential {
125    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
126        match self {
127            AuthCredential::Anonymous => write!(fmt, "Anonymous"),
128            AuthCredential::Password(_) => write!(fmt, "Password(_)"),
129            AuthCredential::Totp(_) => write!(fmt, "TOTP(_)"),
130            AuthCredential::SecurityKey(_) => write!(fmt, "SecurityKey(_)"),
131            AuthCredential::BackupCode(_) => write!(fmt, "BackupCode(_)"),
132            AuthCredential::Passkey(_) => write!(fmt, "Passkey(_)"),
133            AuthCredential::OAuth2AuthorisationResponse { .. } => {
134                write!(fmt, "OAuth2AuthorisationResponse{{..}}")
135            }
136            AuthCredential::OAuth2AccessTokenResponse { .. } => {
137                write!(fmt, "OAuth2AccessTokenResponse{{..}}")
138            }
139        }
140    }
141}
142
143#[derive(Debug, Clone, Default)]
144pub(crate) enum PreValidatedTokenStatus {
145    #[default]
146    None,
147    Valid(Box<UserAuthToken>),
148    NotAuthenticated,
149    SessionExpired,
150}
151
152#[derive(Debug, Clone)]
153pub struct ClientAuthInfo {
154    pub(crate) source: Source,
155    pub(crate) client_cert: Option<ClientCertInfo>,
156    pub(crate) bearer_token: Option<JwsCompact>,
157    pub(crate) basic_authz: Option<String>,
158    pub(crate) pre_validated_token: PreValidatedTokenStatus,
159}
160
161impl ClientAuthInfo {
162    pub fn new(
163        source: Source,
164        client_cert: Option<ClientCertInfo>,
165        bearer_token: Option<JwsCompact>,
166        basic_authz: Option<String>,
167    ) -> Self {
168        Self {
169            source,
170            client_cert,
171            bearer_token,
172            basic_authz,
173            pre_validated_token: Default::default(),
174        }
175    }
176
177    pub fn bearer_token(&self) -> Option<&JwsCompact> {
178        self.bearer_token.as_ref()
179    }
180
181    pub fn pre_validated_uat(&self) -> Result<&UserAuthToken, OperationError> {
182        match &self.pre_validated_token {
183            PreValidatedTokenStatus::Valid(uat) => Ok(uat),
184            PreValidatedTokenStatus::None => Err(OperationError::AU0008ClientAuthInfoPrevalidation),
185            PreValidatedTokenStatus::NotAuthenticated => Err(OperationError::NotAuthenticated),
186            PreValidatedTokenStatus::SessionExpired => Err(OperationError::SessionExpired),
187        }
188    }
189
190    pub(crate) fn set_pre_validated_uat(&mut self, status: PreValidatedTokenStatus) {
191        self.pre_validated_token = status
192    }
193}
194
195#[derive(Debug, Clone)]
196pub struct ClientCertInfo {
197    pub public_key_s256: Sha256Output,
198    pub certificate: Certificate,
199}
200
201#[cfg(test)]
202impl ClientAuthInfo {
203    pub(crate) fn none() -> Self {
204        ClientAuthInfo {
205            source: Source::Internal,
206            client_cert: None,
207            bearer_token: None,
208            basic_authz: None,
209            pre_validated_token: Default::default(),
210        }
211    }
212}
213
214#[cfg(test)]
215impl From<Source> for ClientAuthInfo {
216    fn from(value: Source) -> ClientAuthInfo {
217        ClientAuthInfo {
218            source: value,
219            client_cert: None,
220            bearer_token: None,
221            basic_authz: None,
222            pre_validated_token: Default::default(),
223        }
224    }
225}
226
227#[cfg(test)]
228impl From<JwsCompact> for ClientAuthInfo {
229    fn from(value: JwsCompact) -> ClientAuthInfo {
230        ClientAuthInfo {
231            source: Source::Internal,
232            client_cert: None,
233            bearer_token: Some(value),
234            basic_authz: None,
235            pre_validated_token: Default::default(),
236        }
237    }
238}
239
240#[cfg(test)]
241impl From<ClientCertInfo> for ClientAuthInfo {
242    fn from(value: ClientCertInfo) -> ClientAuthInfo {
243        ClientAuthInfo {
244            source: Source::Internal,
245            client_cert: Some(value),
246            bearer_token: None,
247            basic_authz: None,
248            pre_validated_token: Default::default(),
249        }
250    }
251}
252
253#[cfg(test)]
254impl From<&str> for ClientAuthInfo {
255    fn from(value: &str) -> ClientAuthInfo {
256        ClientAuthInfo {
257            source: Source::Internal,
258            client_cert: None,
259            bearer_token: None,
260            basic_authz: Some(value.to_string()),
261            pre_validated_token: Default::default(),
262        }
263    }
264}
265
266#[cfg(test)]
267impl ClientAuthInfo {
268    pub(crate) fn encode_basic(id: &str, secret: &str) -> ClientAuthInfo {
269        use base64::{engine::general_purpose, Engine as _};
270        let value = format!("{id}:{secret}");
271        let value = general_purpose::STANDARD.encode(&value);
272        ClientAuthInfo {
273            source: Source::Internal,
274            client_cert: None,
275            bearer_token: None,
276            basic_authz: Some(value),
277            pre_validated_token: Default::default(),
278        }
279    }
280}