1use http::{HeaderMap, HeaderValue};
23
24use std::fmt::{self, Debug};
25use tracing::error;
26
27use thiserror::Error;
28
29pub mod auth_helper;
30mod auth_token_endpoint;
31pub mod authtoken;
32pub mod authtoken_scope;
33pub mod v3applicationcredential;
34pub mod v3oidcaccesstoken;
35pub mod v3password;
36pub mod v3token;
37pub mod v3totp;
38pub mod v3websso;
39#[cfg(feature = "keystone_ng")]
40pub mod v4federation;
41
42use authtoken::{AuthToken, AuthTokenError};
43use authtoken_scope::AuthTokenScopeError;
44use v3oidcaccesstoken::OidcAccessTokenError;
45use v3websso::WebSsoError;
46#[cfg(feature = "keystone_ng")]
47use v4federation::FederationError;
48
49#[derive(Debug, Error)]
51#[non_exhaustive]
52pub enum AuthError {
53 #[error("header value error: {}", source)]
55 HeaderValue {
56 #[from]
58 source: http::header::InvalidHeaderValue,
59 },
60
61 #[error("AuthToken error: {}", source)]
63 AuthToken {
64 #[from]
66 source: AuthTokenError,
67 },
68
69 #[error("token missing in the response")]
70 AuthTokenNotInResponse,
71}
72
73impl From<AuthTokenScopeError> for AuthError {
75 fn from(source: AuthTokenScopeError) -> Self {
76 Self::AuthToken {
77 source: AuthTokenError::Scope { source },
78 }
79 }
80}
81
82impl From<OidcAccessTokenError> for AuthError {
83 fn from(source: v3oidcaccesstoken::OidcAccessTokenError) -> Self {
84 Self::AuthToken {
85 source: source.into(),
86 }
87 }
88}
89impl From<WebSsoError> for AuthError {
90 fn from(source: v3websso::WebSsoError) -> Self {
91 Self::AuthToken {
92 source: source.into(),
93 }
94 }
95}
96
97#[cfg(feature = "keystone_ng")]
98impl From<FederationError> for AuthError {
99 fn from(source: v4federation::FederationError) -> Self {
100 Self::AuthToken {
101 source: source.into(),
102 }
103 }
104}
105
106#[derive(Debug, Eq, PartialEq)]
108pub enum AuthState {
109 Valid,
111 Expired,
113 AboutToExpire,
115 Unset,
117}
118
119#[derive(Clone)]
121#[non_exhaustive]
122pub enum Auth {
123 AuthToken(Box<AuthToken>),
125 None,
127}
128
129impl Auth {
130 pub fn set_header<'a>(
134 &self,
135 headers: &'a mut HeaderMap<HeaderValue>,
136 ) -> Result<&'a mut HeaderMap<HeaderValue>, AuthError> {
137 if let Auth::AuthToken(token) = self {
138 let _ = token.set_header(headers);
139 }
140
141 Ok(headers)
142 }
143}
144
145impl fmt::Debug for Auth {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 write!(
148 f,
149 "Auth {}",
150 match self {
151 Auth::AuthToken(_) => "Token",
152 Auth::None => "unauthed",
153 }
154 )
155 }
156}
157
158#[cfg(test)]
159mod tests {
160 use super::*;
161 use crate::types::identity::v3::{AuthResponse, AuthToken};
162
163 #[test]
164 fn test_auth_validity_unset() {
165 let auth = super::AuthToken::default();
166 assert!(matches!(auth.get_state(None), AuthState::Unset));
167 }
168
169 #[test]
170 fn test_auth_validity_expired() {
171 let auth = super::AuthToken {
172 token: String::new(),
173 auth_info: Some(AuthResponse {
174 token: AuthToken {
175 expires_at: chrono::Utc::now() - chrono::TimeDelta::days(1),
176 ..Default::default()
177 },
178 }),
179 };
180 assert!(matches!(auth.get_state(None), AuthState::Expired));
181 }
182
183 #[test]
184 fn test_auth_validity_expire_soon() {
185 let auth = super::AuthToken {
186 token: String::new(),
187 auth_info: Some(AuthResponse {
188 token: AuthToken {
189 expires_at: chrono::Utc::now() + chrono::TimeDelta::minutes(10),
190 ..Default::default()
191 },
192 }),
193 };
194 assert!(matches!(
195 auth.get_state(Some(chrono::TimeDelta::minutes(15))),
196 AuthState::AboutToExpire
197 ));
198 }
199
200 #[test]
201 fn test_auth_validity_valid() {
202 let auth = super::AuthToken {
203 token: String::new(),
204 auth_info: Some(AuthResponse {
205 token: AuthToken {
206 expires_at: chrono::Utc::now() + chrono::TimeDelta::days(1),
207 ..Default::default()
208 },
209 }),
210 };
211 assert!(matches!(auth.get_state(None), AuthState::Valid));
212 }
213}