chromium/chrome/browser/ash/smb_client/smb_service.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/ash/smb_client/smb_service.h"

#include <string>
#include <utility>

#include "base/check_deref.h"
#include "base/containers/span.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/default_tick_clock.h"
#include "base/unguessable_token.h"
#include "base/values.h"
#include "chrome/browser/ash/file_manager/file_manager_pref_names.h"
#include "chrome/browser/ash/file_system_provider/mount_path_util.h"
#include "chrome/browser/ash/file_system_provider/provided_file_system_info.h"
#include "chrome/browser/ash/kerberos/kerberos_credentials_manager.h"
#include "chrome/browser/ash/kerberos/kerberos_credentials_manager_factory.h"
#include "chrome/browser/ash/profiles/profile_helper.h"
#include "chrome/browser/ash/smb_client/discovery/mdns_host_locator.h"
#include "chrome/browser/ash/smb_client/discovery/netbios_client.h"
#include "chrome/browser/ash/smb_client/discovery/netbios_host_locator.h"
#include "chrome/browser/ash/smb_client/smb_file_system.h"
#include "chrome/browser/ash/smb_client/smb_file_system_id.h"
#include "chrome/browser/ash/smb_client/smb_kerberos_credentials_updater.h"
#include "chrome/browser/ash/smb_client/smb_provider.h"
#include "chrome/browser/ash/smb_client/smb_service_helper.h"
#include "chrome/browser/ash/smb_client/smb_share_info.h"
#include "chrome/browser/ash/smb_client/smb_url.h"
#include "chrome/browser/platform_util.h"
#include "chrome/browser/ui/webui/ash/smb_shares/smb_credentials_dialog.h"
#include "chrome/browser/ui/webui/ash/smb_shares/smb_handler.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/pref_names.h"
#include "chromeos/ash/components/dbus/dbus_thread_manager.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/user_manager/user.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "crypto/random.h"
#include "net/base/network_interfaces.h"
#include "url/url_util.h"

namespace ash::smb_client {

namespace {

const char kShareUrlKey[] = "share_url";
const char kModeKey[] = "mode";
const char kModeDropDownValue[] = "drop_down";
const char kModePreMountValue[] = "pre_mount";
const char kModeUnknownValue[] = "unknown";
// Maximum number of smbfs shares to be mounted at the same time, only enforced
// on user-initiated mount requests.
const size_t kMaxSmbFsShares = 16;
// Length of salt used to obfuscate stored password in smbfs.
const size_t kSaltLength = 16;
static_assert(kSaltLength >=
                  smbfs::mojom::CredentialStorageOptions::kMinSaltLength,
              "Minimum salt length is "
              "smbfs::mojom::CredentialStorageOptions::kMinSaltLength");

net::NetworkInterfaceList GetInterfaces() {
  net::NetworkInterfaceList list;
  if (!net::GetNetworkList(&list, net::EXCLUDE_HOST_SCOPE_VIRTUAL_INTERFACES)) {
    LOG(ERROR) << "GetInterfaces failed";
  }
  return list;
}

std::unique_ptr<NetBiosClientInterface> GetNetBiosClient(Profile* profile) {
  auto* network_context =
      profile->GetDefaultStoragePartition()->GetNetworkContext();
  return std::make_unique<NetBiosClient>(network_context);
}

// Metric recording functions.

// This enum is used to define the buckets for an enumerated UMA histogram.
// Hence,
//   (a) existing enumerated constants should never be deleted or reordered, and
//   (b) new constants should only be appended at the end of the enumeration.
enum class AuthMethod {
  kNoCredentials = 0,
  kUsernameOnly = 1,
  kUsernameAndPassword = 2,
  kSSOKerberosAD = 3,
  kSSOKerberosGaia = 4,
  kMaxValue = kSSOKerberosGaia,
};

void RecordMountResult(SmbMountResult result) {
  DCHECK_LE(result, SmbMountResult::kMaxValue);
  UMA_HISTOGRAM_ENUMERATION("NativeSmbFileShare.MountResult", result);
}

void RecordAuthenticationMethod(AuthMethod method) {
  DCHECK_LE(method, AuthMethod::kMaxValue);
  UMA_HISTOGRAM_ENUMERATION("NativeSmbFileShare.AuthenticationMethod", method);
}

}  // namespace

bool SmbService::disable_share_discovery_for_testing_ = false;

SmbService::SmbService(Profile* profile,
                       std::unique_ptr<base::TickClock> tick_clock)
    : provider_id_(file_system_provider::ProviderId::CreateFromNativeId("smb")),
      profile_(profile),
      registry_(profile) {
  user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile_);
  DCHECK(user);

