| // Copyright 2017 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "peridot/lib/firebase_auth/firebase_auth_impl.h" |
| |
| #include <utility> |
| |
| #include <lib/backoff/exponential_backoff.h> |
| #include <lib/callback/cancellable_helper.h> |
| #include <lib/fit/function.h> |
| #include <lib/fsl/vmo/file.h> |
| #include <lib/fxl/functional/closure.h> |
| #include <lib/fxl/functional/make_copyable.h> |
| |
| namespace firebase_auth { |
| namespace { |
| |
| constexpr char kConfigBinProtoPath[] = |
| "/pkg/data/firebase_auth_cobalt_config.pb"; |
| constexpr int32_t kCobaltAuthFailureMetricId = 4; |
| |
| // Returns true if the authentication failure may be transient. |
| bool IsRetriableError(fuchsia::auth::Status status) { |
| switch (status) { |
| case fuchsia::auth::Status::OK: // This should never happen. |
| case fuchsia::auth::Status::AUTH_PROVIDER_SERVER_ERROR: |
| case fuchsia::auth::Status::AUTH_PROVIDER_SERVICE_UNAVAILABLE: |
| case fuchsia::auth::Status::INVALID_AUTH_CONTEXT: |
| case fuchsia::auth::Status::INVALID_REQUEST: |
| case fuchsia::auth::Status::USER_NOT_FOUND: |
| case fuchsia::auth::Status::USER_CANCELLED: |
| case fuchsia::auth::Status::REAUTH_REQUIRED: |
| return false; |
| case fuchsia::auth::Status::UNKNOWN_ERROR: |
| case fuchsia::auth::Status::NETWORK_ERROR: |
| case fuchsia::auth::Status::INTERNAL_ERROR: |
| case fuchsia::auth::Status::IO_ERROR: |
| return true; |
| } |
| // In case of unexpected status, retry just in case. |
| return true; |
| } |
| } // namespace |
| |
| FirebaseAuthImpl::FirebaseAuthImpl( |
| Config config, async_dispatcher_t* dispatcher, rng::Random* random, |
| fuchsia::auth::TokenManagerPtr token_manager, |
| component::StartupContext* startup_context) |
| : config_(std::move(config)), |
| token_manager_(std::move(token_manager)), |
| backoff_(std::make_unique<backoff::ExponentialBackoff>( |
| random->NewBitGenerator<uint64_t>())), |
| max_retries_(config_.max_retries), |
| cobalt_client_name_(config_.cobalt_client_name), |
| task_runner_(dispatcher) { |
| if (startup_context) { |
| cobalt_logger_ = cobalt::NewCobaltLogger(dispatcher, startup_context, |
| kConfigBinProtoPath); |
| } else { |
| cobalt_logger_ = nullptr; |
| } |
| } |
| |
| FirebaseAuthImpl::FirebaseAuthImpl( |
| Config config, async_dispatcher_t* dispatcher, |
| fuchsia::auth::TokenManagerPtr token_manager, |
| std::unique_ptr<backoff::Backoff> backoff, |
| std::unique_ptr<cobalt::CobaltLogger> cobalt_logger) |
| : config_(std::move(config)), |
| token_manager_(std::move(token_manager)), |
| backoff_(std::move(backoff)), |
| max_retries_(config_.max_retries), |
| cobalt_client_name_(config_.cobalt_client_name), |
| cobalt_logger_(std::move(cobalt_logger)), |
| task_runner_(dispatcher) {} |
| |
| void FirebaseAuthImpl::set_error_handler(fit::closure on_error) { |
| token_manager_.set_error_handler( |
| [on_error = std::move(on_error)](zx_status_t status) { on_error(); }); |
| } |
| |
| fxl::RefPtr<callback::Cancellable> FirebaseAuthImpl::GetFirebaseToken( |
| fit::function<void(AuthStatus, std::string)> callback) { |
| if (config_.api_key.empty()) { |
| FXL_LOG(WARNING) << "No Firebase API key provided. Connection to Firebase " |
| "may be unauthenticated."; |
| } |
| auto cancellable = callback::CancellableImpl::Create([] {}); |
| GetToken(max_retries_, [callback = cancellable->WrapCallback( |
| std::move(callback))](auto status, auto token) { |
| callback(status, token ? token->id_token : ""); |
| }); |
| return cancellable; |
| } |
| |
| fxl::RefPtr<callback::Cancellable> FirebaseAuthImpl::GetFirebaseUserId( |
| fit::function<void(AuthStatus, std::string)> callback) { |
| auto cancellable = callback::CancellableImpl::Create([] {}); |
| GetToken(max_retries_, [callback = cancellable->WrapCallback( |
| std::move(callback))](auto status, auto token) { |
| callback(status, token ? token->local_id : ""); |
| }); |
| return cancellable; |
| } |
| |
| void FirebaseAuthImpl::GetToken( |
| int max_retries, |
| fit::function<void(AuthStatus, fuchsia::auth::FirebaseTokenPtr)> callback) { |
| fuchsia::auth::AppConfig oauth_config; |
| oauth_config.auth_provider_type = "google"; |
| |
| token_manager_->GetFirebaseToken( |
| std::move(oauth_config), "", /*user_profile_id*/ |
| "", /*audience*/ |
| config_.api_key, |
| fxl::MakeCopyable([this, max_retries, callback = std::move(callback)]( |
| fuchsia::auth::Status status, |
| fuchsia::auth::FirebaseTokenPtr token) mutable { |
| if (!token || status != fuchsia::auth::Status::OK) { |
| if (!token && status == fuchsia::auth::Status::OK) { |
| FXL_LOG(ERROR) |
| << "null Firebase token returned from token provider with no " |
| << "error reported. This should never happen. Retrying."; |
| status = fuchsia::auth::Status::UNKNOWN_ERROR; |
| } else { |
| FXL_LOG(ERROR) |
| << "Error retrieving the Firebase token from token provider: " |
| << fidl::ToUnderlying(status) << "', retrying."; |
| } |
| |
| if (max_retries > 0 && IsRetriableError(status)) { |
| task_runner_.PostDelayedTask( |
| [this, max_retries, callback = std::move(callback)]() mutable { |
| GetToken(max_retries - 1, std::move(callback)); |
| }, |
| backoff_->GetNext()); |
| return; |
| } |
| } |
| |
| backoff_->Reset(); |
| if (status == fuchsia::auth::Status::OK) { |
| callback(AuthStatus::OK, std::move(token)); |
| } else { |
| ReportError(kCobaltAuthFailureMetricId, |
| static_cast<uint32_t>(status)); |
| callback(AuthStatus::ERROR, std::move(token)); |
| } |
| })); |
| } |
| |
| void FirebaseAuthImpl::ReportError(int32_t metric_id, uint32_t status) { |
| if (cobalt_client_name_.empty() || cobalt_logger_ == nullptr) { |
| return; |
| } |
| |
| cobalt_logger_->LogEventCount(metric_id, status, cobalt_client_name_, |
| zx::duration(0), 1); |
| } |
| } // namespace firebase_auth |