chromium/chrome/credential_provider/gaiacp/associated_user_validator.h

// 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.

#ifndef CHROME_CREDENTIAL_PROVIDER_GAIACP_ASSOCIATED_USER_VALIDATOR_H_
#define CHROME_CREDENTIAL_PROVIDER_GAIACP_ASSOCIATED_USER_VALIDATOR_H_

#include <credentialprovider.h>

#include <map>
#include <set>
#include <string>

#include "base/memory/raw_ptr.h"
#include "base/synchronization/lock.h"
#include "base/time/time.h"
#include "base/win/scoped_handle.h"

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

namespace credential_provider {

// Caches the current validity of token handles and updates the validity if
// it is older than a specified validity lifetime.
// NOTE: This class is thread safe.
//
// The following functions are called at a time when it is impossible for
// the validator to be accessed by multiple threads. The validator will only
// be accessed from another thread through the BackgroundTokenHandleUpdater
// that is created in CGaiaCredentialProvider::Advise and destroyed in
// CGaiaCredentialProvider::Unadvise:
// StartRefreshingTokenHandleValidity: Only called on the main thread during
// a call to DllGetClassObject.
// IsUserAccessBlockingEnforced: Only called on the main thread in
//   CGaiaCredentialProvider::Advise and in
//   CGaiaCredentialProviderFilter::UpdateRemoteCredential.
// AllowSigninForUsersWithInvalidTokenHandles: Only called on the main thread
//   in CGaiaCredentialProvider::FinalRelease.
// AllowSigninForAllAssociatedUsers: Only called on the main thread in
//   CGaiaCredentialProviderFilter::Filter.
//
// The following functions can be called while the validator can be accessed
// from another thread:
// IsAuthEnforcedForUser: Called on the main thread indirectly in
// CGaiaCredentialProvider::GetCredentialCount. Also called on the update
// thread while checking DenySigninForUsersWithInvalidTokenHandles.
// GetAssociatedUsersCount: Only called on the main thread indirectly in
// CGaiaCredentialProvider::GetCredentialCount.
// RestoreUserAccess: Only called on the main thread in
// CGaiaCredentialBase::HandleAutologon.
//
// Finally the one function that can be called on the update thread is
// DenySigninForUsersWithInvalidTokenHandles. If this function returns
// true, it will queue a credential update which will only be executed
// on the main thread. The update thread will then be dormant for
// |kTokenHandleValidityLifetime| seconds and in this time the expected
// update of the credentials on the main thread via a call to
// CGaiaCredentialProvider::GetCredentialCount should be able to complete
// before a new update is requested on the update thread. This timing will
// protect the two functions IsAuthEnforcedForUser and
// GetAssociatedUsersCount from being called by multiple threads at the same
// time.
class AssociatedUserValidator {
 public:
  // Prevent update of user access through the call to
  // DenySigninForUsersWithInvalidTokenHandles. This will be used to prevent
  // locking out users that are in the process of signing in.
  class ScopedBlockDenyAccessUpdate {
   public:
    explicit ScopedBlockDenyAccessUpdate(AssociatedUserValidator* validator);
    ~ScopedBlockDenyAccessUpdate();

   private:
    raw_ptr<AssociatedUserValidator> validator_;
  };
  // Default timeout when querying token info for token handles. If a timeout
  // occurs the token handle is assumed to be valid.
  static const base::TimeDelta kDefaultTokenHandleValidationTimeout;

  // Minimum time between token handle info refreshes. When trying to get token
  // info, if the info is older than this value, a new token info query will
  // be made.
  static const base::TimeDelta kTokenHandleValidityLifetime;

  // Default URL used to fetch token info for token handles.
  static const char kTokenInfoUrl[];

  static AssociatedUserValidator* Get();

  // Get all the token handles for all associated users and start queries
  // for their validity. The queries are fired in separate threads but
  // no wait is done for the result. This allows background processing of
  // the queries until they are actually needed. An eventual call to
  // IsAuthEnforcedForUser will cause the wait for the result as needed.
  void StartRefreshingTokenHandleValidity();

  // Checks whether the token handle for the given user is valid or not.
  // This function is blocking and may fire off a query for a token handle that
  // needs to complete before the function returns.
  bool IsAuthEnforcedForUser(const std::wstring& sid);

  enum EnforceAuthReason {
    NOT_ENFORCED = 0,
    NOT_ENROLLED_WITH_MDM,
    MISSING_PASSWORD_RECOVERY_INFO,
    INVALID_TOKEN_HANDLE,
    ONLINE_LOGIN_STALE,
    UPLOAD_DEVICE_DETAILS_FAILED,
    ONLINE_LOGIN_ENFORCED,
    MISSING_OR_STALE_USER_POLICIES
  };

  // Returns the reason for enforcing authentication for the provided |sid|.
  // This function is blocking and may fire off a query for a token handle that
  // needs to complete before the function returns.
  EnforceAuthReason GetAuthEnforceReason(const std::wstring& sid);

