Skip to content

Commit 5fd26a6

Browse files
authored
Use client assertion credential within AzurePipelinesCredential and WorkloadIdentityCredential (#5802)
* Use ClientAssertionCredential within AzurePipelinesCredential. * Use ClientAssertionCredential in WorkloadIdentityCredential. * Fix DefaultAzureCredentia.LogMessages test since an extra log got added. * Disable tests that dont correctly simulate the token request and return the test response. * Address PR feedback and make sure base options are passed in to underlying client assertion credential. * Address PR feedback - move credential ctor into validation checks. * Address PR feedback, add const. * Add a ClientAssertionCredentialImpl to make sure logs use the calling credential name.
1 parent f9ea69c commit 5fd26a6

10 files changed

Lines changed: 181 additions & 228 deletions

sdk/identity/azure-identity/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ set(
7777
src/managed_identity_credential.cpp
7878
src/managed_identity_source.cpp
7979
src/private/chained_token_credential_impl.hpp
80+
src/private/client_assertion_credential_impl.hpp
8081
src/private/identity_log.hpp
8182
src/private/managed_identity_source.hpp
8283
src/private/package_version.hpp

sdk/identity/azure-identity/inc/azure/identity/azure_pipelines_credential.hpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#pragma once
1010

11+
#include "azure/identity/client_assertion_credential.hpp"
1112
#include "azure/identity/detail/client_credential_core.hpp"
1213
#include "azure/identity/detail/token_cache.hpp"
1314

@@ -20,7 +21,7 @@
2021

2122
namespace Azure { namespace Identity {
2223
namespace _detail {
23-
class TokenCredentialImpl;
24+
class ClientAssertionCredentialImpl;
2425
} // namespace _detail
2526

2627
/**
@@ -57,12 +58,9 @@ namespace Azure { namespace Identity {
5758
private:
5859
std::string m_serviceConnectionId;
5960
std::string m_systemAccessToken;
60-
_detail::ClientCredentialCore m_clientCredentialCore;
6161
Azure::Core::Http::_internal::HttpPipeline m_httpPipeline;
6262
std::string m_oidcRequestUrl;
63-
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
64-
std::string m_requestBody;
65-
_detail::TokenCache m_tokenCache;
63+
std::unique_ptr<_detail::ClientAssertionCredentialImpl> m_clientAssertionCredentialImpl;
6664

6765
std::string GetAssertion(Core::Context const& context) const;
6866
Azure::Core::Http::Request CreateOidcRequestMessage() const;

sdk/identity/azure-identity/inc/azure/identity/client_assertion_credential.hpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,15 @@
99
#pragma once
1010

1111
#include "azure/identity/detail/client_credential_core.hpp"
12-
#include "azure/identity/detail/token_cache.hpp"
1312

1413
#include <azure/core/credentials/token_credential_options.hpp>
15-
#include <azure/core/http/http.hpp>
1614

1715
#include <string>
1816
#include <vector>
1917

2018
namespace Azure { namespace Identity {
2119
namespace _detail {
22-
class TokenCredentialImpl;
20+
class ClientAssertionCredentialImpl;
2321
} // namespace _detail
2422

2523
/**
@@ -55,11 +53,7 @@ namespace Azure { namespace Identity {
5553
*/
5654
class ClientAssertionCredential final : public Core::Credentials::TokenCredential {
5755
private:
58-
std::function<std::string(Core::Context const&)> m_assertionCallback;
59-
_detail::ClientCredentialCore m_clientCredentialCore;
60-
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
61-
std::string m_requestBody;
62-
_detail::TokenCache m_tokenCache;
56+
std::unique_ptr<_detail::ClientAssertionCredentialImpl> m_impl;
6357

6458
public:
6559
/**

sdk/identity/azure-identity/inc/azure/identity/workload_identity_credential.hpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#pragma once
1010

11+
#include "azure/identity/client_assertion_credential.hpp"
1112
#include "azure/identity/detail/client_credential_core.hpp"
1213
#include "azure/identity/detail/token_cache.hpp"
1314

@@ -18,7 +19,7 @@
1819

1920
namespace Azure { namespace Identity {
2021
namespace _detail {
21-
class TokenCredentialImpl;
22+
class ClientAssertionCredentialImpl;
2223
} // namespace _detail
2324

2425
/**
@@ -74,12 +75,11 @@ namespace Azure { namespace Identity {
7475
*/
7576
class WorkloadIdentityCredential final : public Core::Credentials::TokenCredential {
7677
private:
77-
_detail::TokenCache m_tokenCache;
78-
_detail::ClientCredentialCore m_clientCredentialCore;
79-
std::unique_ptr<_detail::TokenCredentialImpl> m_tokenCredentialImpl;
80-
std::string m_requestBody;
78+
std::unique_ptr<_detail::ClientAssertionCredentialImpl> m_clientAssertionCredentialImpl;
8179
std::string m_tokenFilePath;
8280

81+
std::string GetAssertion(Core::Context const& context) const;
82+
8383
public:
8484
/**
8585
* @brief Constructs a Workload Identity Credential.

sdk/identity/azure-identity/src/azure_pipelines_credential.cpp

Lines changed: 21 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33

44
#include "azure/identity/azure_pipelines_credential.hpp"
55

6+
#include "private/client_assertion_credential_impl.hpp"
67
#include "private/identity_log.hpp"
78
#include "private/package_version.hpp"
89
#include "private/tenant_id_resolver.hpp"
9-
#include "private/token_credential_impl.hpp"
1010

1111
#include <azure/core/internal/json/json.hpp>
1212

@@ -28,30 +28,6 @@ using Azure::Core::Json::_internal::json;
2828
using Azure::Identity::_detail::IdentityLog;
2929
using Azure::Identity::_detail::PackageVersion;
3030
using Azure::Identity::_detail::TenantIdResolver;
31-
using Azure::Identity::_detail::TokenCredentialImpl;
32-
33-
namespace {
34-
bool IsValidTenantId(std::string const& tenantId)
35-
{
36-
const std::string allowedChars = ".-";
37-
if (tenantId.empty())
38-
{
39-
return false;
40-
}
41-
for (auto const c : tenantId)
42-
{
43-
if (allowedChars.find(c) != std::string::npos)
44-
{
45-
continue;
46-
}
47-
if (!StringExtensions::IsAlphaNumeric(c))
48-
{
49-
return false;
50-
}
51-
}
52-
return true;
53-
}
54-
} // namespace
5531

5632
AzurePipelinesCredential::AzurePipelinesCredential(
5733
std::string tenantId,
@@ -61,26 +37,10 @@ AzurePipelinesCredential::AzurePipelinesCredential(
6137
AzurePipelinesCredentialOptions const& options)
6238
: TokenCredential("AzurePipelinesCredential"), m_serviceConnectionId(serviceConnectionId),
6339
m_systemAccessToken(systemAccessToken),
64-
m_clientCredentialCore(tenantId, options.AuthorityHost, options.AdditionallyAllowedTenants),
6540
m_httpPipeline(HttpPipeline(options, "identity", PackageVersion::ToString(), {}, {}))
6641
{
6742
m_oidcRequestUrl = _detail::DefaultOptionValues::GetOidcRequestUrl();
6843

69-
bool isTenantIdValid = IsValidTenantId(tenantId);
70-
if (!isTenantIdValid)
71-
{
72-
IdentityLog::Write(
73-
IdentityLog::Level::Warning,
74-
"Invalid tenant ID provided for " + GetCredentialName()
75-
+ ". The tenant ID must be a non-empty string containing only alphanumeric characters, "
76-
"periods, or hyphens. You can locate your tenant ID by following the instructions "
77-
"listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names");
78-
}
79-
if (clientId.empty())
80-
{
81-
IdentityLog::Write(
82-
IdentityLog::Level::Warning, "No client ID specified for " + GetCredentialName() + ".");
83-
}
8444
if (serviceConnectionId.empty())
8545
{
8646
IdentityLog::Write(
@@ -101,20 +61,24 @@ AzurePipelinesCredential::AzurePipelinesCredential(
10161
+ "' needed by " + GetCredentialName() + ". This should be set by Azure Pipelines.");
10262
}
10363

104-
if (isTenantIdValid && !clientId.empty() && !serviceConnectionId.empty()
105-
&& !systemAccessToken.empty() && !m_oidcRequestUrl.empty())
64+
if (TenantIdResolver::IsValidTenantId(tenantId) && !clientId.empty()
65+
&& !serviceConnectionId.empty() && !systemAccessToken.empty() && !m_oidcRequestUrl.empty())
10666
{
107-
m_tokenCredentialImpl = std::make_unique<TokenCredentialImpl>(options);
108-
m_requestBody
109-
= std::string(
110-
"grant_type=client_credentials"
111-
"&client_assertion_type="
112-
"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line
113-
"&client_id=")
114-
+ Url::Encode(clientId);
115-
116-
IdentityLog::Write(
117-
IdentityLog::Level::Informational, GetCredentialName() + " was created successfully.");
67+
ClientAssertionCredentialOptions clientAssertionCredentialOptions{};
68+
// Get the options from the base class (including ClientOptions).
69+
static_cast<Core::Credentials::TokenCredentialOptions&>(clientAssertionCredentialOptions)
70+
= options;
71+
clientAssertionCredentialOptions.AuthorityHost = options.AuthorityHost;
72+
clientAssertionCredentialOptions.AdditionallyAllowedTenants
73+
= options.AdditionallyAllowedTenants;
74+
75+
std::function<std::string(Context const&)> callback
76+
= [this](Context const& context) { return GetAssertion(context); };
77+
78+
// ClientAssertionCredential validates the tenant ID, client ID, and assertion callback and logs
79+
// warning messages otherwise.
80+
m_clientAssertionCredentialImpl = std::make_unique<_detail::ClientAssertionCredentialImpl>(
81+
GetCredentialName(), tenantId, clientId, callback, clientAssertionCredentialOptions);
11882
}
11983
else
12084
{
@@ -214,7 +178,7 @@ AccessToken AzurePipelinesCredential::GetToken(
214178
TokenRequestContext const& tokenRequestContext,
215179
Context const& context) const
216180
{
217-
if (!m_tokenCredentialImpl)
181+
if (!m_clientAssertionCredentialImpl)
218182
{
219183
auto const AuthUnavailable = GetCredentialName() + " authentication unavailable. ";
220184

@@ -226,41 +190,6 @@ AccessToken AzurePipelinesCredential::GetToken(
226190
AuthUnavailable + "Azure Pipelines environment is not set up correctly.");
227191
}
228192

229-
auto const tenantId = TenantIdResolver::Resolve(
230-
m_clientCredentialCore.GetTenantId(),
231-
tokenRequestContext,
232-
m_clientCredentialCore.GetAdditionallyAllowedTenants());
233-
234-
auto const scopesStr
235-
= m_clientCredentialCore.GetScopesString(tenantId, tokenRequestContext.Scopes);
236-
237-
// TokenCache::GetToken() and m_tokenCredentialImpl->GetToken() can only use the lambda
238-
// argument when they are being executed. They are not supposed to keep a reference to lambda
239-
// argument to call it later. Therefore, any capture made here will outlive the possible time
240-
// frame when the lambda might get called.
241-
return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() {
242-
return m_tokenCredentialImpl->GetToken(context, false, [&]() {
243-
auto body = m_requestBody;
244-
if (!scopesStr.empty())
245-
{
246-
body += "&scope=" + scopesStr;
247-
}
248-
249-
// Get the request url before calling GetAssertion to validate the authority host scheme.
250-
// This is to avoid making a request to the OIDC endpoint if the authority host scheme is
251-
// invalid.
252-
auto const requestUrl = m_clientCredentialCore.GetRequestUrl(tenantId);
253-
254-
const std::string assertion = GetAssertion(context);
255-
256-
body += "&client_assertion=" + Azure::Core::Url::Encode(assertion);
257-
258-
auto request
259-
= std::make_unique<TokenCredentialImpl::TokenRequest>(HttpMethod::Post, requestUrl, body);
260-
261-
request->HttpRequest.SetHeader("Host", requestUrl.GetHost());
262-
263-
return request;
264-
});
265-
});
193+
return m_clientAssertionCredentialImpl->GetToken(
194+
GetCredentialName(), tokenRequestContext, context);
266195
}

0 commit comments

Comments
 (0)