Skip to content

Commit

Permalink
make create entity and group closer to the specification
Browse files Browse the repository at this point in the history
  • Loading branch information
stormshield-gt committed Mar 4, 2024
1 parent 660a4eb commit 74bd058
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 41 deletions.
48 changes: 48 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,54 @@ where
.ok_or(ClientError::ResponseDataEmptyError)
}

/// Executes an [Endpoint] and if it returns an empty HTTP response, it returns `None`, otherwise it returns the result.
///
/// This function is mostly useful for endpoints that can both *update* and *create*.
/// The *create* operation will return a result and the *update* one not.
///
/// If there is a result, a few operations are performed on it:
///
/// * Any potential API error responses from the execution are searched for and,
/// if found, converted to a [ClientError::APIError]
/// * All other errors are mapped from [rustify::errors::ClientError] to
/// [ClientError::RestClientError]
/// * An empty content body from the execution is rejected and a
/// [ClientError::ResponseEmptyError] is returned instead
/// * The enclosing [EndpointResult] is stripped off and any warnings found in
/// the result are logged
/// * An empty `data` field in the [EndpointResult] is rejected and a
/// [ClientError::ResponseDataEmptyError] is returned instead
/// * The value from the enclosed `data` field is returned along with any
/// propagated errors.
pub async fn exec_with_result_or_empty<E>(
client: &impl Client,
endpoint: E,
) -> Result<Option<E::Response>, ClientError>
where
E: Endpoint,
{
info!(
"Executing {} and expecting maybe a response",
endpoint.path()
);
endpoint
.with_middleware(client.middle())
.exec(client.http())
.await
.map(|res| {
if res.response.body().is_empty() {
Ok(None)
} else {
res.wrap::<EndpointResult<_>>()
.map_err(ClientError::from)
.map(strip)?
.map(Some)
.ok_or(ClientError::ResponseDataEmptyError)
}
})
.map_err(parse_err)?
}

/// Executes the given endpoint but requests that the Vault server to return a
/// token wrapped response.
///
Expand Down
13 changes: 9 additions & 4 deletions src/api/identity/entity/requests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use serde::Serialize;
use std::{collections::HashMap, fmt::Debug};

use super::responses::{
ListEntitiesByIdResponse, ListEntitiesByNameResponse, ReadEntityByIdResponse,
ReadEntityByNameResponse,
CreateEntityResponse, ListEntitiesByIdResponse, ListEntitiesByNameResponse,
ReadEntityByIdResponse, ReadEntityByNameResponse,
};

