chromium/chromeos/ash/components/dbus/biod/fake_biod_client_unittest.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 "chromeos/ash/components/dbus/biod/fake_biod_client.h"

#include <vector>

#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/test_simple_task_runner.h"
#include "chromeos/ash/components/dbus/biod/test_utils.h"
#include "dbus/object_path.h"
#include "testing/gtest/include/gtest/gtest.h"

using biod::ERROR_UNABLE_TO_PROCESS;
using biod::SCAN_RESULT_SUCCESS;

namespace ash {

namespace {

const char kTestUserId[] = "[email protected]";
const char kTestLabel[] = "testLabel";
// Template of a scan string to be used in GenerateTestFingerprint. The # and $
// are two wildcards that will be replaced by numbers to ensure unique scans.
const char kTestScan[] = "finger#scan$";

}  // namespace

class FakeBiodClientTest : public testing::Test {
 public:
  FakeBiodClientTest()
      : task_runner_(new base::TestSimpleTaskRunner),
        task_runner_current_default_handle_(task_runner_) {}

  FakeBiodClientTest(const FakeBiodClientTest&) = delete;
  FakeBiodClientTest& operator=(const FakeBiodClientTest&) = delete;

  ~FakeBiodClientTest() override = default;

  // Returns the stored records for |user_id|. Verified to work in
  // TestGetRecordsForUser.
  std::vector<dbus::ObjectPath> GetRecordsForUser(const std::string& user_id) {
    std::vector<dbus::ObjectPath> enrollment_paths;
    bool enrollment_success = false;
    auto enrollment_callback =
        [](std::vector<dbus::ObjectPath>& enrollment_paths,
           bool& enrollment_success, const std::vector<dbus::ObjectPath>& paths,
           bool success) {
          test_utils::CopyObjectPathArray(&enrollment_paths, paths);
          enrollment_success = success;
        };

    fake_biod_client_.GetRecordsForUser(
        user_id, base::BindOnce(enrollment_callback, std::ref(enrollment_paths),
                                std::ref(enrollment_success)));
    task_runner_->RunUntilIdle();
    return enrollment_paths;
  }

  // Helper function which enrolls a fingerprint. Each element in
  // |fingerprint_data| corresponds to a finger tap.
  void EnrollFingerprint(const std::string& id,
                         const std::string& label,
                         const std::vector<std::string>& fingerprint_data) {
    ASSERT_FALSE(fingerprint_data.empty());

    dbus::ObjectPath returned_path;
    fake_biod_client_.StartEnrollSession(
        id, label, base::BindOnce(&test_utils::CopyObjectPath, &returned_path));
    task_runner_->RunUntilIdle();
    EXPECT_NE(dbus::ObjectPath(), returned_path);

    // Send |fingerprint_data| size - 1 incomplete scans, then finish the
    // enrollment by sending a complete scan signal.
    for (size_t i = 0; i < fingerprint_data.size(); ++i) {
      fake_biod_client_.SendEnrollScanDone(
          fingerprint_data[i], SCAN_RESULT_SUCCESS,
          i == fingerprint_data.size() - 1 /* is_complete */,
          -1 /* percent_complete */);
    }
  }

  // Helper function which enrolls |n| fingerprints with the same |id|, |label|
  // and |fingerprint_data|.
  void EnrollNTestFingerprints(const std::string& id,
                               const std::string& label,
                               const std::vector<std::string>& fingerprint_data,
                               int n) {
    for (int i = 0; i < n; ++i)
      EnrollFingerprint(id, label, fingerprint_data);
  }

  // Creates a new unique fingerprint consisting of unique scans.
  std::vector<std::string> GenerateTestFingerprint(int scans) {
    EXPECT_GE(scans, 0);
    num_test_fingerprints_++;

    std::vector<std::string> fingerprint;
    for (int i = 0; i < scans; ++i) {
      std::string scan = kTestScan;
      base::ReplaceSubstringsAfterOffset(
          &scan, 0, "#", base::NumberToString(num_test_fingerprints_));
      base::ReplaceSubstringsAfterOffset(&scan, 0, "$",
                                         base::NumberToString(i));
      fingerprint.push_back(scan);
    }
    return fingerprint;
  }