  SmbProviderClient* client = GetSmbProviderClient();
  if (!client) {
    return;
  }

  KerberosCredentialsManager* credentials_manager =
      KerberosCredentialsManagerFactory::GetExisting(profile);
  if (credentials_manager) {
    kerberos_credentials_updater_ =
        std::make_unique<SmbKerberosCredentialsUpdater>(
            credentials_manager,
            base::BindRepeating(&SmbService::UpdateKerberosCredentials,
                                weak_ptr_factory_.GetWeakPtr()));
    SetupKerberos(kerberos_credentials_updater_->active_account_name());
    return;
  }

  // Post a task to complete setup. This is to allow unit tests to perform
  // expectations setup after constructing an instance. It also mirrors the
  // behaviour when Kerberos is being used.
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&SmbService::CompleteSetup,
                                weak_ptr_factory_.GetWeakPtr()));
}

SmbService::~SmbService() {
  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
}

void SmbService::Shutdown() {
  // Unmount and destroy all smbfs instances explicitly before destruction,
  // since SmbFsShare accesses KeyedServices on destruction.
  smbfs_shares_.clear();
}

// static
void SmbService::RegisterProfilePrefs(
    user_prefs::PrefRegistrySyncable* registry) {
  registry->RegisterBooleanPref(prefs::kNetworkFileSharesAllowed, true);
  registry->RegisterBooleanPref(prefs::kNetBiosShareDiscoveryEnabled, true);
  registry->RegisterBooleanPref(prefs::kNTLMShareAuthenticationEnabled, true);
  registry->RegisterListPref(prefs::kNetworkFileSharesPreconfiguredShares);
  registry->RegisterStringPref(prefs::kMostRecentlyUsedNetworkFileShareURL, "");
  SmbPersistedShareRegistry::RegisterProfilePrefs(registry);
}

void SmbService::UnmountSmbFs(const base::FilePath& mount_path) {
  DCHECK(!mount_path.empty());

  for (auto it = smbfs_shares_.begin(); it != smbfs_shares_.end(); ++it) {
    SmbFsShare* share = it->second.get();
    if (share->mount_path() == mount_path) {
      if (share->options().save_restore_password) {
        share->RemoveSavedCredentials(
            base::BindOnce(&SmbService::OnSmbfsRemoveSavedCredentialsDone,
                           base::Unretained(this), it->first));
      } else {
        // If the password wasn't saved, there's nothing for smbfs to do.
        OnSmbfsRemoveSavedCredentialsDone(it->first, true /* success */);
      }
      return;
    }
  }

  LOG(WARNING) << "Smbfs mount path not found: " << mount_path;
}

void SmbService::OnSmbfsRemoveSavedCredentialsDone(const std::string& mount_id,
                                                   bool success) {
  DCHECK(!mount_id.empty());

  auto it = smbfs_shares_.find(mount_id);
  if (it == smbfs_shares_.end()) {
    LOG(WARNING) << "Smbfs mount id " << mount_id << " already deleted";
    return;
  }

  // UnmountSmbFs() is called by an explicit unmount by the user. In this
  // case, forget the share.
  registry_.Delete(it->second->share_url());
  smbfs_shares_.erase(it);
}

SmbFsShare* SmbService::GetSmbFsShareForPath(const base::FilePath& path) {
  DCHECK(!path.empty());
  DCHECK(path.IsAbsolute());

  for (const auto& entry : smbfs_shares_) {
    const base::FilePath mount_path = entry.second->mount_path();
    if (mount_path == path || mount_path.IsParent(path)) {
      return entry.second.get();
    }
  }
  return nullptr;
}

void SmbService::GatherSharesInNetwork(HostDiscoveryResponse discovery_callback,
                                       GatherSharesResponse shares_callback) {
  auto preconfigured_shares = GetPreconfiguredSharePathsForDropdown();
  if (!preconfigured_shares.empty()) {
    shares_callback.Run(std::move(preconfigured_shares), false);
  }
  share_finder_->GatherSharesInNetwork(
      std::move(discovery_callback),
      base::BindOnce(
          [](GatherSharesResponse shares_callback,
             const std::vector<SmbUrl>& shares_gathered) {
            std::move(shares_callback).Run(shares_gathered, true);
          },
          std::move(shares_callback)));
}

