Commit 46a027bf authored by William Lin's avatar William Lin Committed by Commit Bot

Add support to enable granular permissions in GetAuthToken

For unbundled consent to be available for extensions to use, Chrome
needs to be able to explicitly request for it in an API call body.
Additionally, extension developers should be able to toggle the use of
unbundled consent during the transition into using unbundled consent.

This CL adds both requirements. The changes include adding an optional
parameter 'enableGranularPermissions' in GetAuthToken for developers to
toggle unbundled consent and adjusting Chrome's API calls accordingly
depending on the value of this optional parameter.

Bug: 1100535
Change-Id: I5051ebe2b304a0a93f4e98aea447c0668e5e0a3a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2350544
Commit-Queue: William Lin <williamlin@google.com>
Reviewed-by: default avatarDevlin <rdevlin.cronin@chromium.org>
Reviewed-by: default avatarAlex Ilin <alexilin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#799918}
parent 88e441a8
......@@ -523,6 +523,10 @@ class FakeGetAuthTokenFunction : public IdentityGetAuthTokenFunction {
return flow;
}
bool enable_granular_permissions() const {
return IdentityGetAuthTokenFunction::enable_granular_permissions();
}
private:
~FakeGetAuthTokenFunction() override {}
bool login_access_token_result_;
......@@ -2863,6 +2867,34 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, SubsetMatchCachePopulate) {
1);
}
// Ensure that the scopes returned by the function reflects the granted scopes
// and not the requested scopes.
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, GranularPermissionsResponse) {
SignIn("primary@example.com");
auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>();
auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES));
func->set_extension(extension.get());
std::set<std::string> scopes = {"email", "foobar"};
func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS,
scopes);
std::string access_token;
std::set<std::string> granted_scopes;
RunGetAuthTokenFunction(func.get(),
"[{\"enableGranularPermissions\": true,"
"\"scopes\": [\"email\", \"bar\"]}]",
browser(), &access_token, &granted_scopes);
EXPECT_EQ(kAccessToken, access_token);
EXPECT_EQ(scopes, granted_scopes);
EXPECT_TRUE(func->enable_granular_permissions());
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
histogram_tester()->ExpectUniqueSample(
kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone,
1);
}
#if defined(OS_CHROMEOS)
class GetAuthTokenFunctionPublicSessionTest : public GetAuthTokenFunctionTest {
public:
......@@ -2942,6 +2974,52 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionPublicSessionTest, Whitelisted) {
#endif
// There are two parameters, which are stored in a std::pair, for these tests.
//
// std::string: the GetAuthToken arguments
// bool: the expected value of GetAuthToken's enable_granular_permissions
class GetAuthTokenFunctionEnableGranularPermissionsTest
: public GetAuthTokenFunctionTest,
public testing::WithParamInterface<std::pair<std::string, bool>> {};
// Provided with the arguments for GetAuthToken, ensures that GetAuthToken's
// enable_granular_permissions is some expected value when the
// 'ReturnScopesInGetAuthToken' feature flag is enabled.
IN_PROC_BROWSER_TEST_P(GetAuthTokenFunctionEnableGranularPermissionsTest,
EnableGranularPermissions) {
const std::string& args = GetParam().first;
bool expected_enable_granular_permissions = GetParam().second;
SignIn("primary@example.com");
auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>();
auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES));
func->set_extension(extension.get());
func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::string access_token;
std::set<std::string> granted_scopes;
RunGetAuthTokenFunction(func.get(), "[{" + args + "}]", browser(),
&access_token, &granted_scopes);
EXPECT_EQ(kAccessToken, access_token);
EXPECT_EQ(func->GetExtensionTokenKeyForTest()->scopes, granted_scopes);
EXPECT_EQ(expected_enable_granular_permissions,
func->enable_granular_permissions());
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
histogram_tester()->ExpectUniqueSample(
kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone,
1);
}
INSTANTIATE_TEST_SUITE_P(
All,
GetAuthTokenFunctionEnableGranularPermissionsTest,
testing::Values(std::make_pair("\"enableGranularPermissions\": true", true),
std::make_pair("\"enableGranularPermissions\": false",
false),
std::make_pair("", false)));
class GetAuthTokenFunctionReturnScopesDisabledTest
: public GetAuthTokenFunctionTest {
public:
......@@ -3008,6 +3086,51 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionReturnScopesDisabledTest,
IdentityGetAuthTokenError::State::kMintTokenAuthFailure, 1);
}
// There are two parameters, which are stored in a std::pair, for these tests.
//
// std::string: the GetAuthToken arguments
// bool: the expected value of GetAuthToken's enable_granular_permissions
class GetAuthTokenFunctionReturnScopesDisabledEnableGranularPermissionsTest
: public GetAuthTokenFunctionReturnScopesDisabledTest,
public testing::WithParamInterface<std::pair<std::string, bool>> {};
// Provided with the arguments for GetAuthToken, ensures that GetAuthToken's
// enable_granular_permissions is some expected value when the
// 'ReturnScopesInGetAuthToken' feature flag is disabled.
IN_PROC_BROWSER_TEST_P(
GetAuthTokenFunctionReturnScopesDisabledEnableGranularPermissionsTest,
EnableGranularPermissions) {
const std::string& args = GetParam().first;
bool expected_enable_granular_permissions = GetParam().second;
SignIn("primary@example.com");
auto func = base::MakeRefCounted<FakeGetAuthTokenFunction>();
auto extension = base::WrapRefCounted(CreateExtension(CLIENT_ID | SCOPES));
func->set_extension(extension.get());
func->push_mint_token_result(TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS);
std::string access_token;
RunGetAuthTokenFunctionReturnScopesDisabled(func.get(), "[{" + args + "}]",
browser(), &access_token);
EXPECT_EQ(kAccessToken, access_token);
EXPECT_EQ(expected_enable_granular_permissions,
func->enable_granular_permissions());
EXPECT_FALSE(func->login_ui_shown());
EXPECT_FALSE(func->scope_ui_shown());
histogram_tester()->ExpectUniqueSample(
kGetAuthTokenResultHistogramName, IdentityGetAuthTokenError::State::kNone,
1);
}
INSTANTIATE_TEST_SUITE_P(
All,
GetAuthTokenFunctionReturnScopesDisabledEnableGranularPermissionsTest,
testing::Values(
std::make_pair("\"enableGranularPermissions\": true", false),
std::make_pair("\"enableGranularPermissions\": false", false),
std::make_pair("", false)));
class RemoveCachedAuthTokenFunctionTest : public ExtensionBrowserTest {
protected:
bool InvalidateDefaultToken() {
......
......@@ -99,18 +99,11 @@ bool IsReturnScopesInGetAuthTokenEnabled() {
} // namespace
IdentityGetAuthTokenFunction::IdentityGetAuthTokenFunction()
:
#if defined(OS_CHROMEOS)
OAuth2AccessTokenManager::Consumer(
kExtensionsIdentityAPIOAuthConsumerName),
: OAuth2AccessTokenManager::Consumer(
kExtensionsIdentityAPIOAuthConsumerName)
#endif
interactive_(false),
should_prompt_for_scopes_(false),
should_prompt_for_signin_(false),
token_key_(/*extension_id=*/std::string(),
/*account_id=*/CoreAccountId(),
/*scopes=*/std::set<std::string>()),
scoped_identity_manager_observer_(this) {
{
}
IdentityGetAuthTokenFunction::~IdentityGetAuthTokenFunction() {
......@@ -139,6 +132,11 @@ ExtensionFunction::ResponseAction IdentityGetAuthTokenFunction::Run() {
should_prompt_for_signin_ =
interactive_ && IsBrowserSigninAllowed(GetProfile());
enable_granular_permissions_ =
IsReturnScopesInGetAuthTokenEnabled() && params->details.get() &&
params->details->enable_granular_permissions.get() &&
*params->details->enable_granular_permissions;
const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension());
// Check that the necessary information is present in the manifest.
......@@ -1041,8 +1039,8 @@ IdentityGetAuthTokenFunction::CreateMintTokenFlow() {
extension()->id(), oauth2_client_id_,
std::vector<std::string>(token_key_.scopes.begin(),
token_key_.scopes.end()),
signin_scoped_device_id, consent_result_,
GetOAuth2MintTokenFlowVersion(),
enable_granular_permissions_, signin_scoped_device_id,
consent_result_, GetOAuth2MintTokenFlowVersion(),
GetOAuth2MintTokenFlowChannel(), gaia_mint_token_mode_));
return mint_token_flow;
}
......@@ -1075,4 +1073,8 @@ Profile* IdentityGetAuthTokenFunction::GetProfile() const {
return Profile::FromBrowserContext(browser_context());
}
bool IdentityGetAuthTokenFunction::enable_granular_permissions() const {
return enable_granular_permissions_;
}
} // namespace extensions
......@@ -128,6 +128,10 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction,
std::unique_ptr<signin::AccessTokenFetcher>
token_key_account_access_token_fetcher_;
// Returns whether granular permissions will be requested.
// Exposed for testing.
bool enable_granular_permissions() const;
private:
FRIEND_TEST_ALL_PREFIXES(GetAuthTokenFunctionTest,
ComponentWithChromeClientId);
......@@ -216,17 +220,20 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction,
// Returns true if extensions are restricted to the primary account.
bool IsPrimaryAccountOnly() const;
bool interactive_;
bool should_prompt_for_scopes_;
bool interactive_ = false;
bool should_prompt_for_scopes_ = false;
IdentityMintRequestQueue::MintType mint_token_flow_type_;
std::unique_ptr<OAuth2MintTokenFlow> mint_token_flow_;
OAuth2MintTokenFlow::Mode gaia_mint_token_mode_;
bool should_prompt_for_signin_;
bool should_prompt_for_signin_ = false;
bool enable_granular_permissions_ = false;
// Shown in the extension login prompt.
std::string email_for_default_web_account_;
ExtensionTokenKey token_key_;
ExtensionTokenKey token_key_{/*extension_id=*/"",
/*account_id=*/CoreAccountId(),
/*scopes=*/{}};
std::string oauth2_client_id_;
// When launched in interactive mode, and if there is no existing grant,
// a permissions prompt will be popped up to the user.
......@@ -244,7 +251,7 @@ class IdentityGetAuthTokenFunction : public ExtensionFunction,
identity_api_shutdown_subscription_;
ScopedObserver<signin::IdentityManager, signin::IdentityManager::Observer>
scoped_identity_manager_observer_;
scoped_identity_manager_observer_{this};
// This class can be listening to account changes, but only for one type of
// events at a time.
......
......@@ -58,6 +58,11 @@ namespace identity {
// When the <code>scopes</code> field is present, it overrides the
// list of scopes specified in manifest.json.
DOMString[]? scopes;
// The <code>enableGranularPermissions</code> flag allows extensions to
// opt-in early to the granular permissions consent screen, in which
// requested permissions are granted or denied individually.
[nodoc] boolean? enableGranularPermissions;
};
dictionary InvalidTokenDetails {
......
......@@ -52,8 +52,8 @@ const base::Feature kContentScriptsMatchOriginAsFallback{
const base::Feature kReportKeepaliveUkm{"ReportKeepaliveUkm",
base::FEATURE_ENABLED_BY_DEFAULT};
// Enables the granted OAuth2 scopes to be returned in the GetAuthToken callback
// function.
// Enables callers of the GetAuthToken API to request for the unbundled consent
// UI and populates the scopes parameter in the GetAuthToken callback function.
const base::Feature kReturnScopesInGetAuthToken{
"ReturnScopesInGetAuthToken", base::FEATURE_DISABLED_BY_DEFAULT};
......
......@@ -31,8 +31,8 @@
namespace {
const char kForceValueFalse[] = "false";
const char kForceValueTrue[] = "true";
const char kValueFalse[] = "false";
const char kValueTrue[] = "true";
const char kResponseTypeValueNone[] = "none";
const char kResponseTypeValueToken[] = "token";
......@@ -40,6 +40,7 @@ const char kOAuth2IssueTokenBodyFormat[] =
"force=%s"
"&response_type=%s"
"&scope=%s"
"&enable_granular_permissions=%s"
"&client_id=%s"
"&origin=%s"
"&lib_ver=%s"
......@@ -147,6 +148,7 @@ OAuth2MintTokenFlow::Parameters::Parameters(
const std::string& eid,
const std::string& cid,
const std::vector<std::string>& scopes_arg,
bool enable_granular_permissions,
const std::string& device_id,
const std::string& consent_result,
const std::string& version,
......@@ -155,6 +157,7 @@ OAuth2MintTokenFlow::Parameters::Parameters(
: extension_id(eid),
client_id(cid),
scopes(scopes_arg),
enable_granular_permissions(enable_granular_permissions),
device_id(device_id),
consent_result(consent_result),
version(version),
......@@ -210,20 +213,24 @@ GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
}
std::string OAuth2MintTokenFlow::CreateApiCallBody() {
const char* force_value =
(parameters_.mode == MODE_MINT_TOKEN_FORCE ||
const char* force_value = (parameters_.mode == MODE_MINT_TOKEN_FORCE ||
parameters_.mode == MODE_RECORD_GRANT)
? kForceValueTrue : kForceValueFalse;
? kValueTrue
: kValueFalse;
const char* response_type_value =
(parameters_.mode == MODE_MINT_TOKEN_NO_FORCE ||
parameters_.mode == MODE_MINT_TOKEN_FORCE)
? kResponseTypeValueToken : kResponseTypeValueNone;
const char* enable_granular_permissions_value =
parameters_.enable_granular_permissions ? kValueTrue : kValueFalse;
std::string body = base::StringPrintf(
kOAuth2IssueTokenBodyFormat,
net::EscapeUrlEncodedData(force_value, true).c_str(),
net::EscapeUrlEncodedData(response_type_value, true).c_str(),
net::EscapeUrlEncodedData(base::JoinString(parameters_.scopes, " "), true)
.c_str(),
net::EscapeUrlEncodedData(enable_granular_permissions_value, true)
.c_str(),
net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(),
net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str(),
net::EscapeUrlEncodedData(parameters_.version, true).c_str(),
......
......@@ -115,6 +115,7 @@ class OAuth2MintTokenFlow : public OAuth2ApiCallFlow {
Parameters(const std::string& eid,
const std::string& cid,
const std::vector<std::string>& scopes_arg,
bool enable_granular_permissions,
const std::string& device_id,
const std::string& consent_result,
const std::string& version,
......@@ -126,6 +127,7 @@ class OAuth2MintTokenFlow : public OAuth2ApiCallFlow {
std::string extension_id;
std::string client_id;
std::vector<std::string> scopes;
bool enable_granular_permissions;
std::string device_id;
std::string consent_result;
std::string version;
......
......@@ -235,21 +235,28 @@ class OAuth2MintTokenFlowTest : public testing::Test {
const network::mojom::URLResponseHeadPtr head_200_;
void CreateFlow(OAuth2MintTokenFlow::Mode mode) {
return CreateFlow(&delegate_, mode, "", "");
return CreateFlow(&delegate_, mode, false, "", "");
}
void CreateFlowWithDeviceId(const std::string& device_id) {
void CreateFlowWithEnableGranularPermissions(
const bool enable_granular_permissions) {
return CreateFlow(&delegate_, OAuth2MintTokenFlow::MODE_ISSUE_ADVICE,
enable_granular_permissions, "", "");
}
void CreateFlowWithDeviceId(const std::string& device_id) {
return CreateFlow(&delegate_, OAuth2MintTokenFlow::MODE_ISSUE_ADVICE, false,
device_id, "");
}
void CreateFlowWithConsentResult(const std::string& consent_result) {
return CreateFlow(&delegate_, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE,
"", consent_result);
false, "", consent_result);
}
void CreateFlow(MockDelegate* delegate,
OAuth2MintTokenFlow::Mode mode,
const bool enable_granular_permissions,
const std::string& device_id,
const std::string& consent_result) {
std::string ext_id = "ext1";
......@@ -258,9 +265,9 @@ class OAuth2MintTokenFlowTest : public testing::Test {
std::string channel = "test_channel";
std::vector<std::string> scopes(CreateTestScopes());
flow_ = std::make_unique<MockMintTokenFlow>(
delegate, OAuth2MintTokenFlow::Parameters(ext_id, client_id, scopes,
device_id, consent_result,
version, channel, mode));
delegate, OAuth2MintTokenFlow::Parameters(
ext_id, client_id, scopes, enable_granular_permissions,
device_id, consent_result, version, channel, mode));
}
void ProcessApiCallSuccess(const network::mojom::URLResponseHead* head,
......@@ -295,6 +302,7 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
"force=false"
"&response_type=none"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=false"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
......@@ -308,6 +316,7 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
"force=true"
"&response_type=none"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=false"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
......@@ -321,6 +330,7 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
"force=false"
"&response_type=token"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=false"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
......@@ -334,6 +344,21 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
"force=true"
"&response_type=token"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=false"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
"&release_channel=test_channel");
EXPECT_EQ(expected_body, body);
}
{ // Mint token with granular permissions enabled.
CreateFlowWithEnableGranularPermissions(true);
std::string body = flow_->CreateApiCallBody();
std::string expected_body(
"force=false"
"&response_type=none"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=true"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
......@@ -347,6 +372,7 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
"force=false"
"&response_type=none"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=false"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
......@@ -362,6 +388,7 @@ TEST_F(OAuth2MintTokenFlowTest, CreateApiCallBody) {
"force=false"
"&response_type=token"
"&scope=http://scope1+http://scope2"
"&enable_granular_permissions=false"
"&client_id=client1"
"&origin=ext1"
"&lib_ver=test_version"
......@@ -679,7 +706,8 @@ TEST_F(OAuth2MintTokenFlowTest, ProcessApiCallSuccess_RemoteConsentFailure) {
TEST_F(OAuth2MintTokenFlowTest, ProcessApiCallFailure_NullDelegate) {
network::mojom::URLResponseHead head;
CreateFlow(nullptr, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE, "", "");
CreateFlow(nullptr, OAuth2MintTokenFlow::MODE_MINT_TOKEN_NO_FORCE, false, "",
"");
ProcessApiCallFailure(net::ERR_FAILED, &head, nullptr);
histogram_tester_.ExpectUniqueSample(
kOAuth2MintTokenApiCallResultHistogram,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment