chromium/chrome/browser/ui/webui/ash/settings/pages/people/fingerprint_handler.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/ui/webui/ash/settings/pages/people/fingerprint_handler.h"

#include <algorithm>
#include <memory>

#include "ash/constants/ash_features.h"
#include "ash/constants/ash_pref_names.h"
#include "base/containers/contains.h"
#include "base/functional/bind.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "base/values.h"
#include "chrome/browser/ash/login/quick_unlock/auth_token.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_factory.h"
#include "chrome/browser/ash/login/quick_unlock/quick_unlock_storage.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/grit/generated_resources.h"
#include "chromeos/ash/components/osauth/public/auth_session_storage.h"
#include "components/prefs/pref_service.h"
#include "content/public/browser/device_service.h"
#include "ui/base/l10n/l10n_util.h"

// Enable VLOG level 1.
#undef ENABLED_VLOG_LEVEL
#define ENABLED_VLOG_LEVEL 1

using session_manager::SessionManager;
using session_manager::SessionState;

namespace ash::settings {
namespace {

// The max number of fingerprints that can be stored.
constexpr int kMaxAllowedFingerprints = 3;

base::Value::Dict GetFingerprintsInfo(
    const std::vector<std::string>& fingerprints_list) {
  base::Value::Dict response;
  base::Value::List fingerprints;

  DCHECK_LE(static_cast<int>(fingerprints_list.size()),
            kMaxAllowedFingerprints);
  for (auto& fingerprint_name : fingerprints_list) {
    fingerprints.Append(fingerprint_name);
  }

  response.Set("fingerprintsList", std::move(fingerprints));
  response.Set("isMaxed", static_cast<int>(fingerprints_list.size()) >=
                              kMaxAllowedFingerprints);
  return response;
}

}  // namespace

FingerprintHandler::FingerprintHandler(Profile* profile) : profile_(profile) {
  content::GetDeviceService().BindFingerprint(
      fp_service_.BindNewPipeAndPassReceiver());
  user_id_ = ProfileHelper::Get()->GetUserIdHashFromProfile(profile);
}

FingerprintHandler::~FingerprintHandler() {}

void FingerprintHandler::RegisterMessages() {
  // Note: getFingerprintsList must be called before observers will be added.
  web_ui()->RegisterMessageCallback(
      "getFingerprintsList",
      base::BindRepeating(&FingerprintHandler::HandleGetFingerprintsList,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getNumFingerprints",
      base::BindRepeating(&FingerprintHandler::HandleGetNumFingerprints,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "startEnroll", base::BindRepeating(&FingerprintHandler::HandleStartEnroll,
                                         base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "cancelCurrentEnroll",
      base::BindRepeating(&FingerprintHandler::HandleCancelCurrentEnroll,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "getEnrollmentLabel",
      base::BindRepeating(&FingerprintHandler::HandleGetEnrollmentLabel,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "removeEnrollment",
      base::BindRepeating(&FingerprintHandler::HandleRemoveEnrollment,
                          base::Unretained(this)));
  web_ui()->RegisterMessageCallback(
      "changeEnrollmentLabel",
      base::BindRepeating(&FingerprintHandler::HandleChangeEnrollmentLabel,
                          base::Unretained(this)));
}

void FingerprintHandler::OnJavascriptAllowed() {
  // SessionManager may not exist in some tests.
  if (SessionManager::Get()) {
    session_observation_.Observe(SessionManager::Get());
  }

  fp_service_->AddFingerprintObserver(receiver_.BindNewPipeAndPassRemote());
}

void FingerprintHandler::OnJavascriptDisallowed() {
  session_observation_.Reset();
  receiver_.reset();
}

void FingerprintHandler::OnRestarted() {}

void FingerprintHandler::OnStatusChanged(
    device::mojom::BiometricsManagerStatus status) {}

void FingerprintHandler::OnEnrollScanDone(device::mojom::ScanResult scan_result,
                                          bool enroll_session_complete,
                                          int percent_complete) {
  VLOG(1) << "Receive fingerprint enroll scan result. scan_result="
          << scan_result
          << ", enroll_session_complete=" << enroll_session_complete
          << ", percent_complete=" << percent_complete;
  base::Value::Dict scan_attempt;
  scan_attempt.Set("result", static_cast<int>(scan_result));
  scan_attempt.Set("isComplete", enroll_session_complete);
  scan_attempt.Set("percentComplete", percent_complete);

  FireWebUIListener("on-fingerprint-scan-received", scan_attempt);
}

void FingerprintHandler::OnAuthScanDone(
    const device::mojom::FingerprintMessagePtr msg,
    const base::flat_map<std::string, std::vector<std::string>>& matches) {}

void FingerprintHandler::OnSessionFailed() {
  LOG(ERROR) << "Fingerprint session failed.";
}

void FingerprintHandler::OnSessionStateChanged() {
  TRACE_EVENT0("ui", "FingerprintHandler::OnSessionStateChanged");
  SessionState state = SessionManager::Get()->session_state();

  FireWebUIListener("on-screen-locked",
                    base::Value(state == SessionState::LOCKED));
}

void FingerprintHandler::HandleGetFingerprintsList(
    const base::Value::List& args) {
  CHECK_EQ(1U, args.size());
  const std::string& callback_id = args[0].GetString();

  AllowJavascript();
  fp_service_->GetRecordsForUser(
      user_id_, base::BindOnce(&FingerprintHandler::OnGetFingerprintsList,
                               weak_ptr_factory_.GetWeakPtr(), callback_id));
}

void FingerprintHandler::OnGetFingerprintsList(
    const std::string& callback_id,
    const base::flat_map<std::string, std::string>& fingerprints_list_mapping,
    bool success) {
  if (!success) {
    LOG(ERROR) << "OnGetFingerprintsList failed";
    return;
  }
  fingerprints_labels_.clear();
  fingerprints_paths_.clear();
  for (auto it = fingerprints_list_mapping.begin();
       it != fingerprints_list_mapping.end(); ++it) {
    fingerprints_paths_.push_back(it->first);
    fingerprints_labels_.push_back(it->second);
  }

  profile_->GetPrefs()->SetInteger(prefs::kQuickUnlockFingerprintRecord,
                                   fingerprints_list_mapping.size());

  ResolveJavascriptCallback(base::Value(callback_id),
                            GetFingerprintsInfo(fingerprints_labels_));
}

void FingerprintHandler::HandleGetNumFingerprints(
    const base::Value::List& args) {
  CHECK_EQ(1U, args.size());
  const std::string& callback_id = args[0].GetString();

  int fingerprints_num =
      profile_->GetPrefs()->GetInteger(prefs::kQuickUnlockFingerprintRecord);

  AllowJavascript();
  ResolveJavascriptCallback(base::Value(callback_id),
                            base::Value(fingerprints_num));
}

void FingerprintHandler::HandleStartEnroll(const base::Value::List& args) {
  AllowJavascript();

  const std::string& auth_token = args[0].GetString();

  // Auth token expiration will trigger password prompt.
  // Silently fail if auth token is incorrect.
  if (!CheckAuthTokenValidity(auth_token)) {
    return;
  }

  // Determines what the newly added fingerprint's name should be.
  for (int i = 1; i <= kMaxAllowedFingerprints; ++i) {
    std::string fingerprint_name = l10n_util::GetStringFUTF8(
        IDS_SETTINGS_PEOPLE_LOCK_SCREEN_NEW_FINGERPRINT_DEFAULT_NAME,
        base::NumberToString16(i));
    if (!base::Contains(fingerprints_labels_, fingerprint_name)) {
      fp_service_->StartEnrollSession(user_id_, fingerprint_name);
      break;
    }
  }
}

void FingerprintHandler::HandleCancelCurrentEnroll(
    const base::Value::List& args) {
  AllowJavascript();
  fp_service_->CancelCurrentEnrollSession(
      base::BindOnce(&FingerprintHandler::OnCancelCurrentEnrollSession,
                     weak_ptr_factory_.GetWeakPtr()));
}

void FingerprintHandler::OnCancelCurrentEnrollSession(bool success) {
  if (!success) {
    LOG(ERROR) << "Failed to cancel current fingerprint enroll session.";
  }
}

void FingerprintHandler::HandleGetEnrollmentLabel(
    const base::Value::List& args) {
  const auto& list = args;
  CHECK_EQ(2U, list.size());
  std::string callback_id = list[0].GetString();
  int index = list[1].GetInt();
  CHECK_GE(index, 0);
  CHECK_LT(index, static_cast<int>(fingerprints_paths_.size()));

  AllowJavascript();
  fp_service_->RequestRecordLabel(
      fingerprints_paths_[index],
      base::BindOnce(&FingerprintHandler::OnRequestRecordLabel,
                     weak_ptr_factory_.GetWeakPtr(), callback_id));
}

void FingerprintHandler::OnRequestRecordLabel(const std::string& callback_id,
                                              const std::string& label) {
  ResolveJavascriptCallback(base::Value(callback_id), base::Value(label));
}

void FingerprintHandler::HandleRemoveEnrollment(const base::Value::List& args) {
  const auto& list = args;
  // TODO(b/261412646): add unit tests to this class
  CHECK_EQ(3U, list.size());
  std::string callback_id = list[0].GetString();
  const std::string& auth_token = list[2].GetString();
  int index = list[1].GetInt();
  CHECK_GE(index, 0);
  CHECK_LT(index, static_cast<int>(fingerprints_paths_.size()));

  // Silently fail if auth token is incorrect.
  if (!CheckAuthTokenValidity(auth_token)) {
    return;
  }

  AllowJavascript();
  fp_service_->RemoveRecord(
      fingerprints_paths_[index],
      base::BindOnce(&FingerprintHandler::OnRemoveRecord,
                     weak_ptr_factory_.GetWeakPtr(), callback_id));
}

void FingerprintHandler::OnRemoveRecord(const std::string& callback_id,
                                        bool success) {
  if (!success) {
    LOG(ERROR) << "Failed to remove fingerprint record.";
  }
  ResolveJavascriptCallback(base::Value(callback_id), base::Value(success));
}

void FingerprintHandler::HandleChangeEnrollmentLabel(
    const base::Value::List& args) {
  const auto& list = args;
  CHECK_EQ(3U, list.size());

  std::string callback_id = list[0].GetString();
  int index = list[1].GetInt();
  CHECK_GE(index, 0);
  CHECK_LT(index, static_cast<int>(fingerprints_paths_.size()));

  std::string new_label = list[2].GetString();

  AllowJavascript();
  fp_service_->SetRecordLabel(
      new_label, fingerprints_paths_[index],
      base::BindOnce(&FingerprintHandler::OnSetRecordLabel,
                     weak_ptr_factory_.GetWeakPtr(), callback_id));
}

void FingerprintHandler::OnSetRecordLabel(const std::string& callback_id,
                                          bool success) {
  if (!success) {
    LOG(ERROR) << "Failed to set fingerprint record label.";
  }
  ResolveJavascriptCallback(base::Value(callback_id), base::Value(success));
}

bool FingerprintHandler::CheckAuthTokenValidity(const std::string& auth_token) {
  return ash::AuthSessionStorage::Get()->IsValid(auth_token);
}

}  // namespace ash::settings