diff --git a/sdk/core/azure-core/inc/azure/core/internal/json/json_optional.hpp b/sdk/core/azure-core/inc/azure/core/internal/json/json_optional.hpp new file mode 100644 index 000000000..59ce261e5 --- /dev/null +++ b/sdk/core/azure-core/inc/azure/core/internal/json/json_optional.hpp @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +/** + * @file + * @brief Define a convenience layer on top of Json for setting optional fields. + * + */ + +#pragma once + +#include "azure/core/internal/json/json.hpp" +#include "azure/core/nullable.hpp" + +#include +#include + +namespace Azure { namespace Core { namespace Json { namespace _internal { + + /** + * @brief Define a wrapper for working with Json containing optional fields. + * + */ + struct JsonOptional + { + /** + * @brief If the optional key \p key is present in the json node \p jsonKey set the value of \p + * destination. + * + * @remark If the key is not in the json node, the \p destination is not modified. + * + * @param jsonKey The json node to review. + * @param key The key name for the optional property. + * @param destination The value to update if the key name property is in the json node. + */ + template + static inline void SetIfExists( + Azure::Nullable& destination, + Azure::Core::Json::_internal::json const& jsonKey, + std::string const& key) noexcept + { + if (jsonKey.contains(key)) + { + destination = jsonKey[key].get(); + } + } + + /** + * @brief If the optional key \p key is present in the json node \p jsonKey set the value of \p + * destination. + * + * @remark If the key is not in the json node, the \p destination is not modified. + * + * @param jsonKey The json node to review. + * @param key The key name for the optional property. + * @param destination The value to update if the key name property is in the json node. + * @param decorator A optional function to update the json value before updating the \p + * destination. + */ + template + static inline void SetIfExists( + Azure::Nullable& destination, + Azure::Core::Json::_internal::json const& jsonKey, + std::string const& key, + std::function decorator) noexcept + { + if (jsonKey.contains(key)) + { + destination = decorator(jsonKey[key].get()); + } + } + }; + +}}}} // namespace Azure::Core::Json::_internal diff --git a/sdk/keyvault/azure-security-keyvault-keys/inc/azure/keyvault/keys/key_client.hpp b/sdk/keyvault/azure-security-keyvault-keys/inc/azure/keyvault/keys/key_client.hpp index e2a721392..76a59fff9 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/inc/azure/keyvault/keys/key_client.hpp +++ b/sdk/keyvault/azure-security-keyvault-keys/inc/azure/keyvault/keys/key_client.hpp @@ -163,5 +163,20 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Keys { Azure::Security::KeyVault::Keys::DeleteKeyOperation StartDeleteKey( std::string const& name, Azure::Core::Context const& context = Azure::Core::Context()) const; + + /** + * @brief Gets the public part of a deleted key. + * + * @remark The Get Deleted Key operation is applicable for soft-delete enabled vaults. While the + * operation can be invoked on any vault, it will return an error if invoked on a non + * soft-delete enabled vault. This operation requires the keys/get permission. + * + * @param name The name of the key. + * @param context A #Azure::Core::Context controlling the request lifetime. + * @return Azure::Response + */ + Azure::Response GetDeletedKey( + std::string const& name, + Azure::Core::Context const& context = Azure::Core::Context()) const; }; }}}} // namespace Azure::Security::KeyVault::Keys diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp b/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp index 85dcc355a..a6f39ad18 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/src/delete_key_operation.cpp @@ -36,6 +36,10 @@ Azure::Security::KeyVault::Keys::DeleteKeyOperation::PollInternal(Azure::Core::C rawResponse = m_pipeline->GetResponse( context, Azure::Core::Http::HttpMethod::Get, {_detail::DeletedKeysPath, m_value.Name()}); m_status = CheckCompleted(*rawResponse); + if (m_status == Azure::Core::OperationStatus::Succeeded) + { + m_value = _detail::DeletedKeyDeserialize(m_value.Name(), *rawResponse); + } } // To ensure the success of calling Poll multiple times, even after operation is completed, a diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/key_client.cpp b/sdk/keyvault/azure-security-keyvault-keys/src/key_client.cpp index c6f0946f1..32fd3703a 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/key_client.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/src/key_client.cpp @@ -129,3 +129,16 @@ Azure::Security::KeyVault::Keys::DeleteKeyOperation KeyClient::StartDeleteKey( }, {_detail::KeysPath, name})); } + +Azure::Response KeyClient::GetDeletedKey( + std::string const& name, + Azure::Core::Context const& context) const +{ + return m_pipeline->SendRequest( + context, + Azure::Core::Http::HttpMethod::Get, + [&name](Azure::Core::Http::RawResponse const& rawResponse) { + return _detail::DeletedKeyDeserialize(name, rawResponse); + }, + {_detail::DeletedKeysPath, name}); +} diff --git a/sdk/keyvault/azure-security-keyvault-keys/src/key_vault_key.cpp b/sdk/keyvault/azure-security-keyvault-keys/src/key_vault_key.cpp index c55a0629f..9ce46abc3 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/src/key_vault_key.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/src/key_vault_key.cpp @@ -8,8 +8,11 @@ #include #include +#include +#include using namespace Azure::Security::KeyVault::Keys; +using namespace Azure::Core::Json::_internal; using Azure::Security::KeyVault::Common::_internal::UnixTimeConverter; namespace { @@ -38,39 +41,61 @@ void _detail::KeyVaultKeyDeserialize( Azure::Core::Http::RawResponse const& rawResponse) { auto body = rawResponse.GetBody(); - auto jsonParser = Azure::Core::Json::_internal::json::parse(body); + auto jsonParser = json::parse(body); // "Key" - auto const& jsonKey = jsonParser[_detail::KeyPropertyName]; + if (jsonParser.contains(_detail::KeyPropertyName)) { - // key_ops - auto keyOperationVector = jsonKey[_detail::KeyOpsPropertyName].get>(); - std::vector keyOperations; - ParseStringOperationsToKeyOperations(keyOperations, keyOperationVector); - key.Key.SetKeyOperations(keyOperations); - } - key.Key.Id = jsonKey[_detail::KeyIdPropertyName].get(); - key.Key.KeyType - = _detail::KeyTypeFromString(jsonKey[_detail::KeyTypePropertyName].get()); + auto const& jsonKey = jsonParser[_detail::KeyPropertyName]; + { + // key_ops + auto keyOperationVector + = jsonKey[_detail::KeyOpsPropertyName].get>(); + std::vector keyOperations; + ParseStringOperationsToKeyOperations(keyOperations, keyOperationVector); + key.Key.SetKeyOperations(keyOperations); + } + key.Key.Id = jsonKey[_detail::KeyIdPropertyName].get(); + key.Key.KeyType + = _detail::KeyTypeFromString(jsonKey[_detail::KeyTypePropertyName].get()); - if (jsonKey.contains(_detail::CurveNamePropertyName)) - { - key.Key.CurveName = KeyCurveName(jsonKey[_detail::CurveNamePropertyName].get()); + JsonOptional::SetIfExists( + key.Key.CurveName, jsonKey, _detail::CurveNamePropertyName, [](std::string const& keyName) { + return KeyCurveName(keyName); + }); } // "Attributes" + if (jsonParser.contains(_detail::AttributesPropertyName)) { auto attributes = jsonParser[_detail::AttributesPropertyName]; - key.Properties.CreatedOn - = UnixTimeConverter::UnixTimeToDatetime(attributes["created"].get()); + + JsonOptional::SetIfExists(key.Properties.Enabled, attributes, "enabled"); + JsonOptional::SetIfExists( + key.Properties.NotBefore, attributes, "nbf", UnixTimeConverter::UnixTimeToDatetime); + JsonOptional::SetIfExists( + key.Properties.ExpiresOn, attributes, "exp", UnixTimeConverter::UnixTimeToDatetime); + JsonOptional::SetIfExists( + key.Properties.CreatedOn, attributes, "created", UnixTimeConverter::UnixTimeToDatetime); + JsonOptional::SetIfExists( + key.Properties.UpdatedOn, attributes, "updated", UnixTimeConverter::UnixTimeToDatetime); } // "Tags" - auto const& tags = jsonParser[_detail::TagsPropertyName]; + if (jsonParser.contains(_detail::TagsPropertyName)) { - for (auto tag = tags.begin(); tag != tags.end(); ++tag) + auto const& tags = jsonParser[_detail::TagsPropertyName]; { - key.Properties.Tags.emplace(tag.key(), tag.value().get()); + for (auto tag = tags.begin(); tag != tags.end(); ++tag) + { + key.Properties.Tags.emplace(tag.key(), tag.value().get()); + } } } + + // managed + if (jsonParser.contains(_detail::ManagedPropertyName)) + { + key.Properties.Managed = jsonParser[_detail::ManagedPropertyName].get(); + } } diff --git a/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_create_test_live.cpp b/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_create_test_live.cpp index a2f11d00d..9bda21df5 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_create_test_live.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_create_test_live.cpp @@ -132,6 +132,7 @@ TEST_F(KeyVaultClientTest, CreateEcKeyWithCurve) CheckValidResponse(keyResponse); auto keyVaultKey = keyResponse.ExtractValue(); EXPECT_EQ(keyVaultKey.Name(), keyName); + EXPECT_EQ(ecKey.CurveName->ToString(), keyVaultKey.Key.CurveName->ToString()); } { // Now get the key diff --git a/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_delete_test_live.cpp b/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_delete_test_live.cpp index 58029d2e2..b1a26fe05 100644 --- a/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_delete_test_live.cpp +++ b/sdk/keyvault/azure-security-keyvault-keys/test/ut/key_client_delete_test_live.cpp @@ -271,3 +271,38 @@ TEST_F(KeyVaultClientTest, CreateDeletedKeyBeforePollComplete) } EXPECT_TRUE(wasThrown); } + +// Get Delete Key +TEST_F(KeyVaultClientTest, GetDeletedKey) +{ + Azure::Security::KeyVault::Keys::KeyClient keyClient(m_keyVaultUrl, m_credential); + std::string keyName("deleteThisKeyAndGetItFromDeleteKeys1wwxx3x4"); + + { + auto keyResponse + = keyClient.CreateKey(keyName, Azure::Security::KeyVault::Keys::JsonWebKeyType::Ec); + CheckValidResponse(keyResponse); + auto keyVaultKey = keyResponse.ExtractValue(); + EXPECT_EQ(keyVaultKey.Name(), keyName); + } + { + // Wait until key is deleted + auto duration = std::chrono::system_clock::now() + std::chrono::minutes(3); + auto cancelToken = Azure::Core::Context::GetApplicationContext().WithDeadline(duration); + + auto keyResponseLRO = keyClient.StartDeleteKey(keyName); + auto expectedStatusToken = m_keyVaultUrl + "/" + + std::string(Azure::Security::KeyVault::Keys::_detail::DeletedKeysPath) + "/" + keyName; + auto keyResponse = keyResponseLRO.PollUntilDone(std::chrono::milliseconds(1000), cancelToken); + } + { + // Get the deleted key + auto deletedKey = keyClient.GetDeletedKey(keyName).ExtractValue(); + EXPECT_FALSE(deletedKey.RecoveryId.empty()); + EXPECT_EQ(deletedKey.Name(), keyName); + auto expectedType = Azure::Security::KeyVault::Keys::JsonWebKeyType::Ec; + EXPECT_EQ( + Azure::Security::KeyVault::Keys::_detail::KeyTypeToString(expectedType), + Azure::Security::KeyVault::Keys::_detail::KeyTypeToString(deletedKey.Key.KeyType)); + } +}