| // 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/authorization_server_data.h" |
| |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/memory/ref_counted.h" |
| #include "chrome/browser/ash/printing/oauth2/client_ids_database.h" |
| #include "chrome/browser/ash/printing/oauth2/constants.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 "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "url/gurl.h" |
| |
| namespace ash { |
| namespace printing { |
| namespace oauth2 { |
| |
| AuthorizationServerData::AuthorizationServerData( |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory, |
| const GURL& authorization_server_uri, |
| ClientIdsDatabase* client_ids_database) |
| : authorization_server_uri_(authorization_server_uri), |
| client_ids_database_(client_ids_database), |
| http_exchange_(url_loader_factory) { |
| CHECK(client_ids_database_); |
| } |
| |
| AuthorizationServerData::~AuthorizationServerData() = default; |
| |
| void AuthorizationServerData::Initialize(StatusCallback callback) { |
| DCHECK(!callback_); |
| DCHECK(callback); |
| callback_ = std::move(callback); |
| InitializationProcedure(); |
| } |
| |
| void AuthorizationServerData::InitializationProcedure() { |
| // First, check if `client_id_` is known. |
| if (!client_id_) { |
| client_ids_database_->FetchId( |
| authorization_server_uri_, |
| base::BindOnce(&AuthorizationServerData::OnClientIdFetched, |
| weak_ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| |
| // Check if we have server's metadata. |
| if (authorization_endpoint_uri_.is_empty() || |
| token_endpoint_uri_.is_empty()) { |
| SendMetadataRequest(); |
| return; |
| } |
| |
| // Check if the `client_id_` is known. If not, try to register a new client |
| // and obtain `client_id_`. Return error when the server doesn't support |
| // dynamic registration. |
| if (client_id_->empty()) { |
| if (registration_endpoint_uri_.is_empty()) { |
| std::move(callback_).Run(StatusCode::kClientNotRegistered, ""); |
| } else { |
| SendRegistrationRequest(); |
| } |
| return; |
| } |
| |
| // Everything is done already, just call the callback. |
| std::move(callback_).Run(StatusCode::kOK, ""); |
| } |
| |
| void AuthorizationServerData::OnClientIdFetched(StatusCode status, |
| std::string data) { |
| if (status != StatusCode::kOK) { |
| // Error occurred. Exit. |
| std::move(callback_).Run(status, data); |
| return; |
| } |
| |
| // Success! Set `client_id_` and return to the main procedure. |
| client_id_ = data; |
| InitializationProcedure(); |
| } |
| |
| void AuthorizationServerData::SendMetadataRequest() { |
| net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = |
| net::DefinePartialNetworkTrafficAnnotation( |
| "printing_oauth2_metadata_request", "printing_oauth2_http_exchange", |
| R"( |
| semantics { |
| description: |
| "This request downloads settings of Authorization Server." |
| data: |
| "No data are sent." |
| })"); |
| http_exchange_.Clear(); |
| // Add .well-known prefix to the path, see RFC 8414 (section 3) and RFC 8615. |
| chromeos::Uri uri(authorization_server_uri_.spec()); |
| const std::vector<std::string> prefix = {".well-known", |
| "oauth-authorization-server"}; |
| auto path = uri.GetPath(); |
| path.insert(path.begin(), prefix.begin(), prefix.end()); |
| uri.SetPath(path); |
| http_exchange_.Exchange( |
| "GET", GURL(uri.GetNormalized()), ContentFormat::kEmpty, 200, -1, |
| partial_traffic_annotation, |
| base::BindOnce(&AuthorizationServerData::OnMetadataResponse, |
| base::Unretained(this))); |
| } |
| |
| void AuthorizationServerData::OnMetadataResponse(StatusCode status) { |
| if (status != StatusCode::kOK) { |
| // Error occurred. Exit. |
| std::move(callback_).Run( |
| status, "Metadata Request: " + http_exchange_.GetErrorMessage()); |
| return; |
| } |
| |
| // Parse the response. |
| const bool ok = |
| http_exchange_.ParamURLEquals("issuer", true, |
| authorization_server_uri_) && |
| http_exchange_.ParamURLGet("authorization_endpoint", true, |
| &authorization_endpoint_uri_) && |
| http_exchange_.ParamURLGet("token_endpoint", true, |
| &token_endpoint_uri_) && |
| http_exchange_.ParamURLGet("registration_endpoint", false, |
| ®istration_endpoint_uri_) && |
| http_exchange_.ParamArrayStringContains("response_types_supported", true, |
| "code") && |
| http_exchange_.ParamArrayStringContains("response_modes_supported", false, |
| "query") && |
| http_exchange_.ParamArrayStringContains("grant_types_supported", false, |
| "authorization_code") && |
| http_exchange_.ParamArrayStringContains( |
| "token_endpoint_auth_methods_supported", true, "none") && |
| http_exchange_.ParamURLGet("revocation_endpoint", false, |
| &revocation_endpoint_uri_) && |
| http_exchange_.ParamArrayStringContains( |
| "revocation_endpoint_auth_methods_supported", false, "none") && |
| http_exchange_.ParamArrayStringContains( |
| "code_challenge_methods_supported", true, "S256"); |
| if (!ok) { |
| // Parsing failed. Reset all parameters and exit. |
| authorization_endpoint_uri_ = token_endpoint_uri_ = GURL(); |
| registration_endpoint_uri_ = revocation_endpoint_uri_ = GURL(); |
| std::move(callback_).Run( |
| StatusCode::kInvalidResponse, |
| "Metadata Request: " + http_exchange_.GetErrorMessage()); |
| return; |
| } |
| |
| // Success! Return to the main procedure. |
| InitializationProcedure(); |
| } |
| |
| void AuthorizationServerData::SendRegistrationRequest() { |
| net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = |
| net::DefinePartialNetworkTrafficAnnotation( |
| "printing_oauth2_registration_request", |
| "printing_oauth2_http_exchange", R"( |
| semantics { |
| description: |
| "This request registers this client to the new Authorization Server." |
| data: |
| "No local data are sent." |
| })"); |
| http_exchange_.Clear(); |
| http_exchange_.AddParamArrayString("redirect_uris", {kRedirectURI}); |
| http_exchange_.AddParamArrayString("token_endpoint_auth_method", {"none"}); |
| http_exchange_.AddParamArrayString("grant_types", {"authorization_code"}); |
| http_exchange_.AddParamArrayString("response_types", {"code"}); |
| http_exchange_.AddParamString("client_name", kClientName); |
| http_exchange_.Exchange( |
| "POST", registration_endpoint_uri_, ContentFormat::kJson, 201, 400, |
| partial_traffic_annotation, |
| base::BindOnce(&AuthorizationServerData::OnRegistrationResponse, |
| base::Unretained(this))); |
| } |
| |
| void AuthorizationServerData::OnRegistrationResponse(StatusCode status) { |
| if (status != StatusCode::kOK) { |
| // Error occurred. Exit. |
| std::move(callback_).Run( |
| status, "Registration Request: " + http_exchange_.GetErrorMessage()); |
| return; |
| } |
| |
| // Parse the response. |
| const bool ok = |
| http_exchange_.ParamStringGet("client_id", true, &*client_id_) && |
| http_exchange_.ParamArrayStringEquals("redirect_uris", true, |
| {kRedirectURI}) && |
| http_exchange_.ParamArrayStringEquals("token_endpoint_auth_method", true, |
| {"none"}) && |
| http_exchange_.ParamArrayStringEquals("grant_types", true, |
| {"authorization_code"}) && |
| http_exchange_.ParamArrayStringEquals("response_types", true, {"code"}) && |
| http_exchange_.ParamStringEquals("client_name", true, kClientName); |
| if (!ok) { |
| // Parsing failed. Reset all parameters and exit. |
| client_id_->clear(); |
| std::move(callback_).Run( |
| StatusCode::kInvalidResponse, |
| "Registration Request: " + http_exchange_.GetErrorMessage()); |
| return; |
| } |
| |
| // Save the obtained `client_id_`. |
| client_ids_database_->StoreId(authorization_server_uri_, *client_id_); |
| |
| // Success! Return to the main procedure. |
| InitializationProcedure(); |
| } |
| |
| } // namespace oauth2 |
| } // namespace printing |
| } // namespace ash |