Keyvault adding delete key (#1675)

Adding delete key operation
This commit is contained in:
Victor Vazquez 2021-02-18 01:36:13 -08:00 committed by GitHub
parent 5f523f7030
commit cea5e03a17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 737 additions and 14 deletions

View File

@ -634,6 +634,24 @@ namespace Azure { namespace Core { namespace Http {
{
}
/**
* @brief Copy a raw response to construct a new one.
*
* @remark The body stream won't be copied.
*
* @param response A reference for copying the raw response.
*/
RawResponse(RawResponse const& response)
: RawResponse(
response.m_majorVersion,
response.m_minorVersion,
response.m_statusCode,
response.m_reasonPhrase)
{
// Copy body
m_body = response.GetBody();
}
// ===== Methods used to build HTTP response =====
/**

View File

@ -29,6 +29,7 @@ endif()
set(
AZURE_KEYVAULT_COMMON_HEADER
inc/azure/keyvault/common/internal/keyvault_pipeline.hpp
inc/azure/keyvault/common/internal/unix_time_helper.hpp
inc/azure/keyvault/common/keyvault_constants.hpp
inc/azure/keyvault/common/keyvault_exception.hpp
inc/azure/keyvault/common/version.hpp

View File

@ -130,5 +130,31 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Common { n
auto response = SendRequest(context, request);
return Azure::Core::Response<T>(factoryFn(*response), std::move(response));
}
/**
* @brief Create a key vault request and send it using the Azure Core pipeline directly to avoid
* checking the respone code.
*
* @param context A context for cancellation.
* @param method The Http method for the request.
* @param path The path for the request.
* @return A unique ptr to an Http raw response.
*/
std::unique_ptr<Azure::Core::Http::RawResponse> GetResponse(
Azure::Core::Context const& context,
Azure::Core::Http::HttpMethod method,
std::vector<std::string> const& path)
{
auto request = CreateRequest(method, path);
// Use the core pipeline directly to avoid checking the response code.
return m_pipeline.Send(context, request);
}
/**
* @brief Get the Vault Url which was used to create the #KeyVaultPipeline.
*
* @return The vault Url as string.
*/
std::string GetVaultUrl() const { return m_vaultUrl.GetAbsoluteUrl(); }
};
}}}}} // namespace Azure::Security::KeyVault::Common::Internal

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Provides helper method for using unix time.
*
*/
#pragma once
#include <azure/core/datetime.hpp>
#include <chrono>
namespace Azure { namespace Security { namespace KeyVault { namespace Common { namespace Internal {
/**
* @brief Provides convertion methods for unix time to Azure Core Datetime.
*/
class UnixTimeConverter {
public:
/**
* @brief Converts unix time to a #Azure::Core::Datetime.
*
* @param unixTime The number of seconds since 1970.
* @return Calculated Datetime.
*/
static inline Azure::Core::DateTime UnixTimeToDatetime(uint64_t unixTime)
{
return Azure::Core::DateTime(1970) + std::chrono::seconds(unixTime);
}
};
}}}}} // namespace Azure::Security::KeyVault::Common::Internal

View File