  // Checks if user access blocking is enforced given the usage scenario (and
  // other registry based checks).
  bool IsUserAccessBlockingEnforced(
      CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus) const;

  // Goes through all reauth creds found and denies their access to sign
  // in to the system based on the auth reason being not enforced. Returns true
  // if a user has just been denied signin access.
  bool DenySigninForUsersWithInvalidTokenHandles(
      CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
      const std::vector<std::wstring>& reauth_sids);

  // Restores the access for a user that was denied access (if applicable).
  // Returns S_OK on success, failure otherwise.
  HRESULT RestoreUserAccess(const std::wstring& sid);

  // Allows access for all users that have had their access denied by this
  // token validator.
  void AllowSigninForUsersWithInvalidTokenHandles();

  // Restores access to all associated users. Regardless of their access
  // state. This ensures that no user can be completely locked out due
  // a bad computer state or crash.
  void AllowSigninForAllAssociatedUsers(
      CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus);

  // Gets the updated count of valid associated users that exist on this system.
  size_t GetAssociatedUsersCount();

  // Returns whether the user should be locked out of sign in (only used in
  // tests).
  bool IsDenyAccessUpdateBlocked() const;

  bool HasInternetConnection() const;

  // Checks for the staleness of the last successful GCPW login for the input
  // user.
  bool IsOnlineLoginStale(const std::wstring& sid) const;

  // Keeps the sid mapping in gcpw registry up to date with the latest token
  // handle information.
  HRESULT UpdateAssociatedSids(
      std::map<std::wstring, std::wstring>* sid_to_handle);

 protected:
  // Returns the storage used for the instance pointer.
  static AssociatedUserValidator** GetInstanceStorage();

  explicit AssociatedUserValidator(base::TimeDelta validation_timeout);
  virtual ~AssociatedUserValidator();

  // Returns whether the user should be locked out of sign in (only used in
  // tests).
  bool IsUserAccessBlockedForTesting(const std::wstring& sid) const;

  // Forces a refresh of all token handles the next time they are queried.
  // This function should only be called in tests.
  void ForceRefreshTokenHandlesForTesting();

 private:
  void CheckTokenHandleValidity(
      const std::map<std::wstring, std::wstring>& handles_to_verify);
  void StartTokenValidityQuery(const std::wstring& sid,
                               const std::wstring& token_handle,
                               base::TimeDelta timeout);
  bool IsTokenHandleValidForUser(const std::wstring& sid);
  bool IsUserAssociated(const std::wstring& sid);
  bool HasInvokedUpdateAssociatedSids();

  // Stores information about the current state of a user's token handle.
  // This information includes:
  //   * The last token handle found for the user.
  //   * The validity of this last token handle (if checked).
  //   * The time of the last update of the validity of this token handle.
  //     This will often be the max end time of the last query that was made on
  //     the token handle.
  //   * The handle to the current thread being executed to verify the
  //     validity of the last token handle.
  struct TokenHandleInfo {
    TokenHandleInfo();
    ~TokenHandleInfo();

    // Used when the handle is empty or invalid.
    explicit TokenHandleInfo(const std::wstring& token_handle);

    // Used to create a new token handle info that needs to query validity.
    // The validity is assumed to be invalid at the time of construction.
    TokenHandleInfo(const std::wstring& token_handle,
                    base::Time update_time,
                    base::win::ScopedHandle::Handle thread_handle);

    std::wstring queried_token_handle;
    bool is_valid = false;
    base::Time last_update;
    base::win::ScopedHandle pending_query_thread;
  };

  // Increments / decrements |block_deny_access_update_| to prevent denying
  // user access when a token handle becomes invalid. Only called via a
  // ScopedBlockDenyAccessUpdate object.
  void BlockDenyAccessUpdate();
  void UnblockDenyAccessUpdate();

  // Maps a user's sid to the token handle info associated with this user (if
  // any).
  std::map<std::wstring, std::unique_ptr<TokenHandleInfo>>
      user_to_token_handle_info_;
  base::TimeDelta validation_timeout_;
  std::set<std::wstring> locked_user_sids_;
  mutable base::Lock validator_lock_;

  // When |block_deny_access_update_| != 0, prevent users from being denied
  // access when DenySigninForUsersWithInvalidTokenHandles is called. This
  // prevents users from being locked out while signing is occurring but a token
  // handle update is also being requested at the same time. The functions
  // LockDenyAccessUpdate / UnlockDenyAccessUpdate are called in the lifetime of
  // a ScopedBlockDenyAccessUpdate to update this member.
  size_t block_deny_access_update_ = 0;

  // Keeps track of whether "UpdateAssociatedSids" method is invoked at least
  // once while creation of reauth credentials in the LoginUI.
  bool has_invoked_update_associated_sids_ = false;
};

}  // namespace credential_provider

#endif  // CHROME_CREDENTIAL_PROVIDER_GAIACP_ASSOCIATED_USER_VALIDATOR_H_