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 @@
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
#include "content/public/browser/web_contents.h"
namespace {
void OnParentPermissionDialogComplete(
std::unique_ptr<ParentPermissionDialog> dialog,
extensions::SupervisedUserServiceDelegate::
ParentPermissionDialogDoneCallback delegate_done_callback,
ParentPermissionDialog::Result result) {
......@@ -73,25 +73,15 @@ void SupervisedUserServiceManagementAPIDelegate::
content::BrowserContext* context,
content::WebContents* contents,
ParentPermissionDialogDoneCallback done_callback) {
std::unique_ptr<ParentPermissionDialog> dialog =
std::make_unique<ParentPermissionDialog>(
Profile::FromBrowserContext(context));
// Cache the pointer so we can show the dialog after we pass
// ownership to the callback.
ParentPermissionDialog* dialog_ptr = dialog.get();
// Ownership of the dialog passes to the callback. This allows us
// 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));
ParentPermissionDialog::DoneCallback inner_done_callback = base::BindOnce(
&::OnParentPermissionDialogComplete, std::move(done_callback));
parent_permission_dialog_ =
ParentPermissionDialog::CreateParentPermissionDialogForExtension(
Profile::FromBrowserContext(context), contents,
contents->GetTopLevelNativeWindow(), gfx::ImageSkia(), &extension,
std::move(inner_done_callback));
parent_permission_dialog_->ShowDialog();
}
} // namespace extensions
......@@ -11,6 +11,8 @@ namespace content {
class BrowserContext;
}
class ParentPermissionDialog;
namespace extensions {
class SupervisedUserServiceManagementAPIDelegate
......@@ -34,6 +36,9 @@ class SupervisedUserServiceManagementAPIDelegate
content::WebContents* contents,
extensions::SupervisedUserServiceDelegate::
ParentPermissionDialogDoneCallback done_callback) override;
private:
std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
};
} // namespace extensions
......
......@@ -3957,7 +3957,6 @@ jumbo_static_library("ui") {
"ash/launcher/shelf_spinner_controller.h",
"ash/launcher/shelf_spinner_item_controller.cc",
"ash/launcher/shelf_spinner_item_controller.h",
"supervised_user/parent_permission_dialog.cc",
"supervised_user/parent_permission_dialog.h",
"views/apps/app_dialog/app_block_dialog_view.cc",
"views/apps/app_dialog/app_block_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/supervised_user/parent_permission_dialog.h"
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "components/signin/public/identity_manager/access_token_fetcher.h"
#include "components/signin/public/identity_manager/access_token_info.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/extension_resource.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "google_apis/gaia/gaia_constants.h"
#include "ui/base/resource/scale_factor.h"
#include "ui/gfx/geometry/size.h"
#include "ui/gfx/image/image_skia.h"
namespace {
// Returns bitmap for the default icon with size equal to the default icon's
// pixel size under maximal supported scale factor.
const gfx::ImageSkia& GetDefaultIconBitmapForMaxScaleFactor(bool is_app) {
return is_app ? extensions::util::GetDefaultAppIcon()
: extensions::util::GetDefaultExtensionIcon();
}
signin::IdentityManager* test_identity_manager = nullptr;
} // namespace
ParentPermissionDialog::ParentPermissionDialog(Profile* profile)
: profile_(profile) {
DCHECK(profile_);
DCHECK(profile_->IsChild());
}
ParentPermissionDialog::~ParentPermissionDialog() {
// Close the underlying widget if this object is deleted.
if (close_dialog_view_callback_)
std::move(close_dialog_view_callback_).Run();
}
void ParentPermissionDialog::ShowPrompt(content::WebContents* web_contents,
const base::string16& message,
const SkBitmap& icon,
DoneCallback callback) {
callback_ = std::move(callback);
DCHECK(callback_);
DCHECK(!web_contents_);
web_contents_ = web_contents;
message_ = message;
if (!icon.isNull()) {
const gfx::Image image = gfx::Image::CreateFrom1xBitmap(icon);
if (!image.IsEmpty())
icon_ = *image.ToImageSkia();
}
LoadParentEmailAddresses();
ShowPromptInternal(false /* show_password_incorrect */);
}
void ParentPermissionDialog::ShowPromptForExtensionInstallation(
content::WebContents* web_contents,
const extensions::Extension* extension,
const SkBitmap& fallback_icon,
DoneCallback callback) {
callback_ = std::move(callback);
DCHECK(callback_);
DCHECK(!web_contents_);
web_contents_ = web_contents;
extension_ = extension;
if (!fallback_icon.isNull()) {
const gfx::Image image = gfx::Image::CreateFrom1xBitmap(fallback_icon);
if (!image.IsEmpty())
icon_ = *image.ToImageSkia();
}
LoadParentEmailAddresses();
// Prompt is shown after extension icon is loaded.
LoadExtensionIcon();
}
bool ParentPermissionDialog::CredentialWasInvalid() const {
return invalid_credential_received_;
}
// static
void ParentPermissionDialog::SetFakeIdentityManagerForTesting(
signin::IdentityManager* identity_manager) {
test_identity_manager = identity_manager;
}
void ParentPermissionDialog::LoadParentEmailAddresses() {
// Get the parents' email addresses. There can be a max of 2 parent email
// addresses, the primary and the secondary.
SupervisedUserService* service =
SupervisedUserServiceFactory::GetForProfile(profile_);
base::string16 primary_parent_email =
base::UTF8ToUTF16(service->GetCustodianEmailAddress());
if (!primary_parent_email.empty())
parent_permission_email_addresses_.push_back(primary_parent_email);
base::string16 secondary_parent_email =
base::UTF8ToUTF16(service->GetSecondCustodianEmailAddress());
if (!secondary_parent_email.empty())
parent_permission_email_addresses_.push_back(secondary_parent_email);
if (parent_permission_email_addresses_.empty()) {
// TODO(danan): Add UMA stat for this failure.
// https://crbug.com/1049418
SendResult(Result::kParentPermissionFailed);
}
}
void ParentPermissionDialog::OnExtensionIconLoaded(const gfx::Image& image) {
// The order of preference for the icon to use is:
// 1. Icon loaded from extension, if not empty.
// 2. Icon passed in params, if not empty.
// 3. Default Icon.
if (!image.IsEmpty()) {
// Use the image that was loaded from the extension if it's not empty
icon_ = *image.ToImageSkia();
} else if (icon_.isNull()) {
// If the params icon is empty, use a default icon.:
icon_ = GetDefaultIconBitmapForMaxScaleFactor(extension_->is_app());
}
ShowPromptInternal(false /* show_password_incorrect */);
}
void ParentPermissionDialog::LoadExtensionIcon() {
extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
extension_, extension_misc::EXTENSION_ICON_LARGE,
ExtensionIconSet::MATCH_BIGGER);
// Load the image asynchronously. The response will be sent to
// OnExtensionIconLoaded.
extensions::ImageLoader* loader = extensions::ImageLoader::Get(profile_);
std::vector<extensions::ImageLoader::ImageRepresentation> images_list;
images_list.push_back(extensions::ImageLoader::ImageRepresentation(
image, extensions::ImageLoader::ImageRepresentation::NEVER_RESIZE,
gfx::Size(),
ui::GetScaleFactorForNativeView(web_contents_->GetNativeView())));
loader->LoadImagesAsync(
extension_, images_list,
base::BindOnce(&ParentPermissionDialog::OnExtensionIconLoaded,
weak_factory_.GetWeakPtr()));
}
void ParentPermissionDialog::ShowPromptInternal(bool show_password_incorrect) {
close_dialog_view_callback_ = ShowParentPermissionDialog(
profile_, web_contents_->GetTopLevelNativeWindow(),
parent_permission_email_addresses_, show_password_incorrect, icon_,
message_, extension_,
base::BindOnce(&ParentPermissionDialog::OnParentPermissionPromptDone,
weak_factory_.GetWeakPtr()));
}
void ParentPermissionDialog::OnParentPermissionPromptDone(
internal::ParentPermissionDialogViewResult result) {
if (result.status ==
internal::ParentPermissionDialogViewResult::Status::kAccepted) {
HandleParentPermissionDialogAccepted(result);
} else {
SendResult(Result::kParentPermissionCanceled);
}
}
void ParentPermissionDialog::HandleParentPermissionDialogAccepted(
internal::ParentPermissionDialogViewResult result) {
std::string parent_obfuscated_gaia_id =
GetParentObfuscatedGaiaID(result.selected_parent_permission_email);
std::string parent_credential =
base::UTF16ToUTF8(result.parent_permission_credential);
StartReAuthAccessTokenFetch(parent_obfuscated_gaia_id, parent_credential);
}
std::string ParentPermissionDialog::GetParentObfuscatedGaiaID(
const base::string16& parent_email) const {
SupervisedUserService* service =
SupervisedUserServiceFactory::GetForProfile(profile_);
if (service->GetCustodianEmailAddress() == base::UTF16ToUTF8(parent_email))
return service->GetCustodianObfuscatedGaiaId();
if (service->GetSecondCustodianEmailAddress() ==
base::UTF16ToUTF8(parent_email)) {
return service->GetSecondCustodianObfuscatedGaiaId();
}
NOTREACHED()
<< "Tried to get obfuscated gaia id for a non-custodian email address";
return "";
}
void ParentPermissionDialog::StartReAuthAccessTokenFetch(
const std::string& parent_obfuscated_gaia_id,
const std::string& parent_credential) {
// The first step of ReAuth is to fetch an OAuth2 access token for the
// Reauth API scope.
if (test_identity_manager)
identity_manager_ = test_identity_manager;
else
identity_manager_ = IdentityManagerFactory::GetForProfile(profile_);
signin::ScopeSet scopes;
scopes.insert(GaiaConstants::kAccountsReauthOAuth2Scope);
DCHECK(!oauth2_access_token_fetcher_);
oauth2_access_token_fetcher_ =
identity_manager_->CreateAccessTokenFetcherForAccount(
identity_manager_->GetPrimaryAccountId(),
"chrome_webstore_private_api", scopes,
base::BindOnce(&ParentPermissionDialog::OnAccessTokenFetchComplete,
weak_factory_.GetWeakPtr(), parent_obfuscated_gaia_id,
parent_credential),
signin::AccessTokenFetcher::Mode::kImmediate);
}
void ParentPermissionDialog::OnAccessTokenFetchComplete(
const std::string& parent_obfuscated_gaia_id,
const std::string& parent_credential,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
oauth2_access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
SendResult(Result::kParentPermissionFailed);
return;
}
// Now that we have the OAuth2 access token, we use it when we attempt
// to fetch the ReAuthProof token (RAPT) for the parent.
StartParentReAuthProofTokenFetch(
access_token_info.token, parent_obfuscated_gaia_id, parent_credential);
}
void ParentPermissionDialog::StartParentReAuthProofTokenFetch(
const std::string& child_access_token,
const std::string& parent_obfuscated_gaia_id,
const std::string& credential) {
reauth_token_fetcher_ = std::make_unique<GaiaAuthFetcher>(
this, gaia::GaiaSource::kChromeOS, profile_->GetURLLoaderFactory());
reauth_token_fetcher_->StartCreateReAuthProofTokenForParent(
child_access_token, parent_obfuscated_gaia_id, credential);
}
void ParentPermissionDialog::SendResult(Result result) {
std::move(callback_).Run(result);
}
void ParentPermissionDialog::OnReAuthProofTokenSuccess(
const std::string& reauth_proof_token) {
SendResult(Result::kParentPermissionReceived);
}
void ParentPermissionDialog::OnReAuthProofTokenFailure(
const GaiaAuthConsumer::ReAuthProofTokenStatus error) {
reauth_token_fetcher_.reset();
if (error == GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant) {
// Signal to tests that invalid credential was received.
invalid_credential_received_ = true;
// If invalid password was entered, and the dialog is configured to
// re-prompt show the dialog again with the invalid password error message.
// prompt again, this time with a password error message.
if (reprompt_after_incorrect_credential_)
ShowPromptInternal(true /* show password incorrect */);
else
SendResult(Result::kParentPermissionFailed); // Fail immediately if not
// reprompting.
} else {
SendResult(Result::kParentPermissionFailed);
}
}
......@@ -7,27 +7,16 @@
#include <stddef.h>
#include <memory>
#include <string>
#include <vector>
#include "base/callback_forward.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "google_apis/gaia/gaia_auth_consumer.h"
#include "google_apis/gaia/gaia_auth_fetcher.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/image/image_skia.h"
#include "ui/gfx/native_widget_types.h"
class GoogleServiceAuthError;
class Profile;
namespace signin {
class AccessTokenFetcher;
struct AccessTokenInfo;
class IdentityManager;
} // namespace signin
namespace content {
class WebContents;
}
......@@ -36,205 +25,84 @@ namespace extensions {
class Extension;
}
namespace gfx {
class Image;
}
namespace internal {
struct ParentPermissionDialogViewResult;
}
// ParentPermissionDialog provides a dialog that will prompt a child user's
// parent(s) for their permission for action. The parent(s) approve the action
// by entering their Google password, which is then verified using the Google
// This file provides an API that will prompt a child user's parent(s) for their
// permission for action. The parent(s) can approve the action by entering
// their Google password, which is then verified using the Google
// Reauthentication API's child to parent delegation mode. The prompt can only
// be shown if the user is a child. Otherwise, the prompt will fail.
//
// Clients should provide a ParentPermissionDialog::DoneCallback to
// receive the results of the dialog.
// Example Usage:
// ParentPermissionDialog::DoneCallback callback = base::BindOnce(
// &ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
// &MyClass::OnParentPermissionDialogDone,
// weak_ptr_factory_.GetWeakPtr()))
// ParentPermissionDialog dialog(profile, std::move(callback));
// SkBitmap icon = LoadMyIcon();
// dialog.ShowPrompt(web_contents, "Allow your child to purchase X?", icon);
// gfx::ImageSkia icon = LoadMyIcon();
//
// std::unique_ptr<ParentPermissionDialog> dialog =
// CreateParentPermissionDialog(profile, window, icon, message, done_callback);
//
// This dialog is currently used to display content relevant for a parent to
// provide permission for the installation of an extension. Using the
// ShowPromptForExtensionInstallation() method below.
// dialog->ShowDialog();
//
// This class is not thread safe.
class ParentPermissionDialog : public GaiaAuthConsumer {
// MyClass::ParentPermissionDialogDone(ParentPermissionDialog::Result result) {
// switch (result) {
// ...
// }
// }
// API for the Dialog.
class ParentPermissionDialog {
public:
enum class Result {
// The parent has given their permission for the action.
kParentPermissionReceived,
// The dialog was canceled.
kParentPermissionCanceled,
// Parent Permission was attempted, but failed due to an unrecoverable
// error, i.e. a network error.
// NOTE: This does not indicate that the password entered was incorrect.
kParentPermissionFailed,
};
using DoneCallback = base::OnceCallback<void(Result result)>;
explicit ParentPermissionDialog(Profile* profile);
ParentPermissionDialog(const ParentPermissionDialog&) = delete;
ParentPermissionDialog& operator=(const ParentPermissionDialog&) = delete;
~ParentPermissionDialog() override;
// Shows the Parent Permission Dialog.
// |message| specifies the text to be shown in the dialog.
// |icon| specifies the icon to be displayed. It can be empty.
void ShowPrompt(content::WebContents* web_contents,
const base::string16& message,
const SkBitmap& icon,
DoneCallback callback);
// Shows the Parent Permission Dialog for the specified extension
// installation. The dialog's message will be generated from the extension
// itself. |fallback_icon| can be empty. If it is set, it will be used as a
// backup in the event that the extension's icon couldn't be loaded from the
// extension itself. If it is empty, and the icon couldn't be loaded from the
// extension, a default generic extension icon will be displayed.
void ShowPromptForExtensionInstallation(
content::WebContents* web_contents,
const extensions::Extension* extension,
const SkBitmap& fallback_icon,
DoneCallback callback);
// Sets whether the prompt is shown again automatically after an
// incorrect credential. This defaults to true, and is only disabled for
// testing. Without this, the test will infinitely repeatedly re-prompt
// for a password when it is incorrect.
void set_reprompt_after_incorrect_credential(
bool reprompt_after_incorrect_credential) {
reprompt_after_incorrect_credential_ = reprompt_after_incorrect_credential;
}
static void SetFakeIdentityManagerForTesting(
signin::IdentityManager* identity_manager);
// Only used for testing. Returns true if an invalid credential was received.
bool CredentialWasInvalid() const;
private:
void LoadParentEmailAddresses();
void OnExtensionIconLoaded(const gfx::Image& image);
void LoadExtensionIcon();
// Shows prompt internally. If |show_password_incorrect| is true, a message
// will be displayed indicating that.
void ShowPromptInternal(bool show_password_incorrect);
// Called when the parent permission prompt UI finishes, but before the
// ReAuth process starts.
void OnParentPermissionPromptDone(
internal::ParentPermissionDialogViewResult result);
// Called to handle the case when a user clicks the Accept button in the
// dialog.
void HandleParentPermissionDialogAccepted(
internal::ParentPermissionDialogViewResult result);
// 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);
void SendResult(Result result);
// GaiaAuthConsumer
void OnReAuthProofTokenSuccess(
const std::string& reauth_proof_token) override;
void OnReAuthProofTokenFailure(
const GaiaAuthConsumer::ReAuthProofTokenStatus error) override;
std::vector<base::string16> parent_permission_email_addresses_;
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_;
Profile* const profile_;
DoneCallback callback_;
const extensions::Extension* extension_ = nullptr;
gfx::ImageSkia icon_;
base::string16 message_;
content::WebContents* web_contents_ = nullptr;
// If true, the prompt will be shown again after an incorrect password
// is entered.
bool reprompt_after_incorrect_credential_ = true;
// Callback to call to close the underlying dialog view.
base::OnceClosure close_dialog_view_callback_;
bool invalid_credential_received_ = false;
base::WeakPtrFactory<ParentPermissionDialog> weak_factory_{this};
};
// NOTE: DO NOT USE the following code directly. It is an implementation detail
// of the dialog. Instead use ParentPermissionDialog above.
namespace internal {
// Internal struct use by the view that implements the dialog to
// communicate the result status of the dialog UI itself.
struct ParentPermissionDialogViewResult {
public:
enum class Status {
kAccepted,
kCanceled,
kUnknown,
};
virtual ~ParentPermissionDialog() = default;
Status status = Status::kUnknown;
base::string16 selected_parent_permission_email;
base::string16 parent_permission_credential;
};
// Shows the Dialog. The process to show it can be asynchronous, so the dialog
// may not appear immediately.
virtual void ShowDialog() = 0;
} // namespace internal
// Type of the callback invoked with the dialog completes.
using DoneCallback = base::OnceCallback<void(Result result)>;
// Implemented by the platform specific ui code to actually show the dialog.
// |window| should be the window to which the dialog is modal. It comes from
// whatever widget is associated with opening the parent permission dialog.
// Returns a closure that should be used to close the dialog view if the caller
// disappears. If |show_parent_password_incorrect| is set to true, then the
// dialog will also display a "Password Incorrect" message.
base::OnceClosure ShowParentPermissionDialog(
// Creates a ParentPermissionDialog.
// |profile| is the child user's profile.
// |web_contents| is the web_contents of the initiator.
// |window| is the window to which the dialog will be modal.
// |icon| will be displayed to the side of |message|.
// |message| will be displayed in the body of the dialog.
// |done_callback| will be called on dialog completion.
static std::unique_ptr<ParentPermissionDialog> CreateParentPermissionDialog(
Profile* profile,
content::WebContents* web_contents,
gfx::NativeWindow window,
const std::vector<base::string16>& parent_permission_email_addresses,
bool show_parent_password_incorrect,
const gfx::ImageSkia& icon,
const base::string16& message,
ParentPermissionDialog::DoneCallback done_callback);
// Creates a ParentPermissionDialog customized for the installation of the
// specified |extension|.
// |profile| is the child user's profile.
// |web_contents| is the web_contents of the initiator.
// |window| is the window to which the dialog will be modal.
// |icon| will be used as a backup in case |extension| doesn't have a loaded
// |done_callback| will be called on dialog completion.
static std::unique_ptr<ParentPermissionDialog>
CreateParentPermissionDialogForExtension(
Profile* profile,
content::WebContents* web_contents,
gfx::NativeWindow window,
const gfx::ImageSkia& icon,
const extensions::Extension* extension,
base::OnceCallback<void(internal::ParentPermissionDialogViewResult result)>
view_done_callback);
// Only to be used by tests. Sets the next status returned by the dialog
// widget.
void SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status status);
ParentPermissionDialog::DoneCallback done_callback);
};
#endif // CHROME_BROWSER_UI_SUPERVISED_USER_PARENT_PERMISSION_DIALOG_H_
......@@ -8,6 +8,7 @@
#include <vector>
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/supervised_user/logged_in_user_mixin.h"
......@@ -16,6 +17,8 @@
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/browser/ui/views/parent_permission_dialog_view.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/mixin_based_in_process_browser_test.h"
#include "components/signin/public/identity_manager/identity_test_environment.h"
......@@ -26,19 +29,84 @@
// End to end test of ParentPermissionDialog that exercises the dialog's
// internal logic the orchestrates the parental permission process.
class ParentPermissionDialogBrowserTest
: public MixinBasedInProcessBrowserTest {
: public SupportsTestDialog<MixinBasedInProcessBrowserTest>,
public TestParentPermissionDialogViewObserver {
public:
ParentPermissionDialogBrowserTest() = default;
// The next dialog action to take.
enum class NextDialogAction {
kCancel,
kAccept,
};
ParentPermissionDialogBrowserTest()
: TestParentPermissionDialogViewObserver(this) {}
ParentPermissionDialogBrowserTest(const ParentPermissionDialogBrowserTest&) =
delete;
ParentPermissionDialogBrowserTest& operator=(
const ParentPermissionDialogBrowserTest&) = delete;
void OnParentPermissionDialogDone(base::OnceClosure quit_closure,
ParentPermissionDialog::Result result) {
void OnParentPermissionDialogDone(ParentPermissionDialog::Result result) {
result_ = result;
std::move(quit_closure).Run();
std::move(on_dialog_done_closure_).Run();
}
// TestBrowserUi
void ShowUi(const std::string& name) override {
SkBitmap icon =
*gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Set up a RunLoop with a quit closure to block until
// the dialog is shown, which is what this method is supposed
// to ensure.
base::RunLoop run_loop;
dialog_shown_closure_ = run_loop.QuitClosure();
// These use base::DoNothing because we aren't interested in the dialog's
// results. Unlike the other non-TestBrowserUi tests, this test doesn't
// block, because that interferes with the widget accounting done by
// TestBrowserUi.
if (name == "default") {
parent_permission_dialog_ =
ParentPermissionDialog::CreateParentPermissionDialog(
browser()->profile(), contents,
contents->GetTopLevelNativeWindow(),
gfx::ImageSkia::CreateFrom1xBitmap(icon),
base::UTF8ToUTF16("Test prompt message"), base::DoNothing());
} else if (name == "extension") {
parent_permission_dialog_ =
ParentPermissionDialog::CreateParentPermissionDialogForExtension(
browser()->profile(), contents,
contents->GetTopLevelNativeWindow(),
gfx::ImageSkia::CreateFrom1xBitmap(icon), test_extension_.get(),
base::DoNothing());
}
parent_permission_dialog_->ShowDialog();
run_loop.Run();
}
// TestParentPermissionDialogViewObserver
void OnTestParentPermissionDialogViewCreated(
ParentPermissionDialogView* view) override {
if (dialog_shown_closure_)
std::move(dialog_shown_closure_).Run();
view_ = view;
view_->SetIdentityManagerForTesting(identity_test_env_->identity_manager());
view_->SetRepromptAfterIncorrectCredential(false);
if (next_dialog_action_) {
switch (next_dialog_action_.value()) {
case NextDialogAction::kCancel:
view->CancelDialog();
break;
case NextDialogAction::kAccept:
view->AcceptDialog();
break;
}
}
}
void InitializeFamilyData() {
......@@ -64,12 +132,11 @@ class ParentPermissionDialogBrowserTest
chromeos::FakeGaiaMixin::kFakeUserEmail);
identity_test_env_->SetRefreshTokenForPrimaryAccount();
identity_test_env_->SetAutomaticIssueOfAccessTokens(true);
ParentPermissionDialog::SetFakeIdentityManagerForTesting(
identity_test_env_->identity_manager());
}
void SetUpOnMainThread() override {
MixinBasedInProcessBrowserTest::SetUpOnMainThread();
test_extension_ = extensions::ExtensionBuilder("test extension").Build();
logged_in_user_mixin_.LogInUser(true /* issue_any_scope_token */);
InitializeFamilyData();
SupervisedUserService* service =
......@@ -78,48 +145,61 @@ class ParentPermissionDialogBrowserTest
true);
}
void SetNextReAuthStatus(
void set_next_reauth_status(
const GaiaAuthConsumer::ReAuthProofTokenStatus next_status) {
logged_in_user_mixin_.GetFakeGaiaMixin()->fake_gaia()->SetNextReAuthStatus(
next_status);
}
void set_next_dialog_action(NextDialogAction action) {
next_dialog_action_ = action;
}
// This method will block until the next dialog completing action takes place,
// so that the result can be checked.
void ShowPrompt() {
base::RunLoop run_loop;
parent_permission_dialog_ =
std::make_unique<ParentPermissionDialog>(browser()->profile());
on_dialog_done_closure_ = run_loop.QuitClosure();
ParentPermissionDialog::DoneCallback callback = base::BindOnce(
&ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
base::Unretained(this), run_loop.QuitClosure());
base::Unretained(this));
parent_permission_dialog_->set_reprompt_after_incorrect_credential(false);
SkBitmap icon =
*gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
parent_permission_dialog_->ShowPrompt(
browser()->tab_strip_model()->GetActiveWebContents(),
base::UTF8ToUTF16("Test Prompt Message"), icon, std::move(callback));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
parent_permission_dialog_ =
ParentPermissionDialog::CreateParentPermissionDialog(
browser()->profile(), contents, contents->GetTopLevelNativeWindow(),
gfx::ImageSkia::CreateFrom1xBitmap(icon),
base::UTF8ToUTF16("Test prompt message"), std::move(callback));
parent_permission_dialog_->ShowDialog();
run_loop.Run();
}
void ShowPromptForExtension(
scoped_refptr<const extensions::Extension> extension) {
// This method will block until the next dialog action takes place, so that
// the result can be checked.
void ShowPromptForExtension() {
base::RunLoop run_loop;
parent_permission_dialog_ =
std::make_unique<ParentPermissionDialog>(browser()->profile());
on_dialog_done_closure_ = run_loop.QuitClosure();
ParentPermissionDialog::DoneCallback callback = base::BindOnce(
&ParentPermissionDialogBrowserTest::OnParentPermissionDialogDone,
base::Unretained(this), run_loop.QuitClosure());
base::Unretained(this));
parent_permission_dialog_->set_reprompt_after_incorrect_credential(false);
SkBitmap icon =
*gfx::Image(extensions::util::GetDefaultExtensionIcon()).ToSkBitmap();
parent_permission_dialog_->ShowPromptForExtensionInstallation(
browser()->tab_strip_model()->GetActiveWebContents(), extension.get(),
icon, std::move(callback));
content::WebContents* contents =
browser()->tab_strip_model()->GetActiveWebContents();
parent_permission_dialog_ =
ParentPermissionDialog::CreateParentPermissionDialogForExtension(
browser()->profile(), contents, contents->GetTopLevelNativeWindow(),
gfx::ImageSkia::CreateFrom1xBitmap(icon), test_extension_.get(),
std::move(callback));
parent_permission_dialog_->ShowDialog();
run_loop.Run();
}
......@@ -128,33 +208,57 @@ class ParentPermissionDialogBrowserTest
}
void CheckInvalidCredentialWasReceived() {
EXPECT_TRUE(parent_permission_dialog_->CredentialWasInvalid());
EXPECT_TRUE(view_->invalid_credential_received());
}
private:
ParentPermissionDialog::Result result_;
ParentPermissionDialogView* view_ = nullptr;
std::unique_ptr<ParentPermissionDialog> parent_permission_dialog_;
ParentPermissionDialog::Result result_;
chromeos::LoggedInUserMixin logged_in_user_mixin_{
&mixin_host_, chromeos::LoggedInUserMixin::LogInType::kChild,
embedded_test_server(), this};
// Closure that is triggered once the dialog is shown.
base::OnceClosure dialog_shown_closure_;
// Closure that is triggered once the dialog completes.
base::OnceClosure on_dialog_done_closure_;
scoped_refptr<const extensions::Extension> test_extension_ = nullptr;
std::unique_ptr<signin::IdentityTestEnvironment> identity_test_env_;
base::Optional<NextDialogAction> next_dialog_action_;
};
// Tests that a plain dialog widget is shown using the TestBrowserUi
// infrastructure.
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, InvokeUi_default) {
ShowAndVerifyUi();
}
// Tests that the extension-parameterized dialog widget is shown using the
// TestBrowserUi infrastructure.
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, InvokeUi_extension) {
ShowAndVerifyUi();
}
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest, PermissionReceived) {
SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status::kAccepted);
set_next_reauth_status(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
set_next_dialog_action(
ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
ShowPrompt();
CheckResult(ParentPermissionDialog::Result::kParentPermissionReceived);
}
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
PermissionFailedInvalidPassword) {
SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status::kAccepted);
set_next_reauth_status(
GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
set_next_dialog_action(
ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
ShowPrompt();
CheckInvalidCredentialWasReceived();
CheckResult(ParentPermissionDialog::Result::kParentPermissionFailed);
......@@ -162,39 +266,37 @@ IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
PermissionDialogCanceled) {
SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status::kCanceled);
set_next_dialog_action(
ParentPermissionDialogBrowserTest::NextDialogAction::kCancel);
ShowPrompt();
CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
}
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
PermissionReceivedForExtension) {
SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status::kAccepted);
ShowPromptForExtension(
extensions::ExtensionBuilder("test extension").Build());
set_next_reauth_status(GaiaAuthConsumer::ReAuthProofTokenStatus::kSuccess);
set_next_dialog_action(
ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
ShowPrompt();
CheckResult(ParentPermissionDialog::Result::kParentPermissionReceived);
}
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
PermissionFailedInvalidPasswordForExtension) {
SetNextReAuthStatus(GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status::kAccepted);
ShowPromptForExtension(
extensions::ExtensionBuilder("test extension").Build());
set_next_reauth_status(
GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant);
set_next_dialog_action(
ParentPermissionDialogBrowserTest::NextDialogAction::kAccept);
ShowPromptForExtension();
CheckInvalidCredentialWasReceived();
CheckResult(ParentPermissionDialog::Result::kParentPermissionFailed);
}
IN_PROC_BROWSER_TEST_F(ParentPermissionDialogBrowserTest,
PermissionDialogCanceledForExtension) {
SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status::kCanceled);
set_next_dialog_action(
ParentPermissionDialogBrowserTest::NextDialogAction::kCancel);
ShowPromptForExtension(
extensions::ExtensionBuilder("test extension").Build());
ShowPromptForExtension();
CheckResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
}
......@@ -12,6 +12,9 @@
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/extensions/extension_util.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/signin/identity_manager_factory.h"
#include "chrome/browser/supervised_user/supervised_user_service.h"
#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
#include "chrome/browser/ui/browser_dialogs.h"
#include "chrome/browser/ui/supervised_user/parent_permission_dialog.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
......@@ -19,13 +22,22 @@
#include "chrome/browser/ui/views/extensions/extension_permissions_view.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/signin/public/identity_manager/access_token_fetcher.h"
#include "components/signin/public/identity_manager/identity_manager.h"
#include "components/signin/public/identity_manager/scope_set.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/image_loader.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_icon_set.h"
#include "extensions/common/manifest.h"
#include "extensions/common/manifest_constants.h"
#include "extensions/common/manifest_handlers/icons_handler.h"
#include "extensions/common/permissions/permission_set.h"
#include "google_apis/gaia/gaia_constants.h"
#include "ui/accessibility/ax_enums.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/ui_base_types.h"
......@@ -47,36 +59,28 @@
namespace {
constexpr int kSectionPaddingTop = 20;
// Whether to auto confirm the dialog for test.
bool auto_confirm_dialog_for_test = false;
// Status to use for auto-confirmation for test.
internal::ParentPermissionDialogViewResult::Status
auto_confirm_status_for_test =
internal::ParentPermissionDialogViewResult::Status::kAccepted;
// Returns bitmap for the default icon with size equal to the default icon's
// pixel size under maximal supported scale factor.
const gfx::ImageSkia& GetDefaultIconBitmapForMaxScaleFactor(bool is_app) {
return is_app ? extensions::util::GetDefaultAppIcon()
: extensions::util::GetDefaultExtensionIcon();
}
views::Widget* widget_for_test = nullptr;
TestParentPermissionDialogViewObserver* test_view_observer = nullptr;
} // namespace
void SetAutoConfirmParentPermissionDialogForTest(
internal::ParentPermissionDialogViewResult::Status status) {
auto_confirm_dialog_for_test = true;
auto_confirm_status_for_test = status;
}
// Creates a view for the parent approvals section of the extension info and
// listens for updates to its controls. The view added to the parent contains a
// parent email selection drop-down box, and a password entry field.
class ParentPermissionSection : public views::TextfieldController {
public:
ParentPermissionSection(ParentPermissionDialogView* main_view,
const ParentPermissionDialogView::Params& params,
ParentPermissionSection(
ParentPermissionDialogView* main_view,
const std::vector<base::string16>& parent_permission_email_addresses,
int available_width)
: main_view_(main_view) {
const std::vector<base::string16>& parent_email_addresses =
params.parent_permission_email_addresses;
DCHECK_GT(parent_email_addresses.size(), 0u);
DCHECK_GT(parent_permission_email_addresses.size(), 0u);
auto view = std::make_unique<views::View>();
......@@ -85,7 +89,7 @@ class ParentPermissionSection : public views::TextfieldController {
ChromeLayoutProvider::Get()->GetDistanceMetric(
views::DISTANCE_RELATED_CONTROL_VERTICAL)));
if (parent_email_addresses.size() > 1) {
if (parent_permission_email_addresses.size() > 1) {
// If there is more than one parent listed, show radio buttons.
auto select_parent_label = std::make_unique<views::Label>(
l10n_util::GetStringUTF16(
......@@ -97,7 +101,7 @@ class ParentPermissionSection : public views::TextfieldController {
// Add first parent radio button
auto parent_0_radio_button = std::make_unique<views::RadioButton>(
base::string16(parent_email_addresses[0]), 1 /* group */);
base::string16(parent_permission_email_addresses[0]), 1 /* group */);
// Add a subscription
parent_0_subscription_ =
......@@ -107,7 +111,7 @@ class ParentPermissionSection : public views::TextfieldController {
main_view->set_selected_parent_permission_email_address(
parent_email);
},
main_view, parent_email_addresses[0]));
main_view, parent_permission_email_addresses[0]));
// Select parent 0 by default.
parent_0_radio_button->SetChecked(true);
......@@ -115,7 +119,7 @@ class ParentPermissionSection : public views::TextfieldController {
// Add second parent radio button.
auto parent_1_radio_button = std::make_unique<views::RadioButton>(
base::string16(parent_email_addresses[1]), 1 /* group */);
base::string16(parent_permission_email_addresses[1]), 1 /* group */);
parent_1_subscription_ =
parent_1_radio_button->AddCheckedChangedCallback(base::BindRepeating(
......@@ -124,17 +128,17 @@ class ParentPermissionSection : public views::TextfieldController {
main_view->set_selected_parent_permission_email_address(
parent_email);
},
main_view, parent_email_addresses[1]));
main_view, parent_permission_email_addresses[1]));
view->AddChildView(std::move(parent_1_radio_button));
// Default to first parent in the response.
main_view_->set_selected_parent_permission_email_address(
parent_email_addresses[0]);
parent_permission_email_addresses[0]);
} else {
// If there is just one parent, show a label with that parent's email.
auto parent_email_label = std::make_unique<views::Label>(
parent_email_addresses[0], CONTEXT_BODY_TEXT_LARGE,
parent_permission_email_addresses[0], CONTEXT_BODY_TEXT_LARGE,
views::style::STYLE_SECONDARY);
parent_email_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
parent_email_label->SetMultiLine(true);
......@@ -143,7 +147,7 @@ class ParentPermissionSection : public views::TextfieldController {
// Since there is only one parent, just set the output value of selected
// parent email address here..
main_view->set_selected_parent_permission_email_address(
parent_email_addresses[0]);
parent_permission_email_addresses[0]);
}
// Add the credential input field.
......@@ -195,16 +199,41 @@ class ParentPermissionSection : public views::TextfieldController {
ParentPermissionDialogView* main_view_;
};
// ParentPermissionDialogView::Params
struct ParentPermissionDialogView::Params {
Params();
explicit Params(const Params& params);
~Params();
// The icon to be displayed. Usage depends on whether extension is set.
gfx::ImageSkia icon;
// The message to show. Ignored if extension is set.
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;
// The web contents that initiated the dialog.
content::WebContents* web_contents = nullptr;
// The callback to call on completion.
ParentPermissionDialog::DoneCallback done_callback;
};
ParentPermissionDialogView::Params::Params() = default;
ParentPermissionDialogView::Params::Params(const Params& params) = default;
ParentPermissionDialogView::Params::~Params() = default;
// ParentPermissionDialogView
ParentPermissionDialogView::ParentPermissionDialogView(
std::unique_ptr<Params> params,
ParentPermissionDialogView::DoneCallback done_callback)
: params_(std::move(params)), done_callback_(std::move(done_callback)) {
ParentPermissionDialogView::Observer* observer)
: params_(std::move(params)), observer_(observer) {
DialogDelegate::SetDefaultButton(ui::DIALOG_BUTTON_OK);
DialogDelegate::set_draggable(true);
DialogDelegate::SetButtonLabel(
......@@ -213,9 +242,32 @@ ParentPermissionDialogView::ParentPermissionDialogView(
DialogDelegate::SetButtonLabel(
ui::DIALOG_BUTTON_CANCEL,
l10n_util::GetStringUTF16(IDS_PARENT_PERMISSION_PROMPT_CANCEL_BUTTON));
identity_manager_ = IdentityManagerFactory::GetForProfile(params_->profile);
}
ParentPermissionDialogView::~ParentPermissionDialogView() = default;
ParentPermissionDialogView::~ParentPermissionDialogView() {
// Let the observer know that this object is being destroyed.
if (observer_)
observer_->OnParentPermissionDialogViewDestroyed();
// If the object is being destroyed but the callback hasn't been run, then
// this is a failure case.
if (params_->done_callback) {
std::move(params_->done_callback)
.Run(ParentPermissionDialog::Result::kParentPermissionFailed);
}
}
void ParentPermissionDialogView::SetIdentityManagerForTesting(
signin::IdentityManager* identity_manager) {
identity_manager_ = identity_manager;
}
void ParentPermissionDialogView::SetRepromptAfterIncorrectCredential(
bool reprompt) {
reprompt_after_incorrect_credential_ = reprompt;
}
base::string16 ParentPermissionDialogView::GetActiveUserFirstName() const {
user_manager::UserManager* manager = user_manager::UserManager::Get();
......@@ -254,8 +306,8 @@ void ParentPermissionDialogView::AddedToWidget() {
layout->StartRow(views::GridLayout::kFixedSize, kTitleColumnSetId);
// Scale down to icon size, but allow smaller icons (don't scale up).
if (!params().icon.isNull()) {
const gfx::ImageSkia& image = params().icon;
if (!params_->icon.isNull()) {
const gfx::ImageSkia& image = params_->icon;
auto icon = std::make_unique<views::ImageView>();
gfx::Size size(image.width(), image.height());
size.SetToMin(gfx::Size(icon_size, icon_size));
......@@ -264,9 +316,9 @@ void ParentPermissionDialogView::AddedToWidget() {
layout->AddView(std::move(icon));
}
DCHECK(!params().message.empty());
DCHECK(!params_->message.empty());
std::unique_ptr<views::Label> message_label =
views::BubbleFrameView::CreateDefaultTitleLabel(params().message);
views::BubbleFrameView::CreateDefaultTitleLabel(params_->message);
// Setting the message's preferred size to 0 ensures it won't influence the
// overall size of the dialog. It will be expanded by GridLayout.
message_label->SetPreferredSize(gfx::Size(0, 0));
......@@ -276,27 +328,25 @@ void ParentPermissionDialogView::AddedToWidget() {
}
bool ParentPermissionDialogView::Cancel() {
// This can be called multiple times because ParentPermissionDialog
// calls a callback pointing to OnDialogCloseClosure(), and if this object
// still exists at that time, this method will get called again because
// Cancel() is called by default when the dialog is explicitly asked to close.
// Therefore, we null check the callback here before trying to use it.
if (!done_callback_)
return true;
internal::ParentPermissionDialogViewResult result;
result.status = internal::ParentPermissionDialogViewResult::Status::kCanceled;
std::move(done_callback_).Run(result);
SendResult(ParentPermissionDialog::Result::kParentPermissionCanceled);
return true;
}
bool ParentPermissionDialogView::Accept() {
internal::ParentPermissionDialogViewResult result;
result.status = internal::ParentPermissionDialogViewResult::Status::kAccepted;
result.parent_permission_credential = parent_permission_credential_;
result.selected_parent_permission_email = selected_parent_permission_email_;
std::move(done_callback_).Run(result);
return true;
// Disable the dialog temporarily while we validate the parent's credentials,
// which can take some time because it involves a series of async network
// requests.
SetEnabled(false);
// Clear out the invalid credential label, so that it disappears/reappears to
// the user to emphasize that the password check happened again.
invalid_credential_label_->SetText(base::string16());
std::string parent_obfuscated_gaia_id =
GetParentObfuscatedGaiaID(selected_parent_permission_email_);
std::string parent_credential =
base::UTF16ToUTF8(parent_permission_credential_);
StartReauthAccessTokenFetch(parent_obfuscated_gaia_id, parent_credential);
return false;
}
bool ParentPermissionDialogView::ShouldShowCloseButton() const {
......@@ -304,7 +354,7 @@ bool ParentPermissionDialogView::ShouldShowCloseButton() const {
}
base::string16 ParentPermissionDialogView::GetAccessibleWindowTitle() const {
return params().message;
return params_->message;
}
ui::ModalType ParentPermissionDialogView::GetModalType() const {
......@@ -327,17 +377,17 @@ void ParentPermissionDialogView::CreateContents() {
const int content_width =
GetPreferredSize().width() - section_container->GetInsets().width();
if (params().extension) {
if (params_->extension) {
// Set up the permissions view.
if (!prompt_permissions_.permissions.empty()) {
// Set up the permissions header string.
const extensions::Extension* extension = params().extension;
// Shouldn't be asking for permissions for theme installs.
DCHECK(!extension->is_theme());
DCHECK(!params_->extension->is_theme());
base::string16 extension_type;
if (extension->is_extension()) {
if (params_->extension->is_extension()) {
extension_type = l10n_util::GetStringUTF16(
IDS_PARENT_PERMISSION_PROMPT_EXTENSION_TYPE_EXTENSION);
} else if (extension->is_app()) {
} else if (params_->extension->is_app()) {
extension_type = l10n_util::GetStringUTF16(
IDS_PARENT_PERMISSION_PROMPT_EXTENSION_TYPE_APP);
}
......@@ -366,8 +416,9 @@ void ParentPermissionDialogView::CreateContents() {
// can be arbitrarily long.
section_container->AddChildView(std::move(permissions_view));
}
}
// Add permissions view to the enclosing scroll view.
// Add section container to the enclosing scroll view.
auto scroll_view = std::make_unique<views::ScrollView>();
scroll_view->SetHideHorizontalScrollBar(true);
scroll_view->SetContents(std::move(section_container));
......@@ -375,35 +426,52 @@ void ParentPermissionDialogView::CreateContents() {
0, provider->GetDistanceMetric(
views::DISTANCE_DIALOG_SCROLLABLE_AREA_MAX_HEIGHT));
AddChildView(std::move(scroll_view));
}
// Create the parent approval view, which adds itself
// Create the parent permission section, which adds itself
// to the main view.
parent_permission_section_ =
std::make_unique<ParentPermissionSection>(this, params(), content_width);
// Show the "password incorrect" label if needed.
if (params().show_parent_password_incorrect) {
auto password_incorrect_label = std::make_unique<views::Label>(
l10n_util::GetStringUTF16(
IDS_PARENT_PERMISSION_PROMPT_PASSWORD_INCORRECT_LABEL),
CONTEXT_BODY_TEXT_LARGE, views::style::STYLE_SECONDARY);
password_incorrect_label->SetBorder(views::CreateEmptyBorder(
parent_permission_section_ = std::make_unique<ParentPermissionSection>(
this, parent_permission_email_addresses_, content_width);
// Add the invalid credential label, which is initially empty,
// and hence invisible. It will be updated if the user enters
// an incorrect password.
auto invalid_credential_label = std::make_unique<views::Label>(
base::UTF8ToUTF16(""), CONTEXT_BODY_TEXT_LARGE,
views::style::STYLE_SECONDARY);
invalid_credential_label->SetBorder(views::CreateEmptyBorder(
0, content_insets.left(), 0, content_insets.right()));
password_incorrect_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
password_incorrect_label->SetMultiLine(true);
password_incorrect_label->SetEnabledColor(gfx::kGoogleRed500);
password_incorrect_label->SizeToFit(content_width);
AddChildView(std::move(password_incorrect_label));
}
invalid_credential_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
invalid_credential_label->SetMultiLine(true);
invalid_credential_label->SetEnabledColor(gfx::kGoogleRed500);
invalid_credential_label->SizeToFit(content_width);
// Cache the pointer so we we can update the invalid credential label when we
// get an incorrect password.
invalid_credential_label_ = invalid_credential_label.get();
AddChildView(std::move(invalid_credential_label));
}
void ParentPermissionDialogView::ShowDialog() {
if (params().extension)
InitializeExtensionData(params().extension);
if (is_showing_)
return;
is_showing_ = true;
LoadParentEmailAddresses();
if (params_->extension)
InitializeExtensionData(params_->extension);
else
ShowDialogInternal();
}
void ParentPermissionDialogView::CloseDialog() {
CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
void ParentPermissionDialogView::RemoveObserver() {
observer_ = nullptr;
}
void ParentPermissionDialogView::ShowDialogInternal() {
// The contents have to be created here, instead of during construction
// because they can potentially rely on the side effects of loading info
......@@ -411,34 +479,183 @@ void ParentPermissionDialogView::ShowDialogInternal() {
CreateContents();
chrome::RecordDialogCreation(chrome::DialogIdentifier::PARENT_PERMISSION);
views::Widget* widget =
constrained_window::CreateBrowserModalDialogViews(this, params().window);
constrained_window::CreateBrowserModalDialogViews(this, params_->window);
widget->Show();
// If we are in a test, auto-confirm the dialog since we can't click
// on it directly.
if (auto_confirm_dialog_for_test) {
widget_for_test = widget;
switch (auto_confirm_status_for_test) {
case internal::ParentPermissionDialogViewResult::Status::kCanceled:
CancelDialog();
break;
case internal::ParentPermissionDialogViewResult::Status::kAccepted:
AcceptDialog();
break;
default:
NOTREACHED();
break;
if (test_view_observer)
test_view_observer->OnTestParentPermissionDialogViewCreated(this);
}
void ParentPermissionDialogView::LoadParentEmailAddresses() {
// Get the parents' email addresses. There can be a max of 2 parent email
// addresses, the primary and the secondary.
SupervisedUserService* service =
SupervisedUserServiceFactory::GetForProfile(params_->profile);
base::string16 primary_parent_email =
base::UTF8ToUTF16(service->GetCustodianEmailAddress());
if (!primary_parent_email.empty())
parent_permission_email_addresses_.push_back(primary_parent_email);
base::string16 secondary_parent_email =
base::UTF8ToUTF16(service->GetSecondCustodianEmailAddress());
if (!secondary_parent_email.empty())
parent_permission_email_addresses_.push_back(secondary_parent_email);
if (parent_permission_email_addresses_.empty()) {
// TODO(danan): Add UMA stat for this failure.
// https://crbug.com/1049418
SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
}
}
void ParentPermissionDialogView::OnExtensionIconLoaded(
const gfx::Image& image) {
// The order of preference for the icon to use is:
// 1. Icon loaded from extension, if not empty.
// 2. Icon passed in params, if not empty.
// 3. Default Icon.
if (!image.IsEmpty()) {
// Use the image that was loaded from the extension if it's not empty
params_->icon = *image.ToImageSkia();
} else if (params_->icon.isNull()) {
// If icon is empty, use a default icon.:
params_->icon =
GetDefaultIconBitmapForMaxScaleFactor(params_->extension->is_app());
}
ShowDialogInternal();
}
void ParentPermissionDialogView::LoadExtensionIcon() {
DCHECK(params_->extension);
extensions::ExtensionResource image = extensions::IconsInfo::GetIconResource(
params_->extension, extension_misc::EXTENSION_ICON_LARGE,
ExtensionIconSet::MATCH_BIGGER);
// Load the image asynchronously. The response will be sent to
// OnExtensionIconLoaded.
extensions::ImageLoader* loader =
extensions::ImageLoader::Get(params_->profile);
std::vector<extensions::ImageLoader::ImageRepresentation> images_list;
images_list.push_back(extensions::ImageLoader::ImageRepresentation(
image, extensions::ImageLoader::ImageRepresentation::NEVER_RESIZE,
gfx::Size(),
ui::GetScaleFactorForNativeView(params_->web_contents->GetNativeView())));
loader->LoadImagesAsync(
params_->extension, images_list,
base::BindOnce(&ParentPermissionDialogView::OnExtensionIconLoaded,
weak_factory_.GetWeakPtr()));
}
void ParentPermissionDialogView::CloseDialogView() {
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
void ParentPermissionDialogView::CloseWithReason(
views::Widget::ClosedReason reason) {
views::Widget* widget = GetWidget();
if (widget) {
widget->CloseWithReason(reason);
} else {
// The widget has disappeared, so delete this view.
delete this;
}
}
base::OnceClosure ParentPermissionDialogView::GetCloseDialogClosure() {
return base::BindOnce(&ParentPermissionDialogView::CloseDialogView,
weak_factory_.GetWeakPtr());
std::string ParentPermissionDialogView::GetParentObfuscatedGaiaID(
const base::string16& parent_email) const {
SupervisedUserService* service =
SupervisedUserServiceFactory::GetForProfile(params_->profile);
if (service->GetCustodianEmailAddress() == base::UTF16ToUTF8(parent_email))
return service->GetCustodianObfuscatedGaiaId();
if (service->GetSecondCustodianEmailAddress() ==
base::UTF16ToUTF8(parent_email)) {
return service->GetSecondCustodianObfuscatedGaiaId();
}
NOTREACHED()
<< "Tried to get obfuscated gaia id for a non-custodian email address";
return std::string();
}
void ParentPermissionDialogView::StartReauthAccessTokenFetch(
const std::string& parent_obfuscated_gaia_id,
const std::string& parent_credential) {
// The first step of Reauth is to fetch an OAuth2 access token for the
// Reauth API scope.
signin::ScopeSet scopes;
scopes.insert(GaiaConstants::kAccountsReauthOAuth2Scope);
oauth2_access_token_fetcher_ =
identity_manager_->CreateAccessTokenFetcherForAccount(
identity_manager_->GetPrimaryAccountId(),
"chrome_webstore_private_api", scopes,
base::BindOnce(
&ParentPermissionDialogView::OnAccessTokenFetchComplete,
weak_factory_.GetWeakPtr(), parent_obfuscated_gaia_id,
parent_credential),
signin::AccessTokenFetcher::Mode::kImmediate);
}
void ParentPermissionDialogView::OnAccessTokenFetchComplete(
const std::string& parent_obfuscated_gaia_id,
const std::string& parent_credential,
GoogleServiceAuthError error,
signin::AccessTokenInfo access_token_info) {
oauth2_access_token_fetcher_.reset();
if (error.state() != GoogleServiceAuthError::NONE) {
SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
CloseWithReason(views::Widget::ClosedReason::kUnspecified);
return;
}
// Now that we have the OAuth2 access token, we use it when we attempt
// to fetch the ReauthProof token (RAPT) for the parent.
StartParentReauthProofTokenFetch(
access_token_info.token, parent_obfuscated_gaia_id, parent_credential);
}
void ParentPermissionDialogView::StartParentReauthProofTokenFetch(
const std::string& child_access_token,
const std::string& parent_obfuscated_gaia_id,
const std::string& credential) {
reauth_token_fetcher_ = std::make_unique<GaiaAuthFetcher>(
this, gaia::GaiaSource::kChromeOS,
params_->profile->GetURLLoaderFactory());
reauth_token_fetcher_->StartCreateReAuthProofTokenForParent(
child_access_token, parent_obfuscated_gaia_id, credential);
}
void ParentPermissionDialogView::SendResult(
ParentPermissionDialog::Result result) {
if (!params_->done_callback)
return;
std::move(params_->done_callback).Run(result);
}
void ParentPermissionDialogView::OnReAuthProofTokenSuccess(
const std::string& reauth_proof_token) {
SendResult(ParentPermissionDialog::Result::kParentPermissionReceived);
CloseWithReason(views::Widget::ClosedReason::kAcceptButtonClicked);
}
void ParentPermissionDialogView::OnReAuthProofTokenFailure(
const GaiaAuthConsumer::ReAuthProofTokenStatus error) {
reauth_token_fetcher_.reset();
if (error == GaiaAuthConsumer::ReAuthProofTokenStatus::kInvalidGrant) {
// If invalid password was entered, and the dialog is configured to
// re-prompt show the dialog again with the invalid password error message.
// prompt again, this time with a password error message.
invalid_credential_received_ = true;
if (reprompt_after_incorrect_credential_) {
SetEnabled(true);
invalid_credential_label_->SetText(l10n_util::GetStringUTF16(
IDS_PARENT_PERMISSION_PROMPT_PASSWORD_INCORRECT_LABEL));
return;
}
}
SendResult(ParentPermissionDialog::Result::kParentPermissionFailed);
CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
void ParentPermissionDialogView::InitializeExtensionData(
......@@ -448,41 +665,114 @@ void ParentPermissionDialogView::InitializeExtensionData(
// Load Permissions.
std::unique_ptr<const extensions::PermissionSet> permissions_to_display =
extensions::util::GetInstallPromptPermissionSetForExtension(
extension.get(), params().profile,
extension.get(), params_->profile,
true /* include_optional_permissions */);
extensions::Manifest::Type type = extension->GetType();
prompt_permissions_.LoadFromPermissionSet(permissions_to_display.get(), type);
// Create the dialog's message using the extension's name.
params_->message = l10n_util::GetStringFUTF16(
IDS_PARENT_PERMISSION_PROMPT_GO_GET_A_PARENT_FOR_EXTENSION_LABEL,
base::UTF8ToUTF16(extension->name()));
LoadExtensionIcon();
}
class ParentPermissionDialogImpl : public ParentPermissionDialog,
public ParentPermissionDialogView::Observer {
public:
// Constructor for a generic ParentPermissionDialogImpl
ParentPermissionDialogImpl(
std::unique_ptr<ParentPermissionDialogView::Params> params);
~ParentPermissionDialogImpl() override;
// ParentPermissionDialog
void ShowDialog() override;
// ParentPermissionDialogView::Observer
void OnParentPermissionDialogViewDestroyed() override;
private:
ParentPermissionDialogView* view_ = nullptr;
};
ParentPermissionDialogImpl::ParentPermissionDialogImpl(
std::unique_ptr<ParentPermissionDialogView::Params> params)
: view_(new ParentPermissionDialogView(std::move(params), this)) {}
void ParentPermissionDialogImpl::ShowDialog() {
// Ownership of dialog_view is passed to the views system when the dialog is
// shown here. We check for the validity of view_ because in theory it could
// disappear from beneath this object before ShowDialog() is called.
if (view_)
view_->ShowDialog();
}
ParentPermissionDialogImpl::~ParentPermissionDialogImpl() {
// We check for the validity of view_ because in theory it could
// disappear from beneath this object before ShowDialog() is called.
if (view_) {
// Important to remove the observer here, so that we don't try to use it in
// the destructor to inform the ParentPermissionDialog, which would cause a
// use-after-free.
view_->RemoveObserver();
view_->CloseDialog();
}
}
base::OnceClosure ShowParentPermissionDialog(
void ParentPermissionDialogImpl::OnParentPermissionDialogViewDestroyed() {
// The underlying ParentPermissionDialogView has been destroyed.
view_ = nullptr;
}
// static
std::unique_ptr<ParentPermissionDialog>
ParentPermissionDialog::CreateParentPermissionDialog(
Profile* profile,
content::WebContents* web_contents,
gfx::NativeWindow window,
const std::vector<base::string16>& parent_permission_email_addresses,
bool show_parent_password_incorrect,
const gfx::ImageSkia& icon,
const base::string16& message,
ParentPermissionDialog::DoneCallback done_callback) {
auto params = std::make_unique<ParentPermissionDialogView::Params>();
params->message = message;
params->icon = icon;
params->profile = profile;
params->web_contents = web_contents;
params->window = window;
params->done_callback = std::move(done_callback);
return std::make_unique<ParentPermissionDialogImpl>(std::move(params));
}
// static
std::unique_ptr<ParentPermissionDialog>
ParentPermissionDialog::CreateParentPermissionDialogForExtension(
Profile* profile,
content::WebContents* web_contents,
gfx::NativeWindow window,
const gfx::ImageSkia& icon,
const extensions::Extension* extension,
base::OnceCallback<void(internal::ParentPermissionDialogViewResult result)>
view_done_callback) {
ParentPermissionDialog::DoneCallback done_callback) {
auto params = std::make_unique<ParentPermissionDialogView::Params>();
params->parent_permission_email_addresses = parent_permission_email_addresses;
params->show_parent_password_incorrect = show_parent_password_incorrect;
params->extension = extension;
params->message = message;
params->icon = icon;
params->profile = profile;
params->web_contents = web_contents;
params->window = window;
params->done_callback = std::move(done_callback);
ParentPermissionDialogView* dialog_view = new ParentPermissionDialogView(
std::move(params), std::move(view_done_callback));
return std::make_unique<ParentPermissionDialogImpl>(std::move(params));
}
// Ownership of dialog_view is passed to the views system when the dialog is
// shown.
dialog_view->ShowDialog();
TestParentPermissionDialogViewObserver::TestParentPermissionDialogViewObserver(
TestParentPermissionDialogViewObserver* observer) {
DCHECK(!test_view_observer);
test_view_observer = observer;
}
return dialog_view->GetCloseDialogClosure();
TestParentPermissionDialogViewObserver::
~TestParentPermissionDialogViewObserver() {
test_view_observer = nullptr;
}
......@@ -11,59 +11,47 @@
#include "base/macros.h"
#include "chrome/browser/extensions/install_prompt_permissions.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/native_widget_types.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/view.h"
class Profile;
class GaiaAuthFetcher;
namespace extensions {
class Extension;
} // namespace extensions
namespace signin {
class AccessTokenFetcher;
struct AccessTokenInfo;
class IdentityManager;
} // namespace signin
namespace views {
class Label;
}
class ParentPermissionSection;
// Modal dialog that shows a dialog that prompts a parent for permission by
// asking them to enter their google account credentials.
class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
// asking them to enter their google account credentials. This is created only
// when the dialog is ready to be shown (after the state has been
// asynchronously fetched).
class ParentPermissionDialogView : public views::BubbleDialogDelegateView,
public GaiaAuthConsumer {
public:
using DoneCallback = base::OnceCallback<void(
internal::ParentPermissionDialogViewResult result)>;
struct Params {
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;
class Observer {
public:
// Tells observers that their references to the view are becoming invalid.
virtual void OnParentPermissionDialogViewDestroyed() = 0;
};
struct Params;
ParentPermissionDialogView(std::unique_ptr<Params> params,
DoneCallback done_callback);
Observer* observer);
~ParentPermissionDialogView() override;
......@@ -71,9 +59,15 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
ParentPermissionDialogView& operator=(const ParentPermissionDialogView&) =
delete;
// Closes the dialog.
void CloseDialog();
// Shows the parent permission dialog.
void ShowDialog();
// Removes the observer reference.
void RemoveObserver();
void set_selected_parent_permission_email_address(
const base::string16& email_address) {
selected_parent_permission_email_ = email_address;
......@@ -82,24 +76,19 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
void set_parent_permission_credential(const base::string16& credential) {
parent_permission_credential_ = credential;
}
void CloseDialogView();
// TODO(https://crbug.com/1058501): Instead of doing this with closures, use
// an interface shared with the views code whose implementation wraps the
// underlying view, and delete the instance of that interface in the dtor of
// this class.
base::OnceClosure GetCloseDialogClosure();
bool invalid_credential_received() { return invalid_credential_received_; }
void SetIdentityManagerForTesting(signin::IdentityManager* identity_manager);
void SetRepromptAfterIncorrectCredential(bool reprompt);
private:
const Params& params() const { return *params_; }
base::string16 GetActiveUserFirstName() const;
// views::View:
gfx::Size CalculatePreferredSize() const override;
void AddedToWidget() override;
// views::DialogDeleate:
// views::DialogDelegate:
bool Cancel() override;
bool Accept() override;
......@@ -117,6 +106,41 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
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
// causes the view to show several extension properties including the
// permissions, the icon and the extension name.
......@@ -127,17 +151,52 @@ class ParentPermissionDialogView : public views::BubbleDialogDelegateView {
// if an extension has been set.
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_;
views::Label* invalid_credential_label_ = nullptr;
bool invalid_credential_received_ = false;
// The currently selected parent email.
base::string16 selected_parent_permission_email_;
// The currently entered parent credential.
base::string16 parent_permission_credential_;
// Configuration parameters for the prompt.
// Parameters for the dialog.
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};
};
// 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_
// 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) {
"../browser/ui/ash/tablet_mode_page_behavior_browsertest.cc",
"../browser/ui/ash/volume_controller_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_uninstall_dialog_view_browsertest.cc",
"../browser/ui/views/apps/chrome_native_app_window_views_aura_ash_browsertest.cc",
......@@ -2416,7 +2415,7 @@ if (!is_android) {
"../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/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/web_apps/web_app_ash_interactive_ui_test.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