Add TokenRequestOptions and ClientSecretCredentialOptions (#1527)

* Add GetTokenOptions
This commit is contained in:
Anton Kolesnyk 2021-02-02 17:09:33 -08:00 committed by GitHub
parent 6b9b57815e
commit 06d66492eb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 168 additions and 91 deletions

View File

@ -9,6 +9,8 @@
### Breaking Changes
- Make `ToLower()` and `LocaleInvariantCaseInsensitiveEqual()` internal by moving them from `Azure::Core::Strings` to `Azure::Core::Internal::Strings`.
- `BearerTokenAuthenticationPolicy` constructor takes `TokenRequestOptions` struct instead of scopes vector. `TokenRequestOptions` struct has scopes vector as data member.
- `TokenCredential::GetToken()` takes `TokenRequestOptions` instead of scopes vector.
### Bug Fixes

View File

@ -16,7 +16,6 @@
#include <mutex>
#include <string>
#include <utility>
#include <vector>
namespace Azure { namespace Core {
@ -36,6 +35,10 @@ namespace Azure { namespace Core {
DateTime ExpiresOn;
};
namespace Http {
struct TokenRequestOptions;
} // namespace Http
/**
* @brief Token credential.
*/
@ -45,10 +48,11 @@ namespace Azure { namespace Core {
* @brief Get an authentication token.
*
* @param context #Context so that operation can be cancelled.
* @param scopes Authentication scopes.
* @param tokenRequestOptions Options to get the token.
*/
virtual AccessToken GetToken(Context const& context, std::vector<std::string> const& scopes)
const = 0;
virtual AccessToken GetToken(
Context const& context,
Http::TokenRequestOptions const& tokenRequestOptions) const = 0;
/// Destructor.
virtual ~TokenCredential() = default;

View File

@ -306,13 +306,24 @@ namespace Azure { namespace Core { namespace Http {
NextHttpPolicy nextHttpPolicy) const override;
};
/**
* @brief Defines options for getting token.
*/
struct TokenRequestOptions
{
/**
* @brief Authentication scopes.
*/
std::vector<std::string> Scopes;
};
/**
* @brief Bearer Token authentication policy.
*/
class BearerTokenAuthenticationPolicy : public HttpPolicy {
private:
std::shared_ptr<TokenCredential const> const m_credential;
std::vector<std::string> m_scopes;
TokenRequestOptions m_tokenRequestOptions;
mutable AccessToken m_accessToken;
mutable std::mutex m_accessTokenMutex;
@ -322,54 +333,21 @@ namespace Azure { namespace Core { namespace Http {
public:
/**
* @brief Construct a Bearer Token authentication policy with single authentication scope.
* @brief Construct a Bearer Token authentication policy.
*
* @param credential A #TokenCredential to use with this policy.
* @param scope Authentication scope.
* @param tokenRequestOptions #TokenRequestOptions.
*/
explicit BearerTokenAuthenticationPolicy(
std::shared_ptr<TokenCredential const> credential,
std::string scope)
: m_credential(std::move(credential))
{
m_scopes.emplace_back(std::move(scope));
}
/**
* @brief Construct a Bearer Token authentication policy with multiple authentication scopes.
*
* @param credential A #TokenCredential to use with this policy.
* @param scopes A vector of authentication scopes.
*/
explicit BearerTokenAuthenticationPolicy(
std::shared_ptr<TokenCredential const> credential,
std::vector<std::string> scopes)
: m_credential(std::move(credential)), m_scopes(std::move(scopes))
{
}
/**
* @brief Construct a Bearer Token authentication policy with multiple authentication scopes.
*
* @tparam A type of scopes sequence iterator.
*
* @param credential A #TokenCredential to use with this policy.
* @param scopesBegin An iterator pointing to begin of the sequence of scopes to use.
* @param scopesEnd An iterator pointing to an element after the last element in sequence of
* scopes to use.
*/
template <typename ScopesIterator>
explicit BearerTokenAuthenticationPolicy(
std::shared_ptr<TokenCredential const> credential,
ScopesIterator const& scopesBegin,
ScopesIterator const& scopesEnd)
: m_credential(std::move(credential)), m_scopes(scopesBegin, scopesEnd)
TokenRequestOptions tokenRequestOptions)
: m_credential(std::move(credential)), m_tokenRequestOptions(std::move(tokenRequestOptions))
{
}
std::unique_ptr<HttpPolicy> Clone() const override
{
return std::make_unique<BearerTokenAuthenticationPolicy>(m_credential, m_scopes);
return std::make_unique<BearerTokenAuthenticationPolicy>(m_credential, m_tokenRequestOptions);
}
std::unique_ptr<RawResponse> Send(

View File

@ -16,9 +16,10 @@ std::unique_ptr<RawResponse> BearerTokenAuthenticationPolicy::Send(
{
std::lock_guard<std::mutex> lock(m_accessTokenMutex);
if (std::chrono::system_clock::now() > m_accessToken.ExpiresOn)
// Refresh the token in 2 or less minutes before the actual expiration.
if ((std::chrono::system_clock::now() - std::chrono::minutes(2)) > m_accessToken.ExpiresOn)
{
m_accessToken = m_credential->GetToken(context, m_scopes);
m_accessToken = m_credential->GetToken(context, m_tokenRequestOptions);
}
request.AddHeader("authorization", "Bearer " + m_accessToken.Token);

View File

@ -2,6 +2,9 @@
## 1.0.0-beta.3 (Unreleased)
### Breaking Changes
- `ClientSecretCredential ` constructor takes `ClientSecretCredentialOptions` struct instead of authority host string. `TokenCredentialOptions` struct has authority host string as data member.
## 1.0.0-beta.2 (2021-01-13)

View File

@ -11,11 +11,38 @@
#include "azure/identity/dll_import_export.hpp"
#include <azure/core/credentials.hpp>
#include <azure/core/http/policy.hpp>
#include <string>
#include <utility>
namespace Azure { namespace Identity {
namespace Details {
AZ_IDENTITY_DLLEXPORT extern std::string const g_aadGlobalAuthority;
}
/**
* @brief Defines options for token authentication.
*/
struct ClientSecretCredentialOptions
{
public:
/**
* @brief Authentication authority URL.
* @detail Default value is Azure AD global authority -
* "https://login.microsoftonline.com/".
*
* @note Example of a \p authority string: "https://login.microsoftonline.us/". See national
* clouds' Azure AD authentication endpoints:
* https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud.
*/
std::string AuthorityHost = Details::g_aadGlobalAuthority;
/**
* @brief #TransportPolicyOptions for authentication HTTP pipeline.
*/
Azure::Core::Http::TransportPolicyOptions TransportPolicyOptions;
};
/**
* @brief This class is used by Azure SDK clients to authenticate with the Azure service using a
@ -23,12 +50,10 @@ namespace Azure { namespace Identity {
*/
class ClientSecretCredential : public Core::TokenCredential {
private:
AZ_IDENTITY_DLLEXPORT static std::string const g_aadGlobalAuthority;
std::string m_tenantId;
std::string m_clientId;
std::string m_clientSecret;
std::string m_authority;
ClientSecretCredentialOptions m_options;
public:
/**
@ -37,25 +62,21 @@ namespace Azure { namespace Identity {
* @param tenantId Tenant ID.
* @param clientId Client ID.
* @param clientSecret Client Secret.
* @param authority Authentication authority URL to set. If omitted, initializes credential with
* default authority (Azure AD global authority - "https://login.microsoftonline.com/").
*
* @note Example of a \p authority string: "https://login.microsoftonline.us/". See national
* clouds' Azure AD authentication endpoints:
* https://docs.microsoft.com/en-us/azure/active-directory/develop/authentication-national-cloud.
* @param options #ClientSecretCredentialOptions.
*/
explicit ClientSecretCredential(
std::string tenantId,
std::string clientId,
std::string clientSecret,
std::string authority = g_aadGlobalAuthority)
ClientSecretCredentialOptions options = ClientSecretCredentialOptions())
: m_tenantId(std::move(tenantId)), m_clientId(std::move(clientId)),
m_clientSecret(std::move(clientSecret)), m_authority(std::move(authority))
m_clientSecret(std::move(clientSecret)), m_options(std::move(options))
{
}
Core::AccessToken GetToken(Core::Context const& context, std::vector<std::string> const& scopes)
const override;
Core::AccessToken GetToken(
Core::Context const& context,
Core::Http::TokenRequestOptions const& tokenRequestOptions) const override;
};
}} // namespace Azure::Identity

View File

@ -34,8 +34,9 @@ namespace Azure { namespace Identity {
*/
explicit EnvironmentCredential();
Core::AccessToken GetToken(Core::Context const& context, std::vector<std::string> const& scopes)
const override;
Core::AccessToken GetToken(
Core::Context const& context,
Core::Http::TokenRequestOptions const& tokenRequestOptions) const override;
};
}} // namespace Azure::Identity

View File

@ -36,12 +36,12 @@ std::string UrlEncode(std::string const& s)
}
} // namespace
std::string const ClientSecretCredential::g_aadGlobalAuthority
std::string const Azure::Identity::Details::g_aadGlobalAuthority
= "https://login.microsoftonline.com/";
Azure::Core::AccessToken ClientSecretCredential::GetToken(
Azure::Core::Context const& context,
std::vector<std::string> const& scopes) const
Azure::Core::Http::TokenRequestOptions const& tokenRequestOptions) const
{
using namespace Azure::Core;
using namespace Azure::Core::Http;
@ -49,7 +49,7 @@ Azure::Core::AccessToken ClientSecretCredential::GetToken(
static std::string const errorMsgPrefix("ClientSecretCredential::GetToken: ");
try
{
Url url(m_authority);
Url url(m_options.AuthorityHost);
url.AppendPath(m_tenantId);
url.AppendPath("oauth2/v2.0/token");
@ -58,6 +58,7 @@ Azure::Core::AccessToken ClientSecretCredential::GetToken(
body << "grant_type=client_credentials&client_id=" << UrlEncode(m_clientId)
<< "&client_secret=" << UrlEncode(m_clientSecret);
auto const& scopes = tokenRequestOptions.Scopes;
if (!scopes.empty())
{
auto scopesIter = scopes.begin();
@ -81,12 +82,14 @@ Azure::Core::AccessToken ClientSecretCredential::GetToken(
request.AddHeader("Content-Length", std::to_string(bodyString.size()));
std::vector<std::unique_ptr<HttpPolicy>> policies;
policies.push_back(std::make_unique<RequestIdPolicy>());
policies.emplace_back(std::make_unique<RequestIdPolicy>());
RetryOptions retryOptions;
policies.push_back(std::make_unique<RetryPolicy>(retryOptions));
{
RetryOptions retryOptions;
policies.emplace_back(std::make_unique<RetryPolicy>(retryOptions));
}
policies.push_back(std::make_unique<TransportPolicy>());
policies.emplace_back(std::make_unique<TransportPolicy>(m_options.TransportPolicyOptions));
HttpPipeline httpPipeline(policies);
@ -176,13 +179,11 @@ Azure::Core::AccessToken ClientSecretCredential::GetToken(
}
auto const tokenEnd = responseBodyPos;
expiresInSeconds -= 2 * 60;
auto const responseBodyBegin = responseBody.begin();
return {
std::string(responseBodyBegin + tokenBegin, responseBodyBegin + tokenEnd),
std::chrono::system_clock::now()
+ std::chrono::seconds(expiresInSeconds < 0 ? 0 : expiresInSeconds),
std::chrono::system_clock::now() + std::chrono::seconds(expiresInSeconds),
};
}
catch (AuthenticationException const&)

View File

@ -53,8 +53,11 @@ EnvironmentCredential::EnvironmentCredential()
{
if (authority != nullptr)
{
ClientSecretCredentialOptions options;
options.AuthorityHost = authority;
m_credentialImpl.reset(
new ClientSecretCredential(tenantId, clientId, clientSecret, authority));
new ClientSecretCredential(tenantId, clientId, clientSecret, options));
}
else
{
@ -78,7 +81,7 @@ EnvironmentCredential::EnvironmentCredential()
Azure::Core::AccessToken EnvironmentCredential::GetToken(
Azure::Core::Context const& context,
std::vector<std::string> const& scopes) const
Azure::Core::Http::TokenRequestOptions const& tokenRequestOptions) const
{
using namespace Azure::Core;
@ -88,5 +91,5 @@ Azure::Core::AccessToken EnvironmentCredential::GetToken(
"Environment variables are not fully configured.");
}
return m_credentialImpl->GetToken(context, scopes);
return m_credentialImpl->GetToken(context, tokenRequestOptions);
}

View File

@ -26,8 +26,15 @@ KeyClient::KeyClient(
policies.emplace_back(std::make_unique<TelemetryPolicy>("KeyVault", apiVersion));
policies.emplace_back(std::make_unique<RequestIdPolicy>());
policies.emplace_back(std::make_unique<RetryPolicy>(options.RetryOptions));
policies.emplace_back(std::make_unique<BearerTokenAuthenticationPolicy>(
credential, "https://vault.azure.net/.default"));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{"https://vault.azure.net/.default"}};
policies.emplace_back(
std::make_unique<BearerTokenAuthenticationPolicy>(credential, tokenOptions));
}
policies.emplace_back(std::make_unique<LoggingPolicy>());
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));

View File

@ -88,8 +88,15 @@ namespace Azure { namespace Storage { namespace Blobs {
policies.emplace_back(p->Clone());
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -84,8 +84,15 @@ namespace Azure { namespace Storage { namespace Blobs {
policies.emplace_back(p->Clone());
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -79,8 +79,15 @@ namespace Azure { namespace Storage { namespace Blobs {
policies.emplace_back(p->Clone());
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -92,8 +92,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
policies.emplace_back(p->Clone());
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Azure::Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -162,8 +162,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Azure::Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -122,8 +122,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Azure::Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -151,8 +151,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Azure::Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);

View File

@ -154,8 +154,15 @@ namespace Azure { namespace Storage { namespace Files { namespace DataLake {
policies.emplace_back(p->Clone());
}
policies.emplace_back(std::make_unique<Storage::Details::StoragePerRetryPolicy>());
policies.emplace_back(std::make_unique<Core::Http::BearerTokenAuthenticationPolicy>(
credential, Azure::Storage::Details::StorageScope));
{
Azure::Core::Http::TokenRequestOptions const tokenOptions
= {{Storage::Details::StorageScope}};
policies.emplace_back(std::make_unique<Azure::Core::Http::BearerTokenAuthenticationPolicy>(
credential, tokenOptions));
}
policies.emplace_back(
std::make_unique<Azure::Core::Http::TransportPolicy>(options.TransportPolicyOptions));
m_pipeline = std::make_shared<Azure::Core::Http::HttpPipeline>(policies);