/// ## Create an entity
Expand All @@ -15,11 +15,16 @@ use super::responses::{
/// * Method: POST
/// * Reference: https://developer.hashicorp.com/vault/api-docs/secret/identity/entity#create-an-entity
#[derive(Builder, Debug, Default, Endpoint)]
#[endpoint(path = "identity/entity", method = "POST", builder = "true")]
#[endpoint(
path = "identity/entity",
method = "POST",
builder = "true",
response = "CreateEntityResponse"
)]
#[builder(setter(into, strip_option), default)]
pub struct CreateEntityRequest {
/// Name of the entity.
pub name: String,
pub name: Option<String>,
/// ID of the entity. If set, updates the corresponding existing entity.
pub id: Option<String>,
/// Metadata to be associated with the entity.
Expand Down
8 changes: 8 additions & 0 deletions src/api/identity/entity/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ use std::collections::HashMap;

use serde::{Deserialize, Serialize};

/// Response from executing
/// [CreateEntityRequest](crate::api::identity::requests::CreateEntityRequest)
#[derive(Deserialize, Debug, Serialize)]
pub struct CreateEntityResponse {
pub id: String,
pub alias: Option<Vec<Alias>>,
}

/// Response from executing
/// [ReadEntityByIdRequest](crate::api::identity::requests::ReadEntityByIdRequest)
#[derive(Deserialize, Debug, Serialize)]
Expand Down
11 changes: 8 additions & 3 deletions src/api/identity/group/requests.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::responses::{
ListGroupsByIdResponse, ListGroupsByNameResponse, ReadGroupByIdResponse,
CreateGroupResponse, ListGroupsByIdResponse, ListGroupsByNameResponse, ReadGroupByIdResponse,
ReadGroupByNameResponse,
};
use rustify_derive::Endpoint;
Expand All @@ -14,11 +14,16 @@ use std::{collections::HashMap, fmt::Debug};
/// * Method: POST
/// * Reference: https://developer.hashicorp.com/vault/api-docs/secret/identity/group#create-a-group
#[derive(Builder, Debug, Default, Endpoint, Serialize)]
#[endpoint(path = "identity/group", method = "POST", builder = "true")]
#[endpoint(
path = "identity/group",
method = "POST",
builder = "true",
response = "CreateGroupResponse"
)]
#[builder(setter(into, strip_option), default)]
pub struct CreateGroupRequest {
/// Name of the group. If set (and ID is not set), updates the corresponding existing group.
pub name: String,
pub name: Option<String>,
/// ID of the group. If set, updates the corresponding existing group.
pub id: Option<String>,
/// Type of the group, internal or external. Defaults to internal.
Expand Down
8 changes: 8 additions & 0 deletions src/api/identity/group/responses.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ use std::collections::HashMap;

use serde::{Deserialize, Serialize};

/// Response from executing
/// [CreateGroupRequest](crate::api::identity::requests::CreateGroupRequest)
#[derive(Deserialize, Debug, Serialize)]
pub struct CreateGroupResponse {
pub id: String,
pub name: Option<String>,
}

/// Response from executing
/// [ReadGroupByIdRequest](crate::api::identity::requests::ReadGroupByIdRequest)
#[derive(Deserialize, Debug, Serialize)]
Expand Down
14 changes: 7 additions & 7 deletions src/identity/entity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,27 +12,27 @@ use crate::{
ReadEntityByNameRequest, UpdateEntityByIdRequest, UpdateEntityByIdRequestBuilder,
},
responses::{
ListEntitiesByIdResponse, ListEntitiesByNameResponse, ReadEntityByIdResponse,
ReadEntityByNameResponse,
CreateEntityResponse, ListEntitiesByIdResponse, ListEntitiesByNameResponse,
ReadEntityByIdResponse, ReadEntityByNameResponse,
},
},
},
client::Client,
error::ClientError,
};

/// Creates an entity with the given `name`.
/// Create or update an entity. If the entity already exists, it would update it, and
/// maybe return `None` in that case.
///
/// See [CreateEntityRequest]
#[instrument(skip(client, opts), err)]
pub async fn create(
client: &impl Client,
name: &str,
opts: Option<&mut CreateEntityRequestBuilder>,
) -> Result<(), ClientError> {
) -> Result<Option<CreateEntityResponse>, ClientError> {
let mut t = CreateEntityRequest::builder();
let endpoint = opts.unwrap_or(&mut t).name(name).build().unwrap();
api::exec_with_empty(client, endpoint).await
let endpoint = opts.unwrap_or(&mut t).build().unwrap();
api::exec_with_result_or_empty(client, endpoint).await
}

