// Copyright (c) 2011 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/policy/device_management_backend_impl.h"
#include <utility>
#include <vector>
#if defined(OS_POSIX) && !defined(OS_MACOSX)
#include <sys/utsname.h>
#endif
#include "base/stringprintf.h"
#include "base/sys_info.h"
#include "chrome/browser/policy/device_management_service.h"
#include "chrome/common/chrome_version_info.h"
#include "net/base/escape.h"
#include "net/url_request/url_request_status.h"
#if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/system_access.h"
#endif
namespace policy {
// Name constants for URL query parameters.
const char DeviceManagementBackendImpl::kParamRequest[] = "request";
const char DeviceManagementBackendImpl::kParamDeviceType[] = "devicetype";
const char DeviceManagementBackendImpl::kParamAppType[] = "apptype";
const char DeviceManagementBackendImpl::kParamDeviceID[] = "deviceid";
const char DeviceManagementBackendImpl::kParamAgent[] = "agent";
const char DeviceManagementBackendImpl::kParamPlatform[] = "platform";
// String constants for the device and app type we report to the server.
const char DeviceManagementBackendImpl::kValueRequestRegister[] = "register";
const char DeviceManagementBackendImpl::kValueRequestUnregister[] =
"unregister";
const char DeviceManagementBackendImpl::kValueRequestPolicy[] = "policy";
const char DeviceManagementBackendImpl::kValueDeviceType[] = "2";
const char DeviceManagementBackendImpl::kValueAppType[] = "Chrome";
namespace {
const char kValueAgent[] = "%s %s(%s)";
const char kValuePlatform[] = "%s|%s|%s";
const char kPostContentType[] = "application/protobuf";
const char kServiceTokenAuthHeader[] = "Authorization: GoogleLogin auth=";
const char kDMTokenAuthHeader[] = "Authorization: GoogleDMToken token=";
// HTTP Error Codes of the DM Server with their concrete meinings in the context
// of the DM Server communication.
const int kSuccess = 200;
const int kInvalidArgument = 400;
const int kInvalidAuthCookieOrDMToken = 401;
const int kDeviceManagementNotAllowed = 403;
const int kInvalidURL = 404; // This error is not coming from the GFE.
const int kPendingApproval = 491;
const int kInternalServerError = 500;
const int kServiceUnavailable = 503;
const int kDeviceNotFound = 901;
const int kPolicyNotFound = 902; // This error is not sent as HTTP status code.
#if defined(OS_CHROMEOS)
// Machine info keys.
const char kMachineInfoHWClass[] = "hardware_class";
const char kMachineInfoBoard[] = "CHROMEOS_RELEASE_BOARD";
#endif
} // namespace
// Helper class for URL query parameter encoding/decoding.
class URLQueryParameters {
public:
URLQueryParameters() {}
// Add a query parameter.
void Put(const std::string& name, const std::string& value);
// Produce the query string, taking care of properly encoding and assembling
// the names and values.
std::string Encode();
private:
typedef std::vector<std::pair<std::string, std::string> > ParameterMap;
ParameterMap params_;
DISALLOW_COPY_AND_ASSIGN(URLQueryParameters);
};
void URLQueryParameters::Put(const std::string& name,
const std::string& value) {
params_.push_back(std::make_pair(name, value));
}
std::string URLQueryParameters::Encode() {
std::string result;
for (ParameterMap::const_iterator entry(params_.begin());
entry != params_.end();
++entry) {
if (entry != params_.begin())
result += '&';
result += EscapeQueryParamValue(entry->first, true);
result += '=';
result += EscapeQueryParamValue(entry->second, true);
}
return result;
}
// A base class containing the common code for the jobs created by the backend
// implementation. Subclasses provide custom code for handling actual register,
// unregister, and policy jobs.
class DeviceManagementJobBase
: public DeviceManagementService::DeviceManagementJob {
public:
virtual ~DeviceManagementJobBase() {}
// DeviceManagementJob overrides:
virtual void HandleResponse(const net::URLRequestStatus& status,
int response_code,
const ResponseCookies& cookies,
const std::string& data);
virtual GURL GetURL(const std::string& server_url);
virtual void ConfigureRequest(URLFetcher* fetcher);
protected:
// Constructs a device management job running for the given backend.
DeviceManagementJobBase(DeviceManagementBackendImpl* backend_impl,
const std::string& request_type,
const std::string& device_id)
: backend_impl_(backend_impl) {
query_params_.Put(DeviceManagementBackendImpl::kParamRequest, request_type);
query_params_.Put(DeviceManagementBackendImpl::kParamDeviceType,
DeviceManagementBackendImpl::kValueDeviceType);
query_params_.Put(DeviceManagementBackendImpl::kParamAppType,
DeviceManagementBackendImpl::kValueAppType);
query_params_.Put(DeviceManagementBackendImpl::kParamDeviceID, device_id);
query_params_.Put(DeviceManagementBackendImpl::kParamAgent,
DeviceManagementBackendImpl::GetAgentString());
query_params_.Put(DeviceManagementBackendImpl::kParamPlatform,
DeviceManagementBackendImpl::GetPlatformString());
}
void SetQueryParam(const std::string& name, const std::string& value) {
query_params_.Put(name, value);
}
void SetAuthToken(const std::string& auth_token) {
auth_token_ = auth_token;
}
void SetDeviceManagementToken(const std::string& device_management_token) {
device_management_token_ = device_management_token;
}
void SetPayload(const em::DeviceManagementRequest& request) {
if (!request.SerializeToString(&payload_)) {
NOTREACHED();
LOG(ERROR) << "Failed to serialize request.";
}
}
private:
// Implemented by subclasses to handle decoded responses and errors.
virtual void OnResponse(
const em::DeviceManagementResponse& response) = 0;
virtual void OnError(DeviceManagementBackend::ErrorCode error) = 0;
// The backend this job is handling a request for.
DeviceManagementBackendImpl* backend_impl_;
// Query parameters.
URLQueryParameters query_params_;
// Auth token (if applicaple).
std::string auth_token_;
// Device management token (if applicable).
std::string device_management_token_;
// The payload.
std::string payload_;
DISALLOW_COPY_AND_ASSIGN(DeviceManagementJobBase);
};
void DeviceManagementJobBase::HandleResponse(
const net::URLRequestStatus& status,
int response_code,
const ResponseCookies& cookies,
const std::string& data) {
// Delete ourselves when this is done.
scoped_ptr<DeviceManagementJob> scoped_killer(this);
backend_impl_->JobDone(this);
backend_impl_ = NULL;
if (status.status() != net::URLRequestStatus::SUCCESS) {
OnError(DeviceManagementBackend::kErrorRequestFailed);
return;
}
switch (response_code) {
case kSuccess: {
em::DeviceManagementResponse response;
if (!response.ParseFromString(data)) {
OnError(DeviceManagementBackend::kErrorResponseDecoding);
return;
}
OnResponse(response);
return;
}
case kInvalidArgument: {
OnError(DeviceManagementBackend::kErrorRequestInvalid);
return;
}
case kInvalidAuthCookieOrDMToken: {
OnError(DeviceManagementBackend::kErrorServiceManagementTokenInvalid);
return;
}
case kDeviceManagementNotAllowed: {
OnError(DeviceManagementBackend::kErrorServiceManagementNotSupported);
return;
}
case kPendingApproval: {
OnError(DeviceManagementBackend::kErrorServiceActivationPending);
return;
}
case kInvalidURL:
case kInternalServerError:
case kServiceUnavailable: {
OnError(DeviceManagementBackend::kErrorTemporaryUnavailable);
return;
}
case kDeviceNotFound: {
OnError(DeviceManagementBackend::kErrorServiceDeviceNotFound);
return;
}
case kPolicyNotFound: {
OnError(DeviceManagementBackend::kErrorServicePolicyNotFound);
break;
}
default: {
VLOG(1) << "Unexpected HTTP status in response from DMServer : "
<< response_code << ".";
// 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)
OnError(DeviceManagementBackend::kErrorTemporaryUnavailable);
else
OnError(DeviceManagementBackend::kErrorHttpStatus);
return;
}
}
}
GURL DeviceManagementJobBase::GetURL(
const std::string& server_url) {
return GURL(server_url + '?' + query_params_.Encode());
}
void DeviceManagementJobBase::ConfigureRequest(URLFetcher* fetcher) {
fetcher->set_upload_data(kPostContentType, payload_);
std::string extra_headers;
if (!auth_token_.empty())
extra_headers += kServiceTokenAuthHeader + auth_token_ + "\n";
if (!device_management_token_.empty())
extra_headers += kDMTokenAuthHeader + device_management_token_ + "\n";
fetcher->set_extra_request_headers(extra_headers);
}
// Handles device registration jobs.
class DeviceManagementRegisterJob : public DeviceManagementJobBase {
public:
DeviceManagementRegisterJob(
DeviceManagementBackendImpl* backend_impl,
const std::string& auth_token,
const std::string& device_id,
const em::DeviceRegisterRequest& request,
DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate)
: DeviceManagementJobBase(
backend_impl,
DeviceManagementBackendImpl::kValueRequestRegister,
device_id),
delegate_(delegate) {
SetAuthToken(auth_token);
em::DeviceManagementRequest request_wrapper;
request_wrapper.mutable_register_request()->CopyFrom(request);
SetPayload(request_wrapper);
}
virtual ~DeviceManagementRegisterJob() {}
private:
// DeviceManagementJobBase overrides.
virtual void OnError(DeviceManagementBackend::ErrorCode error) {
delegate_->OnError(error);
}
virtual void OnResponse(const em::DeviceManagementResponse& response) {
delegate_->HandleRegisterResponse(response.register_response());
}
DeviceManagementBackend::DeviceRegisterResponseDelegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(DeviceManagementRegisterJob);
};
// Handles device unregistration jobs.
class DeviceManagementUnregisterJob : public DeviceManagementJobBase {
public:
DeviceManagementUnregisterJob(
DeviceManagementBackendImpl* backend_impl,
const std::string& device_management_token,
const std::string& device_id,
const em::DeviceUnregisterRequest& request,
DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate)
: DeviceManagementJobBase(
backend_impl,
DeviceManagementBackendImpl::kValueRequestUnregister,
device_id),
delegate_(delegate) {
SetDeviceManagementToken(device_management_token);
em::DeviceManagementRequest request_wrapper;
request_wrapper.mutable_unregister_request()->CopyFrom(request);
SetPayload(request_wrapper);
}
virtual ~DeviceManagementUnregisterJob() {}
private:
// DeviceManagementJobBase overrides.
virtual void OnError(DeviceManagementBackend::ErrorCode error) {
delegate_->OnError(error);
}
virtual void OnResponse(const em::DeviceManagementResponse& response) {
delegate_->HandleUnregisterResponse(response.unregister_response());
}
DeviceManagementBackend::DeviceUnregisterResponseDelegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(DeviceManagementUnregisterJob);
};
// Handles policy request jobs.
class DeviceManagementPolicyJob : public DeviceManagementJobBase {
public:
DeviceManagementPolicyJob(
DeviceManagementBackendImpl* backend_impl,
const std::string& device_management_token,
const std::string& device_id,
const em::DevicePolicyRequest& request,
DeviceManagementBackend::DevicePolicyResponseDelegate* delegate)
: DeviceManagementJobBase(
backend_impl,
DeviceManagementBackendImpl::kValueRequestPolicy,
device_id),
delegate_(delegate) {
SetDeviceManagementToken(device_management_token);
em::DeviceManagementRequest request_wrapper;
request_wrapper.mutable_policy_request()->CopyFrom(request);
SetPayload(request_wrapper);
}
virtual ~DeviceManagementPolicyJob() {}
private:
// DeviceManagementJobBase overrides.
virtual void OnError(DeviceManagementBackend::ErrorCode error) {
delegate_->OnError(error);
}
virtual void OnResponse(const em::DeviceManagementResponse& response) {
delegate_->HandlePolicyResponse(response.policy_response());
}
DeviceManagementBackend::DevicePolicyResponseDelegate* delegate_;
DISALLOW_COPY_AND_ASSIGN(DeviceManagementPolicyJob);
};
DeviceManagementBackendImpl::DeviceManagementBackendImpl(
DeviceManagementService* service)
: service_(service) {
}
DeviceManagementBackendImpl::~DeviceManagementBackendImpl() {
for (JobSet::iterator job(pending_jobs_.begin());
job != pending_jobs_.end();
++job) {
service_->RemoveJob(*job);
delete *job;
}
pending_jobs_.clear();
}
std::string DeviceManagementBackendImpl::GetAgentString() {
static std::string agent;
if (!agent.empty())
return agent;
chrome::VersionInfo version_info;
agent = base::StringPrintf(kValueAgent,
version_info.Name().c_str(),
version_info.Version().c_str(),
version_info.LastChange().c_str());
return agent;
}
std::string DeviceManagementBackendImpl::GetPlatformString() {
static std::string platform;
if (!platform.empty())
return platform;
std::string os_name(base::SysInfo::OperatingSystemName());
std::string os_hardware(base::SysInfo::CPUArchitecture());
#if defined(OS_CHROMEOS)
chromeos::SystemAccess* sys_lib = chromeos::SystemAccess::GetInstance();
std::string hwclass;
std::string board;
if (!sys_lib->GetMachineStatistic(kMachineInfoHWClass, &hwclass) ||
!sys_lib->GetMachineStatistic(kMachineInfoBoard, &board)) {
LOG(ERROR) << "Failed to get machine information";
}
os_name += ",CrOS," + board;
os_hardware += "," + hwclass;
#endif
std::string os_version("-");
#if defined(OS_WIN) || defined(OS_MACOSX) || defined(OS_CHROMEOS)
int32 os_major_version = 0;
int32 os_minor_version = 0;
int32 os_bugfix_version = 0;
base::SysInfo::OperatingSystemVersionNumbers(&os_major_version,
&os_minor_version,
&os_bugfix_version);
os_version = base::StringPrintf("%d.%d.%d",
os_major_version,
os_minor_version,
os_bugfix_version);
#endif
platform = base::StringPrintf(kValuePlatform,
os_name.c_str(),
os_hardware.c_str(),
os_version.c_str());
return platform;
}
void DeviceManagementBackendImpl::JobDone(DeviceManagementJobBase* job) {
pending_jobs_.erase(job);
}
void DeviceManagementBackendImpl::AddJob(DeviceManagementJobBase* job) {
pending_jobs_.insert(job);
service_->AddJob(job);
}
void DeviceManagementBackendImpl::ProcessRegisterRequest(
const std::string& auth_token,
const std::string& device_id,
const em::DeviceRegisterRequest& request,
DeviceRegisterResponseDelegate* delegate) {
AddJob(new DeviceManagementRegisterJob(this, auth_token, device_id, request,
delegate));
}
void DeviceManagementBackendImpl::ProcessUnregisterRequest(
const std::string& device_management_token,
const std::string& device_id,
const em::DeviceUnregisterRequest& request,
DeviceUnregisterResponseDelegate* delegate) {
AddJob(new DeviceManagementUnregisterJob(this, device_management_token,
device_id, request, delegate));
}
void DeviceManagementBackendImpl::ProcessPolicyRequest(
const std::string& device_management_token,
const std::string& device_id,
const em::DevicePolicyRequest& request,
DevicePolicyResponseDelegate* delegate) {
AddJob(new DeviceManagementPolicyJob(this, device_management_token, device_id,
request, delegate));
}
} // namespace policy