| // Copyright (c) 2012 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/policy/core/common/cloud/device_management_service.h" |
| |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "components/data_use_measurement/core/data_use_user_data.h" |
| #include "components/policy/core/common/cloud/dm_auth.h" |
| #include "net/base/escape.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/net_errors.h" |
| #include "net/http/http_response_headers.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/shared_url_loader_factory.h" |
| #include "services/network/public/cpp/simple_url_loader.h" |
| #include "url/gurl.h" |
| |
| namespace em = enterprise_management; |
| |
| namespace policy { |
| |
| namespace { |
| |
| const char kPostContentType[] = "application/protobuf"; |
| |
| const char kAuthHeader[] = "Authorization"; |
| const char kServiceTokenAuthHeaderPrefix[] = "GoogleLogin auth="; |
| const char kDMTokenAuthHeaderPrefix[] = "GoogleDMToken token="; |
| const char kEnrollmentTokenAuthHeaderPrefix[] = "GoogleEnrollmentToken token="; |
| |
| // Number of times to retry on ERR_NETWORK_CHANGED errors. |
| const int kMaxRetries = 3; |
| |
| // HTTP Error Codes of the DM Server with their concrete meanings in the context |
| // of the DM Server communication. |
| const int kSuccess = 200; |
| const int kInvalidArgument = 400; |
| const int kInvalidAuthCookieOrDMToken = 401; |
| const int kMissingLicenses = 402; |
| const int kDeviceManagementNotAllowed = 403; |
| const int kInvalidURL = 404; // This error is not coming from the GFE. |
| const int kInvalidSerialNumber = 405; |
| const int kDomainMismatch = 406; |
| const int kDeviceIdConflict = 409; |
| const int kDeviceNotFound = 410; |
| const int kPendingApproval = 412; |
| const int kConsumerAccountWithPackagedLicense = 417; |
| const int kInternalServerError = 500; |
| const int kServiceUnavailable = 503; |
| const int kPolicyNotFound = 902; |
| const int kDeprovisioned = 903; |
| const int kArcDisabled = 904; |
| |
| // Delay after first unsuccessful upload attempt. After each additional failure, |
| // the delay increases exponentially. Can be changed for testing to prevent |
| // timeouts. |
| long g_retry_delay_ms = 10000; |
| |
| bool IsProxyError(int net_error) { |
| switch (net_error) { |
| case net::ERR_PROXY_CONNECTION_FAILED: |
| case net::ERR_TUNNEL_CONNECTION_FAILED: |
| case net::ERR_PROXY_AUTH_UNSUPPORTED: |
| case net::ERR_HTTPS_PROXY_TUNNEL_RESPONSE: |
| case net::ERR_MANDATORY_PROXY_CONFIGURATION_FAILED: |
| case net::ERR_PROXY_CERTIFICATE_INVALID: |
| case net::ERR_SOCKS_CONNECTION_FAILED: |
| case net::ERR_SOCKS_CONNECTION_HOST_UNREACHABLE: |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsConnectionError(int net_error) { |
| switch (net_error) { |
| case net::ERR_NETWORK_CHANGED: |
| case net::ERR_NAME_NOT_RESOLVED: |
| case net::ERR_INTERNET_DISCONNECTED: |
| case net::ERR_ADDRESS_UNREACHABLE: |
| case net::ERR_CONNECTION_TIMED_OUT: |
| case net::ERR_NAME_RESOLUTION_FAILED: |
| return true; |
| } |
| return false; |
| } |
| |
| bool IsProtobufMimeType(const std::string& mime_type) { |
| return mime_type == "application/x-protobuffer"; |
| } |
| |
| bool FailedWithProxy(const std::string& mime_type, |
| int response_code, |
| int net_error, |
| bool was_fetched_via_proxy) { |
| if (IsProxyError(net_error)) { |
| LOG(WARNING) << "Proxy failed while contacting dmserver."; |
| return true; |
| } |
| |
| if (net_error == net::OK && response_code == kSuccess && |
| was_fetched_via_proxy && !IsProtobufMimeType(mime_type)) { |
| // The proxy server can be misconfigured but pointing to an existing |
| // server that replies to requests. Try to recover if a successful |
| // request that went through a proxy returns an unexpected mime type. |
| LOG(WARNING) << "Got bad mime-type in response from dmserver that was " |
| << "fetched via a proxy."; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| const char* JobTypeToRequestType(DeviceManagementRequestJob::JobType type) { |
| switch (type) { |
| case DeviceManagementRequestJob::TYPE_AUTO_ENROLLMENT: |
| return dm_protocol::kValueRequestAutoEnrollment; |
| case DeviceManagementRequestJob::TYPE_REGISTRATION: |
| return dm_protocol::kValueRequestRegister; |
| case DeviceManagementRequestJob::TYPE_POLICY_FETCH: |
| return dm_protocol::kValueRequestPolicy; |
| case DeviceManagementRequestJob::TYPE_API_AUTH_CODE_FETCH: |
| return dm_protocol::kValueRequestApiAuthorization; |
| case DeviceManagementRequestJob::TYPE_UNREGISTRATION: |
| return dm_protocol::kValueRequestUnregister; |
| case DeviceManagementRequestJob::TYPE_UPLOAD_CERTIFICATE: |
| return dm_protocol::kValueRequestUploadCertificate; |
| case DeviceManagementRequestJob::TYPE_DEVICE_STATE_RETRIEVAL: |
| return dm_protocol::kValueRequestDeviceStateRetrieval; |
| case DeviceManagementRequestJob::TYPE_UPLOAD_STATUS: |
| return dm_protocol::kValueRequestUploadStatus; |
| case DeviceManagementRequestJob::TYPE_REMOTE_COMMANDS: |
| return dm_protocol::kValueRequestRemoteCommands; |
| case DeviceManagementRequestJob::TYPE_ATTRIBUTE_UPDATE_PERMISSION: |
| return dm_protocol::kValueRequestDeviceAttributeUpdatePermission; |
| case DeviceManagementRequestJob::TYPE_ATTRIBUTE_UPDATE: |
| return dm_protocol::kValueRequestDeviceAttributeUpdate; |
| case DeviceManagementRequestJob::TYPE_GCM_ID_UPDATE: |
| return dm_protocol::kValueRequestGcmIdUpdate; |
| case DeviceManagementRequestJob::TYPE_ANDROID_MANAGEMENT_CHECK: |
| return dm_protocol::kValueRequestCheckAndroidManagement; |
| case DeviceManagementRequestJob::TYPE_CERT_BASED_REGISTRATION: |
| return dm_protocol::kValueRequestCertBasedRegister; |
| case DeviceManagementRequestJob::TYPE_ACTIVE_DIRECTORY_ENROLL_PLAY_USER: |
| return dm_protocol::kValueRequestActiveDirectoryEnrollPlayUser; |
| case DeviceManagementRequestJob::TYPE_ACTIVE_DIRECTORY_PLAY_ACTIVITY: |
| return dm_protocol::kValueRequestActiveDirectoryPlayActivity; |
| case DeviceManagementRequestJob::TYPE_REQUEST_LICENSE_TYPES: |
| return dm_protocol::kValueRequestCheckDeviceLicense; |
| case DeviceManagementRequestJob::TYPE_UPLOAD_APP_INSTALL_REPORT: |
| return dm_protocol::kValueRequestAppInstallReport; |
| case DeviceManagementRequestJob::TYPE_TOKEN_ENROLLMENT: |
| return dm_protocol::kValueRequestTokenEnrollment; |
| case DeviceManagementRequestJob::TYPE_CHROME_DESKTOP_REPORT: |
| return dm_protocol::kValueRequestChromeDesktopReport; |
| case DeviceManagementRequestJob::TYPE_INITIAL_ENROLLMENT_STATE_RETRIEVAL: |
| return dm_protocol::kValueRequestInitialEnrollmentStateRetrieval; |
| case DeviceManagementRequestJob::TYPE_UPLOAD_POLICY_VALIDATION_REPORT: |
| return dm_protocol::kValueRequestUploadPolicyValidationReport; |
| } |
| NOTREACHED() << "Invalid job type " << type; |
| return ""; |
| } |
| |
| } // namespace |
| |
| // Request job implementation used with DeviceManagementService. |
| class DeviceManagementRequestJobImpl : public DeviceManagementRequestJob { |
| public: |
| DeviceManagementRequestJobImpl( |
| JobType type, |
| const std::string& agent_parameter, |
| const std::string& platform_parameter, |
| DeviceManagementService* service, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory); |
| ~DeviceManagementRequestJobImpl() override; |
| |
| // Handles the URL request response. |
| void HandleResponse(int net_error, |
| int response_code, |
| const std::string& data); |
| |
| // Gets the URL to contact. |
| GURL GetURL(const std::string& server_url); |
| |
| // Configures the headers and flags. |
| void ConfigureRequest(network::ResourceRequest* resource_request); |
| |
| // Attaches the payload. |
| void AddPayload(network::SimpleURLLoader* loader); |
| |
| enum RetryMethod { |
| // No retry required for this request. |
| NO_RETRY, |
| // Should retry immediately (no delay). |
| RETRY_IMMEDIATELY, |
| // Should retry after a delay. |
| RETRY_WITH_DELAY |
| }; |
| |
| // Returns if and how this job should be retried. |
| RetryMethod ShouldRetry(const std::string& mime_type, |
| int response_code, |
| int net_error, |
| bool was_fetched_via_proxy); |
| |
| // Returns the delay before the next retry with the specified RetryMethod. |
| int GetRetryDelay(RetryMethod method); |
| |
| // Invoked right before retrying this job. |
| void PrepareRetry(); |
| |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory() { |
| return url_loader_factory_; |
| } |
| |
| // Get weak pointer |
| base::WeakPtr<DeviceManagementRequestJobImpl> GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| protected: |
| // DeviceManagementRequestJob: |
| void Run() override; |
| |
| private: |
| // Invokes the callback with the given error code. |
| void ReportError(DeviceManagementStatus code); |
| |
| // Pointer to the service this job is associated with. |
| DeviceManagementService* service_; |
| |
| // Whether the BYPASS_PROXY flag should be set by ConfigureRequest(). |
| bool bypass_proxy_; |
| |
| // Number of times that this job has been retried due to connection errors. |
| int retries_count_; |
| |
| // The last error why we had to retry. |
| int last_error_ = 0; |
| |
| // The URLLoaderFactory to use for this job. |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_; |
| |
| // Used to get notified if the job has been canceled while waiting for retry. |
| base::WeakPtrFactory<DeviceManagementRequestJobImpl> weak_ptr_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DeviceManagementRequestJobImpl); |
| }; |
| |
| // Used in the Enterprise.DMServerRequestSuccess histogram, shows how many |
| // retries we had to do to execute the DeviceManagementRequestJob. |
| enum DMServerRequestSuccess { |
| // No retries happened, the request succeeded for the first try. |
| REQUEST_NO_RETRY = 0, |
| |
| // 1..kMaxRetries: number of retries |
| |
| // The request failed (too many retries or non-retriable error). |
| REQUEST_FAILED = 10, |
| // The server responded with an error. |
| REQUEST_ERROR, |
| |
| REQUEST_MAX |
| }; |
| |
| DeviceManagementRequestJobImpl::DeviceManagementRequestJobImpl( |
| JobType type, |
| const std::string& agent_parameter, |
| const std::string& platform_parameter, |
| DeviceManagementService* service, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) |
| : DeviceManagementRequestJob(type, agent_parameter, platform_parameter), |
| service_(service), |
| bypass_proxy_(false), |
| retries_count_(0), |
| url_loader_factory_(url_loader_factory), |
| weak_ptr_factory_(this) {} |
| |
| DeviceManagementRequestJobImpl::~DeviceManagementRequestJobImpl() { |
| service_->RemoveJob(this); |
| } |
| |
| void DeviceManagementRequestJobImpl::Run() { |
| service_->AddJob(this); |
| } |
| |
| void DeviceManagementRequestJobImpl::HandleResponse(int net_error, |
| int response_code, |
| const std::string& data) { |
| if (net_error != net::OK) { |
| UMA_HISTOGRAM_ENUMERATION("Enterprise.DMServerRequestSuccess", |
| DMServerRequestSuccess::REQUEST_FAILED, |
| DMServerRequestSuccess::REQUEST_MAX); |
| LOG(WARNING) << "DMServer request failed, error: " << net_error; |
| em::DeviceManagementResponse dummy_response; |
| callback_.Run(DM_STATUS_REQUEST_FAILED, net_error, dummy_response); |
| return; |
| } |
| |
| if (response_code != kSuccess) { |
| UMA_HISTOGRAM_ENUMERATION("Enterprise.DMServerRequestSuccess", |
| DMServerRequestSuccess::REQUEST_ERROR, |
| DMServerRequestSuccess::REQUEST_MAX); |
| LOG(WARNING) << "DMServer sent an error response: " << response_code; |
| } else { |
| // Success with retries_count_ retries. |
| UMA_HISTOGRAM_EXACT_LINEAR( |
| "Enterprise.DMServerRequestSuccess", retries_count_, |
| static_cast<int>(DMServerRequestSuccess::REQUEST_MAX)); |
| } |
| |
| switch (response_code) { |
| case kSuccess: { |
| em::DeviceManagementResponse response; |
| if (!response.ParseFromString(data)) { |
| ReportError(DM_STATUS_RESPONSE_DECODING_ERROR); |
| return; |
| } |
| callback_.Run(DM_STATUS_SUCCESS, net::OK, response); |
| return; |
| } |
| case kInvalidArgument: |
| ReportError(DM_STATUS_REQUEST_INVALID); |
| return; |
| case kInvalidAuthCookieOrDMToken: |
| ReportError(DM_STATUS_SERVICE_MANAGEMENT_TOKEN_INVALID); |
| return; |
| case kMissingLicenses: |
| ReportError(DM_STATUS_SERVICE_MISSING_LICENSES); |
| return; |
| case kDeviceManagementNotAllowed: |
| ReportError(DM_STATUS_SERVICE_MANAGEMENT_NOT_SUPPORTED); |
| return; |
| case kPendingApproval: |
| ReportError(DM_STATUS_SERVICE_ACTIVATION_PENDING); |
| return; |
| case kConsumerAccountWithPackagedLicense: |
| ReportError(DM_STATUS_SERVICE_CONSUMER_ACCOUNT_WITH_PACKAGED_LICENSE); |
| return; |
| case kInvalidURL: |
| case kInternalServerError: |
| case kServiceUnavailable: |
| ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); |
| return; |
| case kDeviceNotFound: |
| ReportError(DM_STATUS_SERVICE_DEVICE_NOT_FOUND); |
| return; |
| case kPolicyNotFound: |
| ReportError(DM_STATUS_SERVICE_POLICY_NOT_FOUND); |
| return; |
| case kInvalidSerialNumber: |
| ReportError(DM_STATUS_SERVICE_INVALID_SERIAL_NUMBER); |
| return; |
| case kDomainMismatch: |
| ReportError(DM_STATUS_SERVICE_DOMAIN_MISMATCH); |
| return; |
| case kDeprovisioned: |
| ReportError(DM_STATUS_SERVICE_DEPROVISIONED); |
| return; |
| case kDeviceIdConflict: |
| ReportError(DM_STATUS_SERVICE_DEVICE_ID_CONFLICT); |
| return; |
| case kArcDisabled: |
| ReportError(DM_STATUS_SERVICE_ARC_DISABLED); |
| return; |
| default: |
| // Handle all unknown 5xx HTTP error codes as temporary and any other |
| // unknown error as one that needs more time to recover. |
| if (response_code >= 500 && response_code <= 599) |
| ReportError(DM_STATUS_TEMPORARY_UNAVAILABLE); |
| else |
| ReportError(DM_STATUS_HTTP_STATUS_ERROR); |
| return; |
| } |
| } |
| |
| GURL DeviceManagementRequestJobImpl::GetURL( |
| const std::string& server_url) { |
| std::string result(server_url); |
| result += '?'; |
| ParameterMap current_query_params(query_params_); |
| if (last_error_ == 0) { |
| // Not a retry. |
| current_query_params.push_back( |
| std::make_pair(dm_protocol::kParamRetry, "false")); |
| } else { |
| current_query_params.push_back( |
| std::make_pair(dm_protocol::kParamRetry, "true")); |
| current_query_params.push_back(std::make_pair(dm_protocol::kParamLastError, |
| std::to_string(last_error_))); |
| } |
| for (ParameterMap::const_iterator entry(current_query_params.begin()); |
| entry != current_query_params.end(); ++entry) { |
| if (entry != current_query_params.begin()) |
| result += '&'; |
| result += net::EscapeQueryParamValue(entry->first, true); |
| result += '='; |
| result += net::EscapeQueryParamValue(entry->second, true); |
| } |
| return GURL(result); |
| } |
| |
| void DeviceManagementRequestJobImpl::ConfigureRequest( |
| network::ResourceRequest* resource_request) { |
| resource_request->load_flags = |
| net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES | |
| net::LOAD_DISABLE_CACHE | (bypass_proxy_ ? net::LOAD_BYPASS_PROXY : 0); |
| CHECK(auth_data_); |
| if (!auth_data_->gaia_token().empty()) { |
| resource_request->headers.SetHeader( |
| kAuthHeader, |
| std::string(kServiceTokenAuthHeaderPrefix) + auth_data_->gaia_token()); |
| } |
| if (!auth_data_->dm_token().empty()) { |
| resource_request->headers.SetHeader( |
| kAuthHeader, |
| std::string(kDMTokenAuthHeaderPrefix) + auth_data_->dm_token()); |
| } |
| if (!auth_data_->enrollment_token().empty()) { |
| resource_request->headers.SetHeader( |
| kAuthHeader, std::string(kEnrollmentTokenAuthHeaderPrefix) + |
| auth_data_->enrollment_token()); |
| } |
| } |
| |
| void DeviceManagementRequestJobImpl::AddPayload( |
| network::SimpleURLLoader* loader) { |
| std::string payload; |
| CHECK(request_.SerializeToString(&payload)); |
| loader->AttachStringForUpload(payload, kPostContentType); |
| } |
| |
| DeviceManagementRequestJobImpl::RetryMethod |
| DeviceManagementRequestJobImpl::ShouldRetry(const std::string& mime_type, |
| int response_code, |
| int net_error, |
| bool was_fetched_via_proxy) { |
| last_error_ = net_error; |
| if (!bypass_proxy_ && FailedWithProxy(mime_type, response_code, net_error, |
| was_fetched_via_proxy)) { |
| // Retry the job immediately if it failed due to a broken proxy, by |
| // bypassing the proxy on the next try. |
| bypass_proxy_ = true; |
| return RETRY_IMMEDIATELY; |
| } |
| |
| // Early device policy fetches on ChromeOS and Auto-Enrollment checks are |
| // often interrupted during ChromeOS startup when network is not yet ready. |
| // Allowing the fetcher to retry once after that is enough to recover; allow |
| // it to retry up to 3 times just in case. |
| if (IsConnectionError(net_error) && retries_count_ < kMaxRetries) { |
| ++retries_count_; |
| if (type_ == DeviceManagementRequestJob::TYPE_POLICY_FETCH) { |
| // We must not delay when retrying policy fetch, because it is a blocking |
| // call when logging in. |
| return RETRY_IMMEDIATELY; |
| } else { |
| return RETRY_WITH_DELAY; |
| } |
| } |
| |
| // The request didn't fail, or the limit of retry attempts has been reached; |
| // forward the result to the job owner. |
| return NO_RETRY; |
| } |
| |
| int DeviceManagementRequestJobImpl::GetRetryDelay(RetryMethod method) { |
| switch (method) { |
| case RETRY_WITH_DELAY: |
| return g_retry_delay_ms << (retries_count_ - 1); |
| case RETRY_IMMEDIATELY: |
| return 0; |
| default: |
| NOTREACHED(); |
| return 0; |
| } |
| } |
| |
| void DeviceManagementRequestJobImpl::PrepareRetry() { |
| if (!retry_callback_.is_null()) |
| retry_callback_.Run(this); |
| } |
| |
| void DeviceManagementRequestJobImpl::ReportError(DeviceManagementStatus code) { |
| em::DeviceManagementResponse dummy_response; |
| callback_.Run(code, net::OK, dummy_response); |
| } |
| |
| DeviceManagementRequestJob::~DeviceManagementRequestJob() {} |
| |
| void DeviceManagementRequestJob::SetAuthData(std::unique_ptr<DMAuth> auth) { |
| auth_data_ = std::move(auth); |
| if (auth_data_->has_oauth_token()) |
| AddParameter(dm_protocol::kParamOAuthToken, auth_data_->oauth_token()); |
| } |
| |
| void DeviceManagementRequestJob::SetClientID(const std::string& client_id) { |
| AddParameter(dm_protocol::kParamDeviceID, client_id); |
| } |
| |
| void DeviceManagementRequestJob::SetCritical(bool critical) { |
| if (critical) |
| AddParameter(dm_protocol::kParamCritical, "true"); |
| } |
| |
| em::DeviceManagementRequest* DeviceManagementRequestJob::GetRequest() { |
| return &request_; |
| } |
| |
| DeviceManagementRequestJob::DeviceManagementRequestJob( |
| JobType type, |
| const std::string& agent_parameter, |
| const std::string& platform_parameter) |
| : type_(type) { |
| AddParameter(dm_protocol::kParamRequest, JobTypeToRequestType(type)); |
| AddParameter(dm_protocol::kParamDeviceType, dm_protocol::kValueDeviceType); |
| AddParameter(dm_protocol::kParamAppType, dm_protocol::kValueAppType); |
| AddParameter(dm_protocol::kParamAgent, agent_parameter); |
| AddParameter(dm_protocol::kParamPlatform, platform_parameter); |
| } |
| |
| void DeviceManagementRequestJob::SetRetryCallback( |
| const RetryCallback& retry_callback) { |
| retry_callback_ = retry_callback; |
| } |
| |
| void DeviceManagementRequestJob::Start(const Callback& callback) { |
| callback_ = callback; |
| Run(); |
| } |
| |
| void DeviceManagementRequestJob::AddParameter(const std::string& name, |
| const std::string& value) { |
| query_params_.push_back(std::make_pair(name, value)); |
| } |
| |
| DeviceManagementService::~DeviceManagementService() { |
| // All running jobs should have been cancelled by now. |
| DCHECK(pending_jobs_.empty()); |
| DCHECK(queued_jobs_.empty()); |
| } |
| |
| DeviceManagementRequestJob* DeviceManagementService::CreateJob( |
| DeviceManagementRequestJob::JobType type, |
| scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| return new DeviceManagementRequestJobImpl( |
| type, configuration_->GetAgentParameter(), |
| configuration_->GetPlatformParameter(), this, url_loader_factory); |
| } |
| |
| void DeviceManagementService::ScheduleInitialization( |
| int64_t delay_milliseconds) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (initialized_) |
| return; |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&DeviceManagementService::Initialize, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(delay_milliseconds)); |
| } |
| |
| void DeviceManagementService::Initialize() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (initialized_) |
| return; |
| initialized_ = true; |
| |
| while (!queued_jobs_.empty()) { |
| StartJob(queued_jobs_.front()); |
| queued_jobs_.pop_front(); |
| } |
| } |
| |
| void DeviceManagementService::Shutdown() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| weak_ptr_factory_.InvalidateWeakPtrs(); |
| for (auto job(pending_jobs_.begin()); job != pending_jobs_.end(); ++job) { |
| delete job->first; |
| queued_jobs_.push_back(job->second); |
| } |
| pending_jobs_.clear(); |
| } |
| |
| DeviceManagementService::DeviceManagementService( |
| std::unique_ptr<Configuration> configuration) |
| : configuration_(std::move(configuration)), |
| initialized_(false), |
| task_runner_(base::ThreadTaskRunnerHandle::Get()), |
| weak_ptr_factory_(this) { |
| DCHECK(configuration_); |
| } |
| |
| void DeviceManagementService::StartJob(DeviceManagementRequestJobImpl* job) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| GURL url = job->GetURL(GetServerUrl()); |
| DCHECK(url.is_valid()) << "Maybe invalid --device-management-url was passed?"; |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("device_management_service", R"( |
| semantics { |
| sender: "Cloud Policy" |
| description: |
| "Communication with the Cloud Policy backend, used to check for " |
| "the existence of cloud policy for the signed-in account, and to " |
| "load/update cloud policy if it exists." |
| trigger: |
| "Sign in to Chrome, also periodic refreshes." |
| data: |
| "During initial signin or device enrollment, auth data is sent up " |
| "as part of registration. After initial signin/enrollment, if the " |
| "session or device is managed, a unique device or profile ID is " |
| "sent with every future request. On Chrome OS, other diagnostic " |
| "information can be sent up for managed sessions, including which " |
| "users have used the device, device hardware status, connected " |
| "networks, CPU usage, etc." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "This feature cannot be controlled by Chrome settings, but users " |
| "can sign out of Chrome to disable it." |
| chrome_policy { |
| SigninAllowed { |
| policy_options {mode: MANDATORY} |
| SigninAllowed: false |
| } |
| } |
| })"); |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| resource_request->url = std::move(url); |
| resource_request->method = "POST"; |
| job->ConfigureRequest(resource_request.get()); |
| network::SimpleURLLoader* fetcher = |
| network::SimpleURLLoader::Create(std::move(resource_request), |
| traffic_annotation) |
| .release(); |
| job->AddPayload(fetcher); |
| fetcher->SetAllowHttpErrorResults(true); |
| fetcher->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| job->url_loader_factory().get(), |
| base::BindOnce(&DeviceManagementService::OnURLLoaderComplete, |
| base::Unretained(this), fetcher)); |
| |
| pending_jobs_[fetcher] = job; |
| } |
| |
| void DeviceManagementService::StartJobAfterDelay( |
| base::WeakPtr<DeviceManagementRequestJobImpl> job) { |
| // Check if the job still exists (it is possible that it had been canceled |
| // while we were waiting for the retry). |
| if (job) { |
| StartJob(job.get()); |
| } |
| } |
| |
| std::string DeviceManagementService::GetServerUrl() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| return configuration_->GetServerUrl(); |
| } |
| |
| // static |
| void DeviceManagementService::SetRetryDelayForTesting(long retry_delay_ms) { |
| CHECK_GE(retry_delay_ms, 0); |
| g_retry_delay_ms = retry_delay_ms; |
| } |
| |
| void DeviceManagementService::OnURLLoaderComplete( |
| network::SimpleURLLoader* url_loader, |
| std::unique_ptr<std::string> response_body) { |
| int response_code = 0; |
| bool was_fetched_via_proxy = false; |
| std::string mime_type; |
| if (url_loader->ResponseInfo()) { |
| was_fetched_via_proxy = |
| url_loader->ResponseInfo()->proxy_server.is_valid() && |
| !url_loader->ResponseInfo()->proxy_server.is_direct(); |
| mime_type = url_loader->ResponseInfo()->mime_type; |
| if (url_loader->ResponseInfo()->headers) |
| response_code = url_loader->ResponseInfo()->headers->response_code(); |
| } |
| |
| std::string response_body_str; |
| if (response_body.get()) |
| response_body_str = std::move(*response_body.get()); |
| |
| OnURLLoaderCompleteInternal(url_loader, response_body_str, mime_type, |
| url_loader->NetError(), response_code, |
| was_fetched_via_proxy); |
| } |
| |
| void DeviceManagementService::OnURLLoaderCompleteInternal( |
| network::SimpleURLLoader* url_loader, |
| const std::string& response_body, |
| const std::string& mime_type, |
| int net_error, |
| int response_code, |
| bool was_fetched_via_proxy) { |
| auto entry(pending_jobs_.find(url_loader)); |
| if (entry == pending_jobs_.end()) { |
| NOTREACHED() << "Callback from foreign URL loader"; |
| return; |
| } |
| |
| DeviceManagementRequestJobImpl* job = entry->second; |
| pending_jobs_.erase(entry); |
| |
| DeviceManagementRequestJobImpl::RetryMethod retry_method = job->ShouldRetry( |
| mime_type, response_code, net_error, was_fetched_via_proxy); |
| if (retry_method != DeviceManagementRequestJobImpl::RetryMethod::NO_RETRY) { |
| job->PrepareRetry(); |
| int delay = job->GetRetryDelay(retry_method); |
| LOG(WARNING) << "Dmserver request failed, retrying in " << delay / 1000 |
| << "s."; |
| task_runner_->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&DeviceManagementService::StartJobAfterDelay, |
| weak_ptr_factory_.GetWeakPtr(), job->GetWeakPtr()), |
| base::TimeDelta::FromMilliseconds(delay)); |
| } else { |
| job->HandleResponse(net_error, response_code, response_body); |
| } |
| delete url_loader; |
| } |
| |
| network::SimpleURLLoader* |
| DeviceManagementService::GetSimpleURLLoaderForTesting() { |
| DCHECK_EQ(1u, pending_jobs_.size()); |
| return const_cast<network::SimpleURLLoader*>(pending_jobs_.begin()->first); |
| } |
| |
| void DeviceManagementService::AddJob(DeviceManagementRequestJobImpl* job) { |
| if (initialized_) |
| StartJob(job); |
| else |
| queued_jobs_.push_back(job); |
| } |
| |
| void DeviceManagementService::RemoveJob(DeviceManagementRequestJobImpl* job) { |
| for (auto entry(pending_jobs_.begin()); entry != pending_jobs_.end(); |
| ++entry) { |
| if (entry->second == job) { |
| delete entry->first; |
| pending_jobs_.erase(entry); |
| return; |
| } |
| } |
| |
| const JobQueue::iterator elem = |
| std::find(queued_jobs_.begin(), queued_jobs_.end(), job); |
| if (elem != queued_jobs_.end()) |
| queued_jobs_.erase(elem); |
| } |
| |
| } // namespace policy |