diff --git a/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt index 05876dd4a..e8b95f12e 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-secrets/CMakeLists.txt @@ -34,6 +34,7 @@ set( inc/azure/keyvault/secrets/secret_client.hpp inc/azure/keyvault/keyvault_secrets.hpp inc/azure/keyvault/secrets/keyvault_deleted_secret.hpp + inc/azure/keyvault/secrets/keyvault_secret_paged_response.hpp inc/azure/keyvault/secrets/keyvault_backup_secret.hpp inc/azure/keyvault/secrets/keyvault_operations.hpp inc/azure/keyvault/secrets/keyvault_options.hpp @@ -49,6 +50,7 @@ set( src/secret_client.cpp src/secret_serializers.cpp src/keyvault_operations.cpp + src/keyvault_secret_paged_response.cpp ) add_library(azure-security-keyvault-secrets ${AZURE_SECURITY_KEYVAULT_SECRETS_HEADER} ${AZURE_SECURITY_KEYVAULT_SECRETS_SOURCE}) diff --git a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_options.hpp b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_options.hpp index 7777515a4..5055eaaf1 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_options.hpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_options.hpp @@ -87,4 +87,34 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { */ std::string Version; }; + + /** + * @brief The options for calling an operation #GetPropertiesOfSecrets. + * + */ + struct GetPropertiesOfSecretsOptions final + { + Azure::Nullable MaxResults; + Azure::Nullable NextPageToken; + }; + + /** + * @brief The options for calling an operation #GetPropertiesOfSecretVersions. + * + */ + struct GetPropertiesOfSecretVersionsOptions final + { + Azure::Nullable MaxResults; + Azure::Nullable NextPageToken; + }; + + /** + * @brief The options for calling an operation #GetDeletedSecrets. + * + */ + struct GetDeletedSecretsOptions final + { + Azure::Nullable MaxResults; + Azure::Nullable NextPageToken; + }; }}}} // namespace Azure::Security::KeyVault::Secrets diff --git a/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret_paged_response.hpp b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret_paged_response.hpp new file mode 100644 index 000000000..8b68134ec --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-secrets/inc/azure/keyvault/secrets/keyvault_secret_paged_response.hpp @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT +/** + * @file + * @brief Defines the Key Vault Secret paged responses. + * + */ + +#pragma once + +#include "azure/keyvault/secrets/keyvault_deleted_secret.hpp" +#include "azure/keyvault/secrets/keyvault_secret_properties.hpp" +#include +#include +#include + +namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { + + // forward definition + class SecretClient; + + /** + * @brief Define a single page to list the secrets from the Key Vault. + * + */ + class KeyVaultSecretPropertiesPagedResponse final + : public Azure::Core::PagedResponse { + private: + friend class SecretClient; + friend class Azure::Core::PagedResponse; + + std::string m_secretName; + std::shared_ptr m_secretClient; + + void OnNextPage(const Azure::Core::Context& context); + + KeyVaultSecretPropertiesPagedResponse( + KeyVaultSecretPropertiesPagedResponse&& secretProperties, + std::unique_ptr rawResponse, + std::shared_ptr secretClient, + std::string const& secretName = std::string()) + : PagedResponse(std::move(secretProperties)), m_secretName(secretName), + m_secretClient(secretClient), Items(std::move(secretProperties.Items)) + { + RawResponse = std::move(rawResponse); + } + + public: + /** + * @brief Construct a new KeyVaultSecretPropertiesPagedResponse object. + * + */ + KeyVaultSecretPropertiesPagedResponse() = default; + + /** + * @brief Each #KeyvaultSecretProperties represent a Secret in the Key Vault. + * + */ + std::vector Items; + }; + + /** + * @brief Define a single page containing the deleted keys from the Key Vault. + * + */ + class KeyvaultSecretDeletedSecretPagedResponse final + : public Azure::Core::PagedResponse { + private: + friend class SecretClient; + friend class Azure::Core::PagedResponse; + + std::shared_ptr m_secretClient; + void OnNextPage(const Azure::Core::Context& context); + + KeyvaultSecretDeletedSecretPagedResponse( + KeyvaultSecretDeletedSecretPagedResponse&& deletedKeyProperties, + std::unique_ptr rawResponse, + std::shared_ptr secretClient) + : PagedResponse(std::move(deletedKeyProperties)), m_secretClient(secretClient), + Items(std::move(deletedKeyProperties.Items)) + { + RawResponse = std::move(rawResponse); + } + + public: + /** + * @brief Construct a new Deleted Key Single Page object + * + */ + KeyvaultSecretDeletedSecretPagedResponse() = default; + + /** + * @brief Each #DeletedKey represent a deleted key in the Key Vault. + * + */ + std::vector Items; + }; +}}}} // 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 8860e1dcc..ce3876553 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 @@ -13,6 +13,7 @@ #include "azure/keyvault/secrets/keyvault_operations.hpp" #include "azure/keyvault/secrets/keyvault_options.hpp" #include "azure/keyvault/secrets/keyvault_secret.hpp" +#include "azure/keyvault/secrets/keyvault_secret_paged_response.hpp" #include "dll_import_export.hpp" #include #include @@ -242,5 +243,55 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { StartRecoverDeletedSecret( std::string const& name, Azure::Core::Context const& context = Azure::Core::Context()) const; + + /** + * @brief List secrets in a specified key vault. + * The Get Secrets operation is applicable to the entire vault. + * However, only the base secret identifier and its attributes are provided in the response. + * Individual secret versions are not listed in the response. This operation requires the + * secrets/list permission. + * + * @param options The optional parameters for this request + * @param context The context for the operation can be used for request cancellation. + * + * @return Response containing a list of secrets in the vault along with a link to the next page + * of secrets. + */ + KeyVaultSecretPropertiesPagedResponse GetPropertiesOfSecrets( + GetPropertiesOfSecretsOptions const& options = GetPropertiesOfSecretsOptions(), + Azure::Core::Context const& context = Azure::Core::Context()) const; + + /** + * @brief List all versions of the specified secret. + * The full secret identifier and attributes are provided in the response. No values are + * returned for the secrets. This operations requires the secrets/list permission. + * + * @param name The name of the secret. + * @param options The optional parameters for this request + * @param context The context for the operation can be used for request cancellation. + * + * @return Response containing a list of secrets in the vault along with a link to the next page + * of secrets. + */ + KeyVaultSecretPropertiesPagedResponse GetPropertiesOfSecretsVersions( + std::string const& name, + GetPropertiesOfSecretVersionsOptions const& options + = GetPropertiesOfSecretVersionsOptions(), + Azure::Core::Context const& context = Azure::Core::Context()) const; + + /** + * @brief Lists deleted secrets for the specified vault. + * The Get Deleted Secrets operation returns the secrets that have been deleted for a vault + * enabled for soft-delete. This operation requires the secrets/list permission. + * + * @param options The optional parameters for this request + * @param context The context for the operation can be used for request cancellation. + * + * @return Response containing a list of deleted secrets in the vault, along with a link to the + * next page of deleted secrets. + */ + KeyvaultSecretDeletedSecretPagedResponse GetDeletedSecrets( + GetDeletedSecretsOptions const& options = GetDeletedSecretsOptions(), + Azure::Core::Context const& context = Azure::Core::Context()) const; }; }}}} // namespace Azure::Security::KeyVault::Secrets diff --git a/sdk/keyvault/azure-security-keyvault-secrets/src/keyvault_secret_paged_response.cpp b/sdk/keyvault/azure-security-keyvault-secrets/src/keyvault_secret_paged_response.cpp new file mode 100644 index 000000000..ca99794ee --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/keyvault_secret_paged_response.cpp @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT +/** + * @file + * @brief Defines KeyVaultSecretPropertiesPagedResponse. + * + */ + +#include "azure/keyvault/secrets/keyvault_secret_paged_response.hpp" +#include "azure/keyvault/secrets/secret_client.hpp" + +using namespace Azure::Security::KeyVault::Secrets; + +void KeyVaultSecretPropertiesPagedResponse::OnNextPage(const Azure::Core::Context& context) +{ + // Before calling `OnNextPage` pagedResponse validates there is a next page, so we are sure + // NextPageToken is valid. + if (m_secretName.empty()) + { + GetPropertiesOfSecretsOptions options; + options.NextPageToken = NextPageToken; + *this = m_secretClient->GetPropertiesOfSecrets(options, context); + CurrentPageToken = options.NextPageToken.Value(); + } + else + { + GetPropertiesOfSecretVersionsOptions options; + options.NextPageToken = NextPageToken; + *this = m_secretClient->GetPropertiesOfSecretsVersions(m_secretName, options, context); + CurrentPageToken = options.NextPageToken.Value(); + } +} + +void KeyvaultSecretDeletedSecretPagedResponse::OnNextPage(const Azure::Core::Context& context) +{ + // Before calling `OnNextPage` pagedResponse validates there is a next page, so we are sure + // NextPageToken is valid. + GetDeletedSecretsOptions options; + options.NextPageToken = NextPageToken; + *this = m_secretClient->GetDeletedSecrets(options, context); + CurrentPageToken = options.NextPageToken.Value(); +} 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 fd7ce336a..b6905beb0 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 @@ -9,6 +9,8 @@ #pragma once +#include + namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { namespace _detail { /***************** KeyVault Secret *****************/ @@ -42,4 +44,10 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { /**************** KeyVault QueryParameters *********/ static constexpr char const ApiVersion[] = "api-version"; + + /**************** KeyVault Secrets Paged *********/ + static constexpr size_t PagedMaxResults = 25; + static constexpr char const PagedMaxResultsName[] = "maxresults"; + static constexpr char const VersionsName[] = "versions"; + }}}}} // 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 0669ba4ec..fdf4ab95c 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 @@ -11,6 +11,7 @@ #include "azure/keyvault/secrets/keyvault_backup_secret.hpp" #include "azure/keyvault/secrets/keyvault_deleted_secret.hpp" #include "azure/keyvault/secrets/keyvault_secret.hpp" +#include "azure/keyvault/secrets/keyvault_secret_paged_response.hpp" #include #include #include @@ -117,4 +118,17 @@ namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { { static std::string KeyvaultRestoreSecretSerialize(std::vector const& backup); }; + + class KeyVaultSecretPropertiesPagedResultSerializer final { + public: + static KeyVaultSecretPropertiesPagedResponse KeyVaultSecretPropertiesPagedResponseDeserialize( + Azure::Core::Http::RawResponse const& rawResponse); + }; + + class KeyVaultSecretDeletedSecretPagedResultSerializer final { + public: + static KeyvaultSecretDeletedSecretPagedResponse + KeyVaultSecretDeletedSecretPagedResultDeserialize( + 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 e8d9dcf2c..a25073db8 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/secret_client.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/secret_client.cpp @@ -24,7 +24,33 @@ using namespace Azure::Core::Http::Policies::_internal; namespace { constexpr static const char TelemetryName[] = "keyvault-secrets"; + +struct RequestWithContinuationToken final +{ + std::vector Path; + std::unique_ptr> Query; +}; + +static inline RequestWithContinuationToken BuildRequestFromContinuationToken( + const Azure::Nullable& NextPageToken, + std::vector defaultPath) +{ + RequestWithContinuationToken request; + request.Path = std::move(defaultPath); + request.Query = std::make_unique>(); + if (NextPageToken) + { + // Using a continuation token requires to send the request to the continuation token URL instead + // of the default URL which is used only for the first page. + Azure::Core::Url nextPageUrl(NextPageToken.Value()); + auto queryParameters = nextPageUrl.GetQueryParameters(); + request.Query->insert(queryParameters.begin(), queryParameters.end()); + request.Path.clear(); + request.Path.emplace_back(nextPageUrl.GetPath()); + } + return request; } +} // namespace const ServiceVersion ServiceVersion::V7_2("7.2"); @@ -204,3 +230,95 @@ Azure::Security::KeyVault::Secrets::KeyVaultRestoreDeletedSecretOperation Secret }, {_detail::DeletedSecretPath, name, _detail::RecoverDeletedSecretPath})); } + +KeyVaultSecretPropertiesPagedResponse SecretClient::GetPropertiesOfSecrets( + GetPropertiesOfSecretsOptions const& options, + Azure::Core::Context const& context) const +{ + auto const request + = BuildRequestFromContinuationToken(options.NextPageToken, {_detail::SecretPath}); + size_t maxResults = _detail::PagedMaxResults; + if (options.MaxResults.HasValue() && (options.MaxResults.Value() <= _detail::PagedMaxResults)) + { + maxResults = options.MaxResults.Value(); + } + + request.Query->emplace(_detail::PagedMaxResultsName, std::to_string(maxResults)); + + auto response = m_protocolClient->SendRequest( + context, + Azure::Core::Http::HttpMethod::Get, + [](Azure::Core::Http::RawResponse const& rawResponse) { + return _detail::KeyVaultSecretPropertiesPagedResultSerializer:: + KeyVaultSecretPropertiesPagedResponseDeserialize(rawResponse); + }, + request.Path, + request.Query); + + return KeyVaultSecretPropertiesPagedResponse( + std::move(response.Value), + std::move(response.RawResponse), + std::make_unique(*this)); +} + +KeyVaultSecretPropertiesPagedResponse SecretClient::GetPropertiesOfSecretsVersions( + std::string const& name, + GetPropertiesOfSecretVersionsOptions const& options, + Azure::Core::Context const& context) const +{ + auto const request = BuildRequestFromContinuationToken( + options.NextPageToken, {_detail::SecretPath, name, _detail::VersionsName}); + size_t maxResults = _detail::PagedMaxResults; + if (options.MaxResults.HasValue() && (options.MaxResults.Value() <= _detail::PagedMaxResults)) + { + maxResults = options.MaxResults.Value(); + } + + request.Query->emplace(_detail::PagedMaxResultsName, std::to_string(maxResults)); + + auto response = m_protocolClient->SendRequest( + context, + Azure::Core::Http::HttpMethod::Get, + [](Azure::Core::Http::RawResponse const& rawResponse) { + return _detail::KeyVaultSecretPropertiesPagedResultSerializer:: + KeyVaultSecretPropertiesPagedResponseDeserialize(rawResponse); + }, + request.Path, + request.Query); + + return KeyVaultSecretPropertiesPagedResponse( + std::move(response.Value), + std::move(response.RawResponse), + std::make_unique(*this), + name); +} + +KeyvaultSecretDeletedSecretPagedResponse SecretClient::GetDeletedSecrets( + GetDeletedSecretsOptions const& options, + Azure::Core::Context const& context) const +{ + auto const request + = BuildRequestFromContinuationToken(options.NextPageToken, {_detail::DeletedSecretPath}); + size_t maxResults = _detail::PagedMaxResults; + if (options.MaxResults.HasValue() && (options.MaxResults.Value() <= _detail::PagedMaxResults)) + { + maxResults = options.MaxResults.Value(); + } + + request.Query->emplace(_detail::PagedMaxResultsName, std::to_string(maxResults)); + + auto response = m_protocolClient->SendRequest( + context, + Azure::Core::Http::HttpMethod::Get, + [](Azure::Core::Http::RawResponse const& rawResponse) { + return _detail::KeyVaultSecretDeletedSecretPagedResultSerializer:: + KeyVaultSecretDeletedSecretPagedResultDeserialize(rawResponse); + }, + request.Path, + request.Query); + + return KeyvaultSecretDeletedSecretPagedResponse( + std::move(response.Value), + std::move(response.RawResponse), + std::make_unique(*this)); +} 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 3f476011e..e8b2af4f3 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/src/secret_serializers.cpp +++ b/sdk/keyvault/azure-security-keyvault-secrets/src/secret_serializers.cpp @@ -257,3 +257,167 @@ std::string KeyvaultRestoreSecretSerializer::KeyvaultRestoreSecretSerialize( payload[_detail::ValuePropertyName] = Base64Url::Base64UrlEncode(backup); return payload.dump(); } + +KeyVaultSecretPropertiesPagedResponse +KeyVaultSecretPropertiesPagedResultSerializer::KeyVaultSecretPropertiesPagedResponseDeserialize( + Azure::Core::Http::RawResponse const& rawResponse) +{ + KeyVaultSecretPropertiesPagedResponse result; + auto const& body = rawResponse.GetBody(); + auto jsonParser = json::parse(body); + + JsonOptional::SetIfExists(result.NextPageToken, jsonParser, "nextLink"); + + // Key properties + auto secretsPropertiesJson = jsonParser["value"]; + + for (auto const& secretProperties : secretsPropertiesJson) + { + KeyvaultSecretProperties item; + item.Id = secretProperties[_detail::IdPropertyName].get(); + _detail::KeyVaultSecretSerializer::ParseIDUrl(item, item.Id); + // Parse URL for the various attributes + if (secretProperties.contains(_detail::AttributesPropertyName)) + { + auto attributes = secretProperties[_detail::AttributesPropertyName]; + + JsonOptional::SetIfExists(item.Enabled, attributes, _detail::EnabledPropertyName); + + JsonOptional::SetIfExists( + item.NotBefore, + attributes, + _detail::NbfPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.ExpiresOn, + attributes, + _detail::ExpPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.CreatedOn, + attributes, + _detail::CreatedPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.UpdatedOn, + attributes, + _detail::UpdatedPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.RecoveryLevel, attributes, _detail::RecoveryLevelPropertyName); + JsonOptional::SetIfExists( + item.RecoverableDays, attributes, _detail::RecoverableDaysPropertyName); + } + + // "Tags" + if (secretProperties.contains(_detail::TagsPropertyName)) + { + auto const& tags = secretProperties[_detail::TagsPropertyName]; + { + for (auto tag = tags.begin(); tag != tags.end(); ++tag) + { + item.Tags.emplace(tag.key(), tag.value().get()); + } + } + } + + // managed + if (secretProperties.contains(_detail::ManagedPropertyName)) + { + item.Managed = secretProperties[_detail::ManagedPropertyName].get(); + } + + // content type + JsonOptional::SetIfExists( + item.ContentType, secretProperties, _detail::ContentTypePropertyName); + result.Items.emplace_back(item); + } + + return result; +} + +KeyvaultSecretDeletedSecretPagedResponse +KeyVaultSecretDeletedSecretPagedResultSerializer::KeyVaultSecretDeletedSecretPagedResultDeserialize( + Azure::Core::Http::RawResponse const& rawResponse) +{ + + KeyvaultSecretDeletedSecretPagedResponse result; + auto const& body = rawResponse.GetBody(); + auto jsonParser = json::parse(body); + auto string = jsonParser.dump(); + JsonOptional::SetIfExists(result.NextPageToken, jsonParser, "nextLink"); + + // Key properties + auto secretsPropertiesJson = jsonParser["value"]; + + for (auto const& secretProperties : secretsPropertiesJson) + { + KeyVaultDeletedSecret item; + item.Id = secretProperties[_detail::IdPropertyName].get(); + _detail::KeyVaultSecretSerializer::ParseIDUrl(item.Properties, item.Id); + // Parse URL for the various attributes + if (secretProperties.contains(_detail::AttributesPropertyName)) + { + auto attributes = secretProperties[_detail::AttributesPropertyName]; + + JsonOptional::SetIfExists(item.Properties.Enabled, attributes, _detail::EnabledPropertyName); + + JsonOptional::SetIfExists( + item.Properties.NotBefore, + attributes, + _detail::NbfPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.Properties.ExpiresOn, + attributes, + _detail::ExpPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.Properties.CreatedOn, + attributes, + _detail::CreatedPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.Properties.UpdatedOn, + attributes, + _detail::UpdatedPropertyName, + PosixTimeConverter::PosixTimeToDateTime); + JsonOptional::SetIfExists( + item.Properties.RecoveryLevel, attributes, _detail::RecoveryLevelPropertyName); + JsonOptional::SetIfExists( + item.Properties.RecoverableDays, attributes, _detail::RecoverableDaysPropertyName); + } + + // "Tags" + if (secretProperties.contains(_detail::TagsPropertyName)) + { + auto const& tags = secretProperties[_detail::TagsPropertyName]; + { + for (auto tag = tags.begin(); tag != tags.end(); ++tag) + { + item.Properties.Tags.emplace(tag.key(), tag.value().get()); + } + } + } + + // managed + if (secretProperties.contains(_detail::ManagedPropertyName)) + { + item.Properties.Managed = secretProperties[_detail::ManagedPropertyName].get(); + } + + // content type + JsonOptional::SetIfExists( + item.Properties.ContentType, secretProperties, _detail::ContentTypePropertyName); + + item.RecoveryId = secretProperties[_detail::RecoveryIdPropertyName]; + item.ScheduledPurgeDate = PosixTimeConverter::PosixTimeToDateTime( + secretProperties[_detail::ScheduledPurgeDatePropertyName]); + item.DeletedDate = PosixTimeConverter::PosixTimeToDateTime( + secretProperties[_detail::DeletedDatePropertyName]); + + result.Items.emplace_back(item); + } + + return result; +} 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 7f188df90..97f525730 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 @@ -4,8 +4,8 @@ #define _CRT_SECURE_NO_WARNINGS #endif +#include "azure/keyvault/keyvault_secrets.hpp" #include -#include using namespace Azure::Security::KeyVault::Secrets; @@ -32,7 +32,7 @@ int main() // auto response4 = secretClient.BackupSecret("someSecret2"); // auto response5 = secretClient.RestoreSecretBackup(response4.Value.Secret); - auto response = secretClient.PurgeDeletedSecret("someSecret3"); + // auto response = secretClient.PurgeDeletedSecret("someSecret3"); // auto response4 = secretClient.BackupSecret("someSecret2"); // auto response5 = secretClient.RestoreSecretBackup(response4.Value.Secret); @@ -44,5 +44,13 @@ int main() auto response7 = response6.CreateFromResumeToken(resumeToken, secretClient); auto reasponse8 = response7.Poll(); } + // auto response4 = secretClient.BackupSecret("someSecret2"); + // auto response5 = secretClient.RestoreSecretBackup(response4.Value.Secret); + // GetPropertiesOfSecretsOptions options; + // options.MaxResults = 1; + // auto r1 = secretClient.GetPropertiesOfSecrets(options); + // auto r2 = secretClient.GetPropertiesOfSecretsVersions(r1.Items[0].Name); + auto r3 = secretClient.GetDeletedSecrets(); + // r1.MoveToNextPage(); return 0; } diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/CMakeLists.txt b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/CMakeLists.txt index aee5b592b..cbeb35bfb 100644 --- a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/CMakeLists.txt +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/CMakeLists.txt @@ -19,7 +19,7 @@ add_executable ( secret_update_properties_test.cpp secret_backup_deserialize_test.cpp secret_backup_deserialize_test.hpp -) + "secret_paged_deserialize_test.cpp" "secret_paged_deserialize_test.hpp") if (MSVC) target_compile_options(azure-security-keyvault-secrets-test PUBLIC /wd6326 /wd26495 /wd26812) diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_paged_deserialize_test.cpp b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_paged_deserialize_test.cpp new file mode 100644 index 000000000..6b38d2434 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_paged_deserialize_test.cpp @@ -0,0 +1,144 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "secret_paged_deserialize_test.hpp" + +using namespace Azure::Security::KeyVault::Secrets; +using namespace Azure::Security::KeyVault::Secrets::_test; +using namespace Azure::Security::KeyVault::Secrets::_detail; +using namespace Azure::Core::Json::_internal; + +TEST(KeyVaultSecretPropertiesPagedResponse, SingleWithNext) +{ + auto response = _test::PagedHelpers::GetFirstResponse(); + + auto result = _detail::KeyVaultSecretPropertiesPagedResultSerializer:: + KeyVaultSecretPropertiesPagedResponseDeserialize(response); + + EXPECT_EQ(result.Items.size(), size_t(1)); + EXPECT_EQ( + result.NextPageToken.Value(), + "https://gearama-test2.vault.azure.net:443/" + "secrets?api-version=7.2&$skiptoken=" + "eyJOZXh0TWFya2VyIjoiMiE4NCFNREF3TURFM0lYTmxZM0psZEM5VFQwMUZVMFZEVWtWVUlUQXdNREF5T0NFNU9UazVM" + "VEV5TFRNeFZESXpPalU1T2pVNUxqazVPVGs1T1RsYUlRLS0iLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=1"); + + auto item = result.Items[0]; + EXPECT_EQ(item.Enabled.Value(), true); + EXPECT_EQ(item.RecoverableDays.Value(), 90); + EXPECT_EQ(item.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ(item.Id, "https://gearama-test2.vault.azure.net/secrets/gdfgfd"); +} + +TEST(KeyVaultSecretPropertiesPagedResponse, MultipleNoNext) +{ + auto response = _test::PagedHelpers::GetMultipleResponse(); + + auto result = _detail::KeyVaultSecretPropertiesPagedResultSerializer:: + KeyVaultSecretPropertiesPagedResponseDeserialize(response); + + EXPECT_EQ(result.Items.size(), size_t(3)); + EXPECT_EQ(result.NextPageToken.HasValue(), false); + + auto item = result.Items[0]; + EXPECT_EQ(item.Enabled.Value(), true); + EXPECT_EQ(item.RecoverableDays.Value(), 90); + EXPECT_EQ(item.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ( + item.Id, + "https://gearama-test2.vault.azure.net/secrets/gdfgfd/5a0fdd819481420eac6f3282ce722461"); + EXPECT_EQ(item.Name, "gdfgfd"); + EXPECT_EQ(item.Version, "5a0fdd819481420eac6f3282ce722461"); + + item = result.Items[1]; + EXPECT_EQ(item.Enabled.Value(), true); + EXPECT_EQ(item.RecoverableDays.Value(), 90); + EXPECT_EQ(item.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ( + item.Id, + "https://gearama-test2.vault.azure.net/secrets/gdfgfd/8faafbb99216484dbbd75f9dd6bcaadf"); + EXPECT_EQ(item.Name, "gdfgfd"); + EXPECT_EQ(item.Version, "8faafbb99216484dbbd75f9dd6bcaadf"); + + item = result.Items[2]; + EXPECT_EQ(item.Enabled.Value(), true); + EXPECT_EQ(item.RecoverableDays.Value(), 90); + EXPECT_EQ(item.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ( + item.Id, + "https://gearama-test2.vault.azure.net/secrets/gdfgfd/d75080822f03400ab4d658bd0e988ac5"); + EXPECT_EQ(item.Name, "gdfgfd"); + EXPECT_EQ(item.Version, "d75080822f03400ab4d658bd0e988ac5"); +} + +TEST(KeyVaultSecretPropertiesPagedResponse, NoneNoNext) +{ + auto response = _test::PagedHelpers::GetEmptyResponse(); + + auto result = _detail::KeyVaultSecretPropertiesPagedResultSerializer:: + KeyVaultSecretPropertiesPagedResponseDeserialize(response); + + EXPECT_EQ(result.Items.size(), size_t(0)); + EXPECT_EQ(result.NextPageToken.HasValue(), false); +} + +TEST(KeyVaultSecretDeletedSecretPagedResultSerializer, SingleWithNext) +{ + auto response = _test::PagedHelpers::GetDeletedFirstResponse(); + + auto result = _detail::KeyVaultSecretDeletedSecretPagedResultSerializer:: + KeyVaultSecretDeletedSecretPagedResultDeserialize(response); + + EXPECT_EQ(result.Items.size(), size_t(1)); + EXPECT_EQ(result.NextPageToken.Value(), "nextLink"); + + auto item = result.Items[0]; + EXPECT_EQ(item.Properties.Enabled.Value(), true); + EXPECT_EQ(item.Properties.RecoverableDays.Value(), 90); + EXPECT_EQ(item.Properties.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ(item.Id, "https://gearama-test2.vault.azure.net/secrets/eqwewq"); + EXPECT_EQ(item.RecoveryId, "https://gearama-test2.vault.azure.net/deletedsecrets/eqwewq"); +} + +TEST(KeyVaultSecretDeletedSecretPagedResultSerializer, MultipleNoNext) +{ + auto response = _test::PagedHelpers::GetDeletedMultipleResponse(); + + auto result = _detail::KeyVaultSecretDeletedSecretPagedResultSerializer:: + KeyVaultSecretDeletedSecretPagedResultDeserialize(response); + + EXPECT_EQ(result.Items.size(), size_t(3)); + EXPECT_FALSE(result.NextPageToken.HasValue()); + + auto item = result.Items[0]; + EXPECT_EQ(item.Properties.Enabled.Value(), true); + EXPECT_EQ(item.Properties.RecoverableDays.Value(), 90); + EXPECT_EQ(item.Properties.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ(item.Id, "https://gearama-test2.vault.azure.net/secrets/eqwewq"); + EXPECT_EQ(item.RecoveryId, "https://gearama-test2.vault.azure.net/deletedsecrets/eqwewq"); + + item = result.Items[1]; + EXPECT_EQ(item.Properties.Enabled.Value(), true); + EXPECT_EQ(item.Properties.RecoverableDays.Value(), 90); + EXPECT_EQ(item.Properties.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ(item.Id, "https://gearama-test2.vault.azure.net/secrets/someSecret"); + EXPECT_EQ(item.RecoveryId, "https://gearama-test2.vault.azure.net/secrets/someSecret"); + + item = result.Items[2]; + EXPECT_EQ(item.Properties.Enabled.Value(), true); + EXPECT_EQ(item.Properties.RecoverableDays.Value(), 90); + EXPECT_EQ(item.Properties.RecoveryLevel.Value(), "Recoverable+Purgeable"); + EXPECT_EQ(item.Id, "https://gearama-test2.vault.azure.net/secrets/someSecret2"); + EXPECT_EQ(item.RecoveryId, "https://gearama-test2.vault.azure.net/deletedsecrets/someSecret2"); +} + +TEST(KeyVaultSecretDeletedSecretPagedResultSerializer, NoneNoNext) +{ + auto response = _test::PagedHelpers::GetEmptyResponse(); + + auto result = _detail::KeyVaultSecretDeletedSecretPagedResultSerializer:: + KeyVaultSecretDeletedSecretPagedResultDeserialize(response); + + EXPECT_EQ(result.Items.size(), size_t(0)); + EXPECT_EQ(result.NextPageToken.HasValue(), false); +} diff --git a/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_paged_deserialize_test.hpp b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_paged_deserialize_test.hpp new file mode 100644 index 000000000..146b945e7 --- /dev/null +++ b/sdk/keyvault/azure-security-keyvault-secrets/test/ut/secret_paged_deserialize_test.hpp @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// SPDX-License-Identifier: MIT + +#include "private/secret_constants.hpp" +#include "private/secret_serializers.hpp" + +#include "../src/private/secret_serializers.hpp" +#include "azure/keyvault/secrets/secret_client.hpp" +#include +#include +#include + +#include "azure/keyvault/secrets/keyvault_secret_paged_response.hpp" +#include +#include +#include +#include +#include + +using namespace Azure::Security::KeyVault::Secrets; +using namespace Azure::Core::Http::_internal; + +namespace Azure { namespace Security { namespace KeyVault { namespace Secrets { namespace _test { + struct PagedHelpers + { + + static Azure::Core::Http::RawResponse GetFirstResponse() + { + auto response + = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + + constexpr static const uint8_t responseBody[] = R"json({ + "nextLink": "https://gearama-test2.vault.azure.net:443/secrets?api-version=7.2&$skiptoken=eyJOZXh0TWFya2VyIjoiMiE4NCFNREF3TURFM0lYTmxZM0psZEM5VFQwMUZVMFZEVWtWVUlUQXdNREF5T0NFNU9UazVMVEV5TFRNeFZESXpPalU1T2pVNUxqazVPVGs1T1RsYUlRLS0iLCJUYXJnZXRMb2NhdGlvbiI6MH0&maxresults=1", + "value": [{ + "attributes": { + "created": 1627404049, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1627404049 + }, + "id": "https://gearama-test2.vault.azure.net/secrets/gdfgfd" + }] +} +)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; + } + + static Azure::Core::Http::RawResponse GetMultipleResponse() + { + auto response + = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + + constexpr static const uint8_t responseBody[] = R"json({ + "nextLink": null, + "value": [{ + "attributes": { + "created": 1628101925, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1628101925 + }, + "contentType": "fdsfdsfs", + "id": "https://gearama-test2.vault.azure.net/secrets/gdfgfd/5a0fdd819481420eac6f3282ce722461", + "tags": {} + }, { + "attributes": { + "created": 1627404049, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1627404049 + }, + "id": "https://gearama-test2.vault.azure.net/secrets/gdfgfd/8faafbb99216484dbbd75f9dd6bcaadf" + }, { + "attributes": { + "created": 1628101911, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1628101911 + }, + "id": "https://gearama-test2.vault.azure.net/secrets/gdfgfd/d75080822f03400ab4d658bd0e988ac5", + "tags": {} + }] +} +)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; + } + + static Azure::Core::Http::RawResponse GetEmptyResponse() + { + auto response + = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + + constexpr static const uint8_t responseBody[] = R"json({ + "nextLink": null, + "value": [] +} +)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; + } + + static Azure::Core::Http::RawResponse GetDeletedFirstResponse() + { + auto response + = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + + constexpr static const uint8_t responseBody[] = R"json({ + "nextLink": "nextLink", + "value": [{ + "attributes": { + "created": 1628110306, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1628110306 + }, + "deletedDate": 1628110318, + "id": "https://gearama-test2.vault.azure.net/secrets/eqwewq", + "recoveryId": "https://gearama-test2.vault.azure.net/deletedsecrets/eqwewq", + "scheduledPurgeDate": 1635886318, + "tags": {} + }] +} +)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; + } + + static Azure::Core::Http::RawResponse GetDeletedMultipleResponse() + { + auto response + = Azure::Core::Http::RawResponse(1, 1, Azure::Core::Http::HttpStatusCode::Ok, "OK"); + + constexpr static const uint8_t responseBody[] = R"json({ + "nextLink": null, + "value": [{ + "attributes": { + "created": 1628110306, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1628110306 + }, + "deletedDate": 1628110318, + "id": "https://gearama-test2.vault.azure.net/secrets/eqwewq", + "recoveryId": "https://gearama-test2.vault.azure.net/deletedsecrets/eqwewq", + "scheduledPurgeDate": 1635886318, + "tags": {} + }, { + "attributes": { + "created": 1626967532, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1626967532 + }, + "deletedDate": 1628110252, + "id": "https://gearama-test2.vault.azure.net/secrets/someSecret", + "recoveryId": "https://gearama-test2.vault.azure.net/secrets/someSecret", + "scheduledPurgeDate": 1635886252 + }, { + "attributes": { + "created": 1627101774, + "enabled": true, + "recoverableDays": 90, + "recoveryLevel": "Recoverable+Purgeable", + "updated": 1627101774 + }, + "deletedDate": 1628110259, + "id": "https://gearama-test2.vault.azure.net/secrets/someSecret2", + "recoveryId": "https://gearama-test2.vault.azure.net/deletedsecrets/someSecret2", + "scheduledPurgeDate": 1635886259 + }] +} +)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; + } + }; +}}}}} // namespace Azure::Security::KeyVault::Secrets::_test