chromium/chrome/browser/ash/printing/printer_authenticator.cc

// Copyright 2022 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/printing/printer_authenticator.h"

#include <memory>
#include <string>
#include <string_view>
#include <utility>

#include "base/check.h"
#include "base/functional/bind.h"
#include "chrome/browser/ash/printing/cups_printers_manager.h"
#include "chrome/browser/ash/printing/oauth2/authorization_zones_manager.h"
#include "chrome/browser/ash/printing/oauth2/log_entry.h"
#include "chrome/browser/ash/printing/oauth2/signin_dialog.h"
#include "chrome/browser/ash/printing/oauth2/status_code.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/uri.h"
#include "components/device_event_log/device_event_log.h"
#include "ui/views/window/dialog_delegate.h"
#include "url/gurl.h"

namespace ash::printing {

namespace {

// Logs results to device-log and calls `callback` with parameters `status` and
// `data`.
void LogAndCall(oauth2::StatusCallback callback,
                std::string_view method,
                const GURL& auth_server,
                oauth2::StatusCode status,
                std::string data) {
  if (status == oauth2::StatusCode::kOK) {
    PRINTER_LOG(EVENT) << oauth2::LogEntry("", method, auth_server, status);
  } else {
    PRINTER_LOG(ERROR) << oauth2::LogEntry(data, method, auth_server, status);
  }
  std::move(callback).Run(status, std::move(data));
}

}  // namespace

PrinterAuthenticator::PrinterAuthenticator(
    CupsPrintersManager* printers_manager,
    oauth2::AuthorizationZonesManager* auth_manager,
    const chromeos::Printer& printer)
    : cups_manager_(printers_manager),
      auth_manager_(auth_manager),
      printer_(printer) {
  DCHECK(printers_manager);
  DCHECK(auth_manager);
}

PrinterAuthenticator::~PrinterAuthenticator() = default;

void PrinterAuthenticator::ObtainAccessTokenIfNeeded(
    oauth2::StatusCallback callback) {
  DCHECK(!callback_);
  callback_ = std::move(callback);
  cups_manager_->FetchPrinterStatus(
      printer_.id(), base::BindOnce(&PrinterAuthenticator::OnGetPrinterStatus,
                                    weak_factory_.GetWeakPtr()));
}

void PrinterAuthenticator::SetUIResponsesForTesting(
    oauth2::StatusCode is_trusted_dialog_response,
    oauth2::StatusCode signin_dialog_response) {
  is_trusted_dialog_response_for_testing_ = is_trusted_dialog_response;
  signin_dialog_response_for_testing_ = signin_dialog_response;
}

void PrinterAuthenticator::OnGetPrinterStatus(
    const chromeos::CupsPrinterStatus& printer_status) {
  const chromeos::PrinterAuthenticationInfo& auth_mode =
      printer_status.GetAuthenticationInfo();
  if (auth_mode.oauth_server.empty()) {
    // A printer does not require authentication.
    std::move(callback_).Run(oauth2::StatusCode::kOK, "");
    return;
  }

  oauth_server_ = GURL(auth_mode.oauth_server);
  if (!oauth_server_.is_valid()) {
    std::move(callback_).Run(oauth2::StatusCode::kInvalidURL, "");
    return;
  }
  oauth_scope_ = auth_mode.oauth_scope;

  auth_manager_->GetEndpointAccessToken(oauth_server_, printer_.uri(),
                                        oauth_scope_,
                                        OnComplete(Step::kGetAccessToken));
}

oauth2::StatusCallback PrinterAuthenticator::OnComplete(Step step) {
  return base::BindOnce(&PrinterAuthenticator::ToNextStep,
                        weak_factory_.GetWeakPtr(), step);
}

void PrinterAuthenticator::ToNextStep(PrinterAuthenticator::Step current_step,
                                      oauth2::StatusCode status,
                                      std::string data) {
  switch (current_step) {
    case Step::kGetAccessToken:
      if (status == oauth2::StatusCode::kOK) {
        // Success, return the endpoint access token.
        std::move(callback_).Run(status, std::move(data));
        return;
      }
      if (status == oauth2::StatusCode::kUntrustedAuthorizationServer) {
        ShowIsTrustedDialog(oauth_server_,
                            OnComplete(Step::kShowIsTrustedDialog));
        return;
      }
      if (status == oauth2::StatusCode::kAuthorizationNeeded) {
        auth_manager_->InitAuthorization(oauth_server_, oauth_scope_,
                                         OnComplete(Step::kInitAuthorization));
        return;
      }
      break;
    case Step::kShowIsTrustedDialog:
      if (status == oauth2::StatusCode::kOK) {
        status = auth_manager_->SaveAuthorizationServerAsTrusted(oauth_server_);
        if (status == oauth2::StatusCode::kOK) {
          auth_manager_->GetEndpointAccessToken(
              oauth_server_, printer_.uri(), oauth_scope_,
              OnComplete(Step::kGetAccessToken));
          return;
        }
      }
      break;
    case Step::kInitAuthorization:
      if (status == oauth2::StatusCode::kOK) {
        ShowSigninDialog(data, OnComplete(Step::kShowSigninDialog));
        return;
      }
      if (status == oauth2::StatusCode::kUntrustedAuthorizationServer) {
        ShowIsTrustedDialog(oauth_server_,
                            OnComplete(Step::kShowIsTrustedDialog));
        return;
      }
      break;
    case Step::kShowSigninDialog:
      if (status == oauth2::StatusCode::kOK) {
        auth_manager_->FinishAuthorization(
            oauth_server_, GURL(data), OnComplete(Step::kFinishAuthorization));
        return;
      }
      break;
    case Step::kFinishAuthorization:
      if (status == oauth2::StatusCode::kOK) {
        auth_manager_->GetEndpointAccessToken(
            oauth_server_, printer_.uri(), oauth_scope_,
            OnComplete(Step::kGetAccessToken));
        return;
      }
      break;
  }

  // An error occurred.
  std::move(callback_).Run(status, "");
}

void PrinterAuthenticator::ShowIsTrustedDialog(
    const GURL& auth_url,
    oauth2::StatusCallback callback) {
  // Log the callback to device-log.
  callback =
      base::BindOnce(&LogAndCall, std::move(callback), __func__, oauth_server_);

  if (is_trusted_dialog_response_for_testing_) {
    std::move(callback).Run(*is_trusted_dialog_response_for_testing_,
                            "response from mock");
    return;
  }
  // TODO(https://crbug.com/1223535): Add dialog asking the user if
  // the server is trusted. For now, we just save the server as trusted.
  std::move(callback).Run(oauth2::StatusCode::kOK, "");
}

void PrinterAuthenticator::ShowSigninDialog(const std::string& auth_url,
                                            oauth2::StatusCallback callback) {
  // Log the callback to device-log.
  callback =
      base::BindOnce(&LogAndCall, std::move(callback), __func__, oauth_server_);

  const GURL url(auth_url);
  if (!url.is_valid()) {
    std::move(callback).Run(oauth2::StatusCode::kInvalidURL,
                            "auth_url=" + url.possibly_invalid_spec());
    return;
  }

  if (signin_dialog_response_for_testing_) {
    std::move(callback).Run(*signin_dialog_response_for_testing_,
                            "response from mock");
    return;
  }

  auto dialog = std::make_unique<oauth2::SigninDialog>(
      ProfileManager::GetPrimaryUserProfile());
  oauth2::SigninDialog* dialog_ptr = dialog.get();
  views::DialogDelegate::CreateDialogWidget(
      std::move(dialog), /*context=*/nullptr, /*parent=*/nullptr);
  dialog_ptr->StartAuthorizationProcedure(url, std::move(callback));
}

}  // namespace ash::printing