// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/notifications/notification_permission_context.h"

#include <memory>
#include <string>

#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_refptr.h"
#include "base/test/scoped_mock_time_message_loop_task_runner.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/permissions/permission_decision.h"
#include "components/permissions/permission_prompt_decision.h"
#include "components/permissions/permission_request_data.h"
#include "components/permissions/permission_request_id.h"
#include "components/permissions/resolvers/content_setting_permission_resolver.h"
#include "content/public/browser/permission_controller_delegate.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_result.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_renderer_host.h"
#include "extensions/buildflags/buildflags.h"
#include "net/http/http_response_headers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
#include "url/gurl.h"

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/extensions/test_extension_system.h"
#include "chrome/browser/notifications/notifier_state_tracker.h"
#include "chrome/browser/notifications/notifier_state_tracker_factory.h"
#include "extensions/browser/extension_registrar.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "ui/message_center/public/cpp/notifier_id.h"
#endif  // BUILDFLAG(ENABLE_EXTENSIONS_CORE)

namespace {

using PermissionStatus = blink::mojom::PermissionStatus;

void StorePermissionStatus(PermissionStatus* out_permission_status,
                           content::PermissionResult permission_result) {
  DCHECK(out_permission_status);
  *out_permission_status = permission_result.status;
}

class TestNotificationPermissionContext : public NotificationPermissionContext {
 public:
  explicit TestNotificationPermissionContext(Profile* profile)
      : NotificationPermissionContext(profile) {}

  int permission_set_count() const { return permission_set_count_; }
  bool last_permission_set_persisted() const {
    return last_permission_set_persisted_;
  }
  PermissionDecision last_set_decision() const { return last_set_decision_; }

  ContentSetting GetContentSettingFromMap(const GURL& url_a,
                                          const GURL& url_b) {
    return HostContentSettingsMapFactory::GetForProfile(browser_context())
        ->GetContentSetting(url_a.DeprecatedGetOriginAsURL(),
                            url_b.DeprecatedGetOriginAsURL(),
                            content_settings_type());
  }

 private:
  // NotificationPermissionContext:
  void NotifyPermissionSet(
      const permissions::PermissionRequestData& request_data,
      permissions::BrowserPermissionCallback callback,
      bool persist,
      const content::PermissionResult* permission_result,
      const permissions::PermissionPromptDecision& decision) override {
    permission_set_count_++;
    last_permission_set_persisted_ = persist;
    last_set_decision_ = decision.overall_decision;
    NotificationPermissionContext::NotifyPermissionSet(
        request_data, std::move(callback), persist, permission_result,
        decision);
  }

  int permission_set_count_ = 0;
  bool last_permission_set_persisted_ = false;
  PermissionDecision last_set_decision_ = PermissionDecision::kNone;
};

}  // namespace

class NotificationPermissionContextTest
    : public ChromeRenderViewHostTestHarness {
 public:
  void TearDown() override {
    mock_time_task_runner_.reset();
    ChromeRenderViewHostTestHarness::TearDown();
  }

 protected:
  base::TestMockTimeTaskRunner* SwitchToMockTime() {
    EXPECT_FALSE(mock_time_task_runner_);
    mock_time_task_runner_ =
        std::make_unique<base::ScopedMockTimeMessageLoopTaskRunner>();
    return mock_time_task_runner_->task_runner();
  }

  void UpdateContentSetting(NotificationPermissionContext* context,
                            const GURL& requesting_origin,
                            const GURL& embedding_origin,
                            ContentSetting setting) {
    context->UpdateSetting(
        permissions::PermissionRequestData(
            permissions::RequestType::kNotifications,
            /*user_gesture=*/true, requesting_origin, embedding_origin),
        setting, /*is_one_time=*/false);
  }

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
  // Registers the given |extension| with the extension registrar and returns
  // the extension if it could be registered appropriately.
  scoped_refptr<const extensions::Extension> RegisterExtension(
      scoped_refptr<const extensions::Extension> extension) {
    base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
    extensions::TestExtensionSystem* test_extension_system =
        static_cast<extensions::TestExtensionSystem*>(
            extensions::ExtensionSystem::Get(profile()));

    test_extension_system->CreateExtensionService(
        &command_line, base::FilePath() /* install_directory */,
        false /* autoupdate_enabled */);

    extensions::ExtensionRegistrar::Get(profile())->AddExtension(extension);

    extensions::ExtensionRegistry* registry =
        extensions::ExtensionRegistry::Get(profile());

    return base::WrapRefCounted(registry->GetExtensionById(
        extension->id(), extensions::ExtensionRegistry::ENABLED));
  }

  // Proxy to NotificationPermissionContext::GetPermissionStatusForExtension()
  // to avoid needing lots of FRIEND_TEST_ALL_PREFIXES declarations.
  ContentSetting GetPermissionStatusForExtension(
      const NotificationPermissionContext& context,
      const GURL& origin) const {
    return context.GetPermissionStatusForExtension(origin);
  }
#endif

 private:
  std::unique_ptr<base::ScopedMockTimeMessageLoopTaskRunner>
      mock_time_task_runner_;
};