@ -10,9 +10,9 @@
namespace Azure { namespace Security { namespace KeyVault { namespace Common { namespace Details {
/***************** KeyVault headers *****************/
static constexpr char const ContentType[] = "Content-Type";
static constexpr char const ContentType[] = "content-type";
static constexpr char const ApplicationJson[] = "application/json";
static constexpr char const Accept[] = "Accept";
static constexpr char const Accept[] = "accept";
static constexpr char const MsRequestId[] = "x-ms-request-id";
static constexpr char const MsClientRequestId[] = "x-ms-client-request-id";

View File

@ -81,5 +81,14 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Common {
*/
static KeyVaultException CreateFromResponse(
std::unique_ptr<Azure::Core::Http::RawResponse> response);
/**
* @brief Create #Azure::Security::KeyVault::Common::KeyVaultException by parsing the \p
* response reference.
*
* @param response The Http raw response from the network.
* @return KeyVaultException.
*/
static KeyVaultException CreateFromResponse(Azure::Core::Http::RawResponse const& response);
};
}}}} // namespace Azure::Security::KeyVault::Common

View File

@ -30,11 +30,17 @@ inline std::string GetHeaderOrEmptyString(
KeyVaultException KeyVaultException::CreateFromResponse(
std::unique_ptr<Azure::Core::Http::RawResponse> response)
{
std::vector<uint8_t> bodyBuffer = std::move(response->GetBody());
return CreateFromResponse(*response);
}
auto httpStatusCode = response->GetStatusCode();
std::string reasonPhrase = response->GetReasonPhrase();
auto& headers = response->GetHeaders();
KeyVaultException KeyVaultException::CreateFromResponse(
Azure::Core::Http::RawResponse const& response)
{
std::vector<uint8_t> bodyBuffer = std::move(response.GetBody());
auto httpStatusCode = response.GetStatusCode();
std::string reasonPhrase = response.GetReasonPhrase();
auto& headers = response.GetHeaders();
std::string requestId = GetHeaderOrEmptyString(headers, Details::MsRequestId);
std::string clientRequestId = GetHeaderOrEmptyString(headers, Details::MsClientRequestId);
std::string contentType = GetHeaderOrEmptyString(headers, Details::ContentType);
@ -62,6 +68,6 @@ KeyVaultException KeyVaultException::CreateFromResponse(
result.RequestId = std::move(requestId);
result.ErrorCode = std::move(errorCode);
result.Message = std::move(message);
result.RawResponse = std::move(response);
result.RawResponse = std::make_unique<Azure::Core::Http::RawResponse>(response);
return result;
}

View File

@ -28,6 +28,8 @@ endif()
set(
AZURE_KEYVAULT_KEYS_HEADER
inc/azure/keyvault/keys/delete_key_operation.hpp
inc/azure/keyvault/keys/deleted_key.hpp
inc/azure/keyvault/keys/json_web_key.hpp
inc/azure/keyvault/keys/key_client.hpp
inc/azure/keyvault/keys/key_constants.hpp
@ -44,6 +46,8 @@ set(
set(
AZURE_KEYVAULT_KEYS_SOURCE
src/delete_key_operation.cpp
src/deleted_key.cpp
src/key_client.cpp
src/key_request_parameters.cpp
src/key_type.cpp

View File

@ -8,6 +8,8 @@
#pragma once
#include "azure/keyvault/keys/delete_key_operation.hpp"
#include "azure/keyvault/keys/deleted_key.hpp"
#include "azure/keyvault/keys/dll_import_export.hpp"
#include "azure/keyvault/keys/json_web_key.hpp"
#include "azure/keyvault/keys/key_client.hpp"

View File

@ -0,0 +1,122 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief A long-running operation for deleting a Key.
*
*/
#pragma once
#include <azure/core/http/http.hpp>
#include <azure/core/operation.hpp>
#include <azure/core/operation_status.hpp>
#include <azure/core/response.hpp>
#include <azure/keyvault/common/internal/keyvault_pipeline.hpp>
#include <azure/keyvault/common/keyvault_exception.hpp>
#include "azure/keyvault/keys/deleted_key.hpp"
#include <memory>
#include <string>
#include <thread>
namespace Azure { namespace Security { namespace KeyVault { namespace Keys {
/**
* @brief A long running operation to delete a key.
*
*/
class DeleteKeyOperation
: public Azure::Core::Operation<Azure::Security::KeyVault::Keys::DeletedKey> {
private:
/* DeleteKeyOperation can be constructed only by friends classes (internal creation). The
* constructor is private and requires internal components.*/
friend class KeyClient;
std::shared_ptr<Azure::Security::KeyVault::Common::Internal::KeyVaultPipeline> m_pipeline;
Azure::Security::KeyVault::Keys::DeletedKey m_value;
std::unique_ptr<Azure::Core::Http::RawResponse> m_rawResponse;
std::string m_continuationToken;
/* This is the implementation for checking the status of a deleted key. The key is considered
* deleted if querying /deletedkeys/keyName returns 200 from server. Or whenever soft-delete is
* disabled.*/
std::unique_ptr<Azure::Core::Http::RawResponse> PollInternal(
Azure::Core::Context& context) override;
Azure::Core::Response<Azure::Security::KeyVault::Keys::DeletedKey> PollUntilDoneInternal(
Azure::Core::Context& context,
std::chrono::milliseconds period) override
{
while (true)
{
m_rawResponse = Poll(context);
if (IsDone())
{
break;
}
std::this_thread::sleep_for(period);
}
return Azure::Core::Response<Azure::Security::KeyVault::Keys::DeletedKey>(
m_value, std::make_unique<Azure::Core::Http::RawResponse>(*m_rawResponse));
}
/*
* Only friend classes are permitted to construct a DeleteOperation. This is because a
* KeyVaultPipelne is required and it is not exposed to customers.
*
* Since C++ doesn't offer `internal` access, we use friends-only instead.
*/
DeleteKeyOperation(
std::shared_ptr<Azure::Security::KeyVault::Common::Internal::KeyVaultPipeline>
keyvaultPipeline,
Azure::Core::Response<Azure::Security::KeyVault::Keys::DeletedKey> response)
: m_pipeline(keyvaultPipeline)
{
if (!response.HasValue())
{
throw Azure::Security::KeyVault::Common::KeyVaultException(
"The response does not contain a value.");
}
// The response becomes useless and the value and rawResponse are now owned by the
// DeleteKeyOperation. This is fine because the DeleteKeyOperation is what the delete key api
// will return.
m_value = response.ExtractValue();
m_rawResponse = response.ExtractRawResponse();
// Build the full url for continuation token. It is only used in case customers wants to use
// it on their own. The Operation uses the KeyVaultPipeline from the client which knows how to
// build this url.
m_continuationToken = m_pipeline->GetVaultUrl() + "/" + std::string(Details::DeletedKeysPath)
+ "/" + m_value.Name();
// The recoveryId is only returned if soft-delete is enabled.
// The LRO is considered completed for non soft-delete (key will be eventually removed).
if (m_value.RecoveryId.empty())
{
m_status = Azure::Core::OperationStatus::Succeeded;
}
}
public:
/**
* @brief Get the #DeletedKey object.
*
* @remark The deleted key contains the recovery id if the key can be recovered.
*
* @return A deleted key object.
*/
Azure::Security::KeyVault::Keys::DeletedKey Value() const override { return m_value; }
/**
* @brief Get an Url as string which can be used to get the status of the delete key operation.
*
* @return std::string
*/
std::string GetResumeToken() const override { return m_continuationToken; }
};
}}}} // namespace Azure::Security::KeyVault::Keys

View File

@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
/**
* @brief Represents a Key Vault key that has been deleted, allowing it to be recovered, if needed.
*
*/
#pragma once
#include <azure/core/datetime.hpp>
#include "azure/keyvault/keys/key_constants.hpp"
#include "azure/keyvault/keys/key_vault_key.hpp"
namespace Azure { namespace Security { namespace KeyVault { namespace Keys {
/**
* @brief Represents a Key Vault key that has been deleted, allowing it to be recovered, if
* needed.
*
*/
struct DeletedKey : public KeyVaultKey
{
/**
* @brief A recovery url that can be used to recover it.
*
*/
std::string RecoveryId;
/**
* @brief Construct an empty DeletedKey
*
*/
DeletedKey() = default;
/**
* @brief Construct a new Deleted Key object.
*
* @param name THe name of the deleted key.
*/
DeletedKey(std::string name) : KeyVaultKey(name) {}
/**
* @brief Indicate when the key was deleted.
*
*/
Azure::Core::DateTime DeletedDate;
/**
* @brief Indicate when the deleted key will be purged.
*
*/
Azure::Core::DateTime ScheduledPurgeDate;
};
/*********************** Deserializer / Serializer ******************************/
namespace Details {
DeletedKey DeletedKeyDeserialize(
std::string const& name,
Azure::Core::Http::RawResponse const& rawResponse);
} // namespace Details
}}}} // namespace Azure::Security::KeyVault::Keys

View File

@ -11,8 +11,10 @@
#include <azure/core/credentials.hpp>
#include <azure/core/http/http.hpp>
#include <azure/core/response.hpp>
#include <azure/keyvault/common/internal/keyvault_pipeline.hpp>
#include "azure/keyvault/keys/delete_key_operation.hpp"
#include "azure/keyvault/keys/key_client_options.hpp"
#include "azure/keyvault/keys/key_constants.hpp"
#include "azure/keyvault/keys/key_create_options.hpp"
@ -32,7 +34,8 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys {
*/
class KeyClient {
protected:
std::unique_ptr<Azure::Security::KeyVault::Common::Internal::KeyVaultPipeline> m_pipeline;
// Using a shared pipeline for a client to share it with LRO (like delete key)
std::shared_ptr<Azure::Security::KeyVault::Common::Internal::KeyVaultPipeline> m_pipeline;
public:
/**
@ -83,7 +86,7 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys {
return m_pipeline->SendRequest<KeyVaultKey>(
context,
Azure::Core::Http::HttpMethod::Get,
[name](Azure::Core::Http::RawResponse const& rawResponse) {
[&name](Azure::Core::Http::RawResponse const& rawResponse) {
return Details::KeyVaultKeyDeserialize(name, rawResponse);
},
{Details::KeysPath, name, options.Version});
@ -111,10 +114,39 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys {
context,
Azure::Core::Http::HttpMethod::Post,
Details::KeyRequestParameters(keyType, options),
[name](Azure::Core::Http::RawResponse const& rawResponse) {
[&name](Azure::Core::Http::RawResponse const& rawResponse) {
return Details::KeyVaultKeyDeserialize(name, rawResponse);
},
{Details::KeysPath, name, "create"});
}
/**
* @brief Deletes a key of any type from storage in Azure Key Vault.
*
* @remark The delete key operation cannot be used to remove individual versions of a key. This
* operation removes the cryptographic material associated with the key, which means the key is
* not usable for Sign/Verify, Wrap/Unwrap or Encrypt/Decrypt operations. This operation
* requires the keys/delete permission.
*
* @param name The name of the key.
* @param context A cancellation token controlling the request lifetime.
* @return A #DeleteKeyOperation to wait on this long-running operation. If the key is soft
* delete-enabled, you only need to wait for the operation to complete if you need to recover or
* purge the key; otherwise, the key is deleted automatically on purge schedule.
*/
Azure::Security::KeyVault::Keys::DeleteKeyOperation StartDeleteKey(
std::string const& name,
Azure::Core::Context const& context = Azure::Core::Context()) const
{
return Azure::Security::KeyVault::Keys::DeleteKeyOperation(
m_pipeline,
m_pipeline->SendRequest<Azure::Security::KeyVault::Keys::DeletedKey>(
context,
Azure::Core::Http::HttpMethod::Delete,
[&name](Azure::Core::Http::RawResponse const& rawResponse) {
return Details::DeletedKeyDeserialize(name, rawResponse);
},
{Details::KeysPath, name}));
}
};
}}}} // namespace Azure::Security::KeyVault::Keys

View File

@ -52,4 +52,9 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys { nam
constexpr static const char OctValue[] = "oct";
constexpr static const char OctHsmValue[] = "oct-HSM";
/***************** Deleted Key *****************/
constexpr static const char RecoveryIdPropertyName[] = "recoveryId";
constexpr static const char DeletedOnPropertyName[] = "deletedDate";
constexpr static const char ScheduledPurgeDatePropertyName[] = "scheduledPurgeDate";
}}}}} // namespace Azure::Security::KeyVault::Keys::Details