void SmbService::Mount(const std::string& display_name,
                       const base::FilePath& share_path,
                       const std::string& username_input,
                       const std::string& password_input,
                       bool use_kerberos,
                       bool should_open_file_manager_after_mount,
                       bool save_credentials,
                       MountResponse callback) {
  SmbUrl parsed_url(share_path.value());
  if (!parsed_url.IsValid() || parsed_url.GetShare().empty()) {
    // Handle invalid URLs early to avoid having unaccounted for UMA counts for
    // authentication method.
    std::move(callback).Run(SmbMountResult::kInvalidUrl);
    return;
  }

  // When using kerberos, the URL must contain the hostname because that is used
  // to obtain the ticket. If the user enters an IP address, Samba will give us
  // a permission error, which isn't correct or useful to the end user.
  if (use_kerberos && url::HostIsIPAddress(parsed_url.GetHost())) {
    std::move(callback).Run(SmbMountResult::kInvalidSsoUrl);
    return;
  }

  if (IsShareMounted(parsed_url)) {
    // Prevent a share from being mounted twice. Although technically possible,
    // the UX when doing so is incomplete.
    std::move(callback).Run(SmbMountResult::kMountExists);
    return;
  }

  if (smbfs_shares_.size() >= kMaxSmbFsShares) {
    // Prevent users from mounting an excessive number of shares.
    std::move(callback).Run(SmbMountResult::kTooManyOpened);
    return;
  }

  std::string username;
  std::string password;
  std::string workgroup;

  user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile_);
  DCHECK(user);

  if (use_kerberos) {
    // Differentiate between AD and KerberosEnabled via policy in metrics.
    if (IsKerberosEnabledViaPolicy()) {
      RecordAuthenticationMethod(AuthMethod::kSSOKerberosGaia);
    } else {
      RecordAuthenticationMethod(AuthMethod::kSSOKerberosAD);
    }

    // Get the user's username and workgroup from their email address to be used
    // for Kerberos authentication.
    ParseUserPrincipalName(user->GetDisplayEmail(), &username, &workgroup);
  } else {
    // Record authentication method metrics.
    if (!username_input.empty() && !password_input.empty()) {
      RecordAuthenticationMethod(AuthMethod::kUsernameAndPassword);
    } else if (!username_input.empty()) {
      RecordAuthenticationMethod(AuthMethod::kUsernameOnly);
    } else {
      RecordAuthenticationMethod(AuthMethod::kNoCredentials);
    }

    // Use provided credentials and parse the username into username and
    // workgroup if necessary.
    username = username_input;
    password = password_input;
    if (!ParseUserName(username_input, &username, &workgroup)) {
      std::move(callback).Run(SmbMountResult::kInvalidUsername);
      return;
    }
  }

  std::vector<uint8_t> salt;
  if (save_credentials && !password.empty()) {
    // Only generate a salt if there's a password and we've been asked to save
    // credentials. If there is no password, there's nothing for smbfs to store
    // and the salt is unused.
    salt = crypto::RandBytesAsVector(kSaltLength);
  }
  SmbShareInfo info(parsed_url, display_name, username, workgroup, use_kerberos,
                    salt);
  MountInternal(info, password, save_credentials, false /* skip_connect */,
                base::BindOnce(&SmbService::OnUserInitiatedMountDone,
                               base::Unretained(this), std::move(callback),
                               info, should_open_file_manager_after_mount));

  profile_->GetPrefs()->SetString(prefs::kMostRecentlyUsedNetworkFileShareURL,
                                  share_path.value());
}

void SmbService::OnUserInitiatedMountDone(
    MountResponse callback,
    const SmbShareInfo& info,
    bool should_open_file_manager_after_mount,
    SmbMountResult result,
    const base::FilePath& mount_path) {
  if (result != SmbMountResult::kSuccess) {
    std::move(callback).Run(result);
    return;
  }

  DCHECK(!mount_path.empty());
  if (should_open_file_manager_after_mount) {
    platform_util::ShowItemInFolder(profile_, mount_path);
  }

  registry_.Save(info);

  RecordMountCount();
  std::move(callback).Run(SmbMountResult::kSuccess);
}