// Web Notification permission checks will never return ASK for cross-origin
// requests, as permission cannot be requested in that situation.
TEST_F(NotificationPermissionContextTest, CrossOriginPermissionChecks) {
  GURL requesting_origin("https://example.com");
  GURL embedding_origin("https://chrome.com");

  NotificationPermissionContext context(profile());

  // Both same-origin and cross-origin requests for |requesting_origin| should
  // have their default values.
  EXPECT_EQ(
      PermissionStatus::ASK,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, requesting_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);

  // Now grant permission for the |requesting_origin|. This should be granted
  // in both contexts.
  UpdateContentSetting(&context, requesting_origin, requesting_origin,
                       CONTENT_SETTING_ALLOW);

  EXPECT_EQ(
      PermissionStatus::GRANTED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, requesting_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::GRANTED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);

  // Now block permission for |requesting_origin|.

#if BUILDFLAG(IS_ANDROID)
  // Permission must be reset before it can be blocked. This is because granting
  // a permission on Android O+ creates a system-managed notification channel
  // which determines the value of the content setting, so it is not allowed to
  // then toggle the value from ALLOW->BLOCK directly. However, Chrome may reset
  // the permission (which deletes the channel), and *then* grant/block it
  // (creating a new channel).
  context.ResetPermission(requesting_origin, requesting_origin);
#endif  // BUILDFLAG(IS_ANDROID)

  UpdateContentSetting(&context, requesting_origin, requesting_origin,
                       CONTENT_SETTING_BLOCK);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, requesting_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);

  // Resetting the permission should demonstrate the default behaviour again.
  context.ResetPermission(requesting_origin, requesting_origin);

  EXPECT_EQ(
      PermissionStatus::ASK,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, requesting_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);
}

// Web Notifications permission requests should only succeed for top level
// origins (embedding origin == requesting origin). Retrieving previously
// granted permissions should continue to be possible regardless of being top
// level.
TEST_F(NotificationPermissionContextTest, WebNotificationsTopLevelOriginOnly) {
  GURL requesting_origin("https://example.com");
  GURL embedding_origin("https://chrome.com");

  NotificationPermissionContext context(profile());

  EXPECT_EQ(
      PermissionStatus::ASK,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, requesting_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);

  // Requesting permission for different origins should fail.
  permissions::PermissionRequestID request_id(
      web_contents()->GetPrimaryMainFrame()->GetGlobalId(),
      permissions::PermissionRequestID::RequestLocalId());

  auto permission_status = PermissionStatus::ASK;
  context.DecidePermission(
      std::make_unique<permissions::PermissionRequestData>(
          content::PermissionDescriptorUtil::
              CreatePermissionDescriptorForPermissionType(
                  blink::PermissionType::NOTIFICATIONS),
          request_id,
          /*user_gesture=*/true, requesting_origin, embedding_origin),
      base::BindOnce(&StorePermissionStatus, &permission_status));

  ASSERT_EQ(permission_status, PermissionStatus::DENIED);
  EXPECT_EQ(
      PermissionStatus::ASK,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, requesting_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);

  // Reading previously set permissions should continue to work.
  UpdateContentSetting(&context, requesting_origin, embedding_origin,
                       CONTENT_SETTING_ALLOW);

  EXPECT_EQ(
      PermissionStatus::GRANTED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               requesting_origin, embedding_origin)
          .status);

  context.ResetPermission(requesting_origin, embedding_origin);
}

