forked from Azure/azure-sdk-for-cpp
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient_assertion_credential.cpp
More file actions
160 lines (138 loc) · 5.6 KB
/
client_assertion_credential.cpp
File metadata and controls
160 lines (138 loc) · 5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#include "azure/identity/client_assertion_credential.hpp"
#include "private/identity_log.hpp"
#include "private/package_version.hpp"
#include "private/tenant_id_resolver.hpp"
#include "private/token_credential_impl.hpp"
#include <azure/core/internal/json/json.hpp>
using Azure::Identity::ClientAssertionCredential;
using Azure::Identity::ClientAssertionCredentialOptions;
using Azure::Core::Context;
using Azure::Core::Url;
using Azure::Core::_internal::StringExtensions;
using Azure::Core::Credentials::AccessToken;
using Azure::Core::Credentials::AuthenticationException;
using Azure::Core::Credentials::TokenRequestContext;
using Azure::Core::Http::HttpMethod;
using Azure::Identity::_detail::IdentityLog;
using Azure::Identity::_detail::TenantIdResolver;
using Azure::Identity::_detail::TokenCredentialImpl;
namespace {
bool IsValidTenantId(std::string const& tenantId)
{
const std::string allowedChars = ".-";
if (tenantId.empty())
{
return false;
}
for (auto const c : tenantId)
{
if (allowedChars.find(c) != std::string::npos)
{
continue;
}
if (!StringExtensions::IsAlphaNumeric(c))
{
return false;
}
}
return true;
}
} // namespace
ClientAssertionCredential::ClientAssertionCredential(
std::string tenantId,
std::string clientId,
std::function<std::string(Context const&)> const& assertionCallback,
ClientAssertionCredentialOptions const& options)
: TokenCredential("ClientAssertionCredential"), m_assertionCallback(assertionCallback),
m_clientCredentialCore(tenantId, options.AuthorityHost, options.AdditionallyAllowedTenants)
{
bool isTenantIdValid = IsValidTenantId(tenantId);
if (!isTenantIdValid)
{
IdentityLog::Write(
IdentityLog::Level::Warning,
"Invalid tenant ID provided for " + GetCredentialName()
+ ". The tenant ID must be a non-empty string containing only alphanumeric characters, "
"periods, or hyphens. You can locate your tenant ID by following the instructions "
"listed here: https://learn.microsoft.com/partner-center/find-ids-and-domain-names");
}
if (clientId.empty())
{
IdentityLog::Write(
IdentityLog::Level::Warning, "No client ID specified for " + GetCredentialName() + ".");
}
if (!assertionCallback)
{
IdentityLog::Write(
IdentityLog::Level::Warning,
"The assertionCallback must be a valid function that returns assertions for "
+ GetCredentialName() + ".");
}
if (isTenantIdValid && !clientId.empty() && assertionCallback)
{
m_tokenCredentialImpl = std::make_unique<TokenCredentialImpl>(options);
m_requestBody
= std::string(
"grant_type=client_credentials"
"&client_assertion_type="
"urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer" // cspell:disable-line
"&client_id=")
+ Url::Encode(clientId);
IdentityLog::Write(
IdentityLog::Level::Informational, GetCredentialName() + " was created successfully.");
}
else
{
// Rather than throwing an exception in the ctor, following the pattern in existing credentials
// to log the errors, and defer throwing an exception to the first call of GetToken(). This is
// primarily needed for credentials that are part of the DefaultAzureCredential, which this
// credential is not intended for.
IdentityLog::Write(
IdentityLog::Level::Warning, GetCredentialName() + " was not initialized correctly.");
}
}
ClientAssertionCredential::~ClientAssertionCredential() = default;
AccessToken ClientAssertionCredential::GetToken(
TokenRequestContext const& tokenRequestContext,
Context const& context) const
{
if (!m_tokenCredentialImpl)
{
auto const AuthUnavailable = GetCredentialName() + " authentication unavailable. ";
IdentityLog::Write(
IdentityLog::Level::Warning,
AuthUnavailable + "See earlier " + GetCredentialName() + " log messages for details.");
throw AuthenticationException(AuthUnavailable);
}
auto const tenantId = TenantIdResolver::Resolve(
m_clientCredentialCore.GetTenantId(),
tokenRequestContext,
m_clientCredentialCore.GetAdditionallyAllowedTenants());
auto const scopesStr
= m_clientCredentialCore.GetScopesString(tenantId, tokenRequestContext.Scopes);
// TokenCache::GetToken() and m_tokenCredentialImpl->GetToken() can only use the lambda
// argument when they are being executed. They are not supposed to keep a reference to lambda
// argument to call it later. Therefore, any capture made here will outlive the possible time
// frame when the lambda might get called.
return m_tokenCache.GetToken(scopesStr, tenantId, tokenRequestContext.MinimumExpiration, [&]() {
return m_tokenCredentialImpl->GetToken(context, false, [&]() {
auto body = m_requestBody;
if (!scopesStr.empty())
{
body += "&scope=" + scopesStr;
}
// Get the request url before calling m_assertionCallback to validate the authority host
// scheme. This is to avoid calling the assertion callback if the authority host scheme is
// invalid.
auto const requestUrl = m_clientCredentialCore.GetRequestUrl(tenantId);
const std::string assertion = m_assertionCallback(context);
body += "&client_assertion=" + Azure::Core::Url::Encode(assertion);
auto request
= std::make_unique<TokenCredentialImpl::TokenRequest>(HttpMethod::Post, requestUrl, body);
request->HttpRequest.SetHeader("Host", requestUrl.GetHost());
return request;
});
});
}