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