View File

@ -19,24 +19,77 @@
namespace Azure { namespace Security { namespace KeyVault { namespace Keys {
/**
* @brief A key resource and its properties.
*
*/
struct KeyVaultKey
{
/**
* @brief The cryptographic key, the key type, and the operations you can perform using the key.
*
*/
JsonWebKey Key;
/**
* @brief The additional properties.
*
*/
KeyProperties Properties;
/**
* @brief Construct an empty Key.
*
*/
KeyVaultKey() = default;
/**
* @brief Construct a new Key Vault Key object.
*
* @param name
*/
KeyVaultKey(std::string name) : Properties(std::move(name)) {}
/**
* @brief Get the Key identifier.
*
* @return The key id.
*/
std::string const& Id() const { return Key.Id; }
/**
* @brief Gets the name of the Key.
*
* @return The name of the key.
*/
std::string const& Name() const { return Properties.Name; }
/**
* @brief Get the Key Type.
*
* @return The type of the Key.
*/
KeyTypeEnum const& GetKeyType() const { return Key.KeyType; }
/**
* @brief Gets the operations you can perform using the key.
*
* @return A vector with the supported operations for the key.
*/
std::vector<KeyOperation> const& KeyOperations() const { return Key.KeyOperations(); }
};
/*********************** Deserializer / Serializer ******************************/
namespace Details {
// Creates a new key based on a name and an http raw response.
KeyVaultKey KeyVaultKeyDeserialize(
std::string const& name,
Azure::Core::Http::RawResponse const& rawResponse);
// Updates a Key based on an Http raw response.
void KeyVaultKeyDeserialize(
KeyVaultKey& key,
Azure::Core::Http::RawResponse const& rawResponse);
} // namespace Details
}}}} // namespace Azure::Security::KeyVault::Keys

