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
89 changes: 89 additions & 0 deletions crates/keystone/src/api/v3/group/create.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
//
// SPDX-License-Identifier: Apache-2.0
use axum::{Json, debug_handler, extract::State, http::StatusCode, response::IntoResponse};
use validator::Validate;

use super::types::{Group, GroupCreateRequest, GroupResponse};
use crate::api::auth::Auth;
Expand All @@ -35,6 +36,17 @@ pub async fn create(
State(state): State<ServiceState>,
Json(req): Json<GroupCreateRequest>,
) -> Result<impl IntoResponse, KeystoneApiError> {
req.validate()?;
state
.policy_enforcer
.enforce(
"identity/group/create",
&user_auth,
serde_json::to_value(&req.group)?,
None,
)
.await?;

let res = state
.provider
.get_identity_provider()
Expand Down Expand Up @@ -126,4 +138,81 @@ mod tests {
assert_eq!(res.group.name, req.group.name);
assert_eq!(res.group.domain_id, req.group.domain_id);
}

#[tokio::test]
async fn test_create_unauth() {
let state = crate::api::tests::get_mocked_state(
crate::provider::Provider::mocked_builder(),
false,
None,
None,
)
.await;

let mut api = openapi_router()
.layer(TraceLayer::new_for_http())
.with_state(state);

let req = crate::api::v3::group::types::GroupCreateRequest {
group: crate::api::v3::group::types::GroupCreateBuilder::default()
.domain_id("domain")
.name("name")
.build()
.unwrap(),
};

let response = api
.as_service()
.oneshot(
Request::builder()
.method("POST")
.header(header::CONTENT_TYPE, "application/json")
.uri("/")
.body(Body::from(serde_json::to_string(&req).unwrap()))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_create_not_allowed() {
let state = crate::api::tests::get_mocked_state(
crate::provider::Provider::mocked_builder(),
false,
None,
None,
)
.await;

let mut api = openapi_router()
.layer(TraceLayer::new_for_http())
.with_state(state);

let req = crate::api::v3::group::types::GroupCreateRequest {
group: crate::api::v3::group::types::GroupCreateBuilder::default()
.domain_id("domain")
.name("name")
.build()
.unwrap(),
};

let response = api
.as_service()
.oneshot(
Request::builder()
.method("POST")
.header(header::CONTENT_TYPE, "application/json")
.uri("/")
.header("x-auth-token", "foo")
.body(Body::from(serde_json::to_string(&req).unwrap()))
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,27 @@ use crate::keystone::ServiceState;
tag="groups"
)]
#[tracing::instrument(name = "api::group_delete", level = "debug", skip(state))]
pub async fn remove(
pub async fn delete(
Auth(user_auth): Auth,
Path(group_id): Path<String>,
State(state): State<ServiceState>,
) -> Result<impl IntoResponse, KeystoneApiError> {
let current = state
.provider
.get_identity_provider()
.get_group(&state, &group_id)
.await?;

state
.policy_enforcer
.enforce(
"identity/group/delete",
&user_auth,
serde_json::to_value(&current)?,
None,
)
.await?;

state
.provider
.get_identity_provider()
Expand All @@ -56,6 +72,8 @@ mod tests {
use tower::ServiceExt; // for `call`, `oneshot`, and `ready`
use tower_http::trace::TraceLayer;

use openstack_keystone_core_types::identity::*;

use super::super::openapi_router;
use crate::api::tests::get_mocked_state;
use crate::identity::{MockIdentityProvider, error::IdentityProviderError};
Expand All @@ -65,10 +83,22 @@ mod tests {
async fn test_delete() {
let mut identity_mock = MockIdentityProvider::default();
identity_mock
.expect_delete_group()
.expect_get_group()
.withf(|_, id: &'_ str| id == "foo")
.returning(|_, _| Err(IdentityProviderError::GroupNotFound("foo".into())));

identity_mock
.expect_get_group()
.withf(|_, id: &'_ str| id == "bar")
.returning(|_, _| {
Ok(Some(Group {
id: "bar".into(),
name: "name".into(),
domain_id: "did".into(),
..Default::default()
}))
});

identity_mock
.expect_delete_group()
.withf(|_, id: &'_ str| id == "bar")
Expand Down Expand Up @@ -116,4 +146,76 @@ mod tests {

assert_eq!(response.status(), StatusCode::NO_CONTENT);
}

#[tokio::test]
async fn test_remove_unauth() {
let state = crate::api::tests::get_mocked_state(
crate::provider::Provider::mocked_builder(),
false,
None,
None,
)
.await;

let mut api = openapi_router()
.layer(TraceLayer::new_for_http())
.with_state(state);

let response = api
.as_service()
.oneshot(
Request::builder()
.method("DELETE")
.uri("/foo")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_delete_not_allowed() {
let mut identity_mock = MockIdentityProvider::default();
identity_mock
.expect_get_group()
.withf(|_, id: &'_ str| id == "foo")
.returning(|_, _| {
Ok(Some(Group {
id: "foo".into(),
name: "name".into(),
domain_id: "did".into(),
..Default::default()
}))
});

let state = get_mocked_state(
Provider::mocked_builder().mock_identity(identity_mock),
false,
None,
None,
)
.await;

let mut api = openapi_router()
.layer(TraceLayer::new_for_http())
.with_state(state);

let response = api
.as_service()
.oneshot(
Request::builder()
.method("DELETE")
.uri("/foo")
.header("x-auth-token", "foo")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
}
41 changes: 41 additions & 0 deletions crates/keystone/src/api/v3/group/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use axum::{
http::StatusCode,
response::IntoResponse,
};
use validator::Validate;

use super::types::{Group, GroupList, GroupListParameters};
use crate::api::auth::Auth;
Expand All @@ -41,6 +42,17 @@ pub async fn list(
Query(query): Query<GroupListParameters>,
State(state): State<ServiceState>,
) -> Result<impl IntoResponse, KeystoneApiError> {
query.validate()?;
state
.policy_enforcer
.enforce(
"identity/group/list",
&user_auth,
serde_json::to_value(&query)?,
None,
)
.await?;

let groups: Vec<Group> = state
.provider
.get_identity_provider()
Expand Down Expand Up @@ -186,4 +198,33 @@ mod tests {

assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn test_list_not_allowed() {
let state = crate::api::tests::get_mocked_state(
crate::provider::Provider::mocked_builder(),
false,
None,
None,
)
.await;

let mut api = openapi_router()
.layer(TraceLayer::new_for_http())
.with_state(state);

let response = api
.as_service()
.oneshot(
Request::builder()
.uri("/")
.header("x-auth-token", "foo")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

assert_eq!(response.status(), StatusCode::FORBIDDEN);
}
}
7 changes: 5 additions & 2 deletions crates/keystone/src/api/v3/group/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ use utoipa_axum::{router::OpenApiRouter, routes};
use crate::keystone::ServiceState;

pub mod create;
pub mod delete;
pub mod list;
pub mod remove;
pub mod show;
pub mod types;

pub(crate) fn openapi_router() -> OpenApiRouter<ServiceState> {
OpenApiRouter::new()
.routes(routes!(list::list, create::create))
.routes(routes!(show::show, remove::remove))
.routes(routes!(show::show, delete::delete))
}

#[cfg(test)]
mod tests {}
Loading
Loading