-
Notifications
You must be signed in to change notification settings - Fork 127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ADFS support for ClientSecretCredential #1947
Changes from all commits
424a033
b80639d
a5b6bf7
daa8456
f252c90
5bf72b6
42bfcd7
fc473a8
ebc9b4b
33f6028
e003344
b9e6682
6405c09
303770b
0724222
0f2a875
f3476ea
18fbf0b
a29e1c3
cf281a3
537549d
d194d8f
fb7a27a
9bf4acd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// SPDX-License-Identifier: MIT | ||
|
||
/** | ||
* @file | ||
* @brief Options for #Azure::Core::Credentials::TokenCredential. | ||
*/ | ||
|
||
#pragma once | ||
|
||
#include "azure/core/internal/client_options.hpp" | ||
|
||
namespace Azure { namespace Core { namespace Credentials { | ||
/** | ||
* @brief Defines options for #Azure::Core::Credentials::TokenCredential. | ||
*/ | ||
struct TokenCredentialOptions : public Azure::Core::_internal::ClientOptions | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Needs CL entry. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is not a breaking change, and has the default value. All the code you could compile before can still compile now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I understand. It's a new type/new feature (which are also added to CL) :) |
||
{ | ||
}; | ||
}}} // namespace Azure::Core::Credentials |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,8 @@ | |
#include "azure/identity/dll_import_export.hpp" | ||
|
||
#include <azure/core/credentials/credentials.hpp> | ||
#include <azure/core/credentials/token_credential_options.hpp> | ||
#include <azure/core/http/policies/policy.hpp> | ||
#include <azure/core/internal/client_options.hpp> | ||
|
||
#include <string> | ||
#include <utility> | ||
|
@@ -25,7 +25,7 @@ namespace Azure { namespace Identity { | |
/** | ||
* @brief Defines options for token authentication. | ||
*/ | ||
struct ClientSecretCredentialOptions : public Azure::Core::_internal::ClientOptions | ||
struct ClientSecretCredentialOptions : public Azure::Core::Credentials::TokenCredentialOptions | ||
antkmsft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
public: | ||
/** | ||
|
@@ -64,12 +64,32 @@ namespace Azure { namespace Identity { | |
std::string tenantId, | ||
std::string clientId, | ||
std::string clientSecret, | ||
ClientSecretCredentialOptions options = ClientSecretCredentialOptions()) | ||
ClientSecretCredentialOptions options) | ||
: m_tenantId(std::move(tenantId)), m_clientId(std::move(clientId)), | ||
m_clientSecret(std::move(clientSecret)), m_options(std::move(options)) | ||
{ | ||
} | ||
|
||
/** | ||
* @brief Construct a Client Secret credential. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is an odd casing choice. Was it intentional? |
||
* | ||
* @param tenantId Tenant ID. | ||
* @param clientId Client ID. | ||
* @param clientSecret Client Secret. | ||
* @param options #Azure::Core::Credentials::TokenCredentialOptions. | ||
*/ | ||
explicit ClientSecretCredential( | ||
std::string tenantId, | ||
std::string clientId, | ||
std::string clientSecret, | ||
Azure::Core::Credentials::TokenCredentialOptions const& options | ||
= Azure::Core::Credentials::TokenCredentialOptions()) | ||
: m_tenantId(std::move(tenantId)), m_clientId(std::move(clientId)), | ||
m_clientSecret(std::move(clientSecret)) | ||
{ | ||
static_cast<Azure::Core::Credentials::TokenCredentialOptions&>(m_options) = options; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Add a comment explaining why this cast is OK. |
||
} | ||
|
||
Core::Credentials::AccessToken GetToken( | ||
Core::Credentials::TokenRequestContext const& tokenRequestContext, | ||
Core::Context const& context) const override; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,40 @@ | |
#include <chrono> | ||
#include <sstream> | ||
|
||
namespace { | ||
antkmsft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Assumes !scopes.empty() | ||
antkmsft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
std::string FormatScopes(std::vector<std::string> const& scopes, bool asResource) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it is internal function. You can only have single scope as resource, and when you want to build resource, you don't add scope suffix. |
||
{ | ||
if (asResource && scopes.size() == 1) | ||
{ | ||
auto resource = scopes[0]; | ||
constexpr char suffix[] = "/.default"; | ||
constexpr int suffixLen = sizeof(suffix) - 1; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are we doing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this necessarily requires a comment, this is the part of the language - when an array is initialized from a literal, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Source comments are rarely necessary. It's just meant for helpful hints for future maintainability when code changes, moves around. What happens if I change line 19 tomorrow to It's up to your discretion, as the PR author, what's clear/obvious by just reading the code. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can add it, but if we change it to But even without the "-1", |
||
auto const resourceLen = resource.length(); | ||
|
||
// If scopes[0] ends with '/.default', remove it. | ||
if (resourceLen >= suffixLen | ||
&& resource.find(suffix, resourceLen - suffixLen) != std::string::npos) | ||
{ | ||
resource = resource.substr(0, resourceLen - suffixLen); | ||
} | ||
|
||
return Azure::Core::Url::Encode(resource); | ||
} | ||
|
||
auto scopesIter = scopes.begin(); | ||
auto scopesStr = Azure::Core::Url::Encode(*scopesIter); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: re-add the comment here, that we assume |
||
|
||
auto const scopesEnd = scopes.end(); | ||
for (++scopesIter; scopesIter != scopesEnd; ++scopesIter) | ||
{ | ||
scopesStr += std::string(" ") + Azure::Core::Url::Encode(*scopesIter); | ||
} | ||
|
||
return scopesStr; | ||
} | ||
} // namespace | ||
|
||
using namespace Azure::Identity; | ||
|
||
std::string const Azure::Identity::_detail::g_aadGlobalAuthority | ||
|
@@ -27,24 +61,21 @@ Azure::Core::Credentials::AccessToken ClientSecretCredential::GetToken( | |
static std::string const errorMsgPrefix("ClientSecretCredential::GetToken: "); | ||
try | ||
{ | ||
auto const isAdfs = m_tenantId == "adfs"; | ||
|
||
Url url(m_options.AuthorityHost); | ||
url.AppendPath(m_tenantId); | ||
url.AppendPath("oauth2/v2.0/token"); | ||
url.AppendPath(isAdfs ? "oauth2/token" : "oauth2/v2.0/token"); | ||
|
||
std::ostringstream body; | ||
body << "grant_type=client_credentials&client_id=" << Url::Encode(m_clientId) | ||
<< "&client_secret=" << Url::Encode(m_clientSecret); | ||
|
||
auto const& scopes = tokenRequestContext.Scopes; | ||
if (!scopes.empty()) | ||
{ | ||
auto scopesIter = scopes.begin(); | ||
body << "&scope=" << Url::Encode(*scopesIter); | ||
|
||
auto const scopesEnd = scopes.end(); | ||
for (++scopesIter; scopesIter != scopesEnd; ++scopesIter) | ||
auto const& scopes = tokenRequestContext.Scopes; | ||
antkmsft marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (!scopes.empty()) | ||
{ | ||
body << " " << *scopesIter; | ||
body << "&scope=" << FormatScopes(scopes, isAdfs); | ||
} | ||
} | ||
|
||
|
@@ -58,6 +89,11 @@ Azure::Core::Credentials::AccessToken ClientSecretCredential::GetToken( | |
request.SetHeader("Content-Type", "application/x-www-form-urlencoded"); | ||
request.SetHeader("Content-Length", std::to_string(bodyString.size())); | ||
|
||
if (isAdfs) | ||
{ | ||
request.SetHeader("Host", url.GetHost()); | ||
} | ||
|
||
HttpPipeline httpPipeline(m_options, "Identity-client-secret-credential", "", {}, {}); | ||
|
||
std::shared_ptr<RawResponse> response = httpPipeline.Send(request, context); | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -21,7 +21,8 @@ | |||||
|
||||||
using namespace Azure::Identity; | ||||||
|
||||||
EnvironmentCredential::EnvironmentCredential() | ||||||
EnvironmentCredential::EnvironmentCredential( | ||||||
Azure::Core::Credentials::TokenCredentialOptions options) | ||||||
{ | ||||||
#if !defined(WINAPI_PARTITION_DESKTOP) \ | ||||||
|| WINAPI_PARTITION_DESKTOP // See azure/core/platform.hpp for explanation. | ||||||
|
@@ -53,28 +54,29 @@ EnvironmentCredential::EnvironmentCredential() | |||||
{ | ||||||
if (authority != nullptr) | ||||||
{ | ||||||
ClientSecretCredentialOptions options; | ||||||
options.AuthorityHost = authority; | ||||||
ClientSecretCredentialOptions clientSecretCredentialOptions; | ||||||
static_cast<Core::_internal::ClientOptions&>(clientSecretCredentialOptions) = options; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this be
Suggested change
What's this line doing? Is it only copying over the fields into the local Also, add comment why static cast is OK here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @antkmsft does this need to be fixed for correctness? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For now we are fine, but yes, it needs fixing - I'll grab this plus a few other comments and create a follow-up PR. |
||||||
clientSecretCredentialOptions.AuthorityHost = authority; | ||||||
|
||||||
m_credentialImpl.reset( | ||||||
new ClientSecretCredential(tenantId, clientId, clientSecret, options)); | ||||||
m_credentialImpl.reset(new ClientSecretCredential( | ||||||
tenantId, clientId, clientSecret, clientSecretCredentialOptions)); | ||||||
} | ||||||
else | ||||||
{ | ||||||
m_credentialImpl.reset(new ClientSecretCredential(tenantId, clientId, clientSecret)); | ||||||
m_credentialImpl.reset( | ||||||
new ClientSecretCredential(tenantId, clientId, clientSecret, options)); | ||||||
} | ||||||
} | ||||||
// TODO: These credential types are not implemented. Uncomment when implemented. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since you are hereand modifying this code already, remove this TODO comment from source and file an issue. |
||||||
// else if (username != nullptr && password != nullptr) | ||||||
//{ | ||||||
// m_credentialImpl.reset( | ||||||
// new UsernamePasswordCredential(username, password, tenantId, clientId)); | ||||||
//} | ||||||
// { | ||||||
// m_credentialImpl.reset( | ||||||
// new UsernamePasswordCredential(tenantId, clientId, username, password, options)); | ||||||
// } | ||||||
// else if (clientCertificatePath != nullptr) | ||||||
//{ | ||||||
// m_credentialImpl.reset( | ||||||
// new ClientCertificateCredential(tenantId, clientId, clientCertificatePath)); | ||||||
//} | ||||||
// { | ||||||
// m_credentialImpl.reset(new ClientCertificateCredential(tenantId, clientId, options)); | ||||||
// } | ||||||
} | ||||||
#endif | ||||||
} | ||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need to include these headers to
core.hpp
and add tests to the simplified header.Fixed in #2070