 protected:
  FakeBiodClient fake_biod_client_;
  scoped_refptr<base::TestSimpleTaskRunner> task_runner_;
  base::SingleThreadTaskRunner::CurrentDefaultHandle
      task_runner_current_default_handle_;

  // This number is incremented each time GenerateTestFingerprint is called to
  // ensure each fingerprint is unique.
  int num_test_fingerprints_ = 0;
};

TEST_F(FakeBiodClientTest, TestEnrollSessionWorkflow) {
  test_utils::TestBiodObserver observer;
  fake_biod_client_.AddObserver(&observer);

  const std::vector<std::string>& kTestFingerprint = GenerateTestFingerprint(2);
  // Verify that successful enrollments get stored as expected. A fingerprint
  // that was created with 2 scans should have 1 incomplete scan and 1 complete
  // scan.
  EnrollFingerprint(kTestUserId, kTestLabel, kTestFingerprint);
  EXPECT_EQ(1u, GetRecordsForUser(kTestUserId).size());
  EXPECT_EQ(1, observer.num_incomplete_enroll_scans_received());
  EXPECT_EQ(1, observer.num_complete_enroll_scans_received());

  // Verify that the enroll session worflow can be used repeatedly by enrolling
  // 3 more fingerprints (each with 1 incomplete and 1 complete scan).
  observer.ResetAllCounts();
  EnrollNTestFingerprints(kTestUserId, kTestLabel, kTestFingerprint, 3);
  EXPECT_EQ(4u, GetRecordsForUser(kTestUserId).size());
  EXPECT_EQ(3, observer.num_incomplete_enroll_scans_received());
  EXPECT_EQ(3, observer.num_complete_enroll_scans_received());
}

// Test authentication when one user has one or more fingerprints registered.
// This should be the normal scenario.
TEST_F(FakeBiodClientTest, TestAuthSessionWorkflowSingleUser) {
  test_utils::TestBiodObserver observer;
  fake_biod_client_.AddObserver(&observer);
  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());

  const std::vector<std::string>& kTestFingerprint1 =
      GenerateTestFingerprint(2);
  const std::vector<std::string>& kTestFingerprint2 =
      GenerateTestFingerprint(2);
  const std::vector<std::string>& kTestFingerprint3 =
      GenerateTestFingerprint(2);
  const std::vector<std::string>& kTestFingerprint4 =
      GenerateTestFingerprint(2);

  // Add two fingerprints |kTestFingerprint1| and |kTestFingerprint2| and start
  // an auth session.
  EnrollFingerprint(kTestUserId, kTestLabel, kTestFingerprint1);
  EnrollFingerprint(kTestUserId, kTestLabel, kTestFingerprint2);
  dbus::ObjectPath returned_path;
  fake_biod_client_.StartAuthSession(
      base::BindOnce(&test_utils::CopyObjectPath, &returned_path));
  task_runner_->RunUntilIdle();
  EXPECT_NE(returned_path, dbus::ObjectPath());

  biod::FingerprintMessage msg;
  // Verify that by sending two attempt signals of fingerprints that have been
  // enrolled, the observer should receive two matches and zero non-matches.
  msg.set_scan_result(SCAN_RESULT_SUCCESS);
  fake_biod_client_.SendAuthScanDone(kTestFingerprint1[0], msg);
  fake_biod_client_.SendAuthScanDone(kTestFingerprint2[0], msg);
  EXPECT_EQ(2, observer.num_matched_auth_scans_received());
  EXPECT_EQ(0, observer.num_unmatched_auth_scans_received());

  // Verify that by sending two attempt signals of fingerprints that have not
  // been enrolled, the observer should receive two non-matches and zero
  // matches.
  observer.ResetAllCounts();
  fake_biod_client_.SendAuthScanDone(kTestFingerprint3[0], msg);
  fake_biod_client_.SendAuthScanDone(kTestFingerprint4[0], msg);
  EXPECT_EQ(0, observer.num_matched_auth_scans_received());
  EXPECT_EQ(2, observer.num_unmatched_auth_scans_received());

