Skip to content

Commit

Permalink
[OSS-116] Add support for CRUD operations on RabbitMq Users (#51)
Browse files Browse the repository at this point in the history
  • Loading branch information
stefandanaita authored Oct 14, 2024
1 parent 8992deb commit 8fdd7b8
Show file tree
Hide file tree
Showing 3 changed files with 295 additions and 4 deletions.
90 changes: 86 additions & 4 deletions src/api/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ pub trait UserApi {

async fn list_users(&self) -> Result<Vec<RabbitMqUser>, RabbitMqClientError>;

async fn get_user(&self, name: String) -> Result<RabbitMqUser, RabbitMqClientError>;

async fn create_user(&self, user: RabbitMqUserCreateRequest)
-> Result<(), RabbitMqClientError>;

async fn delete_user(&self, name: String) -> Result<(), RabbitMqClientError>;

async fn list_users_without_permissions(
&self,
) -> Result<Vec<RabbitMqUser>, RabbitMqClientError>;
Expand Down Expand Up @@ -55,6 +62,50 @@ impl UserApi for RabbitMqClient {
handle_response(response).await
}

#[tracing::instrument(skip(self))]
async fn get_user(&self, name: String) -> Result<RabbitMqUser, RabbitMqClientError> {
let response = self
.client
.request(
reqwest::Method::GET,
format!("{}/api/users/{}", self.api_url, name),
)
.send()
.await?;

handle_response(response).await
}

async fn create_user(
&self,
user: RabbitMqUserCreateRequest,
) -> Result<(), RabbitMqClientError> {
let response = self
.client
.request(
reqwest::Method::PUT,
format!("{}/api/users/{}", self.api_url, user.name),
)
.json(&user)
.send()
.await?;

handle_empty_response(response).await
}

async fn delete_user(&self, name: String) -> Result<(), RabbitMqClientError> {
let response = self
.client
.request(
reqwest::Method::DELETE,
format!("{}/api/users/{}", self.api_url, name),
)
.send()
.await?;

handle_empty_response(response).await
}

#[tracing::instrument(skip(self))]
async fn list_users_without_permissions(
&self,
Expand All @@ -79,7 +130,7 @@ impl UserApi for RabbitMqClient {
let response = self
.client
.request(
reqwest::Method::DELETE,
reqwest::Method::POST,
format!("{}/api/users/bulk-delete", self.api_url),
)
.json(&users)
Expand Down Expand Up @@ -127,18 +178,49 @@ impl UserApi for RabbitMqClient {
#[derive(Debug, Deserialize)]
pub struct RabbitMqWhoAmI {
pub name: String,
pub tags: Vec<String>,
pub tags: Vec<RabbitMqUserTag>,
}

#[derive(Debug, Deserialize)]
pub struct RabbitMqUser {
pub name: String,
pub password_hash: String,
pub hashing_algorithm: String,
pub tags: Vec<String>,
pub hashing_algorithm: RabbitMqHashingAlgorithm,
pub tags: Vec<RabbitMqUserTag>,
}

#[derive(Debug, Deserialize, Serialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum RabbitMqUserTag {
Administrator,
Management,
Monitoring,
}

#[derive(Debug, Serialize)]
pub struct RabbitMqUserCreateRequest {
#[serde(skip_serializing)]
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub password: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub password_hash: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub hashing_algorithm: Option<RabbitMqHashingAlgorithm>,
pub tags: Vec<RabbitMqUserTag>,
}

#[derive(Debug, Serialize)]
pub struct RabbitMqUsersBulkDeleteRequest {
pub users: Vec<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub enum RabbitMqHashingAlgorithm {
#[serde(rename = "rabbit_password_hashing_sha256")]
RabbitPasswordHashingSha256,
#[serde(rename = "rabbit_password_hashing_sha512")]
RabbitPasswordHashingSha512,
#[serde(rename = "rabbit_password_hashing_md5")]
RabbitPasswordHashingMd5,
}
1 change: 1 addition & 0 deletions tests/all/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ mod exchanges;
mod messages;
mod nodes;
mod queues;
mod users;
mod vhosts;
208 changes: 208 additions & 0 deletions tests/all/users.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
use crate::context::TestContext;
use rabbitmq_management_client::api::user::{
RabbitMqUserCreateRequest, RabbitMqUserTag, RabbitMqUsersBulkDeleteRequest, UserApi,
};
use rabbitmq_management_client::errors::RabbitMqClientError;
use uuid::Uuid;

#[tokio::test]
async fn can_list_users() {
let ctx = TestContext::new();

let users = ctx
.rabbitmq
.list_users()
.await
.expect("failed to list users");

assert!(!users.is_empty());
}

#[tokio::test]
async fn can_crud_users() {
let ctx = TestContext::new();

let admin_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: admin_uuid.clone().to_string(),
password: Some("fake_admin_password".to_string()),
password_hash: None,
hashing_algorithm: None,
tags: vec![RabbitMqUserTag::Administrator],
})
.await
.expect("failed to create admin user");

let admin = ctx
.rabbitmq
.get_user(admin_uuid.clone().to_string())
.await
.expect("failed to get admin user");

assert_eq!(admin.name, admin_uuid.clone().to_string());
assert!(admin.tags.contains(&RabbitMqUserTag::Administrator));

ctx.rabbitmq
.delete_user(admin_uuid.clone().to_string())
.await
.expect("failed to delete admin user");

// Getting the admin user should error
let deleted_admin = ctx.rabbitmq.get_user(admin_uuid.to_string()).await;

assert!(matches!(
deleted_admin,
Err(RabbitMqClientError::NotFound(_))
));

let mnm_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: mnm_uuid.clone().to_string(),
password: None,
password_hash: None,
hashing_algorithm: None,
tags: vec![RabbitMqUserTag::Management, RabbitMqUserTag::Monitoring],
})
.await
.expect("failed to create the management & monitoring user");

let mnm = ctx
.rabbitmq
.get_user(mnm_uuid.clone().to_string())
.await
.expect("failed to get mnm user");

assert_eq!(mnm.name, mnm_uuid.clone().to_string());
assert!(mnm.tags.contains(&RabbitMqUserTag::Management));
assert!(mnm.tags.contains(&RabbitMqUserTag::Monitoring));

ctx.rabbitmq
.delete_user(mnm_uuid.clone().to_string())
.await
.expect("failed to delete the management & monitoring user");

// Getting the admin user should error
let deleted_mnm = ctx.rabbitmq.get_user(mnm_uuid.to_string()).await;

assert!(matches!(deleted_mnm, Err(RabbitMqClientError::NotFound(_))));
}

#[tokio::test]
async fn can_create_empty_password_hash_user() {
let ctx = TestContext::new();

let user_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: user_uuid.clone().to_string(),
password: None,
password_hash: Some("".to_string()),
hashing_algorithm: None,
tags: vec![RabbitMqUserTag::Administrator],
})
.await
.expect("failed to create user");