// Web Notifications require secure origins.
TEST_F(NotificationPermissionContextTest, SecureOriginRequirement) {
  GURL insecure_origin("http://example.com");
  GURL secure_origin("https://chrome.com");

  NotificationPermissionContext web_notification_context(profile());

  EXPECT_EQ(
      PermissionStatus::DENIED,
      web_notification_context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */, insecure_origin,
                               insecure_origin)
          .status);

  EXPECT_EQ(
      PermissionStatus::DENIED,
      web_notification_context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */, insecure_origin,
                               secure_origin)
          .status);
}

#if BUILDFLAG(IS_MAC) && defined(ARCH_CPU_ARM64)
// Bulk-disabled for arm64 bot stabilization: https://crbug.com/40734863
#define MAYBE_TestDenyInIncognitoAfterDelay \
  DISABLED_TestDenyInIncognitoAfterDelay
#else
#define MAYBE_TestDenyInIncognitoAfterDelay TestDenyInIncognitoAfterDelay
#endif

// Tests auto-denial after a time delay in incognito.
TEST_F(NotificationPermissionContextTest, MAYBE_TestDenyInIncognitoAfterDelay) {
  TestNotificationPermissionContext permission_context(
      profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
  GURL url("https://www.example.com");
  NavigateAndCommit(url);

  const permissions::PermissionRequestID id(
      web_contents()->GetPrimaryMainFrame()->GetGlobalId(),
      permissions::PermissionRequestID::RequestLocalId());

  base::TestMockTimeTaskRunner* task_runner = SwitchToMockTime();

  ASSERT_EQ(0, permission_context.permission_set_count());
  ASSERT_FALSE(permission_context.last_permission_set_persisted());
  ASSERT_EQ(PermissionDecision::kNone, permission_context.last_set_decision());

  permission_context.RequestPermission(
      std::make_unique<permissions::PermissionRequestData>(
          content::PermissionDescriptorUtil::
              CreatePermissionDescriptorForPermissionType(
                  blink::PermissionType::NOTIFICATIONS),
          id, /*user_gesture=*/true, url),
      base::DoNothing());

  // Should be blocked after 1-2 seconds, but the timer is reset whenever the
  // tab is not visible, so these 500ms never add up to >= 1 second.
  for (int n = 0; n < 10; n++) {
    web_contents()->WasShown();
    task_runner->FastForwardBy(base::Milliseconds(500));
    web_contents()->WasHidden();
  }

  EXPECT_EQ(0, permission_context.permission_set_count());
  EXPECT_EQ(CONTENT_SETTING_ASK,
            permission_context.GetContentSettingFromMap(url, url));

  // Time elapsed whilst hidden is not counted.
  // n.b. This line also clears out any old scheduled timer tasks. This is
  // important, because otherwise Timer::Reset (triggered by
  // visibility_timer::VisibilityTimerTabHelper::WasShown) may choose to re-use
  // an existing scheduled task, and when it fires Timer::RunScheduledTask will
  // call TimeTicks::Now() (which unlike task_runner->NowTicks(), we can't
  // fake), and miscalculate the remaining delay at which to fire the timer.
  task_runner->FastForwardBy(base::Days(1));

  EXPECT_EQ(0, permission_context.permission_set_count());
  EXPECT_EQ(CONTENT_SETTING_ASK,
            permission_context.GetContentSettingFromMap(url, url));

  // Should be blocked after 1-2 seconds. So 500ms is not enough.
  web_contents()->WasShown();
  task_runner->FastForwardBy(base::Milliseconds(500));

  EXPECT_EQ(0, permission_context.permission_set_count());
  EXPECT_EQ(CONTENT_SETTING_ASK,
            permission_context.GetContentSettingFromMap(url, url));

  // But 5*500ms > 2 seconds, so it should now be blocked.
  for (int n = 0; n < 4; n++) {
    task_runner->FastForwardBy(base::Milliseconds(500));
  }

  EXPECT_EQ(1, permission_context.permission_set_count());
  EXPECT_TRUE(permission_context.last_permission_set_persisted());
  EXPECT_EQ(PermissionDecision::kDeny, permission_context.last_set_decision());
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            permission_context.GetContentSettingFromMap(url, url));
}