void SmbService::MountInternal(
    const SmbShareInfo& info,
    const std::string& password,
    bool save_credentials,
    bool skip_connect,
    MountInternalCallback callback) {
  // Preconfigured or persisted share information could be invalid.
  if (!info.share_url().IsValid() || info.share_url().GetShare().empty()) {
    std::move(callback).Run(SmbMountResult::kInvalidUrl, {});
    return;
  }

  user_manager::User* user = ProfileHelper::Get()->GetUserByProfile(profile_);
  DCHECK(user);

  SmbFsShare::MountOptions smbfs_options;
  smbfs_options.resolved_host =
      share_finder_->GetResolvedHost(info.share_url().GetHost());
  smbfs_options.username = info.username();
  smbfs_options.workgroup = info.workgroup();
  smbfs_options.password = password;
  smbfs_options.allow_ntlm = IsNTLMAuthenticationEnabled();
  smbfs_options.skip_connect = skip_connect;
  smbfs_options.enable_verbose_logging = profile_->GetPrefs()->GetBoolean(
      file_manager::prefs::kSmbfsEnableVerboseLogging);
  if (save_credentials && !info.password_salt().empty()) {
    smbfs_options.save_restore_password = true;
    smbfs_options.account_hash = user->username_hash();
    smbfs_options.password_salt = info.password_salt();
  }
  if (info.use_kerberos()) {
    if (kerberos_credentials_updater_) {
      smbfs_options.kerberos_options =
          std::make_optional<SmbFsShare::KerberosOptions>(
              SmbFsShare::KerberosOptions::Source::kKerberos,
              kerberos_credentials_updater_->active_account_name());
    } else {
      LOG(WARNING) << "No Kerberos credential source available";
      std::move(callback).Run(SmbMountResult::kAuthenticationFailed, {});
      return;
    }
  }

  std::unique_ptr<SmbFsShare> mount = std::make_unique<SmbFsShare>(
      profile_, info.share_url(), info.display_name(), smbfs_options);
  if (smbfs_mounter_creation_callback_) {
    mount->SetMounterCreationCallbackForTest(smbfs_mounter_creation_callback_);
  }

  SmbFsShare* raw_mount = mount.get();
  const std::string mount_id = mount->mount_id();
  smbfs_shares_[mount_id] = std::move(mount);
  raw_mount->Mount(base::BindOnce(&SmbService::OnSmbfsMountDone,
                                  weak_ptr_factory_.GetWeakPtr(), mount_id,
                                  std::move(callback)));
}

void SmbService::OnSmbfsMountDone(const std::string& smbfs_mount_id,
                                  MountInternalCallback callback,
                                  SmbMountResult result) {
  RecordMountResult(result);

  if (result != SmbMountResult::kSuccess) {
    smbfs_shares_.erase(smbfs_mount_id);
    std::move(callback).Run(result, {});
    return;
  }

  SmbFsShare* mount = smbfs_shares_[smbfs_mount_id].get();
  if (!mount) {
    LOG(ERROR) << "smbfs mount " << smbfs_mount_id << " does not exist";
    std::move(callback).Run(SmbMountResult::kUnknownFailure, {});
    return;
  }

  std::move(callback).Run(SmbMountResult::kSuccess, mount->mount_path());
}

file_system_provider::Service* SmbService::GetProviderService() const {
  return file_system_provider::Service::Get(profile_);
}

SmbProviderClient* SmbService::GetSmbProviderClient() const {
  return SmbProviderClient::Get();
}

void SmbService::RestoreMounts() {
  std::vector<SmbUrl> preconfigured_shares =
      GetPreconfiguredSharePathsForPremount();

  std::vector<SmbShareInfo> saved_smbfs_shares;
  // Restore smbfs shares.
  saved_smbfs_shares = registry_.GetAll();

  if (!saved_smbfs_shares.empty() || !preconfigured_shares.empty()) {
    share_finder_->DiscoverHostsInNetwork(base::BindOnce(
        &SmbService::OnHostsDiscovered, weak_ptr_factory_.GetWeakPtr(),
        std::move(saved_smbfs_shares), std::move(preconfigured_shares)));
  }
}

void SmbService::OnHostsDiscovered(
    const std::vector<SmbShareInfo>& saved_smbfs_shares,
    const std::vector<SmbUrl>& preconfigured_shares) {
  for (const auto& smbfs_share : saved_smbfs_shares) {
    MountSavedSmbfsShare(smbfs_share);
  }
  for (const auto& url : preconfigured_shares) {
    MountPreconfiguredShare(url);
  }
}

void SmbService::SetRestoredShareMountDoneCallbackForTesting(
    MountInternalCallback callback) {
  restored_share_mount_done_callback_ = std::move(callback);
}