let user = ctx
.rabbitmq
.get_user(user_uuid.clone().to_string())
.await
.expect("failed to get user");

assert_eq!(user.name, user_uuid.clone().to_string());
assert_eq!(user.password_hash, "");

ctx.rabbitmq
.delete_user(user_uuid.to_string())
.await
.expect("failed to delete user");
}

#[tokio::test]
async fn can_bulk_delete_users() {
let ctx = TestContext::new();

let original_users = ctx
.rabbitmq
.list_users()
.await
.expect("failed to list users");

let foo_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: foo_uuid.clone().to_string(),
password: None,
password_hash: None,
hashing_algorithm: None,
tags: vec![],
})
.await
.expect("failed to create user");

let bar_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: bar_uuid.clone().to_string(),
password: None,
password_hash: None,
hashing_algorithm: None,
tags: vec![],
})
.await
.expect("failed to create user");

let baz_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: baz_uuid.clone().to_string(),
password: None,
password_hash: None,
hashing_algorithm: None,
tags: vec![],
})
.await
.expect("failed to create user");

let qux_uuid = Uuid::new_v4();
ctx.rabbitmq
.create_user(RabbitMqUserCreateRequest {
name: qux_uuid.clone().to_string(),
password: None,
password_hash: None,
hashing_algorithm: None,
tags: vec![],
})
.await
.expect("failed to create user");

let new_users = ctx
.rabbitmq
.list_users()
.await
.expect("failed to list users");

assert_eq!(original_users.len(), new_users.len() - 4);

ctx.rabbitmq
.bulk_delete_users(RabbitMqUsersBulkDeleteRequest {
users: vec![
foo_uuid.to_string(),
bar_uuid.to_string(),
baz_uuid.to_string(),
qux_uuid.to_string(),
],
})
.await
.expect("failed to bulk delete users");

let after_delete_users = ctx
.rabbitmq
.list_users()
.await
.expect("failed to list users");

assert_eq!(original_users.len(), after_delete_users.len());
}

0 comments on commit 8fdd7b8

Please sign in to comment.