Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
OAuth2 for IPP: Class AuthorizationServerSession
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
Showing
6 changed files
with
524 additions
and
0 deletions.
There are no files selected for viewing
182 changes: 182 additions & 0 deletions
182
chrome/browser/ash/printing/oauth2/authorization_server_session.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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
96
chrome/browser/ash/printing/oauth2/authorization_server_session.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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_ |
Oops, something went wrong.