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