blob: 25c49c18074c938ed29cfcd7efcae425f9c2610b [file] [log] [blame]
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "federated_auth_navigation_throttle.h"
#include "base/time/time.h"
#include "content/browser/webid/flags.h"
#include "content/browser/webid/idp_network_request_manager.h"
#include "content/browser/webid/redirect_uri_data.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/browser/federated_identity_request_permission_context_delegate.h"
#include "content/public/browser/federated_identity_sharing_permission_context_delegate.h"
#include "content/public/browser/navigation_controller.h"
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_client.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/base/url_util.h"
#include "net/http/http_request_headers.h"
#include "ui/base/page_transition_types.h"
namespace content {
namespace {
// Determines if the source and destination would need special permission to
// get access to 1st party state in each other's contexts. Currently this only
// determines if they are same-site based on the public suffix list, but later
// should account for other considerations such as first-party sets or
// enterprise policies.
// Returns true if the source and destination would need permission. Both
// arguments must be absolute URLs.
bool AreCookieIsolatedPrincipals(url::Origin src_origin,
url::Origin dest_origin) {
if (src_origin.scheme() != dest_origin.scheme()) {
return true;
}
if (!net::registry_controlled_domains::SameDomainOrHost(
src_origin, dest_origin,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
return true;
}
return false;
}
} // namespace
// static
std::unique_ptr<NavigationThrottle>
FederatedAuthNavigationThrottle::MaybeCreateThrottleFor(
NavigationHandle* handle) {
if (!IsFedCmInterceptionEnabled() || handle->GetParentFrameOrOuterDocument())
return nullptr;
return std::make_unique<FederatedAuthNavigationThrottle>(handle);
}
FederatedAuthNavigationThrottle::FederatedAuthNavigationThrottle(
NavigationHandle* handle)
: NavigationThrottle(handle) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
FederatedAuthNavigationThrottle::~FederatedAuthNavigationThrottle() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
NavigationThrottle::ThrottleCheckResult
FederatedAuthNavigationThrottle::WillStartRequest() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
GURL navigation_url = navigation_handle()->GetURL();
// Explicit FedCM requests are exempt from throttling.
const auto headers = navigation_handle()->GetRequestHeaders();
if (headers.HasHeader(kSecFedCmCsrfHeader))
return NavigationThrottle::PROCEED;
const auto initiator_origin = navigation_handle()->GetInitiatorOrigin();
url::Origin navigation_origin = url::Origin::Create(navigation_url);
if (IsFederationRequest(navigation_url) && initiator_origin &&
AreCookieIsolatedPrincipals(*initiator_origin, navigation_origin)) {
net::GetValueForKeyInQuery(navigation_url, "redirect_uri", &redirect_uri_);
// Permission dialog is skipped if this RP/IdP pair already have the
// Identity Request permission.
auto* request_permission_delegate =
navigation_handle()
->GetWebContents()
->GetBrowserContext()
->GetFederatedIdentityRequestPermissionContext();
if (request_permission_delegate &&
request_permission_delegate->HasRequestPermission(*initiator_origin,
navigation_origin)) {
RedirectUriData::Set(navigation_handle()->GetWebContents(),
redirect_uri_);
return NavigationThrottle::PROCEED;
}
request_dialog_controller_ =
GetContentClient()->browser()->CreateIdentityRequestDialogController();
request_dialog_controller_->ShowInitialPermissionDialog(
navigation_handle()->GetWebContents(), navigation_url,
IdentityRequestDialogController::PermissionDialogMode::kStateless,
base::BindOnce(&FederatedAuthNavigationThrottle::OnSigninApproved,
weak_ptr_factory_.GetWeakPtr()));
return NavigationThrottle::DEFER;
} else if (IsFederationResponse(navigation_url)) {
// TODO(kenrb): Currently no action, this may proceed. Two things to
// change here:
// 1) Check the redirect_uri and verify we are going back to the
// original source, from which the user consented to login.
// Set the session management permission if the IdP wants it.
// First, that permission has to be created.
// https://crbug.com/1223570.
// 2) (In the eventual future where directed identifiers are
// important) Prompt the user for permission to share personalized
// identifiers and store the FEDERATED_IDENTITY_SHARING
// setting. https://crbug.com/1141125.
return NavigationThrottle::PROCEED;
}
return NavigationThrottle::PROCEED;
}
bool FederatedAuthNavigationThrottle::IsFederationRequest(GURL url) {
// Matches OAuth Requests:
// TODO: make a separation between OpenID Connect and OAuth?
// TODO: match SAML requests.
if (!url.has_query()) {
return false;
}
std::string client_id;
if (!net::GetValueForKeyInQuery(url, "client_id", &client_id)) {
return false;
}
std::string scope;
if (!net::GetValueForKeyInQuery(url, "scope", &scope)) {
return false;
}
std::string redirect_uri;
if (!net::GetValueForKeyInQuery(url, "redirect_uri", &redirect_uri)) {
return false;
}
return true;
}
bool FederatedAuthNavigationThrottle::IsFederationResponse(GURL url) {
// Matches an expected OAuth Response
if (!RedirectUriData::Get(navigation_handle()->GetWebContents())) {
return false;
}
GURL redirect_url = GURL(
RedirectUriData::Get(navigation_handle()->GetWebContents())->Value());
if (url.DeprecatedGetOriginAsURL() ==
redirect_url.DeprecatedGetOriginAsURL() &&
url.path() == redirect_url.path()) {
return true;
}
return false;
}
NavigationThrottle::ThrottleCheckResult
FederatedAuthNavigationThrottle::WillRedirectRequest() {
return WillStartRequest();
}
void FederatedAuthNavigationThrottle::OnSigninApproved(
IdentityRequestDialogController::UserApproval approval) {
if (approval == IdentityRequestDialogController::UserApproval::kApproved) {
auto* request_permission_delegate =
navigation_handle()
->GetWebContents()
->GetBrowserContext()
->GetFederatedIdentityRequestPermissionContext();
const auto initiator_origin = navigation_handle()->GetInitiatorOrigin();
if (request_permission_delegate && initiator_origin) {
request_permission_delegate->GrantRequestPermission(
*initiator_origin,
url::Origin::Create(navigation_handle()->GetURL()));
}
RedirectUriData::Set(navigation_handle()->GetWebContents(), redirect_uri_);
Resume();
return;
}
CancelDeferredNavigation(NavigationThrottle::CANCEL);
}
void FederatedAuthNavigationThrottle::OnTokenProvisionApproved(
IdentityRequestDialogController::UserApproval approval) {
if (approval == IdentityRequestDialogController::UserApproval::kApproved) {
auto* sharing_permission_delegate =
navigation_handle()
->GetWebContents()
->GetBrowserContext()
->GetFederatedIdentitySharingPermissionContext();
const auto initiator_origin = navigation_handle()->GetInitiatorOrigin();
if (sharing_permission_delegate && initiator_origin) {
sharing_permission_delegate->GrantSharingPermission(
*initiator_origin,
url::Origin::Create(navigation_handle()->GetURL()));
}
Resume();
return;
}
CancelDeferredNavigation(NavigationThrottle::CANCEL);
}
const char* FederatedAuthNavigationThrottle::GetNameForLogging() {
return "FederatedAuthNavigationThrottle";
}
} // namespace content