Commit 122cf895 authored by Danan S's avatar Danan S Committed by Commit Bot

Refactor the ParentPermissionDialog to prevent memory management issues

This refactor was motivated by revert 71f516df.

Background:

There was an ASAN error that occurred in certain test cases where
the ParentPermissionDialog was deleted from beneath itself before
it was able to set its dialog_closed_callback_ member, causing a
use-after-free.

The use-after-free was caused by the way this feature was originally
implemented:
2 objects with distinct lifetimes separately managing the view and
workflow of the ParentPermissionDialog.   The 2 objects
spanned sections of the codebase (ui and views) which have dependency
restrictions that made it very challenging to coordinate the lifetime
of objects across them.  Furthermore, the flows in this code are full
of asynchronous sections that necessitated an extra callback-based
message passing interface between the 2 objects.

This CL consolidates all the existing to a single object:
ParentPermissionDialogView, thereby removing most of the aforementioned
dependency and object lifetime management complexity.

The API for creating the dialog remains in parent_permission_dialog.h,
however it is now in a much simplified form, consisting of a simple
control  interface and Show() functions.

Original change's description:
> Revert "Changes to Webstore Private API to support child extension installation"
>
> This reverts commit 99ffda8e.
>
> Reason for revert: browser_tests failing on https://ci.chromium.org/p/chromium/builders/ci/Linux%20Chromium%20OS%20ASan%20LSan%20Tests%20%281%29/37129 and https://ci.chromium.org/p/chromium/builders/ci/Linux%20ChromiumOS%20MSan%20Tests/18174
>
> Original change's description:
> > Changes to Webstore Private API to support child extension installation
> >
> > These changes are required in order to prompt a child user to get
> > parent permission when they attempt to install an extension in the
> > Chrome Webstore.
> >
> > This CL also enables the feature by default.
> >
> > Bug: 957832
> > Change-Id: I3a2011b418e31dd491fd35e37d976b492eef197b
> > Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2079620
> > Commit-Queue: Dan S <danan@chromium.org>
> > Reviewed-by: Karan Bhatia <karandeepb@chromium.org>
> > Cr-Commit-Position: refs/heads/master@{#749436}
>
> TBR=karandeepb@chromium.org,danan@chromium.org
>
> Change-Id: If262ccd3dad279dc60e849f9780e340b18d7384c
> No-Presubmit: true
> No-Tree-Checks: true
> No-Try: true
> Bug: 957832
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2100891
> Reviewed-by: Oksana Zhuravlova <oksamyt@chromium.org>
> Commit-Queue: Oksana Zhuravlova <oksamyt@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#749726}

Change-Id: Ia217f68502a6fe8719614b1322af74aeb02f3319
No-Presubmit: false
No-Tree-Checks: false
No-Try: false
Bug: 957832
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2100548
Commit-Queue: Dan S <danan@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#753468}
parent 82d24601
...@@ -11,11 +11,11 @@ ...@@ -11,11 +11,11 @@
#include "chrome/browser/supervised_user/supervised_user_service.h" #include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h" #include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h" #include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
#include "content/public/browser/web_contents.h"
namespace { namespace {
void OnParentPermissionDialogComplete( void OnParentPermissionDialogComplete(
std::unique_ptr<ParentPermissionDialog> dialog,
extensions::SupervisedUserServiceDelegate:: extensions::SupervisedUserServiceDelegate::
ParentPermissionDialogDoneCallback delegate_done_callback, ParentPermissionDialogDoneCallback delegate_done_callback,
ParentPermissionDialog::Result result) { ParentPermissionDialog::Result result) {
...@@ -73,25 +73,15 @@ void SupervisedUserServiceManagementAPIDelegate:: ...@@ -73,25 +73,15 @@ void SupervisedUserServiceManagementAPIDelegate::
content::BrowserContext* context, content::BrowserContext* context,
content::WebContents* contents, content::WebContents* contents,
ParentPermissionDialogDoneCallback done_callback) { ParentPermissionDialogDoneCallback done_callback) {
std::unique_ptr<ParentPermissionDialog> dialog = ParentPermissionDialog::DoneCallback inner_done_callback = base::BindOnce(
std::make_unique<ParentPermissionDialog>( &::OnParentPermissionDialogComplete, std::move(done_callback));
Profile::FromBrowserContext(context));
parent_permission_dialog_ =
// Cache the pointer so we can show the dialog after we pass ParentPermissionDialog::CreateParentPermissionDialogForExtension(
// ownership to the callback. Profile::FromBrowserContext(context), contents,
ParentPermissionDialog* dialog_ptr = dialog.get(); contents->GetTopLevelNativeWindow(), gfx::ImageSkia(), &extension,
std::move(inner_done_callback));
// Ownership of the dialog passes to the callback. This allows us parent_permission_dialog_->ShowDialog();
// to have as many instances of the dialog as calls to the management
// API.
ParentPermissionDialog::DoneCallback inner_done_callback =
base::BindOnce(&::OnParentPermissionDialogComplete, std::move(dialog),
std::move(done_callback));
// This is safe because moving a unique_ptr doesn't change the underlying
// object's address.
dialog_ptr->ShowPromptForExtensionInstallation(
contents, &extension, SkBitmap(), std::move(inner_done_callback));
} }
} // namespace extensions } // namespace extensions
...@@ -11,6 +11,8 @@ namespace content { ...@@ -11,6 +11,8 @@ namespace content {
class BrowserContext; class BrowserContext;
} }
class ParentPermissionDialog;
namespace extensions { namespace extensions {
class SupervisedUserServiceManagementAPIDelegate class SupervisedUserServiceManagementAPIDelegate
...@@ -34,6 +36,9 @@ class SupervisedUserServiceManagementAPIDelegate ...@@ -34,6 +36,9 @@ class SupervisedUserServiceManagementAPIDelegate
content::WebContents* contents, content::WebContents* contents,
extensions::SupervisedUserServiceDelegate:: extensions::SupervisedUserServiceDelegate::
ParentPermissionDialogDoneCallback done_callback) override; ParentPermissionDialogDoneCallback done_callback) override;
private:
std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
}; };
} // namespace extensions } // namespace extensions
......
...@@ -3957,7 +3957,6 @@ jumbo_static_library("ui") { ...@@ -3957,7 +3957,6 @@ jumbo_static_library("ui") {
"ash/launcher/shelf_spinner_controller.h", "ash/launcher/shelf_spinner_controller.h",
"ash/launcher/shelf_spinner_item_controller.cc", "ash/launcher/shelf_spinner_item_controller.cc",
"ash/launcher/shelf_spinner_item_controller.h", "ash/launcher/shelf_spinner_item_controller.h",
"supervised_user/parent_permission_dialog.cc",
"supervised_user/parent_permission_dialog.h", "supervised_user/parent_permission_dialog.h",
"views/apps/app_dialog/app_block_dialog_view.cc", "views/apps/app_dialog/app_block_dialog_view.cc",
"views/apps/app_dialog/app_block_dialog_view.h", "views/apps/app_dialog/app_block_dialog_view.h",
......
...@@ -11,59 +11,47 @@ ...@@ -11,59 +11,47 @@
#include "base/macros.h" #include "base/macros.h"
#include "chrome/browser/extensions/install_prompt_permissions.h" #include "chrome/browser/extensions/install_prompt_permissions.h"
#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h" #include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
#include "ui/gfx/native_widget_types.h" #include "ui/gfx/native_widget_types.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h" #include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/view.h" #include "ui/views/view.h"
class Profile; class GaiaAuthFetcher;
namespace extensions { namespace extensions {
class Extension; class Extension;
} // namespace extensions } // namespace extensions
namespace signin {
class AccessTokenFetcher;
struct AccessTokenInfo;
class IdentityManager;
} // namespace signin
namespace views {
class Label;
}
class ParentPermissionSection; class ParentPermissionSection;
// Modal dialog that shows a dialog that prompts a parent for permission by // Modal dialog that shows a dialog that prompts a parent for permission by
// asking them to enter their google account credentials. // asking them to enter their google account credentials. This is created only
class ParentPermissionDialogView : public views::BubbleDialogDelegateView { // when the dialog is ready to be shown (after the state has been
// asynchronously fetched).
class ParentPermissionDialogView : public views::BubbleDialogDelegateView,
public GaiaAuthConsumer {
public: public:
using DoneCallback = base::OnceCallback<void( class Observer {
internal::ParentPermissionDialogViewResult result)>; public:
// Tells observers that their references to the view are becoming invalid.
struct Params { virtual void OnParentPermissionDialogViewDestroyed() = 0;
Params();
explicit Params(const Params& params);
~Params();
// List of email addresses of parents for whom parent permission for
// installation should be requested. These should be the emails of parent
// accounts that are permitted to approve extension installations
// for the current child user.
std::vector<base::string16> parent_permission_email_addresses;
// If true, shows a message in the parent permission dialog that the
// password entered was incorrect.
bool show_parent_password_incorrect = true;
// The icon to be displayed.
gfx::ImageSkia icon;
// The message to show.
base::string16 message;
// An optional extension whose permissions should be displayed
const extensions::Extension* extension = nullptr;
// The user's profile
Profile* profile = nullptr;
// The parent window to this window.
gfx::NativeWindow window = nullptr;
}; };
struct Params;
ParentPermissionDialogView(std::unique_ptr<Params> params, ParentPermissionDialogView(std::unique_ptr<Params> params,
DoneCallback done_callback); Observer* observer);
~ParentPermissionDialogView() override; ~ParentPermissionDialogView() override;
...@@ -71,9 +59,15 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView { ...@@ -71,9 +59,15 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
ParentPermissionDialogView& operator=(const ParentPermissionDialogView&) = ParentPermissionDialogView& operator=(const ParentPermissionDialogView&) =
delete; delete;
// Closes the dialog.
void CloseDialog();
// Shows the parent permission dialog. // Shows the parent permission dialog.
void ShowDialog(); void ShowDialog();
// Removes the observer reference.
void RemoveObserver();
void set_selected_parent_permission_email_address( void set_selected_parent_permission_email_address(
const base::string16& email_address) { const base::string16& email_address) {
selected_parent_permission_email_ = email_address; selected_parent_permission_email_ = email_address;
...@@ -82,24 +76,19 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView { ...@@ -82,24 +76,19 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
void set_parent_permission_credential(const base::string16& credential) { void set_parent_permission_credential(const base::string16& credential) {
parent_permission_credential_ = credential; parent_permission_credential_ = credential;
} }
void CloseDialogView();
// TODO(https://crbug.com/1058501): Instead of doing this with closures, use bool invalid_credential_received() { return invalid_credential_received_; }
// an interface shared with the views code whose implementation wraps the void SetIdentityManagerForTesting(signin::IdentityManager* identity_manager);
// underlying view, and delete the instance of that interface in the dtor of void SetRepromptAfterIncorrectCredential(bool reprompt);
// this class.
base::OnceClosure GetCloseDialogClosure();
private: private:
const Params& params() const { return *params_; }
base::string16 GetActiveUserFirstName() const; base::string16 GetActiveUserFirstName() const;
// views::View: // views::View:
gfx::Size CalculatePreferredSize() const override; gfx::Size CalculatePreferredSize() const override;
void AddedToWidget() override; void AddedToWidget() override;
// views::DialogDeleate: // views::DialogDelegate:
bool Cancel() override; bool Cancel() override;
bool Accept() override; bool Accept() override;
...@@ -117,6 +106,41 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView { ...@@ -117,6 +106,41 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
void ShowDialogInternal(); void ShowDialogInternal();
void AddInvalidCredentialLabel();
void LoadParentEmailAddresses();
void OnExtensionIconLoaded(const gfx::Image& image);
void LoadExtensionIcon();
void CloseWithReason(views::Widget::ClosedReason reason);
// Given an email address of the child's parent, return the parents'
// obfuscated gaia id.
std::string GetParentObfuscatedGaiaID(
const base::string16& parent_email) const;
// Starts the Reauth-scoped OAuth access token fetch process.
void StartReauthAccessTokenFetch(const std::string& parent_obfuscated_gaia_id,
const std::string& parent_credential);
// Handles the result of the access token
void OnAccessTokenFetchComplete(const std::string& parent_obfuscated_gaia_id,
const std::string& parent_credential,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info);
// Starts the Parent Reauth proof token fetch process.
void StartParentReauthProofTokenFetch(
const std::string& child_access_token,
const std::string& parent_obfuscated_gaia_id,
const std::string& credential);
// GaiaAuthConsumer
void OnReAuthProofTokenSuccess(
const std::string& reauth_proof_token) override;
void OnReAuthProofTokenFailure(
const GaiaAuthConsumer::ReAuthProofTokenStatus error) override;
void SendResult(ParentPermissionDialog::Result result);
// Sets the |extension| to be optionally displayed in the dialog. This // Sets the |extension| to be optionally displayed in the dialog. This
// causes the view to show several extension properties including the // causes the view to show several extension properties including the
// permissions, the icon and the extension name. // permissions, the icon and the extension name.
...@@ -127,17 +151,52 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView { ...@@ -127,17 +151,52 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
// if an extension has been set. // if an extension has been set.
extensions::InstallPromptPermissions prompt_permissions_; extensions::InstallPromptPermissions prompt_permissions_;
// The email address of the parents to display in the dialog.
std::vector<base::string16> parent_permission_email_addresses_;
bool reprompt_after_incorrect_credential_ = true;
// Contains the parent-permission related views widgets.
std::unique_ptr<ParentPermissionSection> parent_permission_section_; std::unique_ptr<ParentPermissionSection> parent_permission_section_;
views::Label* invalid_credential_label_ = nullptr;
bool invalid_credential_received_ = false;
// The currently selected parent email.
base::string16 selected_parent_permission_email_; base::string16 selected_parent_permission_email_;
// The currently entered parent credential.
base::string16 parent_permission_credential_; base::string16 parent_permission_credential_;
// Configuration parameters for the prompt. // Parameters for the dialog.
std::unique_ptr<Params> params_; std::unique_ptr<Params> params_;
DoneCallback done_callback_; // Used to ensure we don't try to show same dialog twice.
bool is_showing_ = false;
// Used to fetch the Reauth token.
std::unique_ptr<GaiaAuthFetcher> reauth_token_fetcher_;
// Used to fetch OAuth2 access tokens.
signin::IdentityManager* identity_manager_ = nullptr;
std::unique_ptr<signin::AccessTokenFetcher> oauth2_access_token_fetcher_;
Observer* observer_;
base::WeakPtrFactory<ParentPermissionDialogView> weak_factory_{this}; base::WeakPtrFactory<ParentPermissionDialogView> weak_factory_{this};
}; };
// Allows tests to observe the create of the testing instance of
// ParentPermissionDialogView
class TestParentPermissionDialogViewObserver {
public:
// Implementers should pass "this" as constructor argument.
TestParentPermissionDialogViewObserver(
TestParentPermissionDialogViewObserver* observer);
~TestParentPermissionDialogViewObserver();
virtual void OnTestParentPermissionDialogViewCreated(
ParentPermissionDialogView* view) = 0;
};
#endif // CHROME_BROWSER_UI_VIEWS_PARENT_PERMISSION_DIALOG_VIEW_H_ #endif // CHROME_BROWSER_UI_VIEWS_PARENT_PERMISSION_DIALOG_VIEW_H_
// Copyright 2020 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/ui/views/parent_permission_dialog_view.h"
#include <memory>
#include <vector>
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/test/base/chrome_test_utils.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
// A simple DialogBrowserTest for the ParentPermissionDialogView that just
// shows the dialog.
class ParentPermissionDialogViewBrowserTest : public DialogBrowserTest {
public:
ParentPermissionDialogViewBrowserTest() {}
ParentPermissionDialogViewBrowserTest(
const ParentPermissionDialogViewBrowserTest&) = delete;
ParentPermissionDialogViewBrowserTest& operator=(
const ParentPermissionDialogViewBrowserTest&) = delete;
void OnParentPermissionPromptDone(
internal::ParentPermissionDialogViewResult result) {}
void ShowUi(const std::string& name) override {
std::vector<base::string16> parent_emails =
std::vector<base::string16>{base::UTF8ToUTF16("parent1@google.com"),
base::UTF8ToUTF16("parent2@google.com")};
gfx::ImageSkia icon = extensions::util::GetDefaultExtensionIcon();
ShowParentPermissionDialog(
browser()->profile(), browser()->window()->GetNativeWindow(),
parent_emails, false, icon, base::UTF8ToUTF16("Test Message"), nullptr,
base::BindOnce(&ParentPermissionDialogViewBrowserTest::
OnParentPermissionPromptDone,
base::Unretained(this)));
}
};
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogViewBrowserTest,
InvokeUi_default) {
ShowAndVerifyUi();
}
...@@ -2396,7 +2396,6 @@ if (!is_android) { ...@@ -2396,7 +2396,6 @@ if (!is_android) {
"../browser/ui/ash/tablet_mode_page_behavior_browsertest.cc", "../browser/ui/ash/tablet_mode_page_behavior_browsertest.cc",
"../browser/ui/ash/volume_controller_browsertest.cc", "../browser/ui/ash/volume_controller_browsertest.cc",
"../browser/ui/browser_finder_chromeos_browsertest.cc", "../browser/ui/browser_finder_chromeos_browsertest.cc",
"../browser/ui/supervised_user/parent_permission_dialog_browsertest.cc",
"../browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc", "../browser/ui/views/apps/app_dialog/app_dialog_view_browsertest.cc",
"../browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc", "../browser/ui/views/apps/app_dialog/app_uninstall_dialog_view_browsertest.cc",
"../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc", "../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
...@@ -2416,7 +2415,7 @@ if (!is_android) { ...@@ -2416,7 +2415,7 @@ if (!is_android) {
"../browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc", "../browser/ui/views/frame/immersive_mode_controller_ash_browsertest.cc",
"../browser/ui/views/frame/system_menu_model_builder_browsertest_chromeos.cc", "../browser/ui/views/frame/system_menu_model_builder_browsertest_chromeos.cc",
"../browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc", "../browser/ui/views/frame/top_controls_slide_controller_chromeos_browsertest.cc",
"../browser/ui/views/parent_permission_dialog_view_browsertest.cc", "../browser/ui/views/parent_permission_dialog_browsertest.cc",
"../browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc", "../browser/ui/views/plugin_vm/plugin_vm_installer_view_browsertest.cc",
"../browser/ui/views/web_apps/web_app_ash_interactive_ui_test.cc", "../browser/ui/views/web_apps/web_app_ash_interactive_ui_test.cc",
"../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc", "../browser/ui/web_applications/web_app_guest_session_browsertest_chromeos.cc",
......
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