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}