blob: f425853867204cdbee05b1c5d37531970259c3f7 [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/devtools/protocol/fedcm_handler.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webid/federated_auth_request_impl.h"
#include "content/browser/webid/federated_auth_request_page_data.h"
#include "content/public/browser/federated_identity_api_permission_context_delegate.h"
#include "content/public/browser/identity_request_dialog_controller.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
namespace content::protocol {
FedCmHandler::FedCmHandler()
: DevToolsDomainHandler(FedCm::Metainfo::domainName) {}
FedCmHandler::~FedCmHandler() = default;
// static
std::vector<FedCmHandler*> FedCmHandler::ForAgentHost(
DevToolsAgentHostImpl* host) {
return host->HandlersByName<FedCmHandler>(FedCm::Metainfo::domainName);
}
void FedCmHandler::SetRenderer(int process_host_id,
RenderFrameHostImpl* frame_host) {
frame_host_ = frame_host;
}
void FedCmHandler::Wire(UberDispatcher* dispatcher) {
frontend_ = std::make_unique<FedCm::Frontend>(dispatcher->channel());
FedCm::Dispatcher::wire(dispatcher, this);
}
DispatchResponse FedCmHandler::Enable(Maybe<bool> in_disableRejectionDelay) {
auto* auth_request = GetFederatedAuthRequest();
bool was_enabled = enabled_;
enabled_ = true;
disable_delay_ = in_disableRejectionDelay.fromMaybe(false);
// OnDialogShown should have been called previously if was_enabled is true.
// This could happen if FedCmHandler::Enable was called to enable/disable the
// rejection delay.
if (!was_enabled && auth_request && auth_request->GetDialogController()) {
OnDialogShown();
}
return DispatchResponse::Success();
}
DispatchResponse FedCmHandler::Disable() {
enabled_ = false;
return DispatchResponse::Success();
}
void FedCmHandler::OnDialogShown() {
DCHECK(frontend_);
if (!enabled_) {
return;
}
static int next_dialog_id_ = 0;
dialog_id_ = base::NumberToString(next_dialog_id_++);
auto* auth_request = GetFederatedAuthRequest();
const auto* idp_data = GetIdentityProviderData(auth_request);
DCHECK(idp_data);
DCHECK(!idp_data->empty());
// We flatten the two-level IDP->account list into a single
// list of accounts because:
// - It's easier to work with for callers
// - When used for automated testing by IDPs, I expect most of their
// tests to involve just one IDP (themselves), and the single level
// list is easier to use in that case
// - If we decide to show, for example, returning accounts across IDPs
// at the top and remaining accounts below that, the two-level list
// requires the same IDP to be present more than once; a single-level
// list is less confusing.
// The idpConfigUrl field allows callers to identify which IDP an account
// belongs to.
auto accounts = std::make_unique<Array<FedCm::Account>>();
for (const auto& data : *idp_data) {
for (const IdentityRequestAccount& account : data.accounts) {
FedCm::LoginState login_state;
absl::optional<std::string> tos_url;
absl::optional<std::string> pp_url;
switch (*account.login_state) {
case IdentityRequestAccount::LoginState::kSignUp:
login_state = FedCm::LoginStateEnum::SignUp;
// Because TOS and PP URLs are only used when the login state is
// sign up, we only populate them in that case.
pp_url = data.client_metadata.privacy_policy_url.spec();
tos_url = data.client_metadata.terms_of_service_url.spec();
break;
case IdentityRequestAccount::LoginState::kSignIn:
login_state = FedCm::LoginStateEnum::SignIn;
break;
}
std::unique_ptr<FedCm::Account> entry =
FedCm::Account::Create()
.SetAccountId(account.id)
.SetEmail(account.email)
.SetName(account.name)
.SetGivenName(account.given_name)
.SetPictureUrl(account.picture.spec())
.SetIdpConfigUrl(data.idp_metadata.config_url.spec())
.SetIdpSigninUrl(data.idp_metadata.idp_signin_url.spec())
.SetLoginState(login_state)
.Build();
if (pp_url) {
entry->SetPrivacyPolicyUrl(*pp_url);
}
if (tos_url) {
entry->SetTermsOfServiceUrl(*tos_url);
}
accounts->push_back(std::move(entry));
}
}
IdentityRequestDialogController* dialog = auth_request->GetDialogController();
CHECK(dialog);
FedCm::DialogType dialog_type = auth_request->IsAutoReauthn()
? FedCm::DialogTypeEnum::AutoReauthn
: FedCm::DialogTypeEnum::AccountChooser;
Maybe<String> maybe_subtitle;
absl::optional<std::string> subtitle = dialog->GetSubtitle();
if (subtitle) {
maybe_subtitle = *subtitle;
}
frontend_->DialogShown(dialog_id_, dialog_type, std::move(accounts),
dialog->GetTitle(), std::move(maybe_subtitle));
}
DispatchResponse FedCmHandler::SelectAccount(const String& in_dialogId,
int in_accountIndex) {
if (in_dialogId != dialog_id_) {
return DispatchResponse::InvalidParams(
"Dialog ID does not match current dialog");
}
auto* auth_request = GetFederatedAuthRequest();
const auto* idp_data = GetIdentityProviderData(auth_request);
if (!idp_data) {
return DispatchResponse::ServerError(
"selectAccount called while no FedCm dialog is shown");
}
int current = 0;
for (const auto& data : *idp_data) {
for (const IdentityRequestAccount& account : data.accounts) {
if (current == in_accountIndex) {
auth_request->AcceptAccountsDialogForDevtools(
data.idp_metadata.config_url, account);
return DispatchResponse::Success();
}
++current;
}
}
return DispatchResponse::InvalidParams("Invalid account index");
}
DispatchResponse FedCmHandler::DismissDialog(const String& in_dialogId,
Maybe<bool> in_triggerCooldown) {
if (in_dialogId != dialog_id_) {
return DispatchResponse::InvalidParams(
"Dialog ID does not match current dialog");
}
auto* auth_request = GetFederatedAuthRequest();
const auto* idp_data = GetIdentityProviderData(auth_request);
if (!idp_data) {
return DispatchResponse::ServerError(
"cancelDialog called while no FedCm dialog is shown");
}
auth_request->DismissAccountsDialogForDevtools(
in_triggerCooldown.fromMaybe(false));
return DispatchResponse::Success();
}
DispatchResponse FedCmHandler::ResetCooldown() {
auto* context = GetApiPermissionContext();
if (!context) {
return DispatchResponse::ServerError("no frame host");
}
context->RemoveEmbargoAndResetCounts(GetEmbeddingOrigin());
return DispatchResponse::Success();
}
url::Origin FedCmHandler::GetEmbeddingOrigin() {
CHECK(frame_host_);
CHECK(frame_host_->GetMainFrame());
return frame_host_->GetMainFrame()->GetLastCommittedOrigin();
}
FederatedAuthRequestPageData* FedCmHandler::GetPageData() {
if (!frame_host_) {
return nullptr;
}
Page& page = frame_host_->GetPage();
return PageUserData<FederatedAuthRequestPageData>::GetOrCreateForPage(page);
}
FederatedAuthRequestImpl* FedCmHandler::GetFederatedAuthRequest() {
FederatedAuthRequestPageData* page_data = GetPageData();
if (!page_data) {
return nullptr;
}
return page_data->PendingWebIdentityRequest();
}
const std::vector<IdentityProviderData>* FedCmHandler::GetIdentityProviderData(
FederatedAuthRequestImpl* auth_request) {
if (!auth_request) {
return nullptr;
}
const auto& idp_data = auth_request->GetSortedIdpData();
// idp_data is empty iff no dialog is shown.
if (idp_data.empty()) {
return nullptr;
}
return &idp_data;
}
FederatedIdentityApiPermissionContextDelegate*
FedCmHandler::GetApiPermissionContext() {
if (!frame_host_) {
return nullptr;
}
return frame_host_->GetBrowserContext()
->GetFederatedIdentityApiPermissionContext();
}
} // namespace content::protocol