Skip to content

Commit

Permalink
Create a new extension API for enterprise browser reporting.
Browse files Browse the repository at this point in the history
It introduces new permission, IDL and skeleton of implementation.

This privacy API is used to upload browsers and devices status that
are collected by extension to the admin console.


Bug: 832887
Change-Id: I60780d76c9e7680843acef38ce906e9cbe150025
Reviewed-on: https://chromium-review.googlesource.com/976819
Reviewed-by: Gayane Petrosyan <gayane@chromium.org>
Reviewed-by: Devlin <rdevlin.cronin@chromium.org>
Commit-Queue: Owen Min <zmin@chromium.org>
Cr-Original-Commit-Position: refs/heads/master@{#550521}(cherry picked from commit fdada01)
Reviewed-on: https://chromium-review.googlesource.com/1013340
Reviewed-by: Owen Min <zmin@chromium.org>
Cr-Commit-Position: refs/branch-heads/3396@{#8}
Cr-Branched-From: 9ef2aa8-refs/heads/master@{#550428}
  • Loading branch information
Owen Min committed Apr 15, 2018
1 parent 6157172 commit 8e87f21
Show file tree
Hide file tree
Showing 15 changed files with 377 additions and 0 deletions.
2 changes: 2 additions & 0 deletions chrome/browser/extensions/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,8 @@ static_library("extensions") {
}
} else {
sources += [
"api/enterprise_reporting_private/enterprise_reporting_private_api.cc",
"api/enterprise_reporting_private/enterprise_reporting_private_api.h",
"api/image_writer_private/operation_nonchromeos.cc",
"api/image_writer_private/removable_storage_provider_linux.cc",
"api/messaging/native_message_process_host.cc",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
georgesak@chromium.org
mad@chromium.org
pastarmovj@chromium.org
zmin@chromium.org
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
// Copyright 2018 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/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h"

#include <memory>

#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/policy/browser_dm_token_storage.h"
#include "chrome/browser/policy/chrome_browser_policy_connector.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/extensions/api/enterprise_reporting_private.h"
#include "components/policy/core/common/cloud/cloud_policy_client.h"
#include "components/policy/core/common/cloud/device_management_service.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "net/url_request/url_request_context_getter.h"

namespace em = enterprise_management;

namespace extensions {
namespace {

const char kMachineName[] = "machineName";
const char kOSInfo[] = "osInfo";
const char kOSUser[] = "osUser";

bool UpdateJSONEncodedStringEntry(const base::Value& dict_value,
const char key[],
std::string* entry) {
if (const base::Value* value = dict_value.FindKey(key)) {
if (!value->is_dict() && !value->is_list())
return false;
base::JSONWriter::Write(*value, entry);
}
return true;
}

// Transfer the input from Json file to protobuf. Return nullptr if the input
// is not valid.
std::unique_ptr<enterprise_management::ChromeDesktopReportRequest>
GenerateChromeDesktopReportRequest(const base::DictionaryValue& report) {
std::unique_ptr<em::ChromeDesktopReportRequest> request =
std::make_unique<em::ChromeDesktopReportRequest>();

if (!UpdateJSONEncodedStringEntry(report, kMachineName,
request->mutable_machine_name()) ||
!UpdateJSONEncodedStringEntry(report, kOSInfo,
request->mutable_os_info()) ||
!UpdateJSONEncodedStringEntry(report, kOSUser,
request->mutable_os_user())) {
return nullptr;
}
return request;
}

} // namespace

namespace enterprise_reporting {

const char kInvalidInputErrorMessage[] = "The report is not valid.";
const char kUploadFailed[] = "Failed to upload the report.";
const char kDeviceNotEnrolled[] = "This device has not been enrolled yet.";

} // namespace enterprise_reporting

EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
EnterpriseReportingPrivateUploadChromeDesktopReportFunction() {
policy::DeviceManagementService* device_management_service =
g_browser_process->browser_policy_connector()
->device_management_service();
// Initial the DeviceManagementService if it exist and hasn't been initialized
if (device_management_service)
device_management_service->ScheduleInitialization(0);
cloud_policy_client_ = std::make_unique<policy::CloudPolicyClient>(
std::string(), std::string(), device_management_service,
g_browser_process->system_request_context(), nullptr,
policy::CloudPolicyClient::DeviceDMTokenCallback());
dm_token_ = policy::BrowserDMTokenStorage::Get()->RetrieveDMToken();
client_id_ = policy::BrowserDMTokenStorage::Get()->RetrieveClientId();
}

EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
~EnterpriseReportingPrivateUploadChromeDesktopReportFunction() {}

ExtensionFunction::ResponseAction
EnterpriseReportingPrivateUploadChromeDesktopReportFunction::Run() {
if (dm_token_.empty() || client_id_.empty())
return RespondNow(Error(enterprise_reporting::kDeviceNotEnrolled));
std::unique_ptr<
api::enterprise_reporting_private::UploadChromeDesktopReport::Params>
params(api::enterprise_reporting_private::UploadChromeDesktopReport::
Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
std::unique_ptr<em::ChromeDesktopReportRequest> request =
GenerateChromeDesktopReportRequest(params->report.additional_properties);
if (!request) {
return RespondNow(Error(enterprise_reporting::kInvalidInputErrorMessage));
}

if (!cloud_policy_client_->is_registered())
cloud_policy_client_->SetupRegistration(dm_token_, client_id_,
std::vector<std::string>());

cloud_policy_client_->UploadChromeDesktopReport(
std::move(request),
base::BindRepeating(
&EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
OnReportUploaded,
this));
return RespondLater();
}

void EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
SetCloudPolicyClientForTesting(
std::unique_ptr<policy::CloudPolicyClient> client) {
cloud_policy_client_ = std::move(client);
}

void EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
SetRegistrationInfoForTesting(const std::string& dm_token,
const std::string& client_id) {
dm_token_ = dm_token;
client_id_ = client_id;
}

void EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
OnResponded() {
cloud_policy_client_.reset();
}

void EnterpriseReportingPrivateUploadChromeDesktopReportFunction::
OnReportUploaded(bool status) {
if (status)
Respond(NoArguments());
else
Respond(Error(enterprise_reporting::kUploadFailed));
}

} // namespace extensions
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright 2018 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_EXTENSIONS_API_ENTERPRISE_REPORTING_PRIVATE_ENTERPRISE_REPORTING_PRIVATE_API_H_
#define CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_REPORTING_PRIVATE_ENTERPRISE_REPORTING_PRIVATE_API_H_

#include "extensions/browser/extension_function.h"

namespace policy {
class CloudPolicyClient;
}

namespace extensions {
namespace enterprise_reporting {

extern const char kInvalidInputErrorMessage[];
extern const char kUploadFailed[];
extern const char kDeviceNotEnrolled[];

} // namespace enterprise_reporting

class EnterpriseReportingPrivateUploadChromeDesktopReportFunction
: public UIThreadExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION(
"enterprise.reportingPrivate.uploadChromeDesktopReport",
ENTERPRISEREPORTINGPRIVATE_UPLOADCHROMEDESKTOPREPORT);
EnterpriseReportingPrivateUploadChromeDesktopReportFunction();

// ExtensionFunction
ExtensionFunction::ResponseAction Run() override;

void SetCloudPolicyClientForTesting(
std::unique_ptr<policy::CloudPolicyClient> client);
void SetRegistrationInfoForTesting(const std::string& dm_token,
const std::string& client_id);

private:
~EnterpriseReportingPrivateUploadChromeDesktopReportFunction() override;

// ExtensionFunction
void OnResponded() override;

// Callback once Chrome get the response from the DM Server.
void OnReportUploaded(bool status);

std::unique_ptr<policy::CloudPolicyClient> cloud_policy_client_;
std::string dm_token_;
std::string client_id_;

DISALLOW_COPY_AND_ASSIGN(
EnterpriseReportingPrivateUploadChromeDesktopReportFunction);
};

} // namespace extensions

#endif // CHROME_BROWSER_EXTENSIONS_API_ENTERPRISE_REPORTING_PRIVATE_ENTERPRISE_REPORTING_PRIVATE_API_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// Copyright 2018 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/extensions/api/enterprise_reporting_private/enterprise_reporting_private_api.h"

#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "chrome/browser/extensions/extension_api_unittest.h"
#include "chrome/browser/extensions/extension_function_test_utils.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "testing/gmock/include/gmock/gmock.h"

using ::testing::_;
using ::testing::Invoke;
using ::testing::WithArgs;

namespace extensions {
namespace {

const char kFakeDMToken[] = "fake-dm-token";
const char kFakeClientId[] = "fake-client-id";
const char kFakeMachineNameReport[] = "{\"computername\":\"name\"}";
const char kFakeInvalidMachineNameReport[] = "\"invalid\"";

class MockCloudPolicyClient : public policy::MockCloudPolicyClient {
public:
MockCloudPolicyClient() = default;

void UploadChromeDesktopReport(
std::unique_ptr<enterprise_management::ChromeDesktopReportRequest>
request,
const StatusCallback& callback) {
UploadChromeDesktopReportProxy(request.get(), callback);
}
MOCK_METHOD2(UploadChromeDesktopReportProxy,
void(enterprise_management::ChromeDesktopReportRequest*,
const StatusCallback&));

void OnReportUploadedFailed(const StatusCallback& callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, false));
}

void OnReportUploadedSucceeded(const StatusCallback& callback) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(callback, true));
}

private:
DISALLOW_COPY_AND_ASSIGN(MockCloudPolicyClient);
};

} // namespace

class EnterpriseReportingPrivateTest : public ExtensionApiUnittest {
public:
EnterpriseReportingPrivateTest() = default;

UIThreadExtensionFunction* CreateChromeDesktopReportingFunction(
const std::string& dm_token) {
EnterpriseReportingPrivateUploadChromeDesktopReportFunction* function =
new EnterpriseReportingPrivateUploadChromeDesktopReportFunction();
std::unique_ptr<MockCloudPolicyClient> client =
std::make_unique<MockCloudPolicyClient>();
client_ = client.get();
function->SetCloudPolicyClientForTesting(std::move(client));
function->SetRegistrationInfoForTesting(dm_token, kFakeClientId);
return function;
}

std::string GenerateArgs(const char* name) {
return base::StringPrintf("[{\"machineName\":%s}]", name);
}

MockCloudPolicyClient* client_;

private:
DISALLOW_COPY_AND_ASSIGN(EnterpriseReportingPrivateTest);
};

TEST_F(EnterpriseReportingPrivateTest, DeviceIsNotEnrolled) {
ASSERT_EQ(enterprise_reporting::kDeviceNotEnrolled,
RunFunctionAndReturnError(
CreateChromeDesktopReportingFunction(std::string()),
GenerateArgs(kFakeMachineNameReport)));
}

TEST_F(EnterpriseReportingPrivateTest, ReportIsNotValid) {
ASSERT_EQ(enterprise_reporting::kInvalidInputErrorMessage,
RunFunctionAndReturnError(
CreateChromeDesktopReportingFunction(kFakeDMToken),
GenerateArgs(kFakeInvalidMachineNameReport)));
}

TEST_F(EnterpriseReportingPrivateTest, UploadFailed) {
UIThreadExtensionFunction* function =
CreateChromeDesktopReportingFunction(kFakeDMToken);
EXPECT_CALL(*client_, SetupRegistration(kFakeDMToken, kFakeClientId, _))
.Times(1);
EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
.WillOnce(WithArgs<1>(
Invoke(client_, &MockCloudPolicyClient::OnReportUploadedFailed)));
ASSERT_EQ(enterprise_reporting::kUploadFailed,
RunFunctionAndReturnError(function,
GenerateArgs(kFakeMachineNameReport)));
::testing::Mock::VerifyAndClearExpectations(client_);
}

TEST_F(EnterpriseReportingPrivateTest, UploadSucceeded) {
UIThreadExtensionFunction* function =
CreateChromeDesktopReportingFunction(kFakeDMToken);
EXPECT_CALL(*client_, SetupRegistration(kFakeDMToken, kFakeClientId, _))
.Times(1);
EXPECT_CALL(*client_, UploadChromeDesktopReportProxy(_, _))
.WillOnce(WithArgs<1>(
Invoke(client_, &MockCloudPolicyClient::OnReportUploadedSucceeded)));
ASSERT_EQ(nullptr, RunFunctionAndReturnValue(
function, GenerateArgs(kFakeMachineNameReport)));
::testing::Mock::VerifyAndClearExpectations(client_);
}

} // namespace extensions
4 changes: 4 additions & 0 deletions chrome/common/extensions/api/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ if (is_chromeos) {
schema_sources += [ "input_ime.json" ]
}

