blob: c602f4336acdd80efb2a42f4ea69be5a45a3ff9a [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/webid/federated_auth_user_info_request.h"
#include "base/functional/callback.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/webid_utils.h"
#include "content/public/browser/federated_identity_api_permission_context_delegate.h"
#include "content/public/browser/federated_identity_permission_context_delegate.h"
#include "content/public/browser/render_frame_host.h"
#include "services/network/public/cpp/is_potentially_trustworthy.h"
#include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h"
#include "url/url_constants.h"
namespace content {
using FederatedApiPermissionStatus =
FederatedIdentityApiPermissionContextDelegate::PermissionStatus;
using LoginState = IdentityRequestAccount::LoginState;
// static
std::unique_ptr<FederatedAuthUserInfoRequest>
FederatedAuthUserInfoRequest::CreateAndStart(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityApiPermissionContextDelegate* api_permission_delegate,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityProviderConfigPtr provider,
blink::mojom::FederatedAuthRequest::RequestUserInfoCallback callback) {
std::unique_ptr<FederatedAuthUserInfoRequest> request =
base::WrapUnique<FederatedAuthUserInfoRequest>(
new FederatedAuthUserInfoRequest(
std::move(network_manager), permission_delegate,
render_frame_host, metrics, std::move(provider),
std::move(callback)));
request->Start(api_permission_delegate);
return request;
}
FederatedAuthUserInfoRequest::~FederatedAuthUserInfoRequest() {
CompleteWithError();
}
FederatedAuthUserInfoRequest::FederatedAuthUserInfoRequest(
std::unique_ptr<IdpNetworkRequestManager> network_manager,
FederatedIdentityPermissionContextDelegate* permission_delegate,
RenderFrameHost* render_frame_host,
FedCmMetrics* metrics,
blink::mojom::IdentityProviderConfigPtr provider,
blink::mojom::FederatedAuthRequest::RequestUserInfoCallback callback)
: network_manager_(std::move(network_manager)),
permission_delegate_(permission_delegate),
metrics_(metrics),
client_id_(provider->client_id),
idp_config_url_(provider->config_url),
origin_(render_frame_host->GetLastCommittedOrigin()),
callback_(std::move(callback)) {
RenderFrameHost* main_frame = render_frame_host->GetMainFrame();
DCHECK(main_frame->IsInPrimaryMainFrame());
embedding_origin_ = main_frame->GetLastCommittedOrigin();
RenderFrameHost* parent_frame = render_frame_host->GetParentOrOuterDocument();
parent_frame_origin_ =
parent_frame ? parent_frame->GetLastCommittedOrigin() : url::Origin();
}
void FederatedAuthUserInfoRequest::Start(
FederatedIdentityApiPermissionContextDelegate* api_permission_delegate) {
// Renderer also checks that the origin is same origin with `idp_config_url_`.
// The check is duplicated in case that the renderer is compromised.
if (!origin_.IsSameOriginWith(idp_config_url_)) {
Complete(blink::mojom::RequestUserInfoStatus::kError, absl::nullopt);
return;
}
// Check that `render_frame_host` is for an iframe.
if (!parent_frame_origin_.GetURL().is_valid()) {
CompleteWithError();
return;
}
if (!network::IsOriginPotentiallyTrustworthy(
url::Origin::Create(idp_config_url_))) {
CompleteWithError();
return;
}
FederatedApiPermissionStatus permission_status =
api_permission_delegate->GetApiPermissionStatus(embedding_origin_);
if (permission_status != FederatedApiPermissionStatus::GRANTED) {
CompleteWithError();
return;
}
if (webid::ShouldFailAccountsEndpointRequestBecauseNotSignedInWithIdp(
idp_config_url_, permission_delegate_) &&
GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::ENABLED) {
CompleteWithError();
return;
}
// FederatedProviderFetcher is stored as a member so that
// FederatedProviderFetcher is destroyed when FederatedAuthRequestImpl is
// destroyed.
provider_fetcher_ =
std::make_unique<FederatedProviderFetcher>(network_manager_.get());
provider_fetcher_->Start(
{idp_config_url_}, /*icon_ideal_size=*/0, /*icon_minimum_size=*/0,
base::BindOnce(
&FederatedAuthUserInfoRequest::OnAllConfigAndWellKnownFetched,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthUserInfoRequest::OnAllConfigAndWellKnownFetched(
std::vector<FederatedProviderFetcher::FetchResult> fetch_results) {
provider_fetcher_.reset();
if (fetch_results.size() != 1u) {
// This could happen when the user info request was sent from a compromised
// renderer (>1) or fetch_results is empty (<1).
CompleteWithError();
return;
}
if (fetch_results[0].error) {
CompleteWithError();
return;
}
// Make sure that we don't fetch accounts if the IDP sign-in bit is reset to
// false during the API call. e.g. by the login/logout HEADER.
does_idp_have_failing_signin_status_ =
webid::ShouldFailAccountsEndpointRequestBecauseNotSignedInWithIdp(
idp_config_url_, permission_delegate_);
if (does_idp_have_failing_signin_status_ &&
GetFedCmIdpSigninStatusMode() == FedCmIdpSigninStatusMode::ENABLED) {
CompleteWithError();
return;
}
network_manager_->SendAccountsRequest(
fetch_results[0].endpoints.accounts, client_id_,
base::BindOnce(&FederatedAuthUserInfoRequest::OnAccountsResponseReceived,
weak_ptr_factory_.GetWeakPtr()));
}
void FederatedAuthUserInfoRequest::OnAccountsResponseReceived(
IdpNetworkRequestManager::FetchStatus fetch_status,
IdpNetworkRequestManager::AccountList accounts) {
webid::UpdateIdpSigninStatusForAccountsEndpointResponse(
idp_config_url_, fetch_status, does_idp_have_failing_signin_status_,
permission_delegate_, metrics_);
if (fetch_status.parse_status !=
IdpNetworkRequestManager::ParseStatus::kSuccess) {
CompleteWithError();
return;
}
MaybeReturnAccounts(std::move(accounts));
}
void FederatedAuthUserInfoRequest::MaybeReturnAccounts(
const IdpNetworkRequestManager::AccountList& accounts) {
DCHECK(!accounts.empty());
bool has_returning_accounts = false;
for (const auto& account : accounts) {
// The |login_state| will only be |kSignUp| if IDP doesn't provide an
// |approved_clients| or the client id is NOT on the |approved_clients|
// list, in which case we trust the IDP that we should treat the user as a
// new user and shouldn't return the user info. This should override browser
// local stored permission since a user can revoke their account out of
// band.
// Note that we start with the restrictive model and can later evaluate what
// the expected behavior is when |approved_clients| list is not provided.
if (account.login_state == LoginState::kSignUp) {
continue;
}
if (!permission_delegate_->HasSharingPermission(
parent_frame_origin_, embedding_origin_,
url::Origin::Create(idp_config_url_), account.id)) {
continue;
}
has_returning_accounts = true;
}
if (!has_returning_accounts) {
CompleteWithError();
return;
}
// The user previously accepted the FedCM prompt for one of the returned IdP
// accounts. Return data for all the IdP accounts.
std::vector<blink::mojom::IdentityUserInfoPtr> user_info;
for (const auto& account : accounts) {
user_info.push_back(blink::mojom::IdentityUserInfo::New(
account.email, account.given_name, account.name,
account.picture.spec()));
}
Complete(blink::mojom::RequestUserInfoStatus::kSuccess, std::move(user_info));
}
void FederatedAuthUserInfoRequest::Complete(
blink::mojom::RequestUserInfoStatus status,
absl::optional<std::vector<blink::mojom::IdentityUserInfoPtr>> user_info) {
if (!callback_) {
return;
}
std::move(callback_).Run(status, std::move(user_info));
}
void FederatedAuthUserInfoRequest::CompleteWithError() {
Complete(blink::mojom::RequestUserInfoStatus::kError, absl::nullopt);
}
} // namespace content