View File

@ -0,0 +1,43 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/keyvault/keys/delete_key_operation.hpp"
using namespace Azure::Security::KeyVault::Keys;
namespace {
// For delete key, the LRO ends when we can retreive the Key from the deleted keys list from the
// server.
inline Azure::Core::OperationStatus CheckCompleted(Azure::Core::Http::RawResponse const& response)
{
auto code = response.GetStatusCode();
switch (code)
{
case Azure::Core::Http::HttpStatusCode::Ok:
case Azure::Core::Http::HttpStatusCode::Forbidden: // Access denied but proof the key was
// deleted.
return Azure::Core::OperationStatus::Succeeded;
case Azure::Core::Http::HttpStatusCode::NotFound:
return Azure::Core::OperationStatus::Running;
default:
throw Azure::Security::KeyVault::Common::KeyVaultException::CreateFromResponse(response);
}
}
} // namespace
std::unique_ptr<Azure::Core::Http::RawResponse>
Azure::Security::KeyVault::Keys::DeleteKeyOperation::PollInternal(Azure::Core::Context& context)
{
if (!IsDone())
{
m_rawResponse = m_pipeline->GetResponse(
context, Azure::Core::Http::HttpMethod::Get, {Details::DeletedKeysPath, m_value.Name()});
m_status = CheckCompleted(*m_rawResponse);
}
// To ensure the success of calling Poll multiple times, even after operation is completed, a
// copy of the raw http response is returned instead of transfering the ownership of the raw
// response inside the Operation.
return std::make_unique<Azure::Core::Http::RawResponse>(*m_rawResponse);
}

