Commit d29b393e authored by Owen Min's avatar Owen Min Committed by Commit Bot

Show notification when user request an approved or rejected extension

Request extension is a multi-steps process. The policy may be updated
during the requests. If policy is updated to whitelist or blacklist an
extension right before a user confirms the request dialog, we will show
the request approved/rejected notification immediately.

Bug: 1006899
Change-Id: I0f41774b8c1a89dd9b34465f8237320bdddc6dce
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2089996
Commit-Queue: Owen Min <zmin@chromium.org>
Reviewed-by: default avatarKaran Bhatia <karandeepb@chromium.org>
Reviewed-by: default avatarJulian Pastarmov <pastarmovj@chromium.org>
Cr-Commit-Position: refs/heads/master@{#748354}
parent 2b3f2a52
...@@ -18,6 +18,11 @@ ExtensionRequestObserver::ExtensionRequestObserver(Profile* profile) ...@@ -18,6 +18,11 @@ ExtensionRequestObserver::ExtensionRequestObserver(Profile* profile)
extensions::ExtensionManagementFactory::GetForBrowserContext(profile_) extensions::ExtensionManagementFactory::GetForBrowserContext(profile_)
->AddObserver(this); ->AddObserver(this);
OnExtensionManagementSettingsChanged(); OnExtensionManagementSettingsChanged();
pref_change_registrar_.Init(profile_->GetPrefs());
pref_change_registrar_.Add(
prefs::kCloudExtensionRequestIds,
base::BindRepeating(&ExtensionRequestObserver::OnPendingListChanged,
weak_factory_.GetWeakPtr()));
} }
ExtensionRequestObserver::~ExtensionRequestObserver() { ExtensionRequestObserver::~ExtensionRequestObserver() {
...@@ -30,6 +35,21 @@ ExtensionRequestObserver::~ExtensionRequestObserver() { ...@@ -30,6 +35,21 @@ ExtensionRequestObserver::~ExtensionRequestObserver() {
} }
void ExtensionRequestObserver::OnExtensionManagementSettingsChanged() { void ExtensionRequestObserver::OnExtensionManagementSettingsChanged() {
ShowAllNotifications();
}
void ExtensionRequestObserver::OnPendingListChanged() {
// The pending list is updated when user confirm the notification and requests
// are removed from the list. There is no need to show new notification at
// this point.
if (closing_notification_and_deleting_requests_) {
closing_notification_and_deleting_requests_ = false;
return;
}
ShowAllNotifications();
}
void ExtensionRequestObserver::ShowAllNotifications() {
if (!profile_->GetPrefs()->GetBoolean(prefs::kCloudExtensionRequestEnabled)) { if (!profile_->GetPrefs()->GetBoolean(prefs::kCloudExtensionRequestEnabled)) {
CloseAllNotifications(); CloseAllNotifications();
return; return;
...@@ -112,6 +132,8 @@ void ExtensionRequestObserver::RemoveExtensionsFromPendingList( ...@@ -112,6 +132,8 @@ void ExtensionRequestObserver::RemoveExtensionsFromPendingList(
prefs::kCloudExtensionRequestIds); prefs::kCloudExtensionRequestIds);
for (auto& id : extension_ids) for (auto& id : extension_ids)
pending_requests_update->RemoveKey(id); pending_requests_update->RemoveKey(id);
closing_notification_and_deleting_requests_ = true;
} }
} // namespace enterprise_reporting } // namespace enterprise_reporting
...@@ -26,6 +26,13 @@ class ExtensionRequestObserver ...@@ -26,6 +26,13 @@ class ExtensionRequestObserver
// extensions::ExtensionManagement::Observer // extensions::ExtensionManagement::Observer
void OnExtensionManagementSettingsChanged() override; void OnExtensionManagementSettingsChanged() override;
// Notifies when request pending list is updated.
void OnPendingListChanged();
// Shows notifications when requests are approved, rejected or
// force-installed. It also closes the notification that is no longer needed.
void ShowAllNotifications();
void ShowNotification(ExtensionRequestNotification::NotifyType type); void ShowNotification(ExtensionRequestNotification::NotifyType type);
void CloseAllNotifications(); void CloseAllNotifications();
...@@ -40,6 +47,9 @@ class ExtensionRequestObserver ...@@ -40,6 +47,9 @@ class ExtensionRequestObserver
Profile* profile_; Profile* profile_;
PrefChangeRegistrar pref_change_registrar_;
bool closing_notification_and_deleting_requests_ = false;
base::WeakPtrFactory<ExtensionRequestObserver> weak_factory_{this}; base::WeakPtrFactory<ExtensionRequestObserver> weak_factory_{this};
}; };
......
...@@ -256,4 +256,16 @@ TEST_F(ExtensionRequestObserverTest, ExtensionRequestPolicyToggle) { ...@@ -256,4 +256,16 @@ TEST_F(ExtensionRequestObserverTest, ExtensionRequestPolicyToggle) {
->DictSize()); ->DictSize());
} }
TEST_F(ExtensionRequestObserverTest, PendingRequestAddedAfterPolicyUpdated) {
ExtensionRequestObserver observer(profile());
VerifyNotification(false);
SetExtensionSettings(kExtensionSettings);
VerifyNotification(false);
SetPendingList({kExtensionId1, kExtensionId2, kExtensionId3, kExtensionId4,
kExtensionId5, kExtensionId6});
VerifyNotification(true);
}
} // namespace enterprise_reporting } // namespace enterprise_reporting
...@@ -220,8 +220,22 @@ ExtensionInstallStatus AddExtensionToPendingList(const ExtensionId& id, ...@@ -220,8 +220,22 @@ ExtensionInstallStatus AddExtensionToPendingList(const ExtensionId& id,
Profile* profile) { Profile* profile) {
ExtensionInstallStatus status = ExtensionInstallStatus status =
GetWebstoreExtensionInstallStatus(id, profile); GetWebstoreExtensionInstallStatus(id, profile);
if (status != kCanRequest) // We put the |id| into the pending request list if it can be requested.
// Ideally we should not get here if the status is not |kCanRequest|. However
// policy might be updated between the client calling |requestExtension| or
// |beginInstallWithManifest3| and us checking the status here. Handle
// approvals and rejections for this case by adding the |id| into the pending
// list. ExtensionRequestObserver will observe this update and show the
// notificaion immediately.
// Please note that only the |id| that can be requested will be uploaded to
// the server and ExtensionRequestObserver will also show notifications once
// it's approved or rejected.
// |id| will be removed from the pending list once the notification is
// confirmed or closed by the user.
if (status != kCanRequest && status != kInstallable &&
status != kBlockedByPolicy) {
return status; return status;
}
DictionaryPrefUpdate pending_requests_update( DictionaryPrefUpdate pending_requests_update(
profile->GetPrefs(), prefs::kCloudExtensionRequestIds); profile->GetPrefs(), prefs::kCloudExtensionRequestIds);
...@@ -231,11 +245,17 @@ ExtensionInstallStatus AddExtensionToPendingList(const ExtensionId& id, ...@@ -231,11 +245,17 @@ ExtensionInstallStatus AddExtensionToPendingList(const ExtensionId& id,
::util::TimeToValue(base::Time::Now())); ::util::TimeToValue(base::Time::Now()));
pending_requests_update->SetKey(id, std::move(request_data)); pending_requests_update->SetKey(id, std::move(request_data));
// Query the new extension install status again. It should be changed from // Query the new extension install status again. It should be changed from
// kCanRequest to kRequestPending if the id has been added into pending list // |kCanRequest| to |kRequestPending| if the id has been added into pending
// successfully. // list successfully. Otherwise, it shouldn't be changed.
status = GetWebstoreExtensionInstallStatus(id, profile); ExtensionInstallStatus new_status =
DCHECK_EQ(kRequestPending, status); GetWebstoreExtensionInstallStatus(id, profile);
return status; #if DCHECK_IS_ON()
if (status == kCanRequest)
DCHECK_EQ(kRequestPending, new_status);
else
DCHECK_EQ(status, new_status);
#endif // DCHECK_IS_ON()
return new_status;
} }
} // namespace } // namespace
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#include <vector> #include <vector>
#include "base/json/json_reader.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/util/values/values_util.h" #include "base/util/values/values_util.h"
...@@ -19,6 +20,7 @@ ...@@ -19,6 +20,7 @@
#include "extensions/browser/api_test_utils.h" #include "extensions/browser/api_test_utils.h"
#include "extensions/browser/extension_dialog_auto_confirm.h" #include "extensions/browser/extension_dialog_auto_confirm.h"
#include "extensions/browser/extension_registry.h" #include "extensions/browser/extension_registry.h"
#include "extensions/browser/pref_names.h"
#include "extensions/common/extension_builder.h" #include "extensions/common/extension_builder.h"
namespace extensions { namespace extensions {
...@@ -31,6 +33,18 @@ constexpr char kExtensionManifest[] = R"({ ...@@ -31,6 +33,18 @@ constexpr char kExtensionManifest[] = R"({
\"manifest_version\": 3, \"manifest_version\": 3,
\"version\": \"0.1\"})"; \"version\": \"0.1\"})";
constexpr char kAllowedExtensionSettings[] = R"({
"abcdefghijklmnopabcdefghijklmnop" : {
"installation_mode": "allowed"
}
})";
constexpr char kBlockedExtensionSettings[] = R"({
"abcdefghijklmnopabcdefghijklmnop" : {
"installation_mode": "blocked"
}
})";
constexpr char kWebstoreUserCancelledError[] = "User cancelled install"; constexpr char kWebstoreUserCancelledError[] = "User cancelled install";
base::Time GetFaketime() { base::Time GetFaketime() {
...@@ -52,6 +66,16 @@ void VerifyPendingList( ...@@ -52,6 +66,16 @@ void VerifyPendingList(
} }
} }
void SetExtensionSettings(const std::string& settings_string,
TestingProfile* profile) {
base::Optional<base::Value> settings =
base::JSONReader::Read(settings_string);
ASSERT_TRUE(settings.has_value());
profile->GetTestingPrefService()->SetManagedPref(
pref_names::kExtensionManagement,
base::Value::ToUniquePtrValue(std::move(*settings)));
}
} // namespace } // namespace
class WebstorePrivateExtensionInstallRequestBase : public ExtensionApiUnittest { class WebstorePrivateExtensionInstallRequestBase : public ExtensionApiUnittest {
...@@ -151,6 +175,29 @@ TEST_F(WebstorePrivateRequestExtensionTest, UnrequestableExtension) { ...@@ -151,6 +175,29 @@ TEST_F(WebstorePrivateRequestExtensionTest, UnrequestableExtension) {
VerifyPendingList({}, profile()); VerifyPendingList({}, profile());
} }
TEST_F(WebstorePrivateRequestExtensionTest, AlreadyApprovedExtension) {
SetExtensionSettings(kAllowedExtensionSettings, profile());
auto function =
base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>();
std::unique_ptr<base::Value> response =
RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId));
VerifyResponse(ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_INSTALLABLE,
response.get());
VerifyPendingList({{kExtensionId, base::Time::Now()}}, profile());
}
TEST_F(WebstorePrivateRequestExtensionTest, AlreadyRejectedExtension) {
SetExtensionSettings(kBlockedExtensionSettings, profile());
auto function =
base::MakeRefCounted<WebstorePrivateRequestExtensionFunction>();
std::unique_ptr<base::Value> response =
RunFunctionAndReturnValue(function.get(), GenerateArgs(kExtensionId));
VerifyResponse(
ExtensionInstallStatus::EXTENSION_INSTALL_STATUS_BLOCKED_BY_POLICY,
response.get());
VerifyPendingList({{kExtensionId, base::Time::Now()}}, profile());
}
TEST_F(WebstorePrivateRequestExtensionTest, AlreadyPendingExtension) { TEST_F(WebstorePrivateRequestExtensionTest, AlreadyPendingExtension) {
SetPendingList({kExtensionId}); SetPendingList({kExtensionId});
VerifyPendingList({{kExtensionId, GetFaketime()}}, profile()); VerifyPendingList({{kExtensionId, GetFaketime()}}, profile());
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment