Skip to content

Commit

Permalink
OAuth2 for IPP: Class AuthorizationServerSession
Browse files Browse the repository at this point in the history
The class AuthorizationServerSession is responsible for acquiring and
refreshing access token for given Authorization Server and scope. This
class is used inside the class AuthorizationZone (CL:3373066).

BUG=b:201445648
TEST=unit_tests --gtest_filter=PrintingOAuth2AuthorizationServerSession*

Change-Id: Ied0b3443fde8e306fb821841744772a3b72c3924
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3373067
Reviewed-by: Christian Dullweber <dullweber@chromium.org>
Commit-Queue: Piotr Pawliczek <pawliczek@chromium.org>
Reviewed-by: Zentaro Kavanagh <zentaro@chromium.org>
Reviewed-by: Pranav Batra <batrapranav@chromium.org>
Cr-Commit-Position: refs/heads/main@{#985036}
  • Loading branch information
Piotr Pawliczek authored and Chromium LUCI CQ committed Mar 24, 2022
1 parent 65c7fc2 commit 8e87eec
Show file tree
Hide file tree
Showing 6 changed files with 524 additions and 0 deletions.
182 changes: 182 additions & 0 deletions chrome/browser/ash/printing/oauth2/authorization_server_session.cc
@@ -0,0 +1,182 @@
// Copyright 2022 The Chromium 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 "chrome/browser/ash/printing/oauth2/authorization_server_session.h"

#include <algorithm>
#include <string>
#include <vector>

#include "base/bind.h"
#include "base/containers/flat_set.h"
#include "base/strings/string_split.h"
#include "chrome/browser/ash/printing/oauth2/constants.h"
#include "chrome/browser/ash/printing/oauth2/http_exchange.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 {

base::flat_set<std::string> ParseScope(const std::string& scope) {
std::vector<std::string> tokens = base::SplitString(
scope, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
base::flat_set<std::string> output(std::move(tokens));
return output;
}

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

AuthorizationServerSession::~AuthorizationServerSession() = default;

bool AuthorizationServerSession::ContainsAll(
const base::flat_set<std::string>& scope) const {
return std::includes(scope_.begin(), scope_.end(), scope.begin(),
scope.end());
}

void AuthorizationServerSession::SendFirstTokenRequest(
const std::string& client_id,
const std::string& authorization_code,
const std::string& code_verifier,
StatusCallback callback) {
net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation =
net::DefinePartialNetworkTrafficAnnotation(
"printing_oauth2_first_token_request",
"printing_oauth2_http_exchange", R"(semantics {
description:
"This request opens OAuth 2 session with the Authorization Server by "
"asking it for an access token."
data:
"Identifier of the client obtained from the Authorization server during "
"registration and temporary security codes used during authorization "
"process."
})");
http_exchange_.Clear();
// Moves 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", "authorization_code");
http_exchange_.AddParamString("code", authorization_code);
http_exchange_.AddParamString("redirect_uri", kRedirectURI);
http_exchange_.AddParamString("client_id", client_id);
http_exchange_.AddParamString("code_verifier", code_verifier);
http_exchange_.Exchange(
"POST", GURL(uri.GetNormalized()), ContentFormat::kXWwwFormUrlencoded,
200, 400, partial_traffic_annotation,
base::BindOnce(&AuthorizationServerSession::OnFirstTokenResponse,
base::Unretained(this), std::move(callback)));
}

void AuthorizationServerSession::SendNextTokenRequest(StatusCallback callback) {
access_token_.clear();
if (refresh_token_.empty()) {
std::move(callback).Run(StatusCode::kAuthorizationNeeded,
"No refresh token");
return;
}
net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation =
net::DefinePartialNetworkTrafficAnnotation(
"printing_oauth2_next_token_request", "printing_oauth2_http_exchange",
R"(semantics {
description:
"This request refreshes OAuth 2 session with the Authorization Server by "
"asking it for a new access token."
data:
"A refresh token previously issued by 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", "refresh_token");
http_exchange_.AddParamString("refresh_token", refresh_token_);
http_exchange_.Exchange(
"POST", GURL(uri.GetNormalized()), ContentFormat::kXWwwFormUrlencoded,
200, 400, partial_traffic_annotation,
base::BindOnce(&AuthorizationServerSession::OnNextTokenResponse,
base::Unretained(this), std::move(callback)));
}

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

// Parses response.
std::string scope;
const bool ok =
http_exchange_.ParamStringGet("access_token", true, &access_token_) &&
http_exchange_.ParamStringEquals("token_type", true, "bearer") &&
http_exchange_.ParamStringGet("refresh_token", false, &refresh_token_) &&
http_exchange_.ParamStringGet("scope", false, &scope);
if (!ok) {
// Error occurred.
access_token_.clear();
refresh_token_.clear();
std::move(callback).Run(StatusCode::kInvalidResponse,
http_exchange_.GetErrorMessage());
return;
}

// Success!
auto new_scope = ParseScope(scope);
scope_.insert(new_scope.begin(), new_scope.end());
std::move(callback).Run(StatusCode::kOK, access_token_);
}