View File

@ -0,0 +1,36 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// SPDX-License-Identifier: MIT
#include "azure/keyvault/keys/deleted_key.hpp"
#include "azure/keyvault/keys/key_constants.hpp"
#include "azure/keyvault/keys/key_vault_key.hpp"
#include <azure/keyvault/common/internal/unix_time_helper.hpp>
#include <azure/core/internal/json.hpp>
using namespace Azure::Security::KeyVault::Keys;
using Azure::Security::KeyVault::Common::Internal::UnixTimeConverter;
DeletedKey Details::DeletedKeyDeserialize(
std::string const& name,
Azure::Core::Http::RawResponse const& rawResponse)
{
auto body = rawResponse.GetBody();
auto jsonParser = Azure::Core::Internal::Json::json::parse(body);
// "Key"
DeletedKey deletedKey(name);
Details::KeyVaultKeyDeserialize(deletedKey, rawResponse);
// recoveryId
// deletedDate
// scheduledPurgeDate
deletedKey.RecoveryId = jsonParser[Details::RecoveryIdPropertyName].get<std::string>();
deletedKey.DeletedDate = UnixTimeConverter::UnixTimeToDatetime(
jsonParser[Details::DeletedOnPropertyName].get<uint64_t>());
deletedKey.ScheduledPurgeDate = UnixTimeConverter::UnixTimeToDatetime(
jsonParser[Details::ScheduledPurgeDatePropertyName].get<uint64_t>());
return deletedKey;
}

View File