// Tests how multiple parallel permission requests get auto-denied in incognito.
TEST_F(NotificationPermissionContextTest, TestParallelDenyInIncognito) {
  TestNotificationPermissionContext permission_context(
      profile()->GetPrimaryOTRProfile(/*create_if_needed=*/true));
  GURL url("https://www.example.com");
  NavigateAndCommit(url);
  web_contents()->WasShown();

  const permissions::PermissionRequestID id1(
      web_contents()->GetPrimaryMainFrame()->GetGlobalId(),
      permissions::PermissionRequestID::RequestLocalId(1));
  const permissions::PermissionRequestID id2(
      web_contents()->GetPrimaryMainFrame()->GetGlobalId(),
      permissions::PermissionRequestID::RequestLocalId(2));

  base::TestMockTimeTaskRunner* task_runner = SwitchToMockTime();

  ASSERT_EQ(0, permission_context.permission_set_count());
  ASSERT_FALSE(permission_context.last_permission_set_persisted());
  ASSERT_EQ(PermissionDecision::kNone, permission_context.last_set_decision());

  permission_context.RequestPermission(
      std::make_unique<permissions::PermissionRequestData>(
          content::PermissionDescriptorUtil::
              CreatePermissionDescriptorForPermissionType(
                  blink::PermissionType::NOTIFICATIONS),
          id1,
          /*user_gesture=*/true, url),
      base::DoNothing());
  permission_context.RequestPermission(
      std::make_unique<permissions::PermissionRequestData>(
          content::PermissionDescriptorUtil::
              CreatePermissionDescriptorForPermissionType(
                  blink::PermissionType::NOTIFICATIONS),
          id2,
          /*user_gesture=*/true, url),
      base::DoNothing());

  EXPECT_EQ(0, permission_context.permission_set_count());
  EXPECT_EQ(CONTENT_SETTING_ASK,
            permission_context.GetContentSettingFromMap(url, url));

  // Fast forward up to 2.5 seconds. Stop as soon as the first permission
  // request is auto-denied.
  for (int n = 0; n < 5; n++) {
    task_runner->FastForwardBy(base::Milliseconds(500));
    if (permission_context.permission_set_count()) {
      break;
    }
  }

  // Only the first permission request receives a response (crbug.com/40452210).
  EXPECT_EQ(1, permission_context.permission_set_count());
  EXPECT_TRUE(permission_context.last_permission_set_persisted());
  EXPECT_EQ(PermissionDecision::kDeny, permission_context.last_set_decision());
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            permission_context.GetContentSettingFromMap(url, url));

  // After another 2.5 seconds, the second permission request should also have
  // received a response.
  task_runner->FastForwardBy(base::Milliseconds(2500));
  EXPECT_EQ(2, permission_context.permission_set_count());
  EXPECT_TRUE(permission_context.last_permission_set_persisted());
  EXPECT_EQ(PermissionDecision::kDeny, permission_context.last_set_decision());
  EXPECT_EQ(CONTENT_SETTING_BLOCK,
            permission_context.GetContentSettingFromMap(url, url));
}

