blob: ff3854b09f65b8ae53d1338b7129124a45717362 [file] [log] [blame]
// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "ash/child_accounts/parent_access_controller_impl.h"
#include <string>
#include "ash/login/mock_login_screen_client.h"
#include "ash/login/ui/login_button.h"
#include "ash/login/ui/login_test_base.h"
#include "ash/login/ui/pin_request_view.h"
#include "ash/login/ui/pin_request_widget.h"
#include "ash/login/ui/views_utils.h"
#include "ash/public/cpp/child_accounts/parent_access_controller.h"
#include "base/dcheck_is_on.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/time/time.h"
#include "components/account_id/account_id.h"
#include "components/session_manager/session_manager_types.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/controls/button/label_button.h"
#include "ui/views/test/button_test_api.h"
namespace ash {
namespace {
using ::testing::_;
AccountId GetChildAccountId() {
return AccountId::FromUserEmail("child@gmail.com");
}
class ParentAccessControllerImplTest : public LoginTestBase {
public:
ParentAccessControllerImplTest(const ParentAccessControllerImplTest&) =
delete;
ParentAccessControllerImplTest& operator=(
const ParentAccessControllerImplTest&) = delete;
protected:
ParentAccessControllerImplTest() : account_id_(GetChildAccountId()) {}
~ParentAccessControllerImplTest() override = default;
// LoginScreenTest:
void SetUp() override {
LoginTestBase::SetUp();
login_client_ = std::make_unique<MockLoginScreenClient>();
}
void TearDown() override {
LoginTestBase::TearDown();
// If the test did not explicitly dismissed the widget, destroy it now.
PinRequestWidget* pin_request_widget = PinRequestWidget::Get();
if (pin_request_widget)
pin_request_widget->Close(false /* validation success */);
}
// Simulates mouse press event on a |button|.
void SimulateButtonPress(views::Button* button) {
ui::MouseEvent event(ui::ET_MOUSE_PRESSED, gfx::Point(), gfx::Point(),
ui::EventTimeForNow(), 0, 0);
views::test::ButtonTestApi(button).NotifyClick(event);
}
// Called when ParentAccessView finished processing.
void OnFinished(bool access_granted) {
access_granted ? ++successful_validation_ : ++back_action_;
}
// Starts parent access validation.
// Use this overloaded method if session state and supervised action are not
// relevant.
void StartParentAccess() {
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED);
StartParentAccess(account_id_, SupervisedAction::kUnlockTimeLimits);
}
// Starts parent access validation with supervised |action|.
// Session state should be configured accordingly to the |action|.
void StartParentAccess(SupervisedAction action) {
StartParentAccess(account_id_, action);
}
// Starts parent access validation with supervised |action| and |account_id|.
// Session state should be configured accordingly to the |action|.
void StartParentAccess(const AccountId& account_id, SupervisedAction action) {
validation_time_ = base::Time::Now();
ash::ParentAccessController::Get()->ShowWidget(
account_id,
base::BindOnce(&ParentAccessControllerImplTest::OnFinished,
base::Unretained(this)),
action, false, validation_time_);
view_ =
PinRequestWidget::TestApi(PinRequestWidget::Get()).pin_request_view();
}
// Verifies expectation that UMA |action| was logged.
void ExpectUMAActionReported(ParentAccessControllerImpl::UMAAction action,
int bucket_count,
int total_count) {
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeAction, action,
bucket_count);
histogram_tester_.ExpectTotalCount(
ParentAccessControllerImpl::kUMAParentAccessCodeAction, total_count);
}
// Verifies expectation that UMA validation |result| was logged for the
// |action| and into the aggregated histogram.
void ExpectUMAValidationResultReported(
ParentAccessControllerImpl::UMAValidationResult result,
SupervisedAction action,
int bucket_count,
int total_count) {
const std::string action_result_histogram =
ParentAccessControllerImpl::GetUMAParentCodeValidationResultHistorgam(
action);
histogram_tester_.ExpectBucketCount(action_result_histogram, result,
bucket_count);
histogram_tester_.ExpectTotalCount(action_result_histogram, total_count);
const std::string all_results_histogram =
ParentAccessControllerImpl::GetUMAParentCodeValidationResultHistorgam(
std::nullopt);
histogram_tester_.ExpectBucketCount(all_results_histogram, result,
bucket_count);
histogram_tester_.ExpectTotalCount(all_results_histogram, total_count);
}
// Simulates entering a code. |success| determines whether the code will be
// accepted.
void SimulateValidation(ParentCodeValidationResult result) {
login_client_->set_validate_parent_access_code_result(result);
EXPECT_CALL(*login_client_, ValidateParentAccessCode_(account_id_, "012345",
validation_time_))
.Times(1);
ui::test::EventGenerator* generator = GetEventGenerator();
for (int i = 0; i < 6; ++i) {
generator->PressKey(ui::KeyboardCode(ui::KeyboardCode::VKEY_0 + i),
ui::EF_NONE);
base::RunLoop().RunUntilIdle();
}
}
const AccountId account_id_;
std::unique_ptr<MockLoginScreenClient> login_client_;
// Number of times the view was dismissed with back button.
int back_action_ = 0;
// Number of times the view was dismissed after successful validation.
int successful_validation_ = 0;
// Time that will be used on the code validation.
base::Time validation_time_;
base::HistogramTester histogram_tester_;
raw_ptr<PinRequestView, DanglingUntriaged> view_ =
nullptr; // Owned by test widget view hierarchy.
};
// Tests parent access dialog showing/hiding and focus behavior for parent
// access.
TEST_F(ParentAccessControllerImplTest, ParentAccessDialogFocus) {
EXPECT_FALSE(PinRequestWidget::Get());
StartParentAccess();
PinRequestView::TestApi view_test_api = PinRequestView::TestApi(view_);
ASSERT_TRUE(PinRequestWidget::Get());
EXPECT_TRUE(login_views_utils::HasFocusInAnyChildView(
view_test_api.access_code_view()));
PinRequestWidget::Get()->Close(false /* validation success */);
EXPECT_FALSE(PinRequestWidget::Get());
}
// Tests correct UMA reporting for parent access.
TEST_F(ParentAccessControllerImplTest, ParentAccessUMARecording) {
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOCKED);
StartParentAccess(SupervisedAction::kUnlockTimeLimits);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kTimeLimits, 1);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 1, 1);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
StartParentAccess(SupervisedAction::kUpdateTimezone);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kTimezoneChange, 1);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 2, 2);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
StartParentAccess(SupervisedAction::kUpdateClock);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kTimeChangeInSession, 1);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 3, 3);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
StartParentAccess(EmptyAccountId(), SupervisedAction::kUpdateClock);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kTimeChangeLoginScreen, 1);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 4, 4);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::ACTIVE);
StartParentAccess(SupervisedAction::kUpdateClock);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kTimeChangeInSession, 2);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 5, 5);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
StartParentAccess(EmptyAccountId(), SupervisedAction::kReauth);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kReauhLoginScreen, 1);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 6, 6);
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
StartParentAccess(EmptyAccountId(), SupervisedAction::kAddUser);
histogram_tester_.ExpectBucketCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage,
ParentAccessControllerImpl::UMAUsage::kAddUserLoginScreen, 1);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 7, 7);
histogram_tester_.ExpectTotalCount(
ParentAccessControllerImpl::kUMAParentAccessCodeUsage, 7);
EXPECT_EQ(7, back_action_);
}
// Tests successful parent access validation flow.
TEST_F(ParentAccessControllerImplTest, ParentAccessSuccessfulValidation) {
StartParentAccess();
SimulateValidation(ParentCodeValidationResult::kValid);
EXPECT_EQ(1, successful_validation_);
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kValidationSuccess, 1, 1);
ExpectUMAValidationResultReported(
ParentAccessControllerImpl::UMAValidationResult::kValid,
SupervisedAction::kUnlockTimeLimits, 1, 1);
}
// Tests unsuccessful parent access flow, including help button and cancelling
// the request.
TEST_F(ParentAccessControllerImplTest, ParentAccessUnsuccessfulValidation) {
StartParentAccess();
SimulateValidation(ParentCodeValidationResult::kInvalid);
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kValidationError, 1, 1);
ExpectUMAValidationResultReported(
ParentAccessControllerImpl::UMAValidationResult::kInvalid,
SupervisedAction::kUnlockTimeLimits, 1, 1);
EXPECT_CALL(*login_client_, ShowParentAccessHelpApp()).Times(1);
SimulateButtonPress(PinRequestView::TestApi(view_).help_button());
ExpectUMAActionReported(ParentAccessControllerImpl::UMAAction::kGetHelp, 1,
2);
SimulateButtonPress(PinRequestView::TestApi(view_).back_button());
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kCanceledByUser, 1, 3);
}
TEST_F(ParentAccessControllerImplTest, ParentAccessNoConfig) {
StartParentAccess();
SimulateValidation(ParentCodeValidationResult::kNoConfig);
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kValidationError, 1, 1);
ExpectUMAValidationResultReported(
ParentAccessControllerImpl::UMAValidationResult::kNoConfig,
SupervisedAction::kUnlockTimeLimits, 1, 1);
}
TEST_F(ParentAccessControllerImplTest, ParentAccessInternalError) {
StartParentAccess();
SimulateValidation(ParentCodeValidationResult::kInternalError);
ExpectUMAActionReported(
ParentAccessControllerImpl::UMAAction::kValidationError, 1, 1);
ExpectUMAValidationResultReported(
ParentAccessControllerImpl::UMAValidationResult::kInternalError,
SupervisedAction::kUnlockTimeLimits, 1, 1);
}
#if DCHECK_IS_ON()
// Tests that on login screen we check parent access code against all accounts.
TEST_F(ParentAccessControllerImplTest, EnforceNoAccountSpecifiedOnLogin) {
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
EXPECT_DEATH_IF_SUPPORTED(
StartParentAccess(GetChildAccountId(), SupervisedAction::kReauth), "");
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
EXPECT_DEATH_IF_SUPPORTED(
StartParentAccess(GetChildAccountId(), SupervisedAction::kAddUser), "");
GetSessionControllerClient()->SetSessionState(
session_manager::SessionState::LOGIN_PRIMARY);
EXPECT_DEATH_IF_SUPPORTED(
StartParentAccess(GetChildAccountId(), SupervisedAction::kUpdateClock),
"");
}
#endif
} // namespace
} // namespace ash