Skip to content
Open
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
22 changes: 22 additions & 0 deletions crates/api-types/src/v3/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,28 @@ pub struct UserListParameters {
/// Filter users by the federated unique ID.
#[cfg_attr(feature = "validate", validate(length(max = 64)))]
pub unique_id: Option<String>,

/// Filter users by type (`local`, `federated`, `nonlocal`, `all`).
#[serde(rename = "type")]
pub user_type: Option<UserType>,
}

/// User type filter for listing users.
#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(feature = "openapi", derive(utoipa::ToSchema))]
#[serde(rename_all = "lowercase")]
pub enum UserType {
/// All users (default behavior).
All,
/// Federated users only.
Federated,
/// Local users only.
Local,
/// Non-local users only.
NonLocal,
/// Service account users only.
#[serde(rename = "service_account")]
ServiceAccount,
}

#[cfg(test)]
Expand Down
14 changes: 13 additions & 1 deletion crates/api-types/src/v3/user_conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,25 @@ impl From<provider_types::FederationProtocol> for api_types::FederationProtocol
}
}

impl From<api_types::UserType> for provider_types::UserType {
fn from(value: api_types::UserType) -> Self {
match value {
api_types::UserType::All => Self::All,
api_types::UserType::Federated => Self::Federated,
api_types::UserType::Local => Self::Local,
api_types::UserType::NonLocal => Self::NonLocal,
api_types::UserType::ServiceAccount => Self::ServiceAccount,
}
}
}

impl From<api_types::UserListParameters> for provider_types::UserListParameters {
fn from(value: api_types::UserListParameters) -> Self {
Self {
domain_id: value.domain_id,
name: value.name,
unique_id: value.unique_id,
..Default::default()
user_type: value.user_type.map(Into::into),
}
}
}
Expand Down
39 changes: 38 additions & 1 deletion crates/keystone/src/api/v3/user/list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ mod tests {
use tower::ServiceExt; // for `call`, `oneshot`, and `ready`
use tower_http::trace::TraceLayer;

use openstack_keystone_core_types::identity::{UserListParameters, UserResponseBuilder};
use openstack_keystone_core_types::identity::{
UserListParameters, UserResponseBuilder, UserType,
};

use super::super::openapi_router;
use crate::api::tests::{get_mocked_state, test_fixture_scoped};
Expand Down Expand Up @@ -188,6 +190,41 @@ mod tests {
let _res: UserList = serde_json::from_slice(&body).unwrap();
}

#[tokio::test]
async fn test_list_qp_type() {
let mut identity_mock = MockIdentityProvider::default();
identity_mock
.expect_list_users()
.withf(|_, qp: &UserListParameters| qp.user_type == Some(UserType::Local))
.returning(|_, _| Ok(Vec::new()));

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

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

let response = api
.as_service()
.oneshot(
Request::builder()
.uri("/?type=local")
.extension(vsc)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

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

#[tokio::test]
async fn test_list_unauth() {
let state = get_mocked_state(Provider::mocked_builder(), false, None).await;
Expand Down
35 changes: 35 additions & 0 deletions crates/keystone/src/api/v4/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,41 @@ mod tests {
let _res: UserList = serde_json::from_slice(&body).unwrap();
}

#[tokio::test]
async fn test_list_qp_type() {
let vsc = test_fixture_scoped();
let mut identity_mock = MockIdentityProvider::default();
identity_mock
.expect_list_users()
.withf(|_, qp: &UserListParameters| qp.user_type == Some(UserType::Local))
.returning(|_, _| Ok(Vec::new()));

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

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

let response = api
.as_service()
.oneshot(
Request::builder()
.uri("/?type=local")
.extension(vsc)
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();

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

#[tokio::test]
async fn test_list_unauth() {
let state = get_mocked_state(Provider::mocked_builder(), false, None).await;
Expand Down
Loading