  // Verify that by sending two attempt signals of failure during match
  // (with enrolled finger or not), the observer should receive two
  // non-matches and zero matches.
  observer.ResetAllCounts();
  // Error and ScanResult are in oneof field, so setting Error member here
  // will automatically clear ScanResult member. For more information please
  // check https://developers.google.com/protocol-buffers/docs/proto3#oneof
  msg.set_error(ERROR_UNABLE_TO_PROCESS);
  fake_biod_client_.SendAuthScanDone(kTestFingerprint1[0], msg);
  fake_biod_client_.SendAuthScanDone(kTestFingerprint3[0], msg);
  EXPECT_EQ(0, observer.num_matched_auth_scans_received());
  EXPECT_EQ(2, observer.num_unmatched_auth_scans_received());
}

// Test authentication when multiple users have fingerprints registered. Cover
// cases such as when both users use the same labels, a user had registered the
// same fingerprint multiple times, or two users use the same fingerprint.
TEST_F(FakeBiodClientTest, TestAuthenticateWorkflowMultipleUsers) {
  test_utils::TestBiodObserver observer;
  fake_biod_client_.AddObserver(&observer);
  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());

  // Add two users, who have scanned three fingers between the two of them.
  const std::string kUserOne = std::string(kTestUserId) + "1";
  const std::string kUserTwo = std::string(kTestUserId) + "2";

  const std::string kLabelOne = std::string(kTestLabel) + "1";
  const std::string kLabelTwo = std::string(kTestLabel) + "2";
  const std::string kLabelThree = std::string(kTestLabel) + "3";

  // Generate 2 test fingerprints per user.
  const std::vector<std::string>& kUser1Finger1 = GenerateTestFingerprint(2);
  const std::vector<std::string>& kUser1Finger2 = GenerateTestFingerprint(2);
  const std::vector<std::string>& kUser2Finger1 = GenerateTestFingerprint(2);
  const std::vector<std::string>& kUser2Finger2 = GenerateTestFingerprint(2);

  EnrollFingerprint(kUserOne, kLabelOne, kUser1Finger1);
  EnrollFingerprint(kUserOne, kLabelTwo, kUser1Finger2);
  // User one has registered finger two twice.
  EnrollFingerprint(kUserOne, kLabelThree, kUser1Finger2);
  EnrollFingerprint(kUserTwo, kLabelOne, kUser2Finger1);
  EnrollFingerprint(kUserTwo, kLabelTwo, kUser2Finger2);
  // User two has allowed user one to unlock their account with their first
  // finger.
  EnrollFingerprint(kUserTwo, kLabelThree, kUser1Finger1);

  dbus::ObjectPath returned_path;
  fake_biod_client_.StartAuthSession(
      base::BindOnce(&test_utils::CopyObjectPath, &returned_path));
  task_runner_->RunUntilIdle();
  EXPECT_NE(returned_path, dbus::ObjectPath());

  // Verify that if a user registers the same finger to two different labels,
  // both ObjectPath that maps to the labels are returned as matches.
  std::vector<dbus::ObjectPath> record_paths_user1 =
      GetRecordsForUser(kUserOne);
  EXPECT_EQ(3u, record_paths_user1.size());

  AuthScanMatches expected_auth_scans_matches;
  biod::FingerprintMessage msg;
  expected_auth_scans_matches[kUserOne] = {record_paths_user1[1],
                                           record_paths_user1[2]};
  msg.set_scan_result(SCAN_RESULT_SUCCESS);
  fake_biod_client_.SendAuthScanDone(kUser1Finger2[0], msg);
  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());

  // Verify that a fingerprint associated with one user and one label returns a
  // match with one user and one ObjectPath that maps to that label.
  std::vector<dbus::ObjectPath> record_paths_user2 =
      GetRecordsForUser(kUserTwo);
  EXPECT_EQ(3u, record_paths_user2.size());

  expected_auth_scans_matches.clear();
  expected_auth_scans_matches[kUserTwo] = {record_paths_user2[0]};
  fake_biod_client_.SendAuthScanDone(kUser2Finger1[0], msg);
  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());

  // Verify if two users register the same fingerprint, the matches contain
  // both users.
  expected_auth_scans_matches.clear();
  expected_auth_scans_matches[kUserOne] = {record_paths_user1[0]};
  expected_auth_scans_matches[kUserTwo] = {record_paths_user2[2]};
  fake_biod_client_.SendAuthScanDone(kUser1Finger1[0], msg);
  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());

  // Verify if a unregistered finger is scanned, the matches are empty.
  expected_auth_scans_matches.clear();
  fake_biod_client_.SendAuthScanDone("Unregistered", msg);
  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());

  // Verify if error is returned, the matches are empty.
  // Error and ScanResult are in oneof field, so setting Error member here
  // will automatically clear ScanResult member. For more information please
  // check https://developers.google.com/protocol-buffers/docs/proto3#oneof
  msg.set_error(ERROR_UNABLE_TO_PROCESS);
  fake_biod_client_.SendAuthScanDone(kUser1Finger1[0], msg);
  EXPECT_EQ(expected_auth_scans_matches, observer.last_auth_scan_matches());
}

