text: // Copyright 2015 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 "chrome/browser/notifications/notification_permission_context.h"
  
  #include <memory>
  #include <string>
  
  #include "base/bind.h"
  #include "base/command_line.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_request_id.h"
  #include "content/public/browser/permission_controller_delegate.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 "extensions/buildflags/buildflags.h"
  #include "testing/gtest/include/gtest/gtest.h"
  #include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
  #include "url/gurl.h"
  
  #if defined(OS_ANDROID)
  #include "base/android/build_info.h"
  #endif  // defined(OS_ANDROID)
  
  #if BUILDFLAG(ENABLE_EXTENSIONS)
  #include "chrome/browser/extensions/extension_service.h"
  #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_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)
  
  namespace {
  
  void StoreContentSetting(ContentSetting* out_content_setting,
                           ContentSetting content_setting) {
    DCHECK(out_content_setting);
    *out_content_setting = content_setting;
  }
  
  class TestNotificationPermissionContext : public NotificationPermissionContext {
   public:
    explicit TestNotificationPermissionContext(Profile* profile)
        : NotificationPermissionContext(profile),
          permission_set_count_(0),
          last_permission_set_persisted_(false),
          last_permission_set_setting_(CONTENT_SETTING_DEFAULT) {}
  
    int permission_set_count() const { return permission_set_count_; }
    bool last_permission_set_persisted() const {
      return last_permission_set_persisted_;
    }
    ContentSetting last_permission_set_setting() const {
      return last_permission_set_setting_;
    }
  
    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::PermissionRequestID& id,
                             const GURL& requesting_origin,
                             const GURL& embedder_origin,
                             permissions::BrowserPermissionCallback callback,
                             bool persist,
                             ContentSetting content_setting,
                             bool is_one_time) override {
      permission_set_count_++;
      last_permission_set_persisted_ = persist;
      last_permission_set_setting_ = content_setting;
      NotificationPermissionContext::NotifyPermissionSet(
          id, requesting_origin, embedder_origin, std::move(callback), persist,
          content_setting, /*is_one_time=*/false);
    }
  
    int permission_set_count_;
    bool last_permission_set_persisted_;
    ContentSetting last_permission_set_setting_;
  };
  
  }  // 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->UpdateContentSetting(requesting_origin, embedding_origin, setting,
                                    /*is_one_time=*/false);
    }
  
  #if BUILDFLAG(ENABLE_EXTENSIONS)
    // Registers the given |extension| with the extension service 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()));
  
      extensions::ExtensionService* extension_service =
          test_extension_system->CreateExtensionService(
              &command_line, base::FilePath() /* install_directory */,
              false /* autoupdate_enabled */);
  
      extension_service->AddExtension(extension.get());
  
      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(CONTENT_SETTING_ASK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, requesting_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  
    // 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(CONTENT_SETTING_ALLOW,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, requesting_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  
  // Now block permission for |requesting_origin|.
  
  #if defined(OS_ANDROID)
    // On Android O+, permission must be reset before it can be blocked. This is
    // because granting a permission on 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).
    if (base::android::BuildInfo::GetInstance()->sdk_int() >=
        base::android::SDK_VERSION_OREO) {
      context.ResetPermission(requesting_origin, requesting_origin);
    }
  #endif  // defined(OS_ANDROID)
  
    UpdateContentSetting(&context, requesting_origin, requesting_origin,
                         CONTENT_SETTING_BLOCK);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, requesting_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  
    // Resetting the permission should demonstrate the default behaviour again.
    context.ResetPermission(requesting_origin, requesting_origin);
  
    EXPECT_EQ(CONTENT_SETTING_ASK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, requesting_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  }
  
  // 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(CONTENT_SETTING_ASK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, requesting_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  
    // Requesting permission for different origins should fail.
    permissions::PermissionRequestID fake_id(
        0 /* render_process_id */, 0 /* render_frame_id */,
        permissions::PermissionRequestID::RequestLocalId());
  
    ContentSetting result = CONTENT_SETTING_DEFAULT;
    context.DecidePermission(web_contents(), fake_id, requesting_origin,
                             embedding_origin, true /* user_gesture */,
                             base::BindOnce(&StoreContentSetting, &result));
  
    ASSERT_EQ(result, CONTENT_SETTING_BLOCK);
    EXPECT_EQ(CONTENT_SETTING_ASK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, requesting_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  
    // Reading previously set permissions should continue to work.
    UpdateContentSetting(&context, requesting_origin, embedding_origin,
                         CONTENT_SETTING_ALLOW);
  
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       requesting_origin, embedding_origin)
                  .content_setting);
  
    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(CONTENT_SETTING_BLOCK,
              web_notification_context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       insecure_origin, insecure_origin)
                  .content_setting);
  
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              web_notification_context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       insecure_origin, secure_origin)
                  .content_setting);
  }
  
  #if defined(OS_MAC) && defined(ARCH_CPU_ARM64)
  // Bulk-disabled for arm64 bot stabilization: https://crbug.com/1154345
  #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()->GetMainFrame()->GetProcess()->GetID(),
        web_contents()->GetMainFrame()->GetRoutingID(),
        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(CONTENT_SETTING_DEFAULT,
              permission_context.last_permission_set_setting());
  
    permission_context.RequestPermission(
        web_contents(), id, url, true /* user_gesture */, 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
    // 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(CONTENT_SETTING_BLOCK,
              permission_context.last_permission_set_setting());
    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()->GetMainFrame()->GetProcess()->GetID(),
        web_contents()->GetMainFrame()->GetRoutingID(),
        permissions::PermissionRequestID::RequestLocalId(1));
    const permissions::PermissionRequestID id2(
        web_contents()->GetMainFrame()->GetProcess()->GetID(),
        web_contents()->GetMainFrame()->GetRoutingID(),
        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(CONTENT_SETTING_DEFAULT,
              permission_context.last_permission_set_setting());
  
    permission_context.RequestPermission(
        web_contents(), id1, url, true /* user_gesture */, base::DoNothing());
    permission_context.RequestPermission(
        web_contents(), id2, url, true /* user_gesture */, 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/577336).
    EXPECT_EQ(1, permission_context.permission_set_count());
    EXPECT_TRUE(permission_context.last_permission_set_persisted());
    EXPECT_EQ(CONTENT_SETTING_BLOCK,
              permission_context.last_permission_set_setting());
    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(CONTENT_SETTING_BLOCK,
              permission_context.last_permission_set_setting());
    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);
  
    // |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)
  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(CONTENT_SETTING_ASK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       extension->url(), extension->url())
                  .content_setting);
  }
  
  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")
            .AddPermission("notifications")
            .Build());
  
    ASSERT_TRUE(extension);
  
    ASSERT_EQ(CONTENT_SETTING_ALLOW,
              GetPermissionStatusForExtension(context, extension->url()));
  
    EXPECT_EQ(CONTENT_SETTING_ALLOW,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       extension->url(), extension->url())
                  .content_setting);
  }
  
  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")
            .AddPermission("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(CONTENT_SETTING_BLOCK,
              context
                  .GetPermissionStatus(nullptr /* render_frame_host */,
                                       extension->url(), extension->url())
                  .content_setting);
  }
  #endif
  