From 65d560dc11239d166e2b89f6c8ddb2bec8356365 Mon Sep 17 00:00:00 2001 From: George Arama <50641385+gearama@users.noreply.github.com> Date: Tue, 27 Jul 2021 13:10:14 -0700 Subject: [PATCH] GetDeletedSecret API (#2666) * fix doc warnings * fix doc warnings * GetDeletedSecretAPI * undo uneeded changes * comments --- .../CMakeLists.txt | 1 + .../secrets/keyvault_deleted_secret.hpp | 48 +++++++++++++++ .../keyvault/secrets/keyvault_secret.hpp | 19 +++++- .../azure/keyvault/secrets/secret_client.hpp | 27 ++++++++- .../src/private/secret_constants.hpp | 6 ++ .../src/private/secret_serializers.hpp | 20 ++++++- .../src/secret_client.cpp | 14 +++++ .../src/secret_serializers.cpp | 45 +++++++++++++- .../test/sample/test_app.cpp | 2 + .../ut/secret_get_client_deserialize_test.cpp | 38 +++++++++++- .../ut/secret_get_client_deserialize_test.hpp | 59 ++++++++++++++++--- 11 files changed, 263 insertions(+), 16 deletions(-) create mode 100644 sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_deleted_secret.hpp diff --git a/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt index a7de07a1c..2f569c5f6 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt @@ -32,6 +32,7 @@ set( inc/azure/keyvault/secrets/secret_client.hpp inc/azure/keyvault/secrets/keyvault_secret.hpp inc/azure/keyvault/keyvault_secrets.hpp + inc/azure/keyvault/secrets/keyvault_deleted_secret.hpp ) set( diff --git a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_deleted_secret.hpp b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_deleted_secret.hpp new file mode 100644 index 000000000..17e0f26e4 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_deleted_secret.hpp @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Keyvault Deleted Secret definition + */ + +#pragma once +#include +#include + +namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { + /** + * @brief A Deleted Secret consisting of its previous id, attributes and its tags, + * as well as information on when it will be purged. + */ + struct KeyVaultDeletedSecret : public KeyVaultSecret + { + /** + * @brief A Deleted Secret consisting of its previous id, attributes and its tags, + * as well as information on when it will be purged. + */ + std::string RecoveryId; + + /** + * @brief The time when the secret is scheduled to be purged, in UTC. + */ + Azure::DateTime ScheduledPurgeDate; + + /** + * @brief The time when the secret was deleted, in UTC. + */ + Azure::DateTime DeletedDate; + + /** + * @brief Default constructor. + */ + KeyVaultDeletedSecret() = default; + + /** + * @brief Constructor. + * + * @param name Name of the deleted secret. + */ + KeyVaultDeletedSecret(std::string name) : KeyVaultSecret(std::move(name)) {} + }; +}}}} // namespace Azure::Security::KeyVault::Secrets diff --git a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret.hpp b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret.hpp index 94ccd53a8..6bfd19055 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret.hpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret.hpp @@ -10,7 +10,7 @@ #include namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { - struct KeyVaultSecret final + struct KeyVaultSecret { /** * @brief The name of the secret. @@ -50,11 +50,28 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { */ KeyVaultSecret(std::string name, std::string value) : Name(std::move(name)), Value(std::move(value)) + { + if (Name.empty()) + { + throw std::invalid_argument("Name cannot be empty"); + } + + if (Value.empty()) + { + throw std::invalid_argument("Value cannot be empty"); + } + }; + + private: + KeyVaultSecret(std::string name) : Name(std::move(name)) { if (Name.empty()) { throw std::invalid_argument("Name cannot be empty"); } } + + friend struct KeyVaultDeletedSecret; }; + }}}} // namespace Azure::Security::KeyVault::Secrets diff --git a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/secret_client.hpp b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/secret_client.hpp index 5e575c48f..5605abf7f 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/secret_client.hpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/secret_client.hpp @@ -7,12 +7,21 @@ */ #pragma once -#include "../src/private/keyvault_protocol.hpp" + #include "dll_import_export.hpp" -#include +#include "azure/keyvault/secrets/keyvault_deleted_secret.hpp" +#include "azure/keyvault/secrets/keyvault_secret.hpp" +#include +#include +#include + #include +namespace Azure { namespace Security { namespace KeyVault { namespace _detail { + class KeyVaultProtocolClient; +}}}} // namespace Azure::Security::KeyVault::_detail + namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { class ServiceVersion final { @@ -132,6 +141,20 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { std::string const& name, GetSecretOptions const& options = GetSecretOptions(), Azure::Core::Context const& context = Azure::Core::Context()) const; + + /** + * @brief The Get Deleted Secret operation returns + * the specified deleted secret along with its attributes. + * This operation requires the secrets/get permission. + * + * @param name The name of the secret. + * @param context The context for the operation can be used for request cancellation. + * + * @return The Secret wrapped in the Response. + */ + Azure::Response GetDeletedSecret( + std::string const& name, + Azure::Core::Context const& context = Azure::Core::Context()) const; }; }}}} // namespace Azure::Security::KeyVault::Secrets diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_constants.hpp b/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_constants.hpp index c2e752e9c..e9e7d51eb 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_constants.hpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_constants.hpp @@ -13,6 +13,7 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { /***************** KeyVault Secret *****************/ constexpr static const char SecretPath[] = "secrets"; + static constexpr char const DeletedSecretPath[] = "deletedsecrets"; /******************* Secret property names ***********/ @@ -31,6 +32,11 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { constexpr static const char ContentTypePropertyName[] = "contentType"; constexpr static const char RecoverableDaysPropertyName[] = "recoverableDays"; + /**************** Deleted Secret property names ********/ + constexpr static const char RecoveryIdPropertyName[] = "recoveryId"; + constexpr static const char ScheduledPurgeDatePropertyName[] = "scheduledPurgeDate"; + constexpr static const char DeletedDatePropertyName[] = "deletedDate"; + /**************** KeyVault QueryParameters *********/ static constexpr char const ApiVersion[] = "api-version"; }}}}} // namespace Azure::Security::KeyVault::Secrets::_detail diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_serializers.hpp b/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_serializers.hpp index 8cd2ad207..b05e03ad3 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_serializers.hpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/private/secret_serializers.hpp @@ -8,9 +8,10 @@ */ #pragma once +#include "azure/keyvault/secrets/keyvault_deleted_secret.hpp" +#include "azure/keyvault/secrets/keyvault_secret.hpp" #include #include -#include using namespace Azure::Security::KeyVault::Secrets; @@ -76,4 +77,21 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { } } }; + + struct KeyVaultDeletedSecretSerializer final + { + // Creates a new deleted secret based on a name and an HTTP raw response. + static KeyVaultDeletedSecret KeyVaultDeletedSecretDeserialize( + std::string const& name, + Azure::Core::Http::RawResponse const& rawResponse); + + // Create deleted secret from HTTP raw response only. + static KeyVaultDeletedSecret KeyVaultDeletedSecretDeserialize( + Azure::Core::Http::RawResponse const& rawResponse); + + // Updates a deleted secret based on an HTTP raw response. + static void KeyVaultDeletedSecretDeserialize( + KeyVaultDeletedSecret& secret, + Azure::Core::Http::RawResponse const& rawResponse); + }; }}}}} // namespace Azure::Security::KeyVault::Secrets::_detail diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/secret_client.cpp b/sdk/keyvault/azure-security-keyvault-secrets/src/secret_client.cpp index cf390aedc..c2bff5c0c 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/secret_client.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/secret_client.cpp @@ -64,4 +64,18 @@ Azure::Response SecretClient::GetSecret( {_detail::SecretPath, name, options.Version}); } +Azure::Response SecretClient::GetDeletedSecret( + std::string const& name, + Azure::Core::Context const& context) const +{ + return m_protocolClient->SendRequest( + context, + Azure::Core::Http::HttpMethod::Get, + [&name](Azure::Core::Http::RawResponse const& rawResponse) { + return _detail::KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize( + name, rawResponse); + }, + {_detail::DeletedSecretPath, name}); +} + const ServiceVersion ServiceVersion::V7_2("7.2"); diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/secret_serializers.cpp b/sdk/keyvault/azure-security-keyvault-secrets/src/secret_serializers.cpp index 0e7de5477..a84ace0b1 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/secret_serializers.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/secret_serializers.cpp @@ -23,7 +23,8 @@ KeyVaultSecret KeyVaultSecretSerializer::KeyVaultSecretDeserialize( std::string const& name, Azure::Core::Http::RawResponse const& rawResponse) { - KeyVaultSecret secret(name, ""); + KeyVaultSecret secret; + secret.Name = name; _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, rawResponse); return secret; } @@ -45,7 +46,6 @@ void KeyVaultSecretSerializer::KeyVaultSecretDeserialize( auto const& body = rawResponse.GetBody(); auto jsonParser = json::parse(body); - secret.Value = jsonParser[_detail::ValuePropertyName]; secret.Id = jsonParser[_detail::IdPropertyName]; secret.Properties.Id = secret.Id; @@ -103,6 +103,12 @@ void KeyVaultSecretSerializer::KeyVaultSecretDeserialize( secret.Properties.Managed = jsonParser[_detail::ManagedPropertyName].get(); } + // value + if (jsonParser.contains(_detail::ValuePropertyName)) + { + secret.Value = jsonParser[_detail::ValuePropertyName]; + } + // key id JsonOptional::SetIfExists( secret.Properties.KeyId, jsonParser, _detail::KeyIdPropertyName); @@ -111,3 +117,38 @@ void KeyVaultSecretSerializer::KeyVaultSecretDeserialize( JsonOptional::SetIfExists( secret.Properties.ContentType, jsonParser, _detail::ContentTypePropertyName); } + +KeyVaultDeletedSecret KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize( + std::string const& name, + Azure::Core::Http::RawResponse const& rawResponse) +{ + KeyVaultDeletedSecret deletedSecret(name); + KeyVaultDeletedSecretDeserialize(deletedSecret, rawResponse); + return deletedSecret; +} + +// Create deleted secret from HTTP raw response only. +KeyVaultDeletedSecret KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize( + Azure::Core::Http::RawResponse const& rawResponse) +{ + KeyVaultDeletedSecret deletedSecret; + KeyVaultDeletedSecretDeserialize(deletedSecret, rawResponse); + return deletedSecret; +} + +// Updates a deleted secret based on an HTTP raw response. +void KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize( + KeyVaultDeletedSecret& secret, + Azure::Core::Http::RawResponse const& rawResponse) +{ + KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, rawResponse); + + auto const& body = rawResponse.GetBody(); + auto jsonParser = json::parse(body); + + secret.RecoveryId = jsonParser[_detail::RecoveryIdPropertyName]; + secret.ScheduledPurgeDate = PosixTimeConverter::PosixTimeToDateTime( + jsonParser[_detail::ScheduledPurgeDatePropertyName]); + secret.DeletedDate + = PosixTimeConverter::PosixTimeToDateTime(jsonParser[_detail::DeletedDatePropertyName]); +} diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/sample/test_app.cpp b/sdk/keyvault/azure-security-keyvault-secrets/test/sample/test_app.cpp index a0f11b61b..b2119876d 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/test/sample/test_app.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/sample/test_app.cpp @@ -22,5 +22,7 @@ int main() // just a response, with a secret auto response = secretClient.GetSecret("testSecret"); + // just a response, with a secret + auto response2 = secretClient.GetDeletedSecret("someSecret"); return 0; } diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.cpp b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.cpp index 44f8664ac..1d35a5d0e 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.cpp @@ -31,7 +31,7 @@ TEST(KeyVaultSecretSerializer, GetClientDeserializePartial3) { auto response = getPartialResponse(); - KeyVaultSecret secret = KeyVaultSecret("name2", ""); + KeyVaultSecret secret = KeyVaultSecret("name2", "a"); _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, response); runPartialExpect(secret); @@ -59,8 +59,42 @@ TEST(KeyVaultSecretSerializer, GetClientdeserializeFull3) { auto response = getFullResponse(); - KeyVaultSecret secret = KeyVaultSecret("name2", ""); + KeyVaultSecret secret = KeyVaultSecret("name2", "a"); _detail::KeyVaultSecretSerializer::KeyVaultSecretDeserialize(secret, response); runFullExpect(secret); } + +TEST(KeyVaultDeletedSecretSerializer, GetDeletedClientDeserializeFull1) +{ + auto response = getDeletedFullResponse(); + + KeyVaultDeletedSecret secret + = _detail::KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize(response); + + runFullExpect(secret, false); + runDeletedExtras(secret); +} + +TEST(KeyVaultDeletedSecretSerializer, GetDeletedClientDeserializeFull2) +{ + auto response = getDeletedFullResponse(); + + KeyVaultDeletedSecret secret + = _detail::KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize( + "name1", response); + + runFullExpect(secret, false); + runDeletedExtras(secret); +} + +TEST(KeyVaultDeletedSecretSerializer, GetDeletedClientDeserializeFull3) +{ + auto response = getDeletedFullResponse(); + + KeyVaultDeletedSecret secret = KeyVaultDeletedSecret("name2"); + _detail::KeyVaultDeletedSecretSerializer::KeyVaultDeletedSecretDeserialize(secret, response); + + runFullExpect(secret, false); + runDeletedExtras(secret); +} diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.hpp b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.hpp index df9852ace..0856e4899 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.hpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_get_client_deserialize_test.hpp @@ -3,8 +3,8 @@ #include "azure/core/http/http.hpp" #include "azure/core/http/policies/policy.hpp" +#include "azure/keyvault/secrets/keyvault_deleted_secret.hpp" #include "azure/keyvault/secrets/keyvault_secret.hpp" - #include #include @@ -68,10 +68,43 @@ Azure::Core::Http::RawResponse getFullResponse() return response; } -void runPartialExpect(KeyVaultSecret& secret) +Azure::Core::Http::RawResponse getDeletedFullResponse() + { + auto response = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + + constexpr static const uint8_t responseBody[] = R"json({ + "recoveryId": "https://myvault.vault.azure.net/deletedsecrets/GetDeletedSecretTest", + "deletedDate": 1493938433, + "scheduledPurgeDate": 1501714433, + "managed": true, + "id": "https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79", + "attributes": { + "enabled": true, + "created": 1493938433, + "updated": 1493938433, + "recoveryLevel": "Recoverable+Purgeable" + } +})json"; + + response.SetHeader(HttpShared::ContentType, "application/json"); + response.SetHeader(HttpShared::MsRequestId, "1"); + response.SetHeader(HttpShared::MsClientRequestId, "2"); + response.SetBody(std::vector(responseBody, responseBody + sizeof(responseBody))); + response.SetBodyStream( + std::make_unique(responseBody, sizeof(responseBody) - 1)); + + return response; +} + +void runPartialExpect(KeyVaultSecret& secret, bool expectValue = true) +{ + if (expectValue) + { + EXPECT_EQ(secret.Value, "mysecretvalue"); + } + EXPECT_EQ(secret.Name, "mysecretname"); - EXPECT_EQ(secret.Value, "mysecretvalue"); EXPECT_EQ(secret.Properties.VaultUrl, "https://myvault.vault.azure.net"); EXPECT_EQ(secret.Properties.Version, "4387e9f3d6e14c459867679a90fd0f79"); EXPECT_EQ(secret.Properties.Id, secret.Id); @@ -79,16 +112,21 @@ void runPartialExpect(KeyVaultSecret& secret) secret.Id, "https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79"); EXPECT_EQ(secret.Properties.KeyId.HasValue(), false); - EXPECT_EQ(secret.Properties.Enabled.Value(), true); EXPECT_EQ(secret.Properties.Managed, false); EXPECT_EQ(secret.Properties.UpdatedOn.HasValue(), true); EXPECT_EQ(secret.Properties.CreatedOn.HasValue(), true); } -void runFullExpect(KeyVaultSecret& secret) +void runFullExpect(KeyVaultSecret& secret, bool expectValue = true) { + if (expectValue) + { + EXPECT_EQ(secret.Value, "mysecretvalue"); + EXPECT_EQ(secret.Properties.ContentType.Value(), "ct"); + EXPECT_EQ(secret.Properties.KeyId.Value(), "kid"); + } + EXPECT_EQ(secret.Name, "mysecretname"); - EXPECT_EQ(secret.Value, "mysecretvalue"); EXPECT_EQ(secret.Properties.VaultUrl, "https://myvault.vault.azure.net"); EXPECT_EQ(secret.Properties.Version, "4387e9f3d6e14c459867679a90fd0f79"); EXPECT_EQ(secret.Properties.Id, secret.Id); @@ -97,10 +135,15 @@ void runFullExpect(KeyVaultSecret& secret) "https://myvault.vault.azure.net/secrets/mysecretname/4387e9f3d6e14c459867679a90fd0f79"); EXPECT_EQ(secret.Properties.Enabled.Value(), true); EXPECT_EQ(secret.Properties.Managed, true); - EXPECT_EQ(secret.Properties.ContentType.Value(), "ct"); - EXPECT_EQ(secret.Properties.KeyId.Value(), "kid"); EXPECT_EQ(secret.Properties.UpdatedOn.HasValue(), true); EXPECT_EQ(secret.Properties.CreatedOn.HasValue(), true); } +void runDeletedExtras(KeyVaultDeletedSecret& secret) +{ + EXPECT_EQ( + secret.RecoveryId, "https://myvault.vault.azure.net/deletedsecrets/GetDeletedSecretTest"); + EXPECT_EQ(secret.ScheduledPurgeDate.ToString(), "2017-08-02T22:53:53Z"); + EXPECT_EQ(secret.DeletedDate.ToString(), "2017-05-04T22:53:53Z"); +} } // namespace