/// Reads entity by `id`.
Expand Down
11 changes: 5 additions & 6 deletions src/identity/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ use crate::{
ReadGroupByNameRequest, UpdateGroupByIdRequest, UpdateGroupByIdRequestBuilder,
},
responses::{
ListGroupsByIdResponse, ListGroupsByNameResponse, ReadGroupByIdResponse,
ReadGroupByNameResponse,
CreateGroupResponse, ListGroupsByIdResponse, ListGroupsByNameResponse,
ReadGroupByIdResponse, ReadGroupByNameResponse,
},
},
},
Expand All @@ -24,12 +24,11 @@ use crate::{
#[instrument(skip(client, opts), err)]
pub async fn create(
client: &impl Client,
name: &str,
opts: Option<&mut CreateGroupRequestBuilder>,
) -> Result<(), ClientError> {
) -> Result<Option<CreateGroupResponse>, ClientError> {
let mut t = CreateGroupRequest::builder();
let endpoint = opts.unwrap_or(&mut t).name(name).build().unwrap();
api::exec_with_empty(client, endpoint).await
let endpoint = opts.unwrap_or(&mut t).build().unwrap();
api::exec_with_result_or_empty(client, endpoint).await
}

/// Reads group by `id`.
Expand Down
79 changes: 58 additions & 21 deletions tests/identity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ fn test_entity_and_entity_alias() {
let client = server.client();

let entity_id = test_create_entity(&client).await;
create_anonymous_entity(&client).await;
test_list_entity_by_id(&client, &entity_id).await;
test_read_entity_by_id(&client, &entity_id).await;
test_update_entity_by_id(&client, &entity_id).await;
Expand Down Expand Up @@ -93,8 +94,11 @@ fn test_group_and_group_alias() {
async fn test_create_entity(client: &VaultClient) -> String {
identity::entity::create(
client,
ENTITY_NAME,
Some(&mut CreateEntityRequestBuilder::default().policies(vec![POLICY.to_string()])),
Some(
&mut CreateEntityRequestBuilder::default()
.policies(vec![POLICY.to_string()])
.name(ENTITY_NAME),
),
)
.await
.unwrap();
Expand All @@ -104,24 +108,39 @@ async fn test_create_entity(client: &VaultClient) -> String {

assert!(!entity.disabled);

// This really update the entity but the Vault server return a response with an empty body.
identity::entity::create(
client,
ENTITY_NAME,
Some(
&mut CreateEntityRequestBuilder::default()
.disabled(true)
.name(ENTITY_NAME)
.id(&entity.id),
),
)
.await
.unwrap();

let entity = identity::entity::read_by_name(client, ENTITY_NAME)
.await
.unwrap();
assert!(entity.disabled);
entity.id
}

async fn create_anonymous_entity(client: &VaultClient) {
// Without specifying anything Vault will create an entity for us and make sure it got an unique id.
let entity = identity::entity::create(client, None)
.await
.unwrap()
.unwrap();
assert!(!entity.id.is_empty());
assert!(entity.alias.is_none());
identity::entity::delete_by_id(client, &entity.id)
.await
.unwrap();
}

async fn test_read_entity_by_id(client: &VaultClient, expected_id: &str) {
let entity = identity::entity::read_by_id(client, expected_id)
.await
Expand Down Expand Up @@ -208,12 +227,18 @@ async fn test_delete_entity_by_name(client: &VaultClient) {
}

async fn test_batch_delete_entity(client: &VaultClient) {
identity::entity::create(client, "test-entity1", None)
.await
.unwrap();
identity::entity::create(client, "test-entity2", None)
.await
.unwrap();
identity::entity::create(
client,
Some(CreateEntityRequestBuilder::default().name("test-entity1")),
)
.await
.unwrap();
identity::entity::create(
client,
Some(CreateEntityRequestBuilder::default().name("test-entity2")),
)
.await
.unwrap();
let entity1 = identity::entity::read_by_name(client, "test-entity1")
.await
.unwrap();
Expand Down Expand Up @@ -242,15 +267,24 @@ async fn test_batch_delete_entity(client: &VaultClient) {
}

async fn test_merge_entity(client: &VaultClient) {
identity::entity::create(client, "test-entity1", None)
.await
.unwrap();
identity::entity::create(client, "test-entity2", None)
.await
.unwrap();
identity::entity::create(client, "test-entity3", None)
.await
.unwrap();
identity::entity::create(
client,
Some(CreateEntityRequestBuilder::default().name("test-entity1")),
)
.await
.unwrap();
identity::entity::create(
client,
Some(CreateEntityRequestBuilder::default().name("test-entity2")),
)
.await
.unwrap();
identity::entity::create(
client,
Some(CreateEntityRequestBuilder::default().name("test-entity3")),
)
.await
.unwrap();
let entity1 = identity::entity::read_by_name(client, "test-entity1")
.await
.unwrap();
Expand Down Expand Up @@ -376,8 +410,11 @@ async fn test_list_entity_alias_by_id(client: &VaultClient, alias_id: &str, expe
async fn test_create_group(client: &VaultClient) -> String {
identity::group::create(
client,
GROUP_NAME,
Some(&mut CreateGroupRequestBuilder::default().policies(vec![POLICY.to_string()])),
Some(
&mut CreateGroupRequestBuilder::default()
.policies(vec![POLICY.to_string()])
.name(GROUP_NAME),
),
)
.await
.unwrap();
Expand All @@ -391,10 +428,10 @@ async fn test_create_group(client: &VaultClient) -> String {
// This one it's called in "updating mode".
identity::group::create(
client,
GROUP_NAME,
Some(
&mut CreateGroupRequestBuilder::default()
.metadata(metadata.clone())
.name(GROUP_NAME)
.id(&group.id),
),
)
Expand Down

0 comments on commit 74bd058

Please sign in to comment.