void AuthorizationServerSession::OnNextTokenResponse(StatusCallback callback,
StatusCode status) {
if (status == StatusCode::kInvalidAccessToken) {
std::move(callback).Run(StatusCode::kAuthorizationNeeded,
"Refresh token expired");
return;
}

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

// Parses response.
const bool ok =
http_exchange_.ParamStringGet("access_token", true, &access_token_) &&
http_exchange_.ParamStringEquals("token_type", true, "bearer") &&
http_exchange_.ParamStringGet("refresh_token", false, &refresh_token_);
if (!ok) {
// Error occurred.
access_token_.clear();
refresh_token_.clear();
std::move(callback).Run(StatusCode::kInvalidResponse,
http_exchange_.GetErrorMessage());
return;
}

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

} // namespace oauth2
} // namespace printing
} // namespace ash
96 changes: 96 additions & 0 deletions chrome/browser/ash/printing/oauth2/authorization_server_session.h
@@ -0,0 +1,96 @@
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_ASH_PRINTING_OAUTH2_AUTHORIZATION_SERVER_SESSION_H_
#define CHROME_BROWSER_ASH_PRINTING_OAUTH2_AUTHORIZATION_SERVER_SESSION_H_

#include <string>
#include <vector>

#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/memory/ref_counted.h"
#include "chrome/browser/ash/printing/oauth2/http_exchange.h"
#include "chrome/browser/ash/printing/oauth2/status_code.h"
#include "url/gurl.h"

namespace network {
class SharedURLLoaderFactory;
} // namespace network

namespace ash {
namespace printing {
namespace oauth2 {

// Helper function that parse scope field (a list of names).
base::flat_set<std::string> ParseScope(const std::string& scope);

// This class represents single OAuth2 session and is responsible for acquiring
// and refreshing the access token.
class AuthorizationServerSession {
public:
// Constructor.
AuthorizationServerSession(
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory,
const GURL& token_endpoint_uri,
base::flat_set<std::string>&& scope);
// Not copyable.
AuthorizationServerSession(const AuthorizationServerSession&) = delete;
AuthorizationServerSession& operator=(const AuthorizationServerSession&) =
delete;
// Destructor.
~AuthorizationServerSession();

// Returns the access token or an empty string if the access token is not
// known yet.
const std::string& access_token() const { return access_token_; }

// Returns true <=> the scope contains all elements from `scope`.
bool ContainsAll(const base::flat_set<std::string>& scope) const;

// Prepares and sends First Token Request. Results are returned by `callback`.
// If the request is successful, the callback returns StatusCode::kOK and the
// access token as the second parameter. Otherwise, the error code with
// a message is returned.
void SendFirstTokenRequest(const std::string& client_id,
const std::string& authorization_code,
const std::string& code_verifier,
StatusCallback callback);

// Resets the current access token to empty string and sends Next Token
// Request to obtain a new one. If the request is successful, the callback
// returns StatusCode::kOK and the access token as the second parameter.
// If the server does not allow to refresh the access token or the refresh
// token expired, the status StatusCode::kAuthorizationNeeded is returned.
// Otherwise, the error code with a message is returned.
void SendNextTokenRequest(StatusCallback callback);

private:
// Analyzes response for First Token Request.
void OnFirstTokenResponse(StatusCallback callback, StatusCode status);

// Analyzes response for Next Token Request.
void OnNextTokenResponse(StatusCallback callback, StatusCode status);

// URL of the endpoint at the Authorization Server.
const GURL token_endpoint_uri_;

// Set of scopes requested by the client and/or granted by the
// Authorization Server.
base::flat_set<std::string> scope_;

// Access token of the current OAuth2 session.
std::string access_token_;
// Refresh token of the current OAuth2 session.
std::string refresh_token_;

// The object used for communication with the Authorization Server.
HttpExchange http_exchange_;
};

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

#endif // CHROME_BROWSER_ASH_PRINTING_OAUTH2_AUTHORIZATION_SERVER_SESSION_H_

0 comments on commit 8e87eec

Please sign in to comment.