TEST_F(NotificationPermissionContextTest, GetNotificationsSettings) {
  // Verifies that notification permissions, which don't store a secondary URL,
  // are stored appropriately in the HostContentSettingsMap.

  NotificationPermissionContext::UpdatePermission(
      profile(), GURL("https://allowed.com"), CONTENT_SETTING_ALLOW);
  NotificationPermissionContext::UpdatePermission(
      profile(), GURL("https://allowed2.com"), CONTENT_SETTING_ALLOW);

  NotificationPermissionContext::UpdatePermission(
      profile(), GURL("https://denied.com"), CONTENT_SETTING_BLOCK);
  NotificationPermissionContext::UpdatePermission(
      profile(), GURL("https://denied2.com"), CONTENT_SETTING_BLOCK);

  ContentSettingsForOneType settings =
      HostContentSettingsMapFactory::GetForProfile(profile())
          ->GetSettingsForOneType(ContentSettingsType::NOTIFICATIONS);

  // |settings| contains the default setting and 4 exceptions.
  ASSERT_EQ(5u, settings.size());

  // The platform isn't guaranteed to return the settings in any particular
  // order, so sort them first.
  std::sort(settings.begin(), settings.begin() + 4,
            [](const ContentSettingPatternSource& s1,
               const ContentSettingPatternSource& s2) {
              return s1.primary_pattern.GetHost() <
                     s2.primary_pattern.GetHost();
            });

  EXPECT_EQ(
      ContentSettingsPattern::FromURLNoWildcard(GURL("https://allowed.com")),
      settings[0].primary_pattern);
  EXPECT_EQ(CONTENT_SETTING_ALLOW, settings[0].GetContentSetting());
  EXPECT_EQ(
      ContentSettingsPattern::FromURLNoWildcard(GURL("https://allowed2.com")),
      settings[1].primary_pattern);
  EXPECT_EQ(CONTENT_SETTING_ALLOW, settings[1].GetContentSetting());
  EXPECT_EQ(
      ContentSettingsPattern::FromURLNoWildcard(GURL("https://denied.com")),
      settings[2].primary_pattern);
  EXPECT_EQ(CONTENT_SETTING_BLOCK, settings[2].GetContentSetting());
  EXPECT_EQ(
      ContentSettingsPattern::FromURLNoWildcard(GURL("https://denied2.com")),
      settings[3].primary_pattern);
  EXPECT_EQ(CONTENT_SETTING_BLOCK, settings[3].GetContentSetting());
  EXPECT_EQ(ContentSettingsPattern::Wildcard(), settings[4].primary_pattern);
  EXPECT_EQ(CONTENT_SETTING_ASK, settings[4].GetContentSetting());
}

#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
TEST_F(NotificationPermissionContextTest, ExtensionPermissionAskByDefault) {
  // Verifies that notification permission is not granted to extensions by
  // default. They need to explicitly declare this in their manifest.
  NotificationPermissionContext context(profile());

  scoped_refptr<const extensions::Extension> extension = RegisterExtension(
      extensions::ExtensionBuilder("Notification Permission Test").Build());

  ASSERT_TRUE(extension);

  ASSERT_EQ(CONTENT_SETTING_ASK,
            GetPermissionStatusForExtension(context, extension->url()));

  EXPECT_EQ(
      PermissionStatus::ASK,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               extension->url(), extension->url())
          .status);
}

TEST_F(NotificationPermissionContextTest, ExtensionPermissionGranted) {
  // Verifies that extensions that declare the "notifications" permission in
  // their manifest get notification permission granted.
  NotificationPermissionContext context(profile());

  scoped_refptr<const extensions::Extension> extension = RegisterExtension(
      extensions::ExtensionBuilder("Notification Permission Test")
          .AddAPIPermission("notifications")
          .Build());

  ASSERT_TRUE(extension);

  ASSERT_EQ(CONTENT_SETTING_ALLOW,
            GetPermissionStatusForExtension(context, extension->url()));

  EXPECT_EQ(
      PermissionStatus::GRANTED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               extension->url(), extension->url())
          .status);
}

TEST_F(NotificationPermissionContextTest, ExtensionPermissionOverrideDenied) {
  // Verifies that extensions that declare the "notifications" permission in
  // their manifest can still have permission disabled by the user.
  NotificationPermissionContext context(profile());

  scoped_refptr<const extensions::Extension> extension = RegisterExtension(
      extensions::ExtensionBuilder("Notification Permission Test")
          .AddAPIPermission("notifications")
          .Build());

  ASSERT_TRUE(extension);

  NotifierStateTracker* notifier_state_tracker =
      NotifierStateTrackerFactory::GetForProfile(profile());
  DCHECK(notifier_state_tracker);

  // Disable the |extension|'s notification ability through the state tracker.
  message_center::NotifierId notifier_id(
      message_center::NotifierType::APPLICATION, extension->id());
  notifier_state_tracker->SetNotifierEnabled(notifier_id, /* enabled= */ false);

  ASSERT_EQ(CONTENT_SETTING_BLOCK,
            GetPermissionStatusForExtension(context, extension->url()));

  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               nullptr /* render_frame_host */,
                               extension->url(), extension->url())
          .status);
}
#endif

