Skip to main content

openstack_keystone_core/api/
error.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12//
13// SPDX-License-Identifier: Apache-2.0
14//! # Keystone API error.
15use 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/// Keystone API operation errors.
37#[derive(Debug, Error)]
38pub enum KeystoneApiError {
39    /// Selected authentication is forbidden.
40    #[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    /// Base64 decoding error.
50    #[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        /// The source of the error.
62        #[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    /// Others.
85    #[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    /// Selected authentication is forbidden.
101    #[error("selected authentication is forbidden")]
102    SelectedAuthenticationForbidden,
103
104    /// (de)serialization error.
105    #[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        /// The source of the error.
121        #[source]
122        source: Box<dyn std::error::Error + Send + Sync>,
123    },
124
125    /// Request validation error.
126    #[error("request validation failed: {source}")]
127    Validator {
128        /// The source of the error.
129        #[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        //{
150        //    source: Box::new(error),
151        //}
152    }
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}