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#[allow(clippy::large_enum_variant)]
72pub enum AuthState {
73 Choose(Vec<AuthMech>),
74 Continue(Vec<AuthAllowed>),
75
76 External(AuthExternal),
80 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 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}