TEST_F(NotificationPermissionContextTest, SandboxedIFrameSameOrigin) {
  // Set default notification permission to ALLOW.
  HostContentSettingsMapFactory::GetForProfile(profile())
      ->SetDefaultContentSetting(ContentSettingsType::NOTIFICATIONS,
                                 CONTENT_SETTING_ALLOW);

  GURL main_url("https://example.com");
  NavigateAndCommit(main_url);

  NotificationPermissionContext context(profile());

  // Verify main frame is allowed.
  EXPECT_EQ(
      PermissionStatus::GRANTED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               main_rfh(), main_url, main_url)
          .status);

  // Create child frame.
  content::RenderFrameHost* child_rfh =
      content::RenderFrameHostTester::For(main_rfh())->AppendChild("child");

  // Navigate child frame with CSP sandbox.
  // Same-origin URL, but sandbox will make it opaque.
  auto simulator = content::NavigationSimulator::CreateRendererInitiated(
      main_url, child_rfh);
  simulator->SetResponseHeaders(
      net::HttpResponseHeaders::Builder(net::HttpVersion(1, 1), "200 OK")
          .AddHeader("Content-Security-Policy", "sandbox")
          .Build());
  simulator->Start();
  simulator->Commit();
  child_rfh = simulator->GetFinalRenderFrameHost();

  // The child frame should have an opaque origin.
  EXPECT_TRUE(child_rfh->GetLastCommittedOrigin().opaque());

  // Expect DENIED for the sandboxed child frame.
  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               child_rfh, main_url, main_url)
          .status);
}

TEST_F(NotificationPermissionContextTest, SandboxedIFrameCrossOrigin) {
  // Set default notification permission to ALLOW.
  HostContentSettingsMapFactory::GetForProfile(profile())
      ->SetDefaultContentSetting(ContentSettingsType::NOTIFICATIONS,
                                 CONTENT_SETTING_ALLOW);

  GURL main_url("https://example.com");
  GURL cross_origin_url("https://example.org");
  NavigateAndCommit(main_url);

  NotificationPermissionContext context(profile());

  // Create normal child frame.
  content::RenderFrameHost* normal_child_rfh =
      content::RenderFrameHostTester::For(main_rfh())
          ->AppendChild("normal_child");
  auto simulator = content::NavigationSimulator::CreateRendererInitiated(
      cross_origin_url, normal_child_rfh);
  simulator->Commit();
  normal_child_rfh = simulator->GetFinalRenderFrameHost();

  // Verify normal cross-origin child frame is allowed (because default is
  // ALLOW).
  EXPECT_EQ(
      PermissionStatus::GRANTED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               normal_child_rfh, cross_origin_url, main_url)
          .status);

  // Create sandboxed child frame.
  content::RenderFrameHost* sandboxed_child_rfh =
      content::RenderFrameHostTester::For(main_rfh())
          ->AppendChild("sandboxed_child");
  auto simulator2 = content::NavigationSimulator::CreateRendererInitiated(
      cross_origin_url, sandboxed_child_rfh);
  simulator2->SetResponseHeaders(
      net::HttpResponseHeaders::Builder(net::HttpVersion(1, 1), "200 OK")
          .AddHeader("Content-Security-Policy", "sandbox")
          .Build());
  simulator2->Start();
  simulator2->Commit();
  sandboxed_child_rfh = simulator2->GetFinalRenderFrameHost();

  // The child frame should have an opaque origin.
  EXPECT_TRUE(sandboxed_child_rfh->GetLastCommittedOrigin().opaque());

  // Expect DENIED for the sandboxed child frame.
  EXPECT_EQ(
      PermissionStatus::DENIED,
      context
          .GetPermissionStatus(content::PermissionDescriptorUtil::
                                   CreatePermissionDescriptorForPermissionType(
                                       blink::PermissionType::NOTIFICATIONS),
                               sandboxed_child_rfh, cross_origin_url, main_url)
          .status);
}