void SmbService::MountSavedSmbfsShare(const SmbShareInfo& info) {
  MountInternal(info, "" /* password */, true /* save_credentials */,
                true /* skip_connect */,
                restored_share_mount_done_callback_.is_null()
                    ? base::BindOnce(&SmbService::OnMountSavedSmbfsShareDone,
                                     weak_ptr_factory_.GetWeakPtr())
                    : std::move(restored_share_mount_done_callback_));
}

void SmbService::OnMountSavedSmbfsShareDone(SmbMountResult result,
                                            const base::FilePath& mount_path) {
  LOG_IF(ERROR, result != SmbMountResult::kSuccess)
      << "Error restoring saved share: " << static_cast<int>(result);
}

void SmbService::MountPreconfiguredShare(const SmbUrl& share_url) {
  std::string display_name =
      base::FilePath(share_url.ToString()).BaseName().value();
  // Note: Preconfigured shares are mounted without credentials.
  SmbShareInfo info(share_url, display_name, "" /* username */,
                    "" /* workgroup */, false /* use_kerberos */);
  MountInternal(info, "" /* password */, false /* save_credentials */,
                true /* skip_connect */,
                restored_share_mount_done_callback_.is_null()
                    ? base::BindOnce(&SmbService::OnMountPreconfiguredShareDone,
                                     weak_ptr_factory_.GetWeakPtr())
                    : std::move(restored_share_mount_done_callback_));
}

void SmbService::OnMountPreconfiguredShareDone(
    SmbMountResult result,
    const base::FilePath& mount_path) {
  LOG_IF(ERROR, result != SmbMountResult::kSuccess)
      << "Error mounting preconfigured share: " << static_cast<int>(result);
}

bool SmbService::IsKerberosEnabledViaPolicy() const {
  return kerberos_credentials_updater_ &&
         kerberos_credentials_updater_->IsKerberosEnabled();
}

void SmbService::SetupKerberos(const std::string& account_identifier) {
  SmbProviderClient* client = GetSmbProviderClient();
  if (!client) {
    return;
  }

  client->SetupKerberos(account_identifier,
                        base::BindOnce(&SmbService::OnSetupKerberosResponse,
                                       weak_ptr_factory_.GetWeakPtr()));
}

void SmbService::UpdateKerberosCredentials(
    const std::string& account_identifier) {
  SmbProviderClient* client = GetSmbProviderClient();
  if (!client) {
    return;
  }

  client->SetupKerberos(
      account_identifier,
      base::BindOnce(&SmbService::OnUpdateKerberosCredentialsResponse,
                     weak_ptr_factory_.GetWeakPtr()));
}

void SmbService::OnUpdateKerberosCredentialsResponse(bool success) {
  LOG_IF(ERROR, !success) << "Update Kerberos credentials failed.";
}

void SmbService::OnSetupKerberosResponse(bool success) {
  if (!success) {
    LOG(ERROR) << "SmbService: Kerberos setup failed.";
  }

  CompleteSetup();
}

void SmbService::CompleteSetup() {
  share_finder_ = std::make_unique<SmbShareFinder>(GetSmbProviderClient());
  RegisterHostLocators();

  GetProviderService()->RegisterProvider(std::make_unique<SmbProvider>());
  RestoreMounts();
  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);

  if (setup_complete_callback_) {
    std::move(setup_complete_callback_).Run();
  }
}

void SmbService::OnSetupCompleteForTesting(base::OnceClosure callback) {
  DCHECK(!setup_complete_callback_);
  if (share_finder_) {
    std::move(callback).Run();
    return;
  }
  setup_complete_callback_ = std::move(callback);
}

void SmbService::SetSmbFsMounterCreationCallbackForTesting(
    SmbFsShare::MounterCreationCallback callback) {
  smbfs_mounter_creation_callback_ = std::move(callback);
}

void SmbService::RegisterHostLocators() {
  if (disable_share_discovery_for_testing_) {
    return;
  }

  SetUpMdnsHostLocator();
  if (IsNetBiosDiscoveryEnabled()) {
    SetUpNetBiosHostLocator();
  } else {
    LOG(WARNING) << "SmbService: NetBios discovery disabled.";
  }
}

void SmbService::SetUpMdnsHostLocator() {
  share_finder_->RegisterHostLocator(std::make_unique<MDnsHostLocator>());
}

