diff --git a/crates/api-types/src/v4.rs b/crates/api-types/src/v4.rs index 60263d5f2..b22b1fd6d 100644 --- a/crates/api-types/src/v4.rs +++ b/crates/api-types/src/v4.rs @@ -19,3 +19,5 @@ pub mod user; #[cfg(feature = "conv")] mod token_restriction_conv; +#[cfg(feature = "conv")] +mod user_conv; diff --git a/crates/api-types/src/v4/user.rs b/crates/api-types/src/v4/user.rs index 973c20cef..a9fd031bd 100644 --- a/crates/api-types/src/v4/user.rs +++ b/crates/api-types/src/v4/user.rs @@ -13,7 +13,52 @@ // SPDX-License-Identifier: Apache-2.0 //! User resource types. +use serde::{Deserialize, Serialize}; + pub use crate::v3::user::{ - Federation, FederationProtocol, User, UserCreate, UserCreateRequest, UserList, - UserListParameters, UserOptions, UserResponse, UserUpdateRequest, + Federation, FederationProtocol, User, UserCreate, UserCreateRequest, UserList, UserOptions, + UserResponse, UserUpdateRequest, }; + +/// User list parameters. +/// +/// V4 extends the v3 listing with a `type` filter; this is intentionally a +/// v4-only parameter (Python Keystone does not support it on v3). +#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] +#[cfg_attr(feature = "openapi", derive(utoipa::IntoParams))] +#[cfg_attr(feature = "validate", derive(validator::Validate))] +pub struct UserListParameters { + /// Filter users by Domain ID. + #[cfg_attr(feature = "validate", validate(length(max = 64)))] + pub domain_id: Option, + + /// Filter users by Name. + #[cfg_attr(feature = "validate", validate(length(max = 255)))] + pub name: Option, + + /// Filter users by the federated unique ID. + #[cfg_attr(feature = "validate", validate(length(max = 64)))] + pub unique_id: Option, + + /// Filter users by type (`local`, `federated`, `nonlocal`, `all`). + #[serde(rename = "type")] + pub user_type: Option, +} + +/// 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, +} diff --git a/crates/api-types/src/v4/user_conv.rs b/crates/api-types/src/v4/user_conv.rs new file mode 100644 index 000000000..10a5863e8 --- /dev/null +++ b/crates/api-types/src/v4/user_conv.rs @@ -0,0 +1,40 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// SPDX-License-Identifier: Apache-2.0 + +use openstack_keystone_core_types::identity as provider_types; + +use crate::v4::user as api_types; + +impl From 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 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, + user_type: value.user_type.map(Into::into), + } + } +} diff --git a/crates/keystone/src/api/v4/user/mod.rs b/crates/keystone/src/api/v4/user/mod.rs index 0d77c850e..ae1235665 100644 --- a/crates/keystone/src/api/v4/user/mod.rs +++ b/crates/keystone/src/api/v4/user/mod.rs @@ -317,6 +317,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; diff --git a/crates/keystone/src/api/v4/user/types.rs b/crates/keystone/src/api/v4/user/types.rs index 6ebd16271..e0d947e33 100644 --- a/crates/keystone/src/api/v4/user/types.rs +++ b/crates/keystone/src/api/v4/user/types.rs @@ -14,6 +14,7 @@ //! User resource types. pub use crate::api::v3::user::types::{ - Federation, FederationProtocol, User, UserCreate, UserCreateRequest, UserList, - UserListParameters, UserOptions, UserResponse, UserUpdateRequest, + Federation, FederationProtocol, User, UserCreate, UserCreateRequest, UserList, UserOptions, + UserResponse, UserUpdateRequest, }; +pub use openstack_keystone_api_types::v4::user::UserListParameters;