TEST_F(FakeBiodClientTest, TestGetRecordsForUser) {
  // Verify that initially |kTestUserId| will have no fingerprints.
  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());

  // Verify that after enrolling 2 fingerprints, a GetRecords call return 2
  // items.
  EnrollNTestFingerprints(kTestUserId, kTestLabel, GenerateTestFingerprint(2),
                          2);
  EXPECT_EQ(2u, GetRecordsForUser(kTestUserId).size());

  // Verify that GetRecords call for a user with no registered fingerprints
  // should return 0 items.
  EXPECT_EQ(0u, GetRecordsForUser("[email protected]").size());
}

TEST_F(FakeBiodClientTest, TestDestroyingRecords) {
  // Verify that after enrolling 2 fingerprints and destroying them, 0
  // fingerprints will remain.
  EnrollNTestFingerprints(kTestUserId, kTestLabel, GenerateTestFingerprint(2),
                          2);
  EXPECT_EQ(2u, GetRecordsForUser(kTestUserId).size());
  fake_biod_client_.DestroyAllRecords(base::DoNothing());
  EXPECT_EQ(0u, GetRecordsForUser(kTestUserId).size());
}

TEST_F(FakeBiodClientTest, TestGetAndSetRecordLabels) {
  const std::string kLabelOne = "Finger 1";
  const std::string kLabelTwo = "Finger 2";

  EnrollFingerprint(kTestUserId, kLabelOne, GenerateTestFingerprint(2));
  EnrollFingerprint(kTestUserId, kLabelTwo, GenerateTestFingerprint(2));
  std::vector<dbus::ObjectPath> enrollment_paths =
      GetRecordsForUser(kTestUserId);
  EXPECT_EQ(2u, enrollment_paths.size());

  // Verify the labels we get using GetLabel are the same as the one we
  // originally set.
  std::string returned_str;
  fake_biod_client_.RequestRecordLabel(
      enrollment_paths[0],
      base::BindOnce(&test_utils::CopyString, &returned_str));
  task_runner_->RunUntilIdle();
  EXPECT_EQ(kLabelOne, returned_str);

  returned_str = "";
  fake_biod_client_.RequestRecordLabel(
      enrollment_paths[1],
      base::BindOnce(&test_utils::CopyString, &returned_str));
  task_runner_->RunUntilIdle();
  EXPECT_EQ(kLabelTwo, returned_str);

  // Verify that by setting a new label, getting the label will return the value
  // of the new label.
  const std::string kNewLabelTwo = "Finger 2 New";
  fake_biod_client_.SetRecordLabel(enrollment_paths[1], kNewLabelTwo,
                                   base::DoNothing());
  fake_biod_client_.RequestRecordLabel(
      enrollment_paths[1],
      base::BindOnce(&test_utils::CopyString, &returned_str));
  task_runner_->RunUntilIdle();
  EXPECT_EQ(kNewLabelTwo, returned_str);
}

}  // namespace ash