void SmbService::SetUpNetBiosHostLocator() {
  auto get_interfaces = base::BindRepeating(&GetInterfaces);
  auto client_factory = base::BindRepeating(&GetNetBiosClient, profile_);

  auto netbios_host_locator = std::make_unique<NetBiosHostLocator>(
      std::move(get_interfaces), std::move(client_factory),
      GetSmbProviderClient());

  share_finder_->RegisterHostLocator(std::move(netbios_host_locator));
}

bool SmbService::IsNetBiosDiscoveryEnabled() const {
  return profile_->GetPrefs()->GetBoolean(prefs::kNetBiosShareDiscoveryEnabled);
}

bool SmbService::IsNTLMAuthenticationEnabled() const {
  return profile_->GetPrefs()->GetBoolean(
      prefs::kNTLMShareAuthenticationEnabled);
}

bool SmbService::IsShareMounted(const SmbUrl& share) const {
  std::vector<file_system_provider::ProvidedFileSystemInfo> file_systems =
      GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);

  for (const auto& info : file_systems) {
    base::FilePath share_path =
        GetSharePathFromFileSystemId(info.file_system_id());
    SmbUrl parsed_url(share_path.value());
    DCHECK(parsed_url.IsValid());
    if (parsed_url.ToString() == share.ToString()) {
      return true;
    }
  }

  for (const auto& entry : smbfs_shares_) {
    if (entry.second->share_url().ToString() == share.ToString()) {
      return true;
    }
  }
  return false;
}

std::vector<SmbUrl> SmbService::GetPreconfiguredSharePaths(
    const std::string& policy_mode) const {
  std::vector<SmbUrl> preconfigured_urls;

  const base::Value::List& preconfigured_shares = profile_->GetPrefs()->GetList(
      prefs::kNetworkFileSharesPreconfiguredShares);

  for (const base::Value& info_val : preconfigured_shares) {
    // |info| is a dictionary with entries for `share_url` and `mode`.
    const base::Value::Dict& info = info_val.GetDict();
    const std::string* share_url_ptr = info.FindString(kShareUrlKey);
    const std::string* mode_ptr = info.FindString(kModeKey);

    const std::string& mode = CHECK_DEREF(mode_ptr);
    const std::string& share_url = CHECK_DEREF(share_url_ptr);
    if (policy_mode == kModeUnknownValue) {
      // kModeUnknownValue is used to filter for any shares that do not match
      // a presently known mode for preconfiguration. As new preconfigure
      // modes are added, this should be kept in sync.
      if (mode != kModeDropDownValue && mode != kModePreMountValue) {
        preconfigured_urls.emplace_back(share_url);
      }
    } else {
      // Filter normally.
      if (mode == policy_mode) {
        preconfigured_urls.emplace_back(share_url);
      }
    }
  }
  return preconfigured_urls;
}

std::vector<SmbUrl> SmbService::GetPreconfiguredSharePathsForDropdown() const {
  auto drop_down_paths = GetPreconfiguredSharePaths(kModeDropDownValue);
  auto fallback_paths = GetPreconfiguredSharePaths(kModeUnknownValue);

  for (auto&& fallback_path : fallback_paths) {
    drop_down_paths.push_back(std::move(fallback_path));
  }

  return drop_down_paths;
}

std::vector<SmbUrl> SmbService::GetPreconfiguredSharePathsForPremount() const {
  return GetPreconfiguredSharePaths(kModePreMountValue);
}

void SmbService::OnNetworkChanged(
    net::NetworkChangeNotifier::ConnectionType type) {
  // Run host discovery to refresh list of cached hosts for subsequent name
  // resolution attempts.
  share_finder_->DiscoverHostsInNetwork(base::DoNothing()
                                        /* HostDiscoveryResponse */);
}

void SmbService::RecordMountCount() const {
  const std::vector<file_system_provider::ProvidedFileSystemInfo> file_systems =
      GetProviderService()->GetProvidedFileSystemInfoList(provider_id_);
  UMA_HISTOGRAM_COUNTS_100("NativeSmbFileShare.MountCount",
                           file_systems.size() + smbfs_shares_.size());
}

bool SmbService::IsAnySmbShareConfigured() {
  std::vector<SmbUrl> preconfigured_shares =
      GetPreconfiguredSharePathsForPremount();

  std::vector<SmbShareInfo> saved_smbfs_shares;
  saved_smbfs_shares = registry_.GetAll();

  return !saved_smbfs_shares.empty() || !preconfigured_shares.empty();
}

}  // namespace ash::smb_client