openstack_keystone_core/api/
error.rs1use axum::{
16 Json,
17 extract::rejection::JsonRejection,
18 http::StatusCode,
19 response::{IntoResponse, Response},
20};
21use serde_json::json;
22use thiserror::Error;
23use tracing::error;
24
25use crate::assignment::error::AssignmentProviderError;
26use crate::auth::AuthenticationError;
27use crate::catalog::error::CatalogProviderError;
28use crate::error::BuilderError;
29use crate::identity::error::IdentityProviderError;
30use crate::policy::PolicyError;
31use crate::resource::error::ResourceProviderError;
32use crate::revoke::error::RevokeProviderError;
33use crate::role::error::RoleProviderError;
34use crate::token::error::TokenProviderError;
35
36#[derive(Debug, Error)]
38pub enum KeystoneApiError {
39 #[error("changing current authentication scope is forbidden")]
41 AuthenticationRescopeForbidden,
42
43 #[error("Attempted to authenticate with an unsupported method.")]
44 AuthMethodNotSupported,
45
46 #[error("{0}.")]
47 BadRequest(String),
48
49 #[error(transparent)]
51 Base64Decode(#[from] base64::DecodeError),
52
53 #[error("conflict, resource already existing")]
54 Conflict(String),
55
56 #[error("domain id or name must be present")]
57 DomainIdOrName,
58
59 #[error("You are not authorized to perform the requested action.")]
60 Forbidden {
61 #[source]
63 source: Box<dyn std::error::Error + Send + Sync>,
64 },
65
66 #[error("invalid header header")]
67 InvalidHeader,
68
69 #[error("invalid token")]
70 InvalidToken,
71
72 #[error(transparent)]
73 JsonExtractorRejection(#[from] JsonRejection),
74
75 #[error("internal server error: {0}")]
76 InternalError(String),
77
78 #[error("could not find {resource}: {identifier}")]
79 NotFound {
80 resource: String,
81 identifier: String,
82 },
83
84 #[error(transparent)]
86 Other(#[from] eyre::Report),
87
88 #[error(transparent)]
89 Policy {
90 #[from]
91 source: PolicyError,
92 },
93
94 #[error("project id or name must be present")]
95 ProjectIdOrName,
96
97 #[error("project domain must be present")]
98 ProjectDomain,
99
100 #[error("selected authentication is forbidden")]
102 SelectedAuthenticationForbidden,
103
104 #[error(transparent)]
106 Serde {
107 #[from]
108 source: serde_json::Error,
109 },
110
111 #[error("missing x-subject-token header")]
112 SubjectTokenMissing,
113
114 #[error("The request you have made requires authentication.")]
115 UnauthorizedNoContext,
116
117 #[error("{}", .context.clone().unwrap_or("The request you have made requires authentication.".to_string()))]
118 Unauthorized {
119 context: Option<String>,
120 #[source]
122 source: Box<dyn std::error::Error + Send + Sync>,
123 },
124
125 #[error("request validation failed: {source}")]
127 Validator {
128 #[from]
130 source: validator::ValidationErrors,
131 },
132}
133
134impl KeystoneApiError {
135 pub fn forbidden<E>(error: E) -> Self
136 where
137 E: std::error::Error + Send + Sync + 'static,
138 {
139 Self::Forbidden {
140 source: Box::new(error),
141 }
142 }
143
144 pub fn internal<E>(error: E) -> Self
145 where
146 E: std::error::Error + Send + Sync + 'static,
147 {
148 Self::InternalError(error.to_string())
149 }
153
154 pub fn unauthorized<E, C>(error: E, context: Option<C>) -> Self
155 where
156 E: std::error::Error + Send + Sync + 'static,
157 C: Into<String>,
158 {
159 Self::Unauthorized {
160 context: context.map(Into::into),
161 source: Box::new(error),
162 }
163 }
164}
165
166impl IntoResponse for KeystoneApiError {
167 fn into_response(self) -> Response {
168 error!("Error happened during request processing: {:#?}", self);
169
170 let status_code = match self {
171 KeystoneApiError::Conflict(_) => StatusCode::CONFLICT,
172 KeystoneApiError::NotFound { .. } => StatusCode::NOT_FOUND,
173 KeystoneApiError::BadRequest(..) => StatusCode::BAD_REQUEST,
174 KeystoneApiError::Unauthorized { .. } => StatusCode::UNAUTHORIZED,
175 KeystoneApiError::UnauthorizedNoContext => StatusCode::UNAUTHORIZED,
176 KeystoneApiError::Forbidden { .. } => StatusCode::FORBIDDEN,
177 KeystoneApiError::Policy { .. } => StatusCode::FORBIDDEN,
178 KeystoneApiError::SelectedAuthenticationForbidden
179 | KeystoneApiError::AuthenticationRescopeForbidden => StatusCode::BAD_REQUEST,
180 KeystoneApiError::InternalError(_) | KeystoneApiError::Other(..) => {
181 StatusCode::INTERNAL_SERVER_ERROR
182 }
183 _ => StatusCode::BAD_REQUEST,
184 };
185
186 (
187 status_code,
188 Json(json!({"error": {"code": status_code.as_u16(), "message": self.to_string()}})),
189 )
190 .into_response()
191 }
192}
193
194impl From<AuthenticationError> for KeystoneApiError {
195 fn from(value: AuthenticationError) -> Self {
196 match value {
197 AuthenticationError::DomainDisabled(..) => {
198 KeystoneApiError::unauthorized(value, None::<String>)
199 }
200 AuthenticationError::ProjectDisabled(..) => {
201 KeystoneApiError::unauthorized(value, None::<String>)
202 }
203 AuthenticationError::StructBuilder { source } => {
204 KeystoneApiError::InternalError(source.to_string())
205 }
206 AuthenticationError::UserDisabled(ref user_id) => {
207 let uid = user_id.clone();
208 KeystoneApiError::unauthorized(
209 value,
210 Some(format!("The account is disabled for the user: {uid}")),
211 )
212 }
213 AuthenticationError::UserLocked(ref user_id) => {
214 let uid = user_id.clone();
215 KeystoneApiError::unauthorized(
216 value,
217 Some(format!("The account is locked for the user: {uid}")),
218 )
219 }
220 AuthenticationError::UserPasswordExpired(ref user_id) => {
221 let uid = user_id.clone();
222 KeystoneApiError::unauthorized(
223 value,
224 Some(format!(
225 "The password is expired and need to be changed for user: {uid}"
226 )),
227 )
228 }
229 AuthenticationError::UserNameOrPasswordWrong => KeystoneApiError::unauthorized(
230 value,
231 Some("Invalid username or password".to_string()),
232 ),
233 AuthenticationError::TokenRenewalForbidden => {
234 KeystoneApiError::SelectedAuthenticationForbidden
235 }
236 AuthenticationError::Unauthorized => {
237 KeystoneApiError::unauthorized(value, None::<String>)
238 }
239 }
240 }
241}
242
243impl From<AssignmentProviderError> for KeystoneApiError {
244 fn from(source: AssignmentProviderError) -> Self {
245 match source {
246 AssignmentProviderError::AssignmentNotFound(x) => Self::NotFound {
247 resource: "assignment".into(),
248 identifier: x,
249 },
250 AssignmentProviderError::RoleNotFound(x) => Self::NotFound {
251 resource: "role".into(),
252 identifier: x,
253 },
254 ref err @ AssignmentProviderError::Conflict(..) => Self::Conflict(err.to_string()),
255 ref err @ AssignmentProviderError::Validation { .. } => {
256 Self::BadRequest(err.to_string())
257 }
258 other => Self::InternalError(other.to_string()),
259 }
260 }
261}
262
263impl From<BuilderError> for KeystoneApiError {
264 fn from(value: crate::error::BuilderError) -> Self {
265 Self::InternalError(value.to_string())
266 }
267}
268
269impl From<openstack_keystone_api_types::error::BuilderError> for KeystoneApiError {
270 fn from(value: openstack_keystone_api_types::error::BuilderError) -> Self {
271 Self::InternalError(value.to_string())
272 }
273}
274
275impl From<RoleProviderError> for KeystoneApiError {
276 fn from(source: RoleProviderError) -> Self {
277 match source {
278 RoleProviderError::RoleNotFound(x) => Self::NotFound {
279 resource: "role".into(),
280 identifier: x,
281 },
282 ref err @ RoleProviderError::Conflict(..) => Self::Conflict(err.to_string()),
283 ref err @ RoleProviderError::Validation { .. } => Self::BadRequest(err.to_string()),
284 other => Self::InternalError(other.to_string()),
285 }
286 }
287}
288
289impl From<serde_urlencoded::ser::Error> for KeystoneApiError {
290 fn from(value: serde_urlencoded::ser::Error) -> Self {
291 Self::InternalError(value.to_string())
292 }
293}
294
295impl From<url::ParseError> for KeystoneApiError {
296 fn from(value: url::ParseError) -> Self {
297 Self::InternalError(value.to_string())
298 }
299}
300
301impl From<CatalogProviderError> for KeystoneApiError {
302 fn from(value: CatalogProviderError) -> Self {
303 match value {
304 ref err @ CatalogProviderError::Conflict(..) => Self::Conflict(err.to_string()),
305 other => Self::InternalError(other.to_string()),
306 }
307 }
308}
309
310impl From<IdentityProviderError> for KeystoneApiError {
311 fn from(value: IdentityProviderError) -> Self {
312 match value {
313 IdentityProviderError::Authentication { source } => source.into(),
314 IdentityProviderError::UserNotFound(x) => Self::NotFound {
315 resource: "user".into(),
316 identifier: x,
317 },
318 IdentityProviderError::GroupNotFound(x) => Self::NotFound {
319 resource: "group".into(),
320 identifier: x,
321 },
322 other => Self::InternalError(other.to_string()),
323 }
324 }
325}
326
327impl From<ResourceProviderError> for KeystoneApiError {
328 fn from(value: ResourceProviderError) -> Self {
329 match value {
330 ref err @ ResourceProviderError::Conflict(..) => Self::BadRequest(err.to_string()),
331 ResourceProviderError::DomainNotFound(x) => Self::NotFound {
332 resource: "domain".into(),
333 identifier: x,
334 },
335 other => Self::InternalError(other.to_string()),
336 }
337 }
338}
339
340impl From<RevokeProviderError> for KeystoneApiError {
341 fn from(value: RevokeProviderError) -> Self {
342 match value {
343 ref err @ RevokeProviderError::Conflict(..) => Self::BadRequest(err.to_string()),
344 other => Self::InternalError(other.to_string()),
345 }
346 }
347}
348
349impl From<TokenProviderError> for KeystoneApiError {
350 fn from(value: TokenProviderError) -> Self {
351 match value {
352 TokenProviderError::Authentication(source) => source.into(),
353 TokenProviderError::DomainDisabled(x) => Self::NotFound {
354 resource: "domain".into(),
355 identifier: x,
356 },
357 TokenProviderError::TokenRestrictionNotFound(x) => Self::NotFound {
358 resource: "token restriction".into(),
359 identifier: x,
360 },
361 TokenProviderError::ProjectDisabled(x) => Self::NotFound {
362 resource: "project".into(),
363 identifier: x,
364 },
365 other => Self::InternalError(other.to_string()),
366 }
367 }
368}
369
370impl From<uuid::Error> for KeystoneApiError {
371 fn from(value: uuid::Error) -> Self {
372 Self::InternalError(value.to_string())
373 }
374}