// 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/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_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;
}

bool InMemoryFederatedPermissionContext::IsAutoReauthnDisabledByEmbedder(
    content::WebContents* web_contents) {
  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();
}

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::ListValue InMemoryFederatedPermissionContext::GetAccounts(
    const url::Origin& idp_origin) {
  base::ListValue 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::DictValue new_account =
        base::DictValue()
            .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
