// 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 "base/run_loop.h"
#include "base/test/bind.h"
#include "base/test/mock_callback.h"
#include "chrome/browser/ash/printing/fake_cups_printers_manager.h"
#include "chrome/browser/ash/printing/oauth2/mock_authorization_zones_manager.h"
#include "chrome/browser/ash/printing/oauth2/status_code.h"
#include "chromeos/printing/cups_printer_status.h"
#include "chromeos/printing/printer_configuration.h"
#include "chromeos/printing/uri.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
namespace ash::printing {
namespace {
// Represents results returned by callback void(StatusCode, const string&).
struct CallbackResult {
oauth2::StatusCode status = oauth2::StatusCode::kUnexpectedError;
std::string data;
};
class PrintingPrinterAuthenticatorTest : public testing::Test {
public:
PrintingPrinterAuthenticatorTest() : printer_(kPrinterId) {}
protected:
// Init PrintAuthenticator object being tested. Values of parameters define
// results returned by simulated UI dialogs.
void CreateAuthenticator(oauth2::StatusCode is_trusted_dialog_response =
oauth2::StatusCode::kUnexpectedError,
oauth2::StatusCode signin_dialog_response =
oauth2::StatusCode::kUnexpectedError) {
authenticator_ = std::make_unique<PrinterAuthenticator>(
&cups_manager_, &auth_manager_, printer_);
authenticator_->SetUIResponsesForTesting(is_trusted_dialog_response,
signin_dialog_response);
}
// Call PrintAuthenticator::ObtainAccessTokenIfNeeded() and returns result
// reported by the callback.
CallbackResult CallObtainAccessTokenIfNeeded() {
base::MockOnceCallback<void(oauth2::StatusCode, std::string)> callback;
CallbackResult result;
base::RunLoop loop;
EXPECT_CALL(callback, Run)
.InSequence(sequence_)
.WillOnce(
[&result, &loop](oauth2::StatusCode status, std::string data) {
result.status = status;
result.data = std::move(data);
loop.Quit();
});
authenticator_->ObtainAccessTokenIfNeeded(callback.Get());
loop.Run();
return result;
}
// Mock CupsPrintersManager::FetchPrinterStatus. `auth_mode` is passed to the
// object chromeos::CupsPrinterStatus returned in the callback.
void ExpectCallFetchPrinterStatus(
const chromeos::PrinterAuthenticationInfo& auth_mode) {
chromeos::CupsPrinterStatus printer_status(kPrinterId);
printer_status.SetAuthenticationInfo(auth_mode);
cups_manager_.SetPrinterStatus(printer_status);
}
// Mock AuthorizationZoneManager::SaveAuthorizationServerAsTrusted. `result`
// will be return by the call.
void ExpectCallSaveAuthorizationServerAsTrusted(oauth2::StatusCode result) {
EXPECT_CALL(auth_manager_, SaveAuthorizationServerAsTrusted(testing::_))
.InSequence(sequence_)
.WillOnce([result](const GURL& auth_server) { return result; });
}
// Mock AuthorizationZoneManager::InitAuthorization. Callback passed to the
// mocked method will be called with parameters set in `result`.
void ExpectCallInitAuthorization(CallbackResult result) {
EXPECT_CALL(auth_manager_,
InitAuthorization(testing::_, testing::_, testing::_))
.InSequence(sequence_)
.WillOnce([result](const GURL& auth_server, const std::string& scope,
oauth2::StatusCallback callback) {
std::move(callback).Run(result.status, std::move(result.data));
});
}
// Mock AuthorizationZoneManager::FinishAuthorization. Callback passed to the
// mocked method will be called with parameters set in `result`.
void ExpectCallFinishAuthorization(CallbackResult result) {
EXPECT_CALL(auth_manager_,
FinishAuthorization(testing::_, testing::_, testing::_))
.InSequence(sequence_)
.WillOnce([result](const GURL& auth_server, const GURL& redirect_url,
oauth2::StatusCallback callback) {
std::move(callback).Run(result.status, std::move(result.data));
});
}
// Mock AuthorizationZoneManager::GetEndpointAccessToken. Callback passed to
// the mocked method will be called with parameters set in `result`.
void ExpectCallGetEndpointAccessToken(CallbackResult result) {
EXPECT_CALL(auth_manager_, GetEndpointAccessToken(testing::_, testing::_,
testing::_, testing::_))
.InSequence(sequence_)
.WillOnce([result](const GURL& auth_server,
const chromeos::Uri& ipp_endpoint,
const std::string& scope,
oauth2::StatusCallback callback) {
std::move(callback).Run(result.status, std::move(result.data));
});
}
const std::string kPrinterId = "printer_id";
const chromeos::PrinterAuthenticationInfo kAuthMode = {"https://auth/server",
"scope1 scope2"};
const std::string kAuthURL = "https://auth/server/login?ala=ma;kota";
private:
content::BrowserTaskEnvironment task_environment_;
testing::Sequence sequence_;
FakeCupsPrintersManager cups_manager_;
testing::StrictMock<oauth2::MockAuthorizationZoneManager> auth_manager_;
chromeos::Printer printer_;
std::unique_ptr<PrinterAuthenticator> authenticator_;
};
TEST_F(PrintingPrinterAuthenticatorTest, AuthenticationNotNeeded) {
CreateAuthenticator();
ExpectCallFetchPrinterStatus({"", ""});
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kOK);
EXPECT_TRUE(result.data.empty());
}
TEST_F(PrintingPrinterAuthenticatorTest, AlreadyAuthenticated) {
CreateAuthenticator(oauth2::StatusCode::kOK);
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kOK, "access token"});
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kOK);
EXPECT_EQ(result.data, "access token");
}
TEST_F(PrintingPrinterAuthenticatorTest, ServerNotTrusted) {
CreateAuthenticator(oauth2::StatusCode::kUntrustedAuthorizationServer);
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken(
{oauth2::StatusCode::kUntrustedAuthorizationServer});
// Response from IsTrustedDialog: StatusCode::kUntrustedAuthorizationServer
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kUntrustedAuthorizationServer);
EXPECT_TRUE(result.data.empty());
}
TEST_F(PrintingPrinterAuthenticatorTest, ServerSavedAsTrusted) {
CreateAuthenticator(oauth2::StatusCode::kOK);
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken(
{oauth2::StatusCode::kUntrustedAuthorizationServer});
// Response from IsTrustedDialog: StatusCode::kOK
ExpectCallSaveAuthorizationServerAsTrusted(oauth2::StatusCode::kOK);
ExpectCallGetEndpointAccessToken(
{oauth2::StatusCode::kClientNotRegistered, "error"});
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kClientNotRegistered);
EXPECT_TRUE(result.data.empty());
}
TEST_F(PrintingPrinterAuthenticatorTest, InvalidAuthorizationURL) {
CreateAuthenticator();
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
ExpectCallInitAuthorization({oauth2::StatusCode::kOK, "invalid_auth_url"});
// Response from SigninDialog: StatusCode::kInvalidURL
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kInvalidURL);
EXPECT_TRUE(result.data.empty());
}
TEST_F(PrintingPrinterAuthenticatorTest, AuthorizationError) {
CreateAuthenticator(oauth2::StatusCode::kOK,
oauth2::StatusCode::kServerError);
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
ExpectCallInitAuthorization({oauth2::StatusCode::kOK, kAuthURL});
// Response from SigninDialog: StatusCode::kServerError
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kServerError);
EXPECT_TRUE(result.data.empty());
}
TEST_F(PrintingPrinterAuthenticatorTest, AccessDenied) {
CreateAuthenticator(oauth2::StatusCode::kOK, oauth2::StatusCode::kOK);
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
ExpectCallInitAuthorization({oauth2::StatusCode::kOK, kAuthURL});
// Response from SigninDialog: StatusCode::kOK
ExpectCallFinishAuthorization({oauth2::StatusCode::kAccessDenied, "error"});
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kAccessDenied);
EXPECT_TRUE(result.data.empty());
}
TEST_F(PrintingPrinterAuthenticatorTest, AuthorizationSuccessful) {
CreateAuthenticator(oauth2::StatusCode::kOK, oauth2::StatusCode::kOK);
ExpectCallFetchPrinterStatus(kAuthMode);
ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kAuthorizationNeeded});
ExpectCallInitAuthorization({oauth2::StatusCode::kOK, kAuthURL});
// Response from SigninDialog: StatusCode::kOK
ExpectCallFinishAuthorization({oauth2::StatusCode::kOK});
ExpectCallGetEndpointAccessToken({oauth2::StatusCode::kOK, "access token"});
CallbackResult result = CallObtainAccessTokenIfNeeded();
EXPECT_EQ(result.status, oauth2::StatusCode::kOK);
EXPECT_EQ(result.data, "access token");
}
} // namespace
} // namespace ash::printing