@ -41,6 +41,6 @@ KeyClient::KeyClient(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
Azure::Core::Http::Url url(vaultUrl);
m_pipeline = std::make_unique<Azure::Security::KeyVault::Common::Internal::KeyVaultPipeline>(
m_pipeline = std::make_shared<Azure::Security::KeyVault::Common::Internal::KeyVaultPipeline>(
url, apiVersion, std::move(policies));
}

View File

@ -4,9 +4,12 @@
#include "azure/keyvault/keys/key_vault_key.hpp"
#include "azure/keyvault/keys/key_constants.hpp"
#include <azure/keyvault/common/internal/unix_time_helper.hpp>
#include <azure/core/internal/json.hpp>
using namespace Azure::Security::KeyVault::Keys;
using Azure::Security::KeyVault::Common::Internal::UnixTimeConverter;
namespace {
void ParseStringOperationsToKeyOperations(
@ -23,12 +26,20 @@ void ParseStringOperationsToKeyOperations(
KeyVaultKey Details::KeyVaultKeyDeserialize(
std::string const& name,
Azure::Core::Http::RawResponse const& rawResponse)
{
KeyVaultKey key(name);
Details::KeyVaultKeyDeserialize(key, rawResponse);
return key;
}
void Details::KeyVaultKeyDeserialize(
KeyVaultKey& key,
Azure::Core::Http::RawResponse const& rawResponse)
{
auto body = rawResponse.GetBody();
auto jsonParser = Azure::Core::Internal::Json::json::parse(body);
// "Key"
KeyVaultKey key(name);
auto const& jsonKey = jsonParser[Details::KeyPropertyName];
{
// key_ops
@ -42,6 +53,11 @@ KeyVaultKey Details::KeyVaultKeyDeserialize(
= Details::KeyTypeFromString(jsonKey[Details::KeyTypePropertyName].get<std::string>());
// "Attributes"
{
auto attributes = jsonParser[Details::AttributesPropertyName];
key.Properties.CreatedOn
= UnixTimeConverter::UnixTimeToDatetime(attributes["created"].get<uint64_t>());
}
// "Tags"
auto const& tags = jsonParser[Details::TagsPropertyName];
@ -51,6 +67,4 @@ KeyVaultKey Details::KeyVaultKeyDeserialize(
key.Properties.Tags.emplace(tag.key(), tag.value().get<std::string>());
}
}
return key;
}

View File

@ -10,6 +10,7 @@
#include "key_client_base_test.hpp"
#include <azure/keyvault/key_vault.hpp>
#include <azure/keyvault/keys/key_constants.hpp>
#include <string>
@ -22,6 +23,26 @@ void CheckValidResponse(
auto rawResponse = response.ExtractRawResponse();
EXPECT_EQ(rawResponse->GetStatusCode(), expectedCode);
}
std::string GetNotFoundErrorMsg(std::string const& keyName)
{
return "A key with (name/id) " + keyName
+ " was not found in this key vault. If you recently deleted this key you may be able "
"to recover it using the correct recovery command. For help resolving this issue, "
"please see https://go.microsoft.com/fwlink/?linkid=2125182";
}
std::string GetConflictErrorMsg(std::string const& keyName)
{
return "Key " + keyName
+ " is currently in a deleted but recoverable state, and its name cannot be reused; in this "
"state, the key can only be recovered or purged.";
}
std::string GetConflictDeletingErrorMsg(std::string const& keyName)
{
return "Key " + keyName + " is currently being deleted and cannot be re-created; retry later.";
}
} // namespace
using namespace Azure::Security::KeyVault::Keys::Test;
@ -121,3 +142,237 @@ TEST_F(KeyVaultClientTest, CreateKeyWithTags)
};
}
}
// Test Key Delete.
// The test works for either soft-delete or not, but for non soft-delete, the LRO is completed as
// soon as the Operation returns.
TEST_F(KeyVaultClientTest, DeleteKey)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("deleteThisKey");
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
CheckValidResponse(keyResponse);
auto keyVaultKey = keyResponse.ExtractValue();
EXPECT_EQ(keyVaultKey.Name(), keyName);
}
{
// Setting a timeout context to avoid this test to run up to ctest default timeout
// The polling operation would usually complete in ~20 seconds.
// Setting 3 min as timeout just because I like number 3. We just want to prevent test running
// for so long if something happens and no exception is thrown (paranoid scenario)
auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
auto cancelToken = Azure::Core::GetApplicationContext().WithDeadline(duration);
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
auto expectedStatusToken = m_keyVaultUrl + "/"
+ std::string(Azure::Security::KeyVault::Keys::Details::DeletedKeysPath) + "/" + keyName;
EXPECT_EQ(keyResponseLRO.GetResumeToken(), expectedStatusToken);
// poll each second until key is soft-deleted
// Will throw and fail test if test takes more than 3 minutes (token cancelled)
auto keyResponse = keyResponseLRO.PollUntilDone(cancelToken, std::chrono::milliseconds(1000));
}
}
TEST_F(KeyVaultClientTest, DeleteKeyOperationPoll)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("deleteThisKeyPoll");
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
CheckValidResponse(keyResponse);
auto keyVaultKey = keyResponse.ExtractValue();
EXPECT_EQ(keyVaultKey.Name(), keyName);
}
{
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
auto pollResponse = keyResponseLRO.Poll();
// Expected not completed operation
EXPECT_EQ(
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
pollResponse->GetStatusCode()),
404);
}
}
// Delete key which doesn't exists
TEST_F(KeyVaultClientTest, DeleteInvalidKey)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("thisKeyDoesNotExists");
auto wasThrown = false;
try
{
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
}
catch (Azure::Security::KeyVault::Common::KeyVaultException const& error)
{
EXPECT_EQ(
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
error.StatusCode),
404);
EXPECT_EQ(error.Message, GetNotFoundErrorMsg(keyName));
EXPECT_EQ(error.ErrorCode, "KeyNotFound");
wasThrown = true;
}
catch (std::exception const&)
{
throw;
}
EXPECT_TRUE(wasThrown);
}
TEST_F(KeyVaultClientTest, DoubleDelete)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("DeleteMeTwoTimes");
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
}
{
auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
auto cancelToken = Azure::Core::GetApplicationContext().WithDeadline(duration);
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
auto keyResponse = keyResponseLRO.PollUntilDone(cancelToken, std::chrono::milliseconds(1000));
}
// delete same key again
auto wasThrown = false;
try
{
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
}
catch (Azure::Security::KeyVault::Common::KeyVaultException const& error)
{
EXPECT_EQ(
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
error.StatusCode),
404);
EXPECT_EQ(error.Message, GetNotFoundErrorMsg(keyName));
EXPECT_EQ(error.ErrorCode, "KeyNotFound");
wasThrown = true;
}
catch (std::exception const&)
{
throw;
}
EXPECT_TRUE(wasThrown);
}
TEST_F(KeyVaultClientTest, DoubleDeleteBeforePollComplete)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("DeleteMeBeforePollComplete1");
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
}
{
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
}
// delete same key again before waitting for poll complete
auto wasThrown = false;
try
{
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
}
catch (Azure::Security::KeyVault::Common::KeyVaultException const& error)
{
EXPECT_EQ(
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
error.StatusCode),
404);
EXPECT_EQ(error.Message, GetNotFoundErrorMsg(keyName));
EXPECT_EQ(error.ErrorCode, "KeyNotFound");
wasThrown = true;
}
catch (std::exception const&)
{
throw;
}
EXPECT_TRUE(wasThrown);
}
TEST_F(KeyVaultClientTest, CreateDeletedKey)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("YouCanCreateMeAfterYouDeletedMe");
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
}
{
auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3);
auto cancelToken = Azure::Core::GetApplicationContext().WithDeadline(duration);
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
auto keyResponse = keyResponseLRO.PollUntilDone(cancelToken, std::chrono::milliseconds(1000));
}
// Create a key with same name
auto wasThrown = false;
try
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
}
catch (Azure::Security::KeyVault::Common::KeyVaultException const& error)
{
EXPECT_EQ(
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
error.StatusCode),
409);
EXPECT_EQ(error.Message, GetConflictErrorMsg(keyName));
EXPECT_EQ(error.ErrorCode, "Conflict");
wasThrown = true;
}
catch (std::exception const&)
{
throw;
}
EXPECT_TRUE(wasThrown);
}
TEST_F(KeyVaultClientTest, CreateDeletedKeyBeforePollComplete)
{
Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential);
std::string keyName("YouCanCreateMeAfterYouDeletedMeEvenBeforePollComplete");
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
}
{
auto keyResponseLRO = keyClient.StartDeleteKey(keyName);
}
// Create a key with same name
auto wasThrown = false;
try
{
auto keyResponse
= keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::KeyTypeEnum::Ec);
}
catch (Azure::Security::KeyVault::Common::KeyVaultException const& error)
{
EXPECT_EQ(
static_cast<typename std::underlying_type<Azure::Core::Http::HttpStatusCode>::type>(
error.StatusCode),
409);
EXPECT_EQ(error.Message, GetConflictDeletingErrorMsg(keyName));
EXPECT_EQ(error.ErrorCode, "Conflict");
wasThrown = true;
}
catch (std::exception const&)
{
throw;
}
EXPECT_TRUE(wasThrown);
}