chromium/chrome/browser/ash/printing/oauth2/ipp_endpoint_token_fetcher.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/oauth2/ipp_endpoint_token_fetcher.h"

#include <string>
#include <utility>

#include "base/containers/flat_set.h"
#include "base/functional/bind.h"
#include "chrome/browser/ash/printing/oauth2/http_exchange.h"
#include "chrome/browser/ash/printing/oauth2/status_code.h"
#include "chromeos/printing/uri.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
#include "url/gurl.h"

namespace ash {
namespace printing {
namespace oauth2 {

IppEndpointTokenFetcher::IppEndpointTokenFetcher(
    scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
    const GURL& token_endpoint_uri,
    const chromeos::Uri& ipp_endpoint,
    base::flat_set<std::string>&& scope)
    : token_endpoint_uri_(token_endpoint_uri),
      ipp_endpoint_uri_(ipp_endpoint),
      scope_(scope),
      http_exchange_(url_loader_factory) {}

IppEndpointTokenFetcher::~IppEndpointTokenFetcher() = default;

void IppEndpointTokenFetcher::AddToWaitingList(StatusCallback callback) {
  callbacks_.push_back(std::move(callback));
}

std::vector<StatusCallback> IppEndpointTokenFetcher::TakeWaitingList() {
  std::vector<StatusCallback> waitlist;
  waitlist.swap(callbacks_);
  return waitlist;
}

void IppEndpointTokenFetcher::SendTokenExchangeRequest(
    const std::string& access_token,
    StatusCallback callback) {
  net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation =
      net::DefinePartialNetworkTrafficAnnotation(
          "printing_oauth2_token_exchange_request",
          "printing_oauth2_http_exchange", R"(
    semantics {
      description:
        "This request asks Authorization Server for an endpoint access token "
        "to use for communication with a particular printer."
      data:
        "Address of the printer and the access token of the current OAuth 2 "
        "session with the Authorization Server."
    })");
  http_exchange_.Clear();

  // Move query parameters from URL to the content.
  chromeos::Uri uri(token_endpoint_uri_.spec());
  auto query = uri.GetQuery();
  for (const auto& kv : query) {
    http_exchange_.AddParamString(kv.first, kv.second);
  }
  uri.SetQuery({});

  // Prepare the request.
  http_exchange_.AddParamString(
      "grant_type", "urn:ietf:params:oauth:grant-type:token-exchange");
  http_exchange_.AddParamString("resource", ipp_endpoint_uri_.GetNormalized());
  http_exchange_.AddParamString("subject_token", access_token);
  http_exchange_.AddParamString(
      "subject_token_type", "urn:ietf:params:oauth:token-type:access_token");
  http_exchange_.Exchange(
      "POST", GURL(uri.GetNormalized()), ContentFormat::kXWwwFormUrlencoded,
      200, 400, partial_traffic_annotation,
      base::BindOnce(&IppEndpointTokenFetcher::OnTokenExchangeResponse,
                     base::Unretained(this), access_token,
                     std::move(callback)));
}

void IppEndpointTokenFetcher::OnTokenExchangeResponse(
    const std::string& access_token,
    StatusCallback callback,
    StatusCode status) {
  if (status == StatusCode::kInvalidAccessToken) {
    std::move(callback).Run(status, access_token);
    return;
  }

  if (status != StatusCode::kOK) {
    std::move(callback).Run(status, http_exchange_.GetErrorMessage());
    return;
  }

  const bool ok =
      http_exchange_.ParamStringGet("access_token", true,
                                    &endpoint_access_token_) &&
      http_exchange_.ParamStringGet("issued_token_type", true, nullptr) &&
      http_exchange_.ParamStringEquals("token_type", true, "bearer");
  if (!ok) {
    // Error occurred.
    endpoint_access_token_.clear();
    std::move(callback).Run(StatusCode::kInvalidResponse,
                            http_exchange_.GetErrorMessage());
    return;
  }

  // Success!
  std::move(callback).Run(StatusCode::kOK, endpoint_access_token_);
}

}  // namespace oauth2
}  // namespace printing
}  // namespace ash