Skip to main content

kanidmd_lib/idm/
authentication.rs

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