| // Copyright 2023 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/osauth/impl/auth_hub_impl.h" |
| #include <memory> |
| #include <utility> |
| |
| #include "base/check.h" |
| #include "base/check_op.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/notreached.h" |
| #include "chromeos/ash/components/osauth/impl/auth_factor_presence_cache.h" |
| #include "chromeos/ash/components/osauth/impl/auth_hub_common.h" |
| #include "chromeos/ash/components/osauth/impl/auth_hub_mode_lifecycle.h" |
| #include "chromeos/ash/components/osauth/impl/auth_hub_vector_lifecycle.h" |
| #include "chromeos/ash/components/osauth/public/auth_attempt_consumer.h" |
| #include "chromeos/ash/components/osauth/public/auth_factor_engine_factory.h" |
| #include "chromeos/ash/components/osauth/public/auth_factor_status_consumer.h" |
| #include "chromeos/ash/components/osauth/public/common_types.h" |
| #include "chromeos/ash/components/osauth/public/string_utils.h" |
| #include "components/account_id/account_id.h" |
| |
| namespace ash { |
| |
| AuthHubImpl::AuthHubImpl(AuthFactorPresenceCache* factor_cache) |
| : cache_(factor_cache) { |
| mode_lifecycle_ = std::make_unique<AuthHubModeLifecycle>(this); |
| } |
| |
| AuthHubImpl::~AuthHubImpl() = default; |
| |
| void AuthHubImpl::InitializeForMode(AuthHubMode target) { |
| CHECK_NE(target, AuthHubMode::kNone); |
| SwitchToModeImpl(target); |
| } |
| |
| void AuthHubImpl::CancelCurrentAttempt(AuthHubConnector* connector) { |
| if (attempt_handler_->HasOngoingAttempt()) { |
| attempt_handler_->PrepareForShutdown( |
| base::BindOnce(&AuthHubImpl::OnFactorAttemptFinishedForCancel, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| vector_lifecycle_->CancelAttempt(); |
| } |
| |
| void AuthHubImpl::Shutdown() { |
| SwitchToModeImpl(AuthHubMode::kNone); |
| } |
| |
| void AuthHubImpl::SwitchToModeImpl(AuthHubMode target) { |
| if (vector_lifecycle_ && !vector_lifecycle_->IsIdle()) { |
| target_mode_ = target; |
| // Eventually, after the current attempt gets canceled, `OnIdle()` will be |
| // triggered, which then switches the mode to `target_mode_`. |
| if (attempt_handler_->HasOngoingAttempt()) { |
| attempt_handler_->PrepareForShutdown( |
| base::BindOnce(&AuthHubImpl::OnFactorAttemptFinishedForCancel, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| vector_lifecycle_->CancelAttempt(); |
| return; |
| } |
| mode_lifecycle_->SwitchToMode(target); |
| } |
| |
| void AuthHubImpl::OnFactorAttemptFinishedForCancel() { |
| vector_lifecycle_->CancelAttempt(); |
| } |
| |
| void AuthHubImpl::EnsureInitialized(base::OnceClosure on_initialized) { |
| if (mode_lifecycle_->IsReady()) { |
| std::move(on_initialized).Run(); |
| return; |
| } |
| on_initialized_listeners_.AddUnsafe(std::move(on_initialized)); |
| } |
| |
| void AuthHubImpl::StartAuthentication(AccountId account_id, |
| AuthPurpose purpose, |
| AuthAttemptConsumer* consumer) { |
| if (!PurposeMatchesMode(purpose, mode_lifecycle_->GetCurrentMode())) { |
| LOG(ERROR) << "Attempt for " << purpose |
| << " rejected due to incorrect mode " |
| << mode_lifecycle_->GetCurrentMode(); |
| consumer->OnUserAuthAttemptRejected(); |
| return; |
| } |
| |
| CHECK(vector_lifecycle_); |
| AuthAttemptVector attempt{account_id, purpose}; |
| |
| if (current_attempt_.has_value()) { |
| // If we have two login attempts, let new attempt take |
| // over the existing one. |
| if (AttemptShouldOverrideAnother(attempt, *current_attempt_)) { |
| LOG(WARNING) << "Overriding ongoing attempt"; |
| pending_attempt_ = attempt; |
| pending_consumer_ = consumer; |
| if (attempt_handler_->HasOngoingAttempt()) { |
| attempt_handler_->PrepareForShutdown( |
| base::BindOnce(&AuthHubImpl::OnFactorAttemptFinishedForCancel, |
| weak_factory_.GetWeakPtr())); |
| return; |
| } |
| vector_lifecycle_->CancelAttempt(); |
| return; |
| } |
| if (AttemptShouldOverrideAnother(*current_attempt_, attempt)) { |
| LOG(WARNING) << "Attempt rejected: another higher-priority attempt"; |
| consumer->OnUserAuthAttemptRejected(); |
| return; |
| } |
| // Neither attempt is considered "Stronger" one, |
| // so we should preserve ongoing one. |
| LOG(WARNING) << "Attempt rejected: another same-priority attempt"; |
| consumer->OnUserAuthAttemptRejected(); |
| return; |
| } |
| if (pending_attempt_.has_value()) { |
| // If we have two login attempts, let new attempt take |
| // over the pending one. |
| if (AttemptShouldOverrideAnother(attempt, *pending_attempt_)) { |
| LOG(WARNING) << "Overriding pending attempt"; |
| pending_consumer_->OnUserAuthAttemptRejected(); |
| // Override pending attempt. |
| pending_attempt_ = attempt; |
| pending_consumer_ = consumer; |
| return; |
| } |
| if (AttemptShouldOverrideAnother(*pending_attempt_, attempt)) { |
| LOG(WARNING) |
| << "Attempt rejected: another higher-priority pending attempt"; |
| consumer->OnUserAuthAttemptRejected(); |
| return; |
| } |
| // Neither attempt is considered "Stronger" one, |
| // so we should preserve pending one. |
| LOG(WARNING) << "Attempt rejected: pending same-priority attempt"; |
| consumer->OnUserAuthAttemptRejected(); |
| return; |
| } |
| |
| CHECK(!attempt_consumer_); |
| CHECK(!attempt_handler_); |
| attempt_consumer_ = consumer; |
| current_attempt_ = attempt; |
| |
| AuthFactorsSet cached_factors = |
| cache_->GetExpectedFactorsPresence(*current_attempt_); |
| |
| attempt_handler_ = std::make_unique<AuthHubAttemptHandler>( |
| this, *current_attempt_, engines_, cached_factors); |
| raw_ptr<AuthFactorStatusConsumer> status_consumer; |
| attempt_consumer_->OnUserAuthAttemptConfirmed( |
| attempt_handler_->GetConnector(), status_consumer); |
| attempt_handler_->SetConsumer(status_consumer); |
| |
| vector_lifecycle_->StartAttempt(*current_attempt_); |
| } |
| |
| bool AuthHubImpl::PurposeMatchesMode(AuthPurpose purpose, AuthHubMode mode) { |
| switch (mode) { |
| case AuthHubMode::kLoginScreen: |
| return purpose == AuthPurpose::kLogin; |
| case AuthHubMode::kInSession: |
| return purpose != AuthPurpose::kLogin; |
| case AuthHubMode::kNone: |
| NOTREACHED(); |
| } |
| } |
| |
| bool AuthHubImpl::AttemptShouldOverrideAnother( |
| const AuthAttemptVector& first, |
| const AuthAttemptVector& second) { |
| if (first.purpose == AuthPurpose::kLogin && |
| second.purpose == AuthPurpose::kLogin) { |
| // New login attempt always overrides previous. |
| return true; |
| } |
| // All login cases should be covered by check above + `PurposeMatchesMode`. |
| CHECK_NE(first.purpose, AuthPurpose::kLogin); |
| CHECK_NE(second.purpose, AuthPurpose::kLogin); |
| if (first.purpose == AuthPurpose::kScreenUnlock) { |
| // Lock screen always overrides any other attempt. |
| return true; |
| } |
| if (second.purpose == AuthPurpose::kScreenUnlock) { |
| // Nothing in-session can override lock screen. |
| return false; |
| } |
| // Currently various in-session attempts should not override ongoing attempt. |
| return false; |
| } |
| |
| // AuthHubModeLifecycle::Owner: |
| |
| void AuthHubImpl::OnReadyForMode(AuthHubMode mode, |
| AuthEnginesMap available_engines) { |
| CHECK(engines_.empty()); |
| CHECK(!vector_lifecycle_); |
| |
| engines_ = std::move(available_engines); |
| vector_lifecycle_ = |
| std::make_unique<AuthHubVectorLifecycle>(this, mode, engines_); |
| |
| on_initialized_listeners_.Notify(); |
| } |
| |
| void AuthHubImpl::OnExitedMode(AuthHubMode mode) { |
| engines_.clear(); |
| vector_lifecycle_.reset(); |
| } |
| |
| void AuthHubImpl::OnModeShutdown() {} |
| |
| // AuthHubVectorLifecycle::Owner: |
| |
| AuthFactorEngine::FactorEngineObserver* AuthHubImpl::AsEngineObserver() { |
| CHECK(attempt_handler_); |
| return attempt_handler_.get(); |
| } |
| |
| void AuthHubImpl::OnAttemptStarted(const AuthAttemptVector& attempt, |
| AuthFactorsSet available_factors, |
| AuthFactorsSet failed_factors) { |
| CHECK(attempt == *current_attempt_); |
| CHECK(attempt_handler_); |
| attempt_handler_->OnFactorsChecked(available_factors, failed_factors); |
| } |
| |
| void AuthHubImpl::OnAttemptCleanedUp(const AuthAttemptVector& attempt) { |
| CHECK(attempt == *current_attempt_); |
| if (authenticated_factor_.has_value()) { |
| AuthProofToken token = |
| engines_[*authenticated_factor_]->StoreAuthenticationContext(); |
| attempt_consumer_->OnUserAuthSuccess(*authenticated_factor_, token); |
| authenticated_factor_.reset(); |
| } |
| } |
| |
| void AuthHubImpl::OnAttemptFinished(const AuthAttemptVector& attempt) { |
| CHECK(attempt == *current_attempt_); |
| attempt_consumer_ = nullptr; |
| current_attempt_.reset(); |
| attempt_handler_.reset(); |
| } |
| |
| void AuthHubImpl::OnAttemptCancelled(const AuthAttemptVector& attempt) { |
| CHECK(attempt == *current_attempt_); |
| attempt_consumer_->OnUserAuthAttemptCancelled(); |
| attempt_consumer_ = nullptr; |
| current_attempt_.reset(); |
| attempt_handler_.reset(); |
| } |
| |
| void AuthHubImpl::OnIdle() { |
| if (target_mode_.has_value()) { |
| if (pending_attempt_.has_value()) { |
| // Cancel pending attempt. |
| pending_consumer_->OnUserAuthAttemptRejected(); |
| target_mode_.reset(); |
| pending_consumer_ = nullptr; |
| } |
| // We can get into this branch if attempt was never |
| // started/finished, e.g. if Mode change was requested before |
| // one of the engines started. |
| if (attempt_consumer_) { |
| CHECK(current_attempt_); |
| CHECK(attempt_handler_); |
| attempt_consumer_->OnUserAuthAttemptCancelled(); |
| attempt_consumer_ = nullptr; |
| current_attempt_.reset(); |
| attempt_handler_.reset(); |
| } |
| AuthHubMode mode = *target_mode_; |
| target_mode_.reset(); |
| SwitchToModeImpl(mode); |
| return; |
| } |
| |
| if (pending_attempt_.has_value()) { |
| AuthAttemptVector attempt = *pending_attempt_; |
| AuthAttemptConsumer* consumer = pending_consumer_.get(); |
| |
| pending_consumer_ = nullptr; |
| pending_attempt_.reset(); |
| |
| StartAuthentication(attempt.account, attempt.purpose, consumer); |
| return; |
| } |
| } |
| |
| void AuthHubImpl::UpdateFactorUiCache(const AuthAttemptVector& attempt, |
| AuthFactorsSet available_factors) { |
| CHECK(attempt == *current_attempt_); |
| cache_->StoreFactorPresenceCache(attempt, available_factors); |
| } |
| |
| void AuthHubImpl::OnFactorAttemptFailed(const AuthAttemptVector& attempt, |
| AshAuthFactor factor) { |
| CHECK(attempt == *current_attempt_); |
| attempt_consumer_->OnFactorAttemptFailed(factor); |
| } |
| |
| void AuthHubImpl::OnAuthenticationSuccess(const AuthAttemptVector& attempt, |
| AshAuthFactor factor) { |
| CHECK(attempt == *current_attempt_); |
| CHECK(engines_.contains(factor)); |
| |
| // Record the authenticated factor and start terminating all engines. |
| // AuthProofToken will be retrieved after all auth engines clean up their |
| // internal states during the termination process, to avoid race conditions |
| // on authenticated UserContext. |
| authenticated_factor_ = factor; |
| vector_lifecycle_->CancelAttempt(); |
| } |
| |
| } // namespace ash |