if (!is_chromeos) {
schema_sources += [ "enterprise_reporting_private.idl" ]
}

if (enable_print_preview && !is_chromeos) {
schema_sources += [ "cloud_print_private.json" ]
}
Expand Down
4 changes: 4 additions & 0 deletions chrome/common/extensions/api/_api_features.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,10 @@
"dependencies": ["permission:enterprise.platformKeysPrivate"],
"contexts": ["blessed_extension"]
},
"enterprise.reportingPrivate": {
"dependencies": ["permission:enterprise.reportingPrivate"],
"contexts": ["blessed_extension"]
},
"experimental.devtools.audits": {
"nocompile": true,
"dependencies": ["permission:experimental", "manifest:devtools_page"],
Expand Down
9 changes: 9 additions & 0 deletions chrome/common/extensions/api/_permission_features.json
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,15 @@
"2C18988BCDC297196D5D6893005175DA1BC1E893" // http://crbug.com/570127
]
},
"enterprise.reportingPrivate": {
"channel": "beta",
"extension_types": ["extension"],
"platforms" : ["win", "mac", "linux"],
"whitelist" : [
"FD15C63ABA854733FDCBC1D4D34A71E963A12ABD", // https://crbug.com/825015
"08455FA7CB8734168378F731B00B354CEEE0088F" // https://crbug.com/825015 - Test Extension
]
},
"experimental": {
"channel": "stable",
"command_line_switch": "experimental-extension-apis",
Expand Down
Loading

0 comments on commit 8e87f21

Please sign in to comment.