chromium/chrome/credential_provider/gaiacp/gaia_credential_provider.cc

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

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/40285824): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "chrome/credential_provider/gaiacp/gaia_credential_provider.h"

#include <iomanip>
#include <map>
#include <string>
#include <utility>

#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/memory/raw_ptr.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/common/chrome_version.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/associated_user_validator.h"
#include "chrome/credential_provider/gaiacp/auth_utils.h"
#include "chrome/credential_provider/gaiacp/device_policies_manager.h"
#include "chrome/credential_provider/gaiacp/gaia_credential.h"
#include "chrome/credential_provider/gaiacp/gaia_credential_other_user.h"
#include "chrome/credential_provider/gaiacp/gaia_credential_provider_i.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/mdm_utils.h"
#include "chrome/credential_provider/gaiacp/os_user_manager.h"
#include "chrome/credential_provider/gaiacp/reauth_credential.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"

namespace credential_provider {

#define W2CW(p) const_cast<wchar_t*>(p)

static const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR g_field_desc[] = {
    {FID_DESCRIPTION, CPFT_LARGE_TEXT, W2CW(L"Description"), GUID_NULL},
    {FID_CURRENT_PASSWORD_FIELD, CPFT_PASSWORD_TEXT, W2CW(L"Windows Password"),
     GUID_NULL},
    {FID_SUBMIT, CPFT_SUBMIT_BUTTON, W2CW(L"Submit button"), GUID_NULL},
    {FID_FORGOT_PASSWORD_LINK, CPFT_COMMAND_LINK, W2CW(L"Forgot Password"),
     GUID_NULL},
    {FID_PROVIDER_LOGO, CPFT_TILE_IMAGE, W2CW(L"Provider logo"),
     CPFG_CREDENTIAL_PROVIDER_LOGO},
    {FID_PROVIDER_LABEL, CPFT_LARGE_TEXT, W2CW(L"Provider label"),
     CPFG_CREDENTIAL_PROVIDER_LABEL},
};

static_assert(std::size(g_field_desc) == FIELD_COUNT,
              "g_field_desc does not match FIELDID enum");

namespace {

// Initializes an object that implements IReauthCredential.
HRESULT InitializeReauthCredential(
    CGaiaCredentialProvider* provider,
    const std::wstring& sid,
    const std::wstring& domain,
    const std::wstring& username,
    const Microsoft::WRL::ComPtr<IGaiaCredential>& gaia_cred) {
  Microsoft::WRL::ComPtr<IReauthCredential> reauth;
  HRESULT hr = gaia_cred.As(&reauth);
  if (FAILED(hr)) {
    LOG(ERROR) << "Could not get reauth credential interface hr=" << putHR(hr);
    return hr;
  }

  hr = gaia_cred->Initialize(provider);
  if (FAILED(hr)) {
    LOG(ERROR) << "Could not initialize credential hr=" << putHR(hr);
    return hr;
  }

  // Get the user's email address.  If not found, proceed anyway.  The net
  // effect is that the user will need to enter their email address manually
  // instead of it being pre-filled.
  wchar_t email[64];
  ULONG email_length = std::size(email);
  hr = GetUserProperty(sid.c_str(), kUserEmail, email, &email_length);
  if (FAILED(hr))
    email[0] = 0;

  hr = reauth->SetOSUserInfo(CComBSTR(W2COLE(sid.c_str())),
                             CComBSTR(W2COLE(domain.c_str())),
                             CComBSTR(W2COLE(username.c_str())));
  if (FAILED(hr)) {
    LOGFN(ERROR) << "cred->SetOSUserInfo hr=" << putHR(hr);
    return hr;
  }

  if (email[0]) {
    hr = reauth->SetEmailForReauth(CComBSTR(email));
    if (FAILED(hr))
      LOGFN(ERROR) << "reauth->SetEmailForReauth hr=" << putHR(hr);
  } else {
    LOGFN(VERBOSE) << "reauth for sid " << sid
                   << " doesn't contain the email association";
  }

  return S_OK;
}

template <class CredentialT>
HRESULT CreateCredentialObject(
    CGaiaCredentialProvider::CredentialCreatorFn creator_fn,
    CGaiaCredentialProvider::GaiaCredentialComPtrStorage* credential_com_ptr) {
  if (creator_fn) {
    return creator_fn(credential_com_ptr);
  }

  return CComCreator<CComObject<CredentialT>>::CreateInstance(
      nullptr, IID_PPV_ARGS(&credential_com_ptr->gaia_cred));
}

}  // namespace

// Class that when constructed automatically starts a thread that tries
// to update the validity of available token handles. If the background
// thread detects that any token handle has changed, then it will notify
// the provider |event_handler| object of this event.
class BackgroundTokenHandleUpdater {
 public:
  explicit BackgroundTokenHandleUpdater(
      ICredentialUpdateEventsHandler* event_handler,
      const std::vector<std::wstring>* reauth_sids);
  ~BackgroundTokenHandleUpdater();

