blob: 0653f905b5715a591785108fe7db8c883268a1f3 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/webid/federated_identity_account_keyed_permission_context.h"
#include <memory>
#include "base/memory/raw_ptr.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/common/content_settings_types.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace {
const char kTestIdpOriginKey[] = "identity-provider";
}
class FederatedIdentityAccountKeyedPermissionContextTest
: public testing::Test {
public:
FederatedIdentityAccountKeyedPermissionContextTest() {
context_ = std::make_unique<FederatedIdentityAccountKeyedPermissionContext>(
&profile_, ContentSettingsType::FEDERATED_IDENTITY_SHARING,
kTestIdpOriginKey);
}
void TearDown() override { context_.reset(); }
~FederatedIdentityAccountKeyedPermissionContextTest() override = default;
FederatedIdentityAccountKeyedPermissionContextTest(
FederatedIdentityAccountKeyedPermissionContextTest&) = delete;
FederatedIdentityAccountKeyedPermissionContextTest& operator=(
FederatedIdentityAccountKeyedPermissionContextTest&) = delete;
FederatedIdentityAccountKeyedPermissionContext* context() {
return context_.get();
}
TestingProfile* profile() { return &profile_; }
private:
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<FederatedIdentityAccountKeyedPermissionContext> context_;
TestingProfile profile_;
};
// Test that the key is identical reglardless of whether the permission was
// stored using the old format (prior to the relying-party-embedder key being
// added) or the new format.
TEST_F(FederatedIdentityAccountKeyedPermissionContextTest,
VerifyKeyRequesterAndEmbedderIdentical) {
const url::Origin rp = url::Origin::Create(GURL("https://rp.example"));
const url::Origin idp = url::Origin::Create(GURL("https://idp.example"));
const std::string account("consetogo");
// Old Format
{
base::Value::Dict new_object;
new_object.Set(kTestIdpOriginKey, idp.Serialize());
base::Value::List account_list;
account_list.Append(account);
new_object.Set("account-ids", base::Value(std::move(account_list)));
context()->GrantObjectPermission(rp, base::Value(std::move(new_object)));
}
auto granted_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, granted_objects.size());
std::string key_old_format =
context()->GetKeyForObject(granted_objects[0]->value);
// Cleanup
context()->RevokePermission(rp, rp, idp, account);
EXPECT_TRUE(context()->GetAllGrantedObjects().empty());
// New format
context()->GrantPermission(rp, rp, idp, account);
granted_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, granted_objects.size());
std::string key_new_format =
context()->GetKeyForObject(granted_objects[0]->value);
EXPECT_EQ(key_old_format, key_new_format);
}
// Test that '<' in the url::Origin parameters passed to
// FederatedIdentityAccountKeyedPermissionContext::GrantPermission() are
// escaped.
// '<' is used as a separator in
// FederatedIdentityAccountKeyedPermissionContextTest::GetKeyForObject().
TEST_F(FederatedIdentityAccountKeyedPermissionContextTest, VerifyKeySeparator) {
const url::Origin rp = url::Origin::Create(GURL("https://rp<.example</<?<"));
const url::Origin idp =
url::Origin::Create(GURL("https://idp<.example</<?<"));
const url::Origin rp_embedder =
url::Origin::Create(GURL("https://rp-embedder<.example</<?<"));
const std::string account("consetogo");
context()->GrantPermission(rp, rp_embedder, idp, account);
auto granted_objects = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, granted_objects.size());
std::string key = context()->GetKeyForObject(granted_objects[0]->value);
EXPECT_EQ("https://idp%3C.example%3C<https://rp-embedder%3C.example%3C", key);
}
// Test calling
// FederatedIdentityAccountKeyedPermissionContextTest::HasPermission() for
// permissions stored with the pre-relying-party-embedder-format.
TEST_F(FederatedIdentityAccountKeyedPermissionContextTest,
CompatibleWithOldFormat) {
const url::Origin rp = url::Origin::Create(GURL("https://rp.example"));
const url::Origin idp1 = url::Origin::Create(GURL("https://idp1.example"));
const url::Origin idp2 = url::Origin::Create(GURL("https://idp2.example"));
const url::Origin other_origin =
url::Origin::Create(GURL("https://other.example"));
const std::string account_a("conestogo");
const std::string account_b("woolwich");
const std::string account_c("wellesley");
{
base::Value::Dict new_object;
new_object.Set(kTestIdpOriginKey, idp1.Serialize());
base::Value::List account_list;
account_list.Append(account_a);
account_list.Append(account_b);
new_object.Set("account-ids", base::Value(std::move(account_list)));
context()->GrantObjectPermission(rp, base::Value(std::move(new_object)));
}
{
base::Value::Dict new_object;
new_object.Set(kTestIdpOriginKey, idp2.Serialize());
base::Value::List account_list;
account_list.Append(account_c);
new_object.Set("account-ids", base::Value(std::move(account_list)));
context()->GrantObjectPermission(rp, base::Value(std::move(new_object)));
}
// Permissions in the old format should only be returned when
// relying-party-requester == relying-party-embedder.
EXPECT_TRUE(context()->HasPermission(rp, rp, idp1, account_a));
EXPECT_TRUE(context()->HasPermission(rp, rp, idp1, account_b));
EXPECT_TRUE(context()->HasPermission(rp, rp, idp2, account_c));
EXPECT_FALSE(context()->HasPermission(rp, other_origin, idp1, account_a));
EXPECT_FALSE(context()->HasPermission(rp, rp, idp1, account_c));
}
namespace {
struct PermissionGrant {
url::Origin relying_party_requester;
url::Origin relying_party_embedder;
url::Origin identity_provider;
std::string account_id;
};
} // anonymous namespace
void TestGrantAndRevoke(FederatedIdentityAccountKeyedPermissionContext* context,
const PermissionGrant& grant1,
const PermissionGrant& grant2) {
context->GrantPermission(grant1.relying_party_requester,
grant1.relying_party_embedder,
grant1.identity_provider, grant1.account_id);
EXPECT_TRUE(context->HasPermission(
grant1.relying_party_requester, grant1.relying_party_embedder,
grant1.identity_provider, grant1.account_id));
EXPECT_FALSE(context->HasPermission(
grant2.relying_party_requester, grant2.relying_party_embedder,
grant2.identity_provider, grant2.account_id));
context->GrantPermission(grant2.relying_party_requester,
grant2.relying_party_embedder,
grant2.identity_provider, grant2.account_id);
EXPECT_TRUE(context->HasPermission(
grant1.relying_party_requester, grant1.relying_party_embedder,
grant1.identity_provider, grant1.account_id));
EXPECT_TRUE(context->HasPermission(
grant2.relying_party_requester, grant2.relying_party_embedder,
grant2.identity_provider, grant2.account_id));
context->RevokePermission(grant1.relying_party_requester,
grant1.relying_party_embedder,
grant1.identity_provider, grant1.account_id);
EXPECT_FALSE(context->HasPermission(
grant1.relying_party_requester, grant1.relying_party_embedder,
grant1.identity_provider, grant1.account_id));
EXPECT_TRUE(context->HasPermission(
grant2.relying_party_requester, grant2.relying_party_embedder,
grant2.identity_provider, grant2.account_id));
context->RevokePermission(grant2.relying_party_requester,
grant2.relying_party_embedder,
grant2.identity_provider, grant2.account_id);
EXPECT_FALSE(context->HasPermission(
grant1.relying_party_requester, grant1.relying_party_embedder,
grant1.identity_provider, grant1.account_id));
EXPECT_FALSE(context->HasPermission(
grant2.relying_party_requester, grant2.relying_party_embedder,
grant2.identity_provider, grant2.account_id));
EXPECT_TRUE(context->GetAllGrantedObjects().empty());
}
// Test granting and revoking a permission.
TEST_F(FederatedIdentityAccountKeyedPermissionContextTest, GrantAndRevoke) {
const url::Origin rp_requester1 =
url::Origin::Create(GURL("https://rp1.example"));
const url::Origin rp_requester2 =
url::Origin::Create(GURL("https://rp2.example"));
const url::Origin rp_embedder1 =
url::Origin::Create(GURL("https://rp-embedder1.example"));
const url::Origin rp_embedder2 =
url::Origin::Create(GURL("https://rp-embedder2.example"));
const url::Origin idp1 = url::Origin::Create(GURL("https://idp1.example"));
const url::Origin idp2 = url::Origin::Create(GURL("https://idp2.example"));
const std::string account1("consetogo");
const std::string account2("woolwich");
TestGrantAndRevoke(context(), {rp_requester1, rp_embedder1, idp1, account1},
{rp_requester2, rp_embedder1, idp1, account1});
TestGrantAndRevoke(context(), {rp_requester1, rp_embedder1, idp1, account1},
{rp_requester1, rp_embedder2, idp1, account1});
TestGrantAndRevoke(context(), {rp_requester1, rp_embedder1, idp1, account1},
{rp_requester1, rp_embedder1, idp2, account1});
TestGrantAndRevoke(context(), {rp_requester1, rp_embedder1, idp1, account1},
{rp_requester1, rp_embedder1, idp1, account2});
}
// Test that granting a permission for an account, if the permission has already
// been granted, is a noop.
TEST_F(FederatedIdentityAccountKeyedPermissionContextTest,
GrantPermissionForSameAccount) {
const url::Origin rp_requester =
url::Origin::Create(GURL("https://rp.example"));
const url::Origin rp_embedder =
url::Origin::Create(GURL("https://rp-embedder.example"));
const url::Origin idp = url::Origin::Create(GURL("https://idp.example"));
std::string account{"consetogo"};
context()->GrantPermission(rp_requester, rp_embedder, idp, account);
auto granted_objects1 = context()->GetAllGrantedObjects();
context()->GrantPermission(rp_requester, rp_embedder, idp, account);
auto granted_objects2 = context()->GetAllGrantedObjects();
EXPECT_EQ(1u, granted_objects1.size());
EXPECT_EQ(1u, granted_objects2.size());
EXPECT_EQ(granted_objects1[0]->value, granted_objects2[0]->value);
}
// Test that FederatedIdentityAccountKeyedPermissionContext can recover from
// crbug.com/1381130
TEST_F(FederatedIdentityAccountKeyedPermissionContextTest, RecoverFrom1381130) {
// crbug.com/1381130 only occurred when RP=IDP.
const url::Origin site = url::Origin::Create(GURL("https://example.com"));
std::string account{"conestogo"};
// Storing data not associated with a signed-in account is bad because it
// makes the expected behaviour of RevokePermission() unclear.
base::Value::Dict new_object;
new_object.Set(kTestIdpOriginKey, site.Serialize());
new_object.Set("bug", base::Value("wrong"));
context()->GrantObjectPermission(site, base::Value(std::move(new_object)));
context()->GrantPermission(site, site, site, account);
EXPECT_TRUE(context()->HasPermission(site, site, site, account));
}