// Copyright 2014 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 "components/cryptauth/cryptauth_client_impl.h" #include #include #include "base/bind.h" #include "base/command_line.h" #include "base/memory/ptr_util.h" #include "chromeos/components/proximity_auth/logging/logging.h" #include "components/cryptauth/switches.h" #include "services/identity/public/cpp/identity_manager.h" #include "services/identity/public/cpp/primary_account_access_token_fetcher.h" #include "services/network/public/cpp/shared_url_loader_factory.h" namespace cryptauth { namespace { // Default URL of Google APIs endpoint hosting CryptAuth. const char kDefaultCryptAuthHTTPHost[] = "https://www.googleapis.com"; // URL subpath hosting the CryptAuth service. const char kCryptAuthPath[] = "cryptauth/v1/"; // URL subpaths for each CryptAuth API. const char kGetMyDevicesPath[] = "deviceSync/getmydevices"; const char kFindEligibleUnlockDevicesPath[] = "deviceSync/findeligibleunlockdevices"; const char kFindEligibleForPromotionPath[] = "deviceSync/findeligibleforpromotion"; const char kSendDeviceSyncTicklePath[] = "deviceSync/senddevicesynctickle"; const char kToggleEasyUnlockPath[] = "deviceSync/toggleeasyunlock"; const char kSetupEnrollmentPath[] = "enrollment/setup"; const char kFinishEnrollmentPath[] = "enrollment/finish"; // Query string of the API URL indicating that the response should be in a // serialized protobuf format. const char kQueryProtobuf[] = "?alt=proto"; // Creates the full CryptAuth URL for endpoint to the API with |request_path|. GURL CreateRequestUrl(const std::string& request_path) { base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); GURL google_apis_url = GURL(command_line->HasSwitch(switches::kCryptAuthHTTPHost) ? command_line->GetSwitchValueASCII(switches::kCryptAuthHTTPHost) : kDefaultCryptAuthHTTPHost); return google_apis_url.Resolve(kCryptAuthPath + request_path + kQueryProtobuf); } } // namespace CryptAuthClientImpl::CryptAuthClientImpl( std::unique_ptr api_call_flow, identity::IdentityManager* identity_manager, scoped_refptr url_loader_factory, const DeviceClassifier& device_classifier) : api_call_flow_(std::move(api_call_flow)), identity_manager_(identity_manager), url_loader_factory_(std::move(url_loader_factory)), device_classifier_(device_classifier), has_call_started_(false), weak_ptr_factory_(this) {} CryptAuthClientImpl::~CryptAuthClientImpl() {} void CryptAuthClientImpl::GetMyDevices( const GetMyDevicesRequest& request, const GetMyDevicesCallback& callback, const ErrorCallback& error_callback, const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) { MakeApiCall(kGetMyDevicesPath, request, callback, error_callback, partial_traffic_annotation); } void CryptAuthClientImpl::FindEligibleUnlockDevices( const FindEligibleUnlockDevicesRequest& request, const FindEligibleUnlockDevicesCallback& callback, const ErrorCallback& error_callback) { net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = net::DefinePartialNetworkTrafficAnnotation( "cryptauth_find_eligible_unlock_devices", "oauth2_api_call_flow", R"( semantics { sender: "CryptAuth Device Manager" description: "Gets the list of mobile devices that can be used by Smart Lock to " "unlock the current device." trigger: "This request is sent when the user starts the Smart Lock setup flow." data: "OAuth 2.0 token and the device's public key." destination: GOOGLE_OWNED_SERVICE } policy { setting: "This feature cannot be disabled in settings, but the request will " "only be sent if the user explicitly tries to enable Smart Lock " "(EasyUnlock), i.e. starts the setup flow." chrome_policy { EasyUnlockAllowed { EasyUnlockAllowed: false } } })"); MakeApiCall(kFindEligibleUnlockDevicesPath, request, callback, error_callback, partial_traffic_annotation); } void CryptAuthClientImpl::FindEligibleForPromotion( const FindEligibleForPromotionRequest& request, const FindEligibleForPromotionCallback& callback, const ErrorCallback& error_callback) { net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = net::DefinePartialNetworkTrafficAnnotation( "cryptauth_find_eligible_for_promotion", "oauth2_api_call_flow", R"( semantics { sender: "Promotion Manager" description: "Return whether the current device is eligible for a Smart Lock promotion." trigger: "This request is sent when the user starts the Smart Lock setup flow." data: "OAuth 2.0 token and the device's public key." destination: GOOGLE_OWNED_SERVICE } policy { setting: "This feature cannot be disabled in settings" chrome_policy { EasyUnlockAllowed { EasyUnlockAllowed: false } } })"); MakeApiCall(kFindEligibleForPromotionPath, request, callback, error_callback, partial_traffic_annotation); } void CryptAuthClientImpl::SendDeviceSyncTickle( const SendDeviceSyncTickleRequest& request, const SendDeviceSyncTickleCallback& callback, const ErrorCallback& error_callback, const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) { MakeApiCall(kSendDeviceSyncTicklePath, request, callback, error_callback, partial_traffic_annotation); } void CryptAuthClientImpl::ToggleEasyUnlock( const ToggleEasyUnlockRequest& request, const ToggleEasyUnlockCallback& callback, const ErrorCallback& error_callback) { net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = net::DefinePartialNetworkTrafficAnnotation("cryptauth_toggle_easyunlock", "oauth2_api_call_flow", R"( semantics { sender: "CryptAuth Device Manager" description: "Enables Smart Lock (EasyUnlock) for the current device." trigger: "This request is send after the user goes through the EasyUnlock " "setup flow." data: "OAuth 2.0 token and the device public key." destination: GOOGLE_OWNED_SERVICE } policy { setting: "This feature cannot be disabled in settings, but the request will " "only be send if the user explicitly enables Smart Lock " "(EasyUnlock), i.e. uccessfully complete the setup flow." chrome_policy { EasyUnlockAllowed { EasyUnlockAllowed: false } } })"); MakeApiCall(kToggleEasyUnlockPath, request, callback, error_callback, partial_traffic_annotation); } void CryptAuthClientImpl::SetupEnrollment( const SetupEnrollmentRequest& request, const SetupEnrollmentCallback& callback, const ErrorCallback& error_callback) { net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = net::DefinePartialNetworkTrafficAnnotation( "cryptauth_enrollment_flow_setup", "oauth2_api_call_flow", R"( semantics { sender: "CryptAuth Device Manager" description: "Starts the CryptAuth registration flow." trigger: "Occurs periodically, at least once a month, because if the device " "does not re-enroll for more than a specific number of days " "(currently 45) it will be removed from the server." data: "Various device information (public key, bluetooth MAC address, " "model, OS version, screen size, manufacturer, has screen lock " "enabled), and OAuth 2.0 token." destination: GOOGLE_OWNED_SERVICE } policy { setting: "This feature cannot be disabled by settings. However, this request " "is made only for signed-in users." chrome_policy { SigninAllowed { SigninAllowed: false } } })"); MakeApiCall(kSetupEnrollmentPath, request, callback, error_callback, partial_traffic_annotation); } void CryptAuthClientImpl::FinishEnrollment( const FinishEnrollmentRequest& request, const FinishEnrollmentCallback& callback, const ErrorCallback& error_callback) { net::PartialNetworkTrafficAnnotationTag partial_traffic_annotation = net::DefinePartialNetworkTrafficAnnotation( "cryptauth_enrollment_flow_finish", "oauth2_api_call_flow", R"( semantics { sender: "CryptAuth Device Manager" description: "Finishes the CryptAuth registration flow." trigger: "Occurs periodically, at least once a month, because if the device " "does not re-enroll for more than a specific number of days " "(currently 45) it will be removed from the server." data: "OAuth 2.0 token." destination: GOOGLE_OWNED_SERVICE } policy { setting: "This feature cannot be disabled by settings. However, this request " "is made only for signed-in users." chrome_policy { SigninAllowed { SigninAllowed: false } } })"); MakeApiCall(kFinishEnrollmentPath, request, callback, error_callback, partial_traffic_annotation); } std::string CryptAuthClientImpl::GetAccessTokenUsed() { return access_token_used_; } template void CryptAuthClientImpl::MakeApiCall( const std::string& request_path, const RequestProto& request_proto, const base::Callback& response_callback, const ErrorCallback& error_callback, const net::PartialNetworkTrafficAnnotationTag& partial_traffic_annotation) { if (has_call_started_) { PA_LOG(ERROR) << "CryptAuthClientImpl::MakeApiCall(): Tried to make an API " << "call, but the client had already been used."; NOTREACHED(); return; } has_call_started_ = true; api_call_flow_->SetPartialNetworkTrafficAnnotation( partial_traffic_annotation); // The |device_classifier| field must be present for all CryptAuth requests. RequestProto request_copy(request_proto); request_copy.mutable_device_classifier()->CopyFrom(device_classifier_); std::string serialized_request; if (!request_copy.SerializeToString(&serialized_request)) { PA_LOG(ERROR) << "CryptAuthClientImpl::MakeApiCall(): Failure serializing " << "request proto."; NOTREACHED(); return; } request_path_ = request_path; error_callback_ = error_callback; OAuth2TokenService::ScopeSet scopes; scopes.insert("https://www.googleapis.com/auth/cryptauth"); access_token_fetcher_ = std::make_unique< identity::PrimaryAccountAccessTokenFetcher>( "cryptauth_client", identity_manager_, scopes, base::BindOnce(&CryptAuthClientImpl::OnAccessTokenFetched, weak_ptr_factory_.GetWeakPtr(), serialized_request, response_callback), identity::PrimaryAccountAccessTokenFetcher::Mode::kWaitUntilAvailable); } template void CryptAuthClientImpl::OnAccessTokenFetched( const std::string& serialized_request, const base::Callback& response_callback, GoogleServiceAuthError error, identity::AccessTokenInfo access_token_info) { access_token_fetcher_.reset(); if (error.state() != GoogleServiceAuthError::NONE) { OnApiCallFailed(NetworkRequestError::kAuthenticationError); return; } access_token_used_ = access_token_info.token; api_call_flow_->Start( CreateRequestUrl(request_path_), url_loader_factory_, access_token_used_, serialized_request, base::Bind(&CryptAuthClientImpl::OnFlowSuccess, weak_ptr_factory_.GetWeakPtr(), response_callback), base::Bind(&CryptAuthClientImpl::OnApiCallFailed, weak_ptr_factory_.GetWeakPtr())); } template void CryptAuthClientImpl::OnFlowSuccess( const base::Callback& result_callback, const std::string& serialized_response) { ResponseProto response; if (!response.ParseFromString(serialized_response)) { OnApiCallFailed(NetworkRequestError::kResponseMalformed); return; } result_callback.Run(response); }; void CryptAuthClientImpl::OnApiCallFailed(NetworkRequestError error) { error_callback_.Run(error); } // CryptAuthClientFactoryImpl CryptAuthClientFactoryImpl::CryptAuthClientFactoryImpl( identity::IdentityManager* identity_manager, scoped_refptr url_loader_factory, const DeviceClassifier& device_classifier) : identity_manager_(identity_manager), url_loader_factory_(std::move(url_loader_factory)), device_classifier_(device_classifier) {} CryptAuthClientFactoryImpl::~CryptAuthClientFactoryImpl() {} std::unique_ptr CryptAuthClientFactoryImpl::CreateInstance() { return std::make_unique( base::WrapUnique(new CryptAuthApiCallFlow()), identity_manager_, url_loader_factory_, device_classifier_); } } // namespace cryptauth