 private:
  static unsigned __stdcall PeriodicTokenHandleUpdate(void* param);
  bool IsAuthEnforcedOnAssociatedUsers();

  // Raw pointer to the interface on CGaiaCredentialProvider that is used
  // to notify that token handle validity has changed. Any instance of this
  // class should be owned by the CGaiaCredentialProvider to ensure that
  // this pointer outlives the updater.
  raw_ptr<ICredentialUpdateEventsHandler> event_handler_;
  raw_ptr<const std::vector<std::wstring>> reauth_sids_;

  base::win::ScopedHandle token_update_thread_;
  base::WaitableEvent token_update_quit_event_;
};

BackgroundTokenHandleUpdater::BackgroundTokenHandleUpdater(
    ICredentialUpdateEventsHandler* event_handler,
    const std::vector<std::wstring>* reauth_sids)
    : event_handler_(event_handler), reauth_sids_(reauth_sids) {
  unsigned wait_thread_id;
  uintptr_t wait_thread =
      _beginthreadex(nullptr, 0, PeriodicTokenHandleUpdate,
                     reinterpret_cast<void*>(this), 0, &wait_thread_id);
  if (wait_thread != 0) {
    token_update_thread_.Set(
        reinterpret_cast<base::win::ScopedHandle::Handle>(wait_thread));
  }
}

BackgroundTokenHandleUpdater::~BackgroundTokenHandleUpdater() {
  if (token_update_thread_.IsValid()) {
    // Tell the background thread to quit and then make sure it does.  This
    // prevents it from accessing data members that have been freed.
    token_update_quit_event_.Signal();
    ::WaitForSingleObject(token_update_thread_.Get(), INFINITE);
  }
}

bool BackgroundTokenHandleUpdater::IsAuthEnforcedOnAssociatedUsers() {
  std::map<std::wstring, UserTokenHandleInfo> sids_to_handle_info;
  HRESULT hr = GetUserTokenHandles(&sids_to_handle_info);
  if (FAILED(hr)) {
    LOGFN(ERROR) << "GetUserTokenHandles hr=" << putHR(hr);
    return hr;
  }

  for (const auto& sid_to_association : sids_to_handle_info) {
    const std::wstring& sid = sid_to_association.first;
    // Checks if the login UI was already refreshed due to
    // auth enforcements on this sid.
    if (reauth_sids_ != nullptr && base::Contains(*reauth_sids_, sid))
      continue;

    // Return true if the associated user sid has auth enforced.
    if (AssociatedUserValidator::Get()->IsAuthEnforcedForUser(sid)) {
      return true;
    }
  }
  return false;
}

unsigned __stdcall BackgroundTokenHandleUpdater::PeriodicTokenHandleUpdate(
    void* param) {
  BackgroundTokenHandleUpdater* updater =
      reinterpret_cast<BackgroundTokenHandleUpdater*>(param);
  ICredentialUpdateEventsHandler* event_handler = updater->event_handler_;
  base::WaitableEvent& stop_event = updater->token_update_quit_event_;

  while (true) {
    HRESULT hr = ::WaitForSingleObject(
        stop_event.handle(),
        AssociatedUserValidator::kTokenHandleValidityLifetime.InMilliseconds());

    if (hr != WAIT_TIMEOUT)
      break;

    bool user_access_changed = updater->IsAuthEnforcedOnAssociatedUsers();
    if (user_access_changed) {
      LOGFN(VERBOSE) << "A user token handle has been invalidated. Refreshing "
                        "credentials";
    }

    if (GetGlobalFlagOrDefault(kRegUpdateCredentialsOnChange, 0))
      event_handler->UpdateCredentialsIfNeeded(user_access_changed);
  }

  return 0;
}

CGaiaCredentialProvider::GaiaCredentialComPtrStorage::
    GaiaCredentialComPtrStorage() = default;
CGaiaCredentialProvider::GaiaCredentialComPtrStorage::
    ~GaiaCredentialComPtrStorage() = default;

CGaiaCredentialProvider::ProviderConcurrentState::ProviderConcurrentState() =
    default;
CGaiaCredentialProvider::ProviderConcurrentState::~ProviderConcurrentState() =
    default;

bool CGaiaCredentialProvider::ProviderConcurrentState::
    RequestUserRefreshIfNeeded(bool user_access_changed) {
  base::AutoLock locker(state_update_lock_);
  if (auto_logon_credential_) {
    // Auto logon has precedence and has already signalled a credential changed,
    // save the user refresh for a later update and don't signal the change
    // again.
    if (user_access_changed)
      pending_users_refresh_needed_ = user_access_changed;
    return false;
  }

  // No auto logon present and there is a user access change or a pending
  // refresh to be executed. We clear the pending the refresh and just set
  // |users_need_to_be_refreshed_| to notify that a refresh is needed on the
  // next GetCredentialCount.
  if (user_access_changed || pending_users_refresh_needed_) {
    users_need_to_be_refreshed_ = true;
    pending_users_refresh_needed_ = false;
  }

  return users_need_to_be_refreshed_;
}

bool CGaiaCredentialProvider::ProviderConcurrentState::SetAutoLogonCredential(
    const Microsoft::WRL::ComPtr<IGaiaCredential>& auto_logon_credential) {
  base::AutoLock locker(state_update_lock_);
  // Always update the credential.
  auto_logon_credential_ = auto_logon_credential;
  if (auto_logon_credential_) {
    // If a previous user refresh was signalled, then the credential changed
    // was already sent. Don't need to send it again, but we need save the
    // user refresh for a later credential change event since auto logon has
    // precedence.
    if (users_need_to_be_refreshed_) {
      pending_users_refresh_needed_ = users_need_to_be_refreshed_;
      users_need_to_be_refreshed_ = false;
      return false;
    }

    return true;
  }

  // No auto logon credential was set, no need to send credential changed event.
  return false;
}

void CGaiaCredentialProvider::ProviderConcurrentState::GetUpdatedState(
    bool* needs_to_refresh_users,
    GaiaCredentialComPtrStorage* auto_logon_credential) {
  DCHECK(needs_to_refresh_users);
  DCHECK(auto_logon_credential);
  base::AutoLock locker(state_update_lock_);

  // States need to be mutually exclusive.
  DCHECK(!users_need_to_be_refreshed_ || !auto_logon_credential_);

  *needs_to_refresh_users = users_need_to_be_refreshed_;
  auto_logon_credential->gaia_cred = auto_logon_credential_;

  // No specific state set this cycle, maybe there was a user update pending
  // that should now be processed.
  if (!users_need_to_be_refreshed_ && !auto_logon_credential_) {
    *needs_to_refresh_users = pending_users_refresh_needed_;
    // Can now reset the pending refresh since it has been processed.
    pending_users_refresh_needed_ = false;
  }

  // State has been extracted for use, reset back to the default state.
  InternalReset();
}

void CGaiaCredentialProvider::ProviderConcurrentState::Reset() {
  base::AutoLock locker(state_update_lock_);
  InternalReset();

  // This is a full explicit reset, we also need to reset any pending refresh
  // that may be needed.
  pending_users_refresh_needed_ = false;
}

void CGaiaCredentialProvider::ProviderConcurrentState::InternalReset() {
  users_need_to_be_refreshed_ = false;
  auto_logon_credential_.Reset();
}

CGaiaCredentialProvider::CGaiaCredentialProvider() {}

CGaiaCredentialProvider::~CGaiaCredentialProvider() {}

HRESULT CGaiaCredentialProvider::FinalConstruct() {
  LOGFN(VERBOSE);
  CleanupOlderVersions();
  return S_OK;
}

void CGaiaCredentialProvider::FinalRelease() {
  LOGFN(VERBOSE);
  CHECK(!token_handle_updater_);
  ClearTransient();
  // Unlock all the users that had their access locked due to invalid token
  // handles.
  AssociatedUserValidator::Get()->AllowSigninForUsersWithInvalidTokenHandles();
}

HRESULT CGaiaCredentialProvider::DestroyCredentials() {
  LOGFN(VERBOSE);
  for (auto it = users_.begin(); it != users_.end(); ++it)
    (*it)->Terminate();

  users_.clear();
  return S_OK;
}

void CGaiaCredentialProvider::ClearTransient() {
  LOGFN(VERBOSE);
  CHECK(!token_handle_updater_);
  // Reset event support.
  advise_context_ = 0;
  events_.Reset();
  set_serialization_sid_.clear();
  concurrent_state_.Reset();
  user_array_.Reset();
}

void CGaiaCredentialProvider::CleanupOlderVersions() {
  base::FilePath versions_directory = GetInstallDirectory();
  if (!versions_directory.empty())
    DeleteVersionsExcept(versions_directory, TEXT(CHROME_VERSION_STRING));
}

HRESULT CGaiaCredentialProvider::CreateAnonymousCredentialIfNeeded(
    bool showing_other_user) {
  GaiaCredentialComPtrStorage cred;
  HRESULT hr = E_FAIL;
  if (showing_other_user) {
    hr = CreateCredentialObject<COtherUserGaiaCredential>(
        other_user_cred_creator_, &cred);
  } else if (CanNewUsersBeCreated(cpus_)) {
    hr =
        CreateCredentialObject<CGaiaCredential>(anonymous_cred_creator_, &cred);
  } else {
    return S_OK;
  }

  if (SUCCEEDED(hr)) {
    hr = cred.gaia_cred->Initialize(this);
    if (SUCCEEDED(hr)) {
      AddCredentialAndCheckAutoLogon(cred.gaia_cred, std::wstring(), nullptr);
    } else {
      LOG(ERROR) << "Could not create credential hr=" << putHR(hr);
    }
  }

  return hr;
}

HRESULT CGaiaCredentialProvider::CreateReauthCredentials(
    ICredentialProviderUserArray* users,
    GaiaCredentialComPtrStorage* auto_logon_credential) {
  std::map<std::wstring, std::pair<std::wstring, std::wstring>> sid_to_username;

  // Get the SIDs of all users being shown in the logon UI.
  if (!users) {
    LOGFN(ERROR) << "hr=" << putHR(E_INVALIDARG);
    return E_INVALIDARG;
  }

  HRESULT hr = users->SetProviderFilter(Identity_LocalUserProvider);
  if (FAILED(hr)) {
    LOGFN(ERROR) << "users->SetProviderFilter hr=" << putHR(hr);
    return hr;
  }

  DWORD count;
  hr = users->GetCount(&count);
  if (FAILED(hr)) {
    LOGFN(ERROR) << "users->GetCount hr=" << putHR(hr);
    return hr;
  }

  LOGFN(VERBOSE) << "count=" << count;
  reauth_cred_sids_.clear();

  for (DWORD i = 0; i < count; ++i) {
    Microsoft::WRL::ComPtr<ICredentialProviderUser> user;
    hr = users->GetAt(i, &user);
    if (FAILED(hr)) {
      LOGFN(ERROR) << "users->GetAt hr=" << putHR(hr);
      return hr;
    }

    wchar_t* sid_buffer = nullptr;

    hr = user->GetSid(&sid_buffer);
    if (FAILED(hr)) {
      LOGFN(ERROR) << "user->GetSid hr=" << putHR(hr);
      continue;
    }

    std::wstring sid = sid_buffer;
    ::CoTaskMemFree(sid_buffer);

    wchar_t username[kWindowsUsernameBufferLength];
    wchar_t domain[kWindowsDomainBufferLength];

    hr = OSUserManager::Get()->FindUserBySidWithFallback(
        sid.c_str(), username, std::size(username), domain, std::size(domain));
    if (FAILED(hr)) {
      LOGFN(ERROR) << "Can't get sid or username hr=" << putHR(hr);
      continue;
    }

    // Get the user's gaia id from registry stored against the sid if it
    // exists.
    wchar_t user_id[64];
    ULONG user_id_length = std::size(user_id);
    hr = GetUserProperty(sid.c_str(), kUserId, user_id, &user_id_length);
    if (FAILED(hr))
      user_id[0] = L'\0';

    bool is_token_handle_valid_for_user =
        (!AssociatedUserValidator::Get()->IsAuthEnforcedForUser(sid));

    // (1) If device doesn't have internet and if the device online login
    // attempt is not stale, then don't add the reauth credential.
    // Note: The stale online login attempt is checked only if IT admin
    // configured "validity_period_in_days" registry entry.
    // (2) For a domain joined user, only check for token validity if the
    // user id is not empty. If user id is empty, we should create the
    // reauth credential by default for all AD user sids.
    // (3) For a non-domain joined user, just check if the token handle is
    // valid. If valid, then no need to create a re-auth credential for
    // this sid.
    if (!AssociatedUserValidator::Get()->HasInternetConnection() &&
        !AssociatedUserValidator::Get()->IsOnlineLoginStale(sid)) {
      continue;
    } else if (CGaiaCredentialBase::IsCloudAssociationEnabled() &&
               OSUserManager::Get()->IsUserDomainJoined(sid)) {
      if (user_id[0] && is_token_handle_valid_for_user) {
        continue;
      }
    } else if (is_token_handle_valid_for_user) {
      // If the token handle is valid, no need to create a reauth credential.
      // The user can just sign in using their password.
      continue;
    }

    GaiaCredentialComPtrStorage cred;
    hr = CreateCredentialObject<CReauthCredential>(reauth_cred_creator_, &cred);
    if (FAILED(hr)) {
      LOG(ERROR) << "Could not create credential hr=" << putHR(hr);
      return hr;
    }

    hr =
        InitializeReauthCredential(this, sid, domain, username, cred.gaia_cred);
    if (FAILED(hr)) {
      LOG(ERROR) << "InitializeReauthCredential hr=" << putHR(hr);
      return hr;
    }

    AddCredentialAndCheckAutoLogon(cred.gaia_cred, sid, auto_logon_credential);

    // Add SID to the vector to keep track of all the users that have a reauth
    // credential created.
    reauth_cred_sids_.push_back(sid);

    LOGFN(VERBOSE) << "Reauth SID : " << sid;
  }

  // Deny sign in access for users that have a reauth credential added to them.
  AssociatedUserValidator::Get()->DenySigninForUsersWithInvalidTokenHandles(
      cpus_, reauth_cred_sids_);

  return S_OK;
}

void CGaiaCredentialProvider::AddCredentialAndCheckAutoLogon(
    const Microsoft::WRL::ComPtr<IGaiaCredential>& cred,
    const std::wstring& sid,
    GaiaCredentialComPtrStorage* auto_logon_credential) {
  USES_CONVERSION;
  users_.emplace_back(cred);

  if (!auto_logon_credential)
    return;

  if (sid.empty())
    return;

  if (set_serialization_sid_.empty())
    return;

  // If serialization sid is set, then try to see if this credential is a reauth
  // credential that needs to be auto signed in.
  Microsoft::WRL::ComPtr<IReauthCredential> associated_user;
  if (FAILED(cred.As(&associated_user)))
    return;

  if (set_serialization_sid_ != sid)
    return;

  auto_logon_credential->gaia_cred = cred;
  set_serialization_sid_.clear();
}

void CGaiaCredentialProvider::RecreateCredentials(
    GaiaCredentialComPtrStorage* auto_logon_credential) {
  LOGFN(VERBOSE);
  DCHECK(user_array_);

  DestroyCredentials();

  CREDENTIAL_PROVIDER_ACCOUNT_OPTIONS options;
  HRESULT hr = user_array_->GetAccountOptions(&options);
  bool showing_other_user =
      SUCCEEDED(hr) && options != CPAO_EMPTY_CONNECTED && options != CPAO_NONE;
  hr = CreateAnonymousCredentialIfNeeded(showing_other_user);
  if (FAILED(hr))
    LOG(ERROR) << "Could not create anonymous credential hr=" << putHR(hr);

  hr = CreateReauthCredentials(user_array_.Get(), auto_logon_credential);
  if (FAILED(hr))
    LOG(ERROR) << "CreateReauthCredentials hr=" << putHR(hr);
}

void CGaiaCredentialProvider::SetCredentialCreatorFunctionsForTesting(
    CredentialCreatorFn anonymous_cred_creator,
    CredentialCreatorFn other_user_cred_creator,
    CredentialCreatorFn reauth_cred_creator) {
  DCHECK(!anonymous_cred_creator_);
  DCHECK(!other_user_cred_creator_);
  DCHECK(!reauth_cred_creator_);

  anonymous_cred_creator_ = anonymous_cred_creator;
  other_user_cred_creator_ = other_user_cred_creator;
  reauth_cred_creator_ = reauth_cred_creator;
}

HRESULT CGaiaCredentialProvider::OnUserAuthenticatedImpl(
    IUnknown* credential,
    BSTR /*username*/,
    BSTR /*password*/,
    BSTR sid,
    BOOL fire_credentials_changed) {
  DCHECK(!credential || sid);

  if (!fire_credentials_changed)
    return S_OK;

  // Ensure that user access cannot be denied at this time so that the user
  // that is about to sign in won't be locked. If a ScopedLockDenyAccessUpdate
  // is created before calling this function this should guarantee that
  // situation because the call to BlockDenyAccessUpdate is locked with the
  // same lock that is used in DenySigninForUsersWithInvalidTokenHandles.
  // So either the call to Deny has finished and no new deny will occur
  // afterwards or the Deny will be disabled because the block has been
  // incremented first.
  CHECK(!credential ||
        AssociatedUserValidator::Get()->IsDenyAccessUpdateBlocked());

  Microsoft::WRL::ComPtr<IGaiaCredential> gaia_credential;
  if (credential->QueryInterface(IID_PPV_ARGS(&gaia_credential)) == S_OK) {
    // Try to set the auto logon credential. If it succeeds we can raise a
    // credential changed event.
    if (concurrent_state_.SetAutoLogonCredential(gaia_credential) && events_)
      events_->CredentialsChanged(advise_context_);
  }

  LOGFN(VERBOSE) << "Signing in authenticated sid=" << OLE2CW(sid);
  return S_OK;
}

// Static.
bool CGaiaCredentialProvider::IsUsageScenarioSupported(
    CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus) {
  return cpus == CPUS_LOGON || cpus == CPUS_UNLOCK_WORKSTATION;
}

// Static.
bool CGaiaCredentialProvider::CanNewUsersBeCreated(
    CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus) {
  // When in an unlock usage, only the user that locked the computer will be
  // allowed to sign in, so no new users can be added.
  if (cpus == CPUS_UNLOCK_WORKSTATION)
    return false;

  bool enable_multi_user_login =
      GetGlobalFlagOrDefault(kRegMdmSupportsMultiUser, 1) != 0;
  if (DevicePoliciesManager::Get()->CloudPoliciesEnabled()) {
    DevicePolicies policies;
    DevicePoliciesManager::Get()->GetDevicePolicies(&policies);
    enable_multi_user_login = policies.enable_multi_user_login;
  }

  return enable_multi_user_login ||
         !AssociatedUserValidator::Get()->GetAssociatedUsersCount();
}

// ICredentialUpdateEventsHandler //////////////////////////////////////////////

void CGaiaCredentialProvider::UpdateCredentialsIfNeeded(
    bool user_access_changed) {
  // Defer refresh of the users to the next GetCredentialCount that will be
  // called after the credentials changed event. This prevents potential
  // contention of the |users_| list in multiple places. If the call to
  // RequestUserRefreshIfNeeded returns true, this means we are allowed to
  // proceed with a credentials changed event. Otherwise either no credentials
  // need to be updated (|user_access_changed| == false) or a higher priority
  // update is pending (e.g. auto logon) and thus we cannot send a
  // credentials changed event.
  if (concurrent_state_.RequestUserRefreshIfNeeded(user_access_changed) &&
      events_) {
    events_->CredentialsChanged(advise_context_);
  }
}

// IGaiaCredentialProvider ////////////////////////////////////////////////////

HRESULT CGaiaCredentialProvider::GetUsageScenario(DWORD* cpus) {
  DCHECK(cpus);
  *cpus = static_cast<DWORD>(cpus_);
  return S_OK;
}

HRESULT CGaiaCredentialProvider::OnUserAuthenticated(
    IUnknown* credential,
    BSTR username,
    BSTR password,
    BSTR sid,
    BOOL fire_credentials_changed) {
  return OnUserAuthenticatedImpl(credential, username, password, sid,
                                 fire_credentials_changed);
}

// ICredentialProviderSetUserArray ////////////////////////////////////////////

HRESULT CGaiaCredentialProvider::SetUserArray(
    ICredentialProviderUserArray* users) {
  LOGFN(VERBOSE);
  CHECK(!token_handle_updater_);

  if (!IsUsageScenarioSupported(cpus_))
    return S_OK;

  if (users_.size() > 0) {
    LOG(ERROR) << "Users should be empty";
    return E_UNEXPECTED;
  }

  user_array_ = users;
  // Force refresh of all users on the next GetCredentialCount.
  concurrent_state_.RequestUserRefreshIfNeeded(true);

  return S_OK;
}

// ICredentialProvider ////////////////////////////////////////////////////////

HRESULT CGaiaCredentialProvider::SetUsageScenario(
    CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
    DWORD flags) {
  ClearTransient();
  CHECK(!token_handle_updater_);

  cpus_ = cpus;
  cpus_flags_ = flags;

  LOGFN(VERBOSE) << " cpu=" << cpus << " flags=" << std::setbase(16) << flags;
  return IsUsageScenarioSupported(cpus_) ? S_OK : E_NOTIMPL;
}

HRESULT CGaiaCredentialProvider::SetSerialization(
    const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs) {
  DCHECK(pcpcs);
  CHECK(!token_handle_updater_);

  if (pcpcs->clsidCredentialProvider != CLSID_GaiaCredentialProvider)
    return E_NOTIMPL;

  ULONG auth_package_id;
  HRESULT hr = GetAuthenticationPackageId(&auth_package_id);
  if (FAILED(hr))
    return E_NOTIMPL;

  if (pcpcs->ulAuthenticationPackage != auth_package_id)
    return E_NOTIMPL;

  if (pcpcs->cbSerialization == 0)
    return E_NOTIMPL;

  // If serialziation data is set, try to extract the sid for the user
  // referenced in the serialization data.
  hr = DetermineUserSidFromAuthenticationBuffer(pcpcs, &set_serialization_sid_);
  if (FAILED(hr))
    LOGFN(ERROR) << "DetermineUserSidFromAuthenticationBuffer hr=" << putHR(hr);

  return S_OK;
}

HRESULT CGaiaCredentialProvider::Advise(ICredentialProviderEvents* pcpe,
                                        UINT_PTR context) {
  DCHECK(pcpe);
  CHECK(!token_handle_updater_);

  events_ = pcpe;
  advise_context_ = context;

  if (AssociatedUserValidator::Get()->IsUserAccessBlockingEnforced(cpus_)) {
    token_handle_updater_ = std::make_unique<BackgroundTokenHandleUpdater>(
        this, &reauth_cred_sids_);
  }

  return S_OK;
}

HRESULT CGaiaCredentialProvider::UnAdvise() {
  LOGFN(VERBOSE);

  // Kill the updater thread (if any).
  token_handle_updater_.reset();

  ClearTransient();
  DestroyCredentials();

  // Delete the startup sentinel file if any still exists. It can still exist in
  // 2 cases:

  // 1. The UnAdvise should only occur after the user has logged in, so if they
  // never selected any gaia credential and just used normal credentials this
  // function will be called in that situation and it is guaranteed that the
  // user has at least been able provide some input to winlogon.
  // 2. When no usage scenario is supported, none of the credentials will be
  // selected and thus the gcpw startup sentinel file will not be deleted. So in
  // the case where the user is asked for CPUS_CRED_UI enough times, the
  // sentinel file size will keep growing without being deleted and eventually
  // GCPW will be disabled completely. In the unsupported usage scenario,
  // FinalRelease will be called shortly after SetUsageScenario if the function
  // returns E_NOTIMPL so try to catch potential crashes of the destruction of
  // the provider when it is not used because crashes in this case will prevent
  // the cred ui from coming up and not allow the user to access their desired
  // resource.
  DeleteStartupSentinel();

  return S_OK;
}

HRESULT CGaiaCredentialProvider::GetFieldDescriptorCount(DWORD* count) {
  *count = FIELD_COUNT;
  return S_OK;
}

HRESULT CGaiaCredentialProvider::GetFieldDescriptorAt(
    DWORD index,
    CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR** ppcpfd) {
  *ppcpfd = nullptr;
  HRESULT hr = E_INVALIDARG;
  if (index < FIELD_COUNT) {
    // Always return a CoTask copy of the structure as well as any strings
    // pointed to by that structure.
    *ppcpfd = reinterpret_cast<CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR*>(
        ::CoTaskMemAlloc(sizeof(**ppcpfd)));
    if (*ppcpfd) {
      **ppcpfd = g_field_desc[index];
      // The password field has special greyed out text that is not set through
      // calls to ICredentialProviderCredential::GetStringValue so we need to
      // localize it manually here.
      if (index == FID_CURRENT_PASSWORD_FIELD) {
        std::wstring password_label(
            GetStringResource(IDS_WINDOWS_PASSWORD_FIELD_LABEL_BASE));
        hr = ::SHStrDupW(password_label.c_str(), &(*ppcpfd)->pszLabel);
      } else if ((*ppcpfd)->pszLabel) {
        hr = ::SHStrDupW((*ppcpfd)->pszLabel, &(*ppcpfd)->pszLabel);
      } else {
        (*ppcpfd)->pszLabel = nullptr;
        hr = S_OK;
      }
    }

    if (FAILED(hr)) {
      ::CoTaskMemFree(*ppcpfd);
      *ppcpfd = nullptr;
    }
  }

  return hr;
}

HRESULT CGaiaCredentialProvider::GetCredentialCount(
    DWORD* count,
    DWORD* default_index,
    BOOL* autologin_with_default) {
  bool needs_to_refresh_users = false;
  GaiaCredentialComPtrStorage local_auto_logon_credential;

  // Get the mutually exclusive state of the provider so that we can
  // determine the correct next step (recreate credentials or auto logon).
  concurrent_state_.GetUpdatedState(&needs_to_refresh_users,
                                    &local_auto_logon_credential);

  // NOTE: assumes SetUserArray() is called before this.
  if (needs_to_refresh_users)
    RecreateCredentials(&local_auto_logon_credential);

  *count = users_.size();
  *default_index = CREDENTIAL_PROVIDER_NO_DEFAULT;
  *autologin_with_default = false;

  // If a user was authenticated, winlogon was notified of credentials changes
  // and is re-enumerating the credentials.  Make sure autologin is enabled.
  if (local_auto_logon_credential.gaia_cred) {
    // Find the index of the credential that should contain the authentication
    // information.
    for (size_t i = 0;
         i < users_.size() && *default_index == CREDENTIAL_PROVIDER_NO_DEFAULT;
         ++i) {
      if (local_auto_logon_credential.gaia_cred == users_[i])
        *default_index = i;
    }

    *autologin_with_default = *default_index != CREDENTIAL_PROVIDER_NO_DEFAULT;
  }

  LOGFN(VERBOSE) << " count=" << *count << " default=" << *default_index
                 << " auto=" << *autologin_with_default;
  return S_OK;
}

HRESULT CGaiaCredentialProvider::GetCredentialAt(
    DWORD index,
    ICredentialProviderCredential** ppcpc) {
  HRESULT hr = E_INVALIDARG;
  if (!ppcpc || index >= users_.size()) {
    LOG(ERROR) << "hr=" << putHR(hr) << " index=" << index;
    return hr;
  }

  *ppcpc = nullptr;
  hr = users_[index]->QueryInterface(IID_ICredentialProviderCredential,
                                     (void**)ppcpc);

  LOGFN(VERBOSE) << "hr=" << putHR(hr) << " index=" << index;
  return hr;
}

}  // namespace credential_provider