Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions src/api/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,15 @@ use crate::resource::{
types::{Domain, Project},
};

/// Get the domain by ID or Name
///
/// # Arguments
/// * `state` - The service state
/// * `id` - The domain ID
/// * `name` - The domain name
///
/// # Returns
/// * `Result<Domain, KeystoneApiError>` - The domain object
pub async fn get_domain<I: AsRef<str>, N: AsRef<str>>(
state: &ServiceState,
id: Option<I>,
Expand Down Expand Up @@ -51,6 +60,14 @@ pub async fn get_domain<I: AsRef<str>, N: AsRef<str>>(
}
}

/// Find the project referred in the scope.
///
/// # Arguments
/// * `state` - The service state.
/// * `scope` - The scope to find the project.
///
/// # Returns
/// The resolved project.
pub async fn find_project_from_scope(
state: &ServiceState,
scope: &ProjectScope,
Expand Down
110 changes: 44 additions & 66 deletions src/api/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ use serde_json::json;
use thiserror::Error;
use tracing::error;

use crate::api::v3::federation::error::OidcError;
use crate::assignment::error::AssignmentProviderError;
use crate::auth::AuthenticationError;
use crate::catalog::error::CatalogProviderError;
Expand All @@ -46,6 +45,9 @@ pub enum KeystoneApiError {
#[error("Attempted to authenticate with an unsupported method.")]
AuthMethodNotSupported,

#[error("{0}.")]
BadRequest(String),

#[error("The request you have made requires authentication.")]
Unauthorized,

Expand Down Expand Up @@ -76,12 +78,11 @@ pub enum KeystoneApiError {
source: AssignmentProviderError,
},

#[error(transparent)]
AuthenticationInfo {
//#[from]
source: crate::auth::AuthenticationError,
},

// #[error(transparent)]
// AuthenticationInfo {
// //#[from]
// source: crate::auth::AuthenticationError,
// },
#[error(transparent)]
CatalogError {
#[from]
Expand All @@ -94,12 +95,11 @@ pub enum KeystoneApiError {
source: FederationProviderError,
},

#[error(transparent)]
Oidc {
#[from]
source: OidcError,
},

// #[error(transparent)]
// Oidc {
// #[from]
// source: OidcError,
// },
#[error(transparent)]
IdentityError {
#[from]
Expand Down Expand Up @@ -148,6 +148,9 @@ pub enum KeystoneApiError {
#[error(transparent)]
JsonExtractorRejection(#[from] JsonRejection),

#[error("The account is disabled for user: {0}")]
UserDisabled(String),

/// Others.
#[error(transparent)]
Other(#[from] eyre::Report),
Expand All @@ -156,60 +159,34 @@ pub enum KeystoneApiError {
impl IntoResponse for KeystoneApiError {
fn into_response(self) -> Response {
error!("Error happened during request processing: {:#?}", self);
//tracing::debug!("stacktrace: {}", self.backtrace());
match self {
KeystoneApiError::Conflict(_) => (
StatusCode::CONFLICT,
Json(json!({"error": {"code": StatusCode::CONFLICT.as_u16(), "message": self.to_string()}})),
).into_response(),
KeystoneApiError::NotFound{..} => (
StatusCode::NOT_FOUND,
Json(json!({"error": {"code": StatusCode::NOT_FOUND.as_u16(), "message": self.to_string()}})),
)
.into_response(),
KeystoneApiError::Unauthorized => {
(StatusCode::UNAUTHORIZED,
Json(json!({"error": {"code": StatusCode::UNAUTHORIZED.as_u16(), "message": self.to_string()}})),
).into_response()
}
KeystoneApiError::AuthenticationInfo{ .. } => {
(StatusCode::UNAUTHORIZED,
Json(json!({"error": {"code": StatusCode::UNAUTHORIZED.as_u16(), "message": self.to_string()}})),
).into_response()
}
KeystoneApiError::Forbidden => {
(StatusCode::FORBIDDEN,
Json(json!({"error": {"code": StatusCode::FORBIDDEN.as_u16(), "message": self.to_string()}})),
).into_response()
}
KeystoneApiError::InternalError(_) | KeystoneApiError::IdentityError { .. } | KeystoneApiError::ResourceError { .. } | KeystoneApiError::AssignmentError { .. } | KeystoneApiError::TokenError{..} | KeystoneApiError::Federation{..} | KeystoneApiError::Other(..) => {
(StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": {"code": StatusCode::INTERNAL_SERVER_ERROR.as_u16(), "message": self.to_string()}})),
).into_response()
}
KeystoneApiError::Oidc{ source: ref err } => {
match err {
OidcError::OpenIdConnectReqwest{..} | OidcError::OpenIdConnectConfiguration{..} => {
(
StatusCode::INTERNAL_SERVER_ERROR,
Json(json!({"error": {"code": StatusCode::INTERNAL_SERVER_ERROR.as_u16(), "message": self.to_string()}})),
).into_response()
}
_ => {
(
StatusCode::BAD_REQUEST,
Json(json!({"error": {"code": StatusCode::BAD_REQUEST.as_u16(), "message": self.to_string()}})),
).into_response()
}
}
}
_ => {

let status_code = match self {
KeystoneApiError::Conflict(_) => StatusCode::CONFLICT,
KeystoneApiError::NotFound { .. } => StatusCode::NOT_FOUND,
KeystoneApiError::BadRequest(..) => StatusCode::BAD_REQUEST,
KeystoneApiError::UserDisabled(..) => StatusCode::UNAUTHORIZED,
KeystoneApiError::Unauthorized => StatusCode::UNAUTHORIZED,
// KeystoneApiError::AuthenticationInfo { .. } => StatusCode::UNAUTHORIZED,
KeystoneApiError::Forbidden => StatusCode::FORBIDDEN,
KeystoneApiError::InternalError(_)
| KeystoneApiError::IdentityError { .. }
| KeystoneApiError::ResourceError { .. }
| KeystoneApiError::AssignmentError { .. }
| KeystoneApiError::TokenError { .. }
| KeystoneApiError::Federation { .. }
| KeystoneApiError::Other(..) => StatusCode::INTERNAL_SERVER_ERROR,
_ =>
// KeystoneApiError::SubjectTokenMissing | KeystoneApiError::InvalidHeader | KeystoneApiError::InvalidToken | KeystoneApiError::Token{..} | KeystoneApiError::WebAuthN{..} | KeystoneApiError::Uuid {..} | KeystoneApiError::Serde {..} | KeystoneApiError::DomainIdOrName | KeystoneApiError::ProjectIdOrName | KeystoneApiError::ProjectDomain =>
(StatusCode::BAD_REQUEST,
Json(json!({"error": {"code": StatusCode::BAD_REQUEST.as_u16(), "message": self.to_string()}})),
).into_response()
{
StatusCode::BAD_REQUEST
}
}
};

(
status_code,
Json(json!({"error": {"code": status_code.as_u16(), "message": self.to_string()}})),
)
.into_response()
}
}

Expand Down Expand Up @@ -323,7 +300,8 @@ impl From<AuthenticationError> for KeystoneApiError {
AuthenticationError::AuthenticatedInfoBuilder { source } => {
KeystoneApiError::InternalError(source.to_string())
}
other => KeystoneApiError::AuthenticationInfo { source: other },
AuthenticationError::UserDisabled(data) => KeystoneApiError::UserDisabled(data),
AuthenticationError::Unauthorized => KeystoneApiError::Unauthorized,
}
}
}
3 changes: 2 additions & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
// limitations under the License.
//
// SPDX-License-Identifier: Apache-2.0

//! Keystone API
//!
use axum::{
http::{HeaderMap, header},
response::IntoResponse,
Expand Down
62 changes: 57 additions & 5 deletions src/api/v3/auth/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,28 @@ async fn authenticate_request(
}
} else if method == "token" {
if let Some(token) = &req.auth.identity.token {
authenticated_info = Some(
let mut authz = state
.provider
.get_token_provider()
.authenticate_by_token(&token.id, Some(false), None)
.await?;
// Resolve the user
authz.user = Some(
state
.provider
.get_token_provider()
.authenticate_by_token(&token.id, Some(false), None)
.await?,
.get_identity_provider()
.get_user(&state.db, &authz.user_id)
.await
.map(|x| {
x.ok_or_else(|| KeystoneApiError::NotFound {
resource: "user".into(),
identifier: authz.user_id.clone(),
})
})??,
);
authenticated_info = Some(authz);

{}
}
}
}
Expand All @@ -85,6 +100,17 @@ async fn authenticate_request(
})
}

/// Build the AuthZ information from the request
///
/// # Arguments
///
/// * `state` - The service state
/// * `req` - The Request
///
/// # Result
///
/// * `Ok(AuthzInfo)` - The AuthZ information
/// * `Err(KeystoneApiError)` - The error
async fn get_authz_info(
state: &ServiceState,
req: &AuthRequest,
Expand Down Expand Up @@ -278,6 +304,7 @@ mod tests {
.user(UserResponse {
id: "uid".to_string(),
domain_id: "udid".into(),
enabled: true,
..Default::default()
})
.build()
Expand Down Expand Up @@ -342,9 +369,22 @@ mod tests {
},
)
.returning(|_, _, _| Ok(AuthenticatedInfo::builder().user_id("uid").build().unwrap()));
let mut identity_mock = MockIdentityProvider::default();
identity_mock
.expect_get_user()
.withf(|_, id: &'_ str| id == "uid")
.returning(|_, id: &'_ str| {
Ok(Some(UserResponse {
id: id.to_string(),
domain_id: "user_domain_id".into(),
enabled: true,
..Default::default()
}))
});

let provider = Provider::mocked_builder()
.config(config.clone())
.identity(identity_mock)
.token(token_mock)
.build()
.unwrap();
Expand All @@ -353,7 +393,16 @@ mod tests {
Arc::new(Service::new(config, DatabaseConnection::Disconnected, provider).unwrap());

assert_eq!(
AuthenticatedInfo::builder().user_id("uid").build().unwrap(),
AuthenticatedInfo::builder()
.user_id("uid")
.user(UserResponse {
id: "uid".to_string(),
domain_id: "user_domain_id".into(),
enabled: true,
..Default::default()
})
.build()
.unwrap(),
authenticate_request(
&state,
&AuthRequest {
Expand Down Expand Up @@ -707,6 +756,7 @@ mod tests {
.user(UserResponse {
id: "uid".to_string(),
domain_id: "udid".into(),
enabled: true,
..Default::default()
})
.build()
Expand Down Expand Up @@ -838,6 +888,7 @@ mod tests {
}

#[tokio::test]
#[traced_test]
async fn test_post_project_disabled() {
let config = Config::default();
let mut identity_mock = MockIdentityProvider::default();
Expand All @@ -849,6 +900,7 @@ mod tests {
.user(UserResponse {
id: "uid".to_string(),
domain_id: "udid".into(),
enabled: true,
..Default::default()
})
.build()
Expand Down
Loading
Loading