| // 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 "content/browser/in_memory_federated_permission_context.h" |
| |
| #include <algorithm> |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/functional/callback_helpers.h" |
| #include "content/browser/webid/flags.h" |
| #include "content/public/browser/webid/constants.h" |
| #include "content/public/common/content_features.h" |
| #include "content/public/common/content_switches.h" |
| #include "mojo/public/cpp/bindings/message.h" |
| #include "third_party/blink/public/common/webid/login_status_account.h" |
| #include "third_party/blink/public/common/webid/login_status_options.h" |
| #include "third_party/blink/public/mojom/webid/federated_auth_request.mojom.h" |
| |
| namespace content { |
| |
| InMemoryFederatedPermissionContext::InMemoryFederatedPermissionContext() = |
| default; |
| |
| InMemoryFederatedPermissionContext::~InMemoryFederatedPermissionContext() = |
| default; |
| |
| content::FederatedIdentityApiPermissionContextDelegate::PermissionStatus |
| InMemoryFederatedPermissionContext::GetApiPermissionStatus( |
| const url::Origin& relying_party_embedder) { |
| if (!base::FeatureList::IsEnabled(features::kFedCm)) { |
| return PermissionStatus::BLOCKED_VARIATIONS; |
| } |
| |
| if (embargoed_origins_.count(relying_party_embedder)) { |
| return PermissionStatus::BLOCKED_EMBARGO; |
| } |
| |
| return PermissionStatus::GRANTED; |
| } |
| |
| // FederatedIdentityApiPermissionContextDelegate |
| void InMemoryFederatedPermissionContext::RecordDismissAndEmbargo( |
| const url::Origin& relying_party_embedder) { |
| embargoed_origins_.insert(relying_party_embedder); |
| } |
| |
| void InMemoryFederatedPermissionContext::RemoveEmbargoAndResetCounts( |
| const url::Origin& relying_party_embedder) { |
| embargoed_origins_.erase(relying_party_embedder); |
| } |
| |
| bool InMemoryFederatedPermissionContext::ShouldCompleteRequestImmediately() |
| const { |
| const base::CommandLine* current_command_line = |
| base::CommandLine::ForCurrentProcess(); |
| return current_command_line->HasSwitch("run-web-tests") || |
| current_command_line->HasSwitch(switches::kBrowserTest) || |
| current_command_line->HasSwitch(switches::kTestType); |
| } |
| |
| bool InMemoryFederatedPermissionContext::HasThirdPartyCookiesAccess( |
| content::RenderFrameHost& host, |
| const GURL& provider_url, |
| const url::Origin& relying_party_embedder) const { |
| return std::find_if( |
| has_third_party_cookies_access_.begin(), |
| has_third_party_cookies_access_.end(), [&](const auto& entry) { |
| return provider_url.spec() == std::get<0>(entry) && |
| relying_party_embedder.Serialize() == std::get<1>(entry); |
| }) != has_third_party_cookies_access_.end(); |
| } |
| |
| bool InMemoryFederatedPermissionContext::AreThirdPartyCookiesEnabledInSettings() |
| const { |
| return true; |
| } |
| |
| void InMemoryFederatedPermissionContext:: |
| SetHasThirdPartyCookiesAccessForTesting( |
| const std::string& identity_provider, |
| const std::string& relying_party_embedder) { |
| has_third_party_cookies_access_.insert( |
| std::pair(identity_provider, relying_party_embedder)); |
| } |
| |
| // FederatedIdentityAutoReauthnPermissionContextDelegate |
| bool InMemoryFederatedPermissionContext::IsAutoReauthnSettingEnabled() { |
| return auto_reauthn_permission_; |
| } |
| |
| bool InMemoryFederatedPermissionContext::IsAutoReauthnEmbargoed( |
| const url::Origin& relying_party_embedder) { |
| return false; |
| } |
| |
| void InMemoryFederatedPermissionContext::SetRequiresUserMediation( |
| const url::Origin& rp_origin, |
| bool requires_user_mediation) { |
| if (requires_user_mediation) { |
| require_user_mediation_sites_.insert(net::SchemefulSite(rp_origin)); |
| } else { |
| require_user_mediation_sites_.erase(net::SchemefulSite(rp_origin)); |
| } |
| OnSetRequiresUserMediation(rp_origin, base::DoNothing()); |
| } |
| |
| bool InMemoryFederatedPermissionContext::RequiresUserMediation( |
| const url::Origin& rp_origin) { |
| return require_user_mediation_sites_.contains(net::SchemefulSite(rp_origin)); |
| } |
| |
| void InMemoryFederatedPermissionContext::OnSetRequiresUserMediation( |
| const url::Origin& relying_party, |
| base::OnceClosure callback) { |
| std::move(callback).Run(); |
| } |
| |
| base::Time InMemoryFederatedPermissionContext::GetAutoReauthnEmbargoStartTime( |
| const url::Origin& relying_party_embedder) { |
| return base::Time(); |
| } |
| |
| void InMemoryFederatedPermissionContext::RecordEmbargoForAutoReauthn( |
| const url::Origin& relying_party_embedder) {} |
| |
| void InMemoryFederatedPermissionContext::RemoveEmbargoForAutoReauthn( |
| const url::Origin& relying_party_embedder) {} |
| |
| void InMemoryFederatedPermissionContext::AddIdpSigninStatusObserver( |
| IdpSigninStatusObserver* observer) { |
| if (idp_signin_status_observer_list_.HasObserver(observer)) { |
| return; |
| } |
| |
| idp_signin_status_observer_list_.AddObserver(observer); |
| } |
| |
| void InMemoryFederatedPermissionContext::RemoveIdpSigninStatusObserver( |
| IdpSigninStatusObserver* observer) { |
| idp_signin_status_observer_list_.RemoveObserver(observer); |
| } |
| |
| bool InMemoryFederatedPermissionContext::HasSharingPermission( |
| const url::Origin& relying_party_requester, |
| const url::Origin& relying_party_embedder, |
| const url::Origin& identity_provider) { |
| return std::find_if(sharing_permissions_.begin(), sharing_permissions_.end(), |
| [&](const auto& entry) { |
| return relying_party_requester.Serialize() == |
| std::get<0>(entry) && |
| relying_party_embedder.Serialize() == |
| std::get<1>(entry) && |
| identity_provider.Serialize() == |
| std::get<2>(entry); |
| }) != sharing_permissions_.end(); |
| } |
| |
| std::optional<base::Time> |
| InMemoryFederatedPermissionContext::GetLastUsedTimestamp( |
| const url::Origin& relying_party_requester, |
| const url::Origin& relying_party_embedder, |
| const url::Origin& identity_provider, |
| const std::string& account_id) { |
| return std::find_if(sharing_permissions_.begin(), sharing_permissions_.end(), |
| [&](const auto& entry) { |
| return relying_party_requester.Serialize() == |
| std::get<0>(entry) && |
| relying_party_embedder.Serialize() == |
| std::get<1>(entry) && |
| identity_provider.Serialize() == |
| std::get<2>(entry) && |
| account_id == std::get<3>(entry); |
| }) != sharing_permissions_.end() |
| ? std::make_optional<base::Time>() |
| : std::nullopt; |
| } |
| |
| bool InMemoryFederatedPermissionContext::HasSharingPermission( |
| const url::Origin& relying_party_requester) { |
| return std::find_if(sharing_permissions_.begin(), sharing_permissions_.end(), |
| [&](const auto& entry) { |
| return relying_party_requester.Serialize() == |
| std::get<0>(entry); |
| }) != sharing_permissions_.end(); |
| } |
| |
| void InMemoryFederatedPermissionContext::GrantSharingPermission( |
| const url::Origin& relying_party_requester, |
| const url::Origin& relying_party_embedder, |
| const url::Origin& identity_provider, |
| const std::string& account_id) { |
| sharing_permissions_.insert(std::tuple( |
| relying_party_requester.Serialize(), relying_party_embedder.Serialize(), |
| identity_provider.Serialize(), account_id)); |
| } |
| |
| void InMemoryFederatedPermissionContext::RevokeSharingPermission( |
| const url::Origin& relying_party_requester, |
| const url::Origin& relying_party_embedder, |
| const url::Origin& identity_provider, |
| const std::string& account_id) { |
| size_t removed = sharing_permissions_.erase(std::tuple( |
| relying_party_requester.Serialize(), relying_party_embedder.Serialize(), |
| identity_provider.Serialize(), account_id)); |
| // If we did not remove any sharing permission, to preserve strong privacy |
| // guarantees of the FedCM API, remove all sharing permissions associated with |
| // the (`relying_party_requester`, `relying_party_embedder`, |
| // `identity_provider` triple). This disabled auto re-authentication on that |
| // account and means revocation may not be invoked repeatedly after a single |
| // successful FedCM flow. |
| if (!removed && !sharing_permissions_.empty()) { |
| auto it = sharing_permissions_.begin(); |
| while (it != sharing_permissions_.end()) { |
| const auto& [requester, embedder, idp, account] = *it; |
| if (requester == relying_party_requester.Serialize() && |
| embedder == relying_party_embedder.Serialize() && |
| idp == identity_provider.Serialize()) { |
| it = sharing_permissions_.erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| } |
| |
| void InMemoryFederatedPermissionContext::RefreshExistingSharingPermission( |
| const url::Origin& relying_party_requester, |
| const url::Origin& relying_party_embedder, |
| const url::Origin& identity_provider, |
| const std::string& account_id) { |
| // `sharing_permissions_` does not currently store timestamps, so this method |
| // does nothing. |
| } |
| |
| std::optional<bool> InMemoryFederatedPermissionContext::GetIdpSigninStatus( |
| const url::Origin& idp_origin) { |
| auto idp_signin_status = idp_signin_status_.find(idp_origin.Serialize()); |
| if (idp_signin_status != idp_signin_status_.end()) { |
| return idp_signin_status->second; |
| } else { |
| return std::nullopt; |
| } |
| } |
| |
| base::Value::List InMemoryFederatedPermissionContext::GetAccounts( |
| const url::Origin& idp_origin) { |
| base::Value::List result; |
| |
| auto options = idp_login_status_options_.find(idp_origin.Serialize()); |
| if (options == idp_login_status_options_.end()) { |
| return result; |
| } |
| |
| for (const auto& account : options->second.accounts) { |
| base::Value::Dict new_account = |
| base::Value::Dict() |
| .Set(webid::kAccountIdKey, account.id) |
| .Set(webid::kAccountEmailKey, account.email) |
| .Set(webid::kAccountNameKey, account.name); |
| |
| if (account.given_name.has_value()) { |
| new_account.Set(webid::kAccountGivenNameKey, account.given_name.value()); |
| } |
| |
| if (account.picture.has_value()) { |
| new_account.Set(webid::kAccountPictureKey, |
| account.picture.value().spec()); |
| } |
| result.Append(std::move(new_account)); |
| } |
| |
| return result; |
| } |
| |
| void InMemoryFederatedPermissionContext::SetIdpSigninStatus( |
| const url::Origin& idp_origin, |
| bool idp_signin_status, |
| base::optional_ref<const blink::common::webid::LoginStatusOptions> |
| options) { |
| if (idp_origin.opaque()) { |
| mojo::ReportBadMessage("Bad IdP Origin from renderer."); |
| return; |
| } |
| |
| idp_signin_status_[idp_origin.Serialize()] = idp_signin_status; |
| for (IdpSigninStatusObserver& observer : idp_signin_status_observer_list_) { |
| observer.OnIdpSigninStatusReceived(idp_origin, idp_signin_status); |
| } |
| |
| if (options && webid::IsLightweightModeEnabled()) { |
| if (idp_signin_status) { |
| idp_login_status_options_[idp_origin.Serialize()] = options.value(); |
| } else { |
| idp_login_status_options_.erase(idp_origin.Serialize()); |
| } |
| } |
| |
| // TODO(crbug.com/40245925): Replace this with AddIdpSigninStatusObserver. |
| if (idp_signin_status_closure_) { |
| idp_signin_status_closure_.Run(); |
| } |
| } |
| |
| void InMemoryFederatedPermissionContext::RegisterIdP(const ::GURL& configURL) { |
| idp_registry_.push_back(configURL); |
| } |
| |
| void InMemoryFederatedPermissionContext::UnregisterIdP( |
| const ::GURL& configURL) { |
| std::erase(idp_registry_, configURL); |
| } |
| |
| std::vector<GURL> InMemoryFederatedPermissionContext::GetRegisteredIdPs() { |
| return idp_registry_; |
| } |
| |
| void InMemoryFederatedPermissionContext::ResetForTesting() { |
| request_permissions_.clear(); |
| sharing_permissions_.clear(); |
| idp_signin_status_.clear(); |
| has_third_party_cookies_access_.clear(); |
| idp_signin_status_observer_list_.Clear(); |
| idp_signin_status_closure_.Reset(); |
| auto_reauthn_permission_ = true; |
| idp_registry_.clear(); |
| embargoed_origins_.clear(); |
| require_user_mediation_sites_.clear(); |
| } |
| |
| } // namespace content |