Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(251)

Unified Diff: chrome/browser/policy/cloud_policy_manager_browsertest.cc

Issue 12209070: Fix cloud policy duplicate registrations issue. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: fixed expected type on desktop, fixed return type Created 7 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/policy/cloud_policy_manager_browsertest.cc
diff --git a/chrome/browser/policy/cloud_policy_manager_browsertest.cc b/chrome/browser/policy/cloud_policy_manager_browsertest.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2cb6a930e4e9357d6ec6dd8e66aaca1f17df6d41
--- /dev/null
+++ b/chrome/browser/policy/cloud_policy_manager_browsertest.cc
@@ -0,0 +1,427 @@
+// Copyright (c) 2013 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 "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/policy/browser_policy_connector.h"
+#include "chrome/browser/policy/cloud_policy_client.h"
+#include "chrome/browser/policy/cloud_policy_constants.h"
+#include "chrome/browser/policy/policy_map.h"
+#include "chrome/browser/policy/policy_service.h"
+#include "chrome/browser/policy/proto/device_management_backend.pb.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "content/public/browser/browser_thread.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_data_stream.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_filter.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_test_job.h"
+#include "policy/policy_constants.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/chromeos/login/user_manager.h"
+#include "chrome/browser/policy/user_cloud_policy_manager_chromeos.h"
+#else
+#include "chrome/browser/policy/user_cloud_policy_manager.h"
+#include "chrome/browser/policy/user_cloud_policy_manager_factory.h"
+#include "chrome/browser/signin/signin_manager.h"
+#include "chrome/browser/signin/signin_manager_factory.h"
+#endif
+
+using testing::AnyNumber;
+using testing::InvokeWithoutArgs;
+using testing::Mock;
+using testing::_;
+
+namespace em = enterprise_management;
+
+namespace policy {
+
+namespace {
+
+// Dummy service URL for testing with request interception enabled.
+const char kServiceUrl[] = "http://dmserver.com/device_management";
+
+// HTTP headers for replies simulating a bad request response.
+const char kBadHeaders[] =
+ "HTTP/1.1 400 Bad request\0"
+ "Content-type: application/protobuf\0"
+ "\0";
+
+// HTTP headers for replies containing good responses.
+const char kGoodHeaders[] =
+ "HTTP/1.1 200 OK\0"
+ "Content-type: application/protobuf\0"
+ "\0";
+
+// A callback that returns a new URLRequestJob given a URLRequest.
+// This is used to queue callbacks that will handle expected requests.
+typedef base::Callback<net::URLRequestJob*(net::URLRequest*,
+ net::NetworkDelegate*)> JobCallback;
+
+// Helper callback for jobs that should fail with a network |error|.
+net::URLRequestJob* ErrorJobCallback(int error,
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) {
+ return new net::URLRequestErrorJob(request, network_delegate, error);
+}
+
+// Helper callback for jobs that should fail with a 400 HTTP error.
+net::URLRequestJob* BadRequestJobCallback(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) {
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 move kBadHeaders definition here?
Joao da Silva 2013/02/13 15:57:15 Done.
+ std::string headers(kBadHeaders, arraysize(kBadHeaders));
+ return new net::URLRequestTestJob(
+ request, network_delegate, headers, std::string(), true);
+}
+
+// Parses the upload data in |request| into |request_msg|, and validates the
+// request. The query string in the URL must contain the |expected_type| for
+// the "request" parameter. Returns true if all checks succeeded, and the
+// request data has been parsed into |request_msg|.
+bool ValidRequest(net::URLRequest* request,
+ const std::string& expected_type,
+ em::DeviceManagementRequest* request_msg) {
+ if (request->method() != "POST")
+ return false;
+ std::string spec = request->url().spec();
+ if (spec.find("request=" + expected_type) == std::string::npos)
+ return false;
+
+ // This destroys the UploadDataStream to read its data, but that's OK since
+ // the request isn't going anywhere.
+ net::UploadDataStream* stream =
+ const_cast<net::UploadDataStream*>(request->get_upload());
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 so there's no way to read a const UploadDataStream
Joao da Silva 2013/02/13 15:57:15 It's actually possible to read from the const obje
+ if (!stream)
+ return false;
+ stream->Init(net::CompletionCallback());
+ scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(1024));
+ int size = stream->Read(buffer.get(), 1024, net::CompletionCallback());
+ std::string data(buffer->data(), size);
+ if (!request_msg->ParseFromString(data))
+ return false;
+
+ return true;
+}
+
+// Helper callback for register jobs that should suceed. Validates the request
+// parameters and returns an appropriate response job. If |expect_reregister|
+// is true then the reregister flag must be set in the DeviceRegisterRequest
+// protobuf.
+net::URLRequestJob* RegisterJobCallback(
+ bool expect_reregister,
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) {
+ em::DeviceManagementRequest request_msg;
+ if (!ValidRequest(request, "register", &request_msg))
+ return BadRequestJobCallback(request, network_delegate);
+
+ if (!request_msg.has_register_request() ||
+ request_msg.has_unregister_request() ||
+ request_msg.has_policy_request() ||
+ request_msg.has_device_status_report_request() ||
+ request_msg.has_session_status_report_request() ||
+ request_msg.has_auto_enrollment_request()) {
+ return BadRequestJobCallback(request, network_delegate);
+ }
+
+ const em::DeviceRegisterRequest& register_request =
+ request_msg.register_request();
+ if (expect_reregister &&
+ (!register_request.has_reregister() || !register_request.reregister())) {
+ return BadRequestJobCallback(request, network_delegate);
+ } else if (!expect_reregister &&
+ register_request.has_reregister() &&
+ register_request.reregister()) {
+ return BadRequestJobCallback(request, network_delegate);
+ }
+
+ em::DeviceRegisterRequest::Type expected_type =
+#if defined(OS_CHROMEOS)
+ em::DeviceRegisterRequest::USER;
+#else
+ em::DeviceRegisterRequest::BROWSER;
+#endif
+ if (!register_request.has_type() || register_request.type() != expected_type)
+ return BadRequestJobCallback(request, network_delegate);
+
+ em::DeviceManagementResponse response;
+ em::DeviceRegisterResponse* register_response =
+ response.mutable_register_response();
+ register_response->set_device_management_token("s3cr3t70k3n");
+ std::string data;
+ response.SerializeToString(&data);
+
+ std::string headers(kGoodHeaders, arraysize(kGoodHeaders));
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 seems like kGoodHeaders could be a static constant
Joao da Silva 2013/02/13 15:57:15 Done.
+ return new net::URLRequestTestJob(
+ request, network_delegate, headers, data, true);
+}
+
+class MockCloudPolicyClientObserver : public CloudPolicyClient::Observer {
+ public:
+ MockCloudPolicyClientObserver() {}
+ virtual ~MockCloudPolicyClientObserver() {}
+
+ MOCK_METHOD1(OnPolicyFetched, void(CloudPolicyClient*));
+ MOCK_METHOD1(OnRegistrationStateChanged, void(CloudPolicyClient*));
+ MOCK_METHOD1(OnClientError, void(CloudPolicyClient*));
+};
+
+// Intercepts all requests to "dmserver.com" while in scope. Must be created and
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 Let's use localhost or example.com to be on the sa
Joao da Silva 2013/02/13 15:57:15 Done.
+// destroyed while the IO thread is valid.
+class RequestInterceptor {
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 It seems this both large and generic enough to mov
Joao da Silva 2013/02/13 15:57:15 Done. Also updated device_management_service_brows
+ public:
+ RequestInterceptor() {
+ delegate_ = new Delegate();
+ scoped_ptr<net::URLRequestJobFactory::ProtocolHandler> handler(delegate_);
+ PostToIOAndWait(
+ base::Bind(&net::URLRequestFilter::AddHostnameProtocolHandler,
+ base::Unretained(net::URLRequestFilter::GetInstance()),
+ "http", "dmserver.com", base::Passed(&handler)));
+ }
+
+ ~RequestInterceptor() {
+ // RemoveHostnameHandler() destroys the |delegate_|, which is owned by
+ // the URLRequestFilter.
+ delegate_ = NULL;
+ PostToIOAndWait(
+ base::Bind(&net::URLRequestFilter::RemoveHostnameHandler,
+ base::Unretained(net::URLRequestFilter::GetInstance()),
+ "http", "dmserver.com"));
+ }
+
+ // Returns the number of pending callback jobs that haven't been used yet.
+ size_t GetPendingSize() {
+ size_t pending_size = 0xffff;
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 That's a weird choice. How about std::numeric_limi
Joao da Silva 2013/02/13 15:57:15 Done.
+ PostToIOAndWait(base::Bind(&Delegate::GetPendingSize,
+ base::Unretained(delegate_),
+ &pending_size));
+ return pending_size;
+ }
+
+ // Queues |callback| to handle a request to "dmserver.com". Each callback is
+ // used only once, and in the order that they're pushed.
+ void PushJobCallback(const JobCallback& callback) {
+ PostToIOAndWait(base::Bind(&Delegate::PushJobCallback,
+ base::Unretained(delegate_),
+ callback));
+ }
+
+ private:
+ class Delegate : public net::URLRequestJobFactory::ProtocolHandler {
+ public:
+ virtual net::URLRequestJob* MaybeCreateJob(
+ net::URLRequest* request,
+ net::NetworkDelegate* network_delegate) const OVERRIDE {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+
+ if (request->url().host() != "dmserver.com") {
+ // Reject requests to other servers.
+ return ErrorJobCallback(
+ net::ERR_CONNECTION_REFUSED, request, network_delegate);
+ }
+
+ if (pending_job_callbacks_.empty()) {
+ // Reject dmserver requests by default.
+ return BadRequestJobCallback(request, network_delegate);
+ }
+
+ JobCallback callback = pending_job_callbacks_.front();
+ pending_job_callbacks_.pop();
+ return callback.Run(request, network_delegate);
+ }
+
+ void GetPendingSize(size_t* pending_size) const {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+ *pending_size = pending_job_callbacks_.size();
+ }
+
+ void PushJobCallback(const JobCallback& callback) {
+ CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
+ pending_job_callbacks_.push(callback);
+ }
+
+ private:
+ // The queue of pending callbacks. 'mutable' because MaybeCreateJob() is a
+ // const method; it can't reenter though, because it runs exclusively on
+ // the IO thread.
+ mutable std::queue<JobCallback> pending_job_callbacks_;
+ };
+
+ // Helper to execute a |task| on IO, and return only after it has completed.
+ void PostToIOAndWait(const base::Closure& task) {
+ base::RunLoop run_loop;
+ content::BrowserThread::PostTaskAndReply(
+ content::BrowserThread::IO, FROM_HERE, task, run_loop.QuitClosure());
+ run_loop.Run();
+ }
+
+ // Owned by URLRequestFilter. This handle is valid on IO and only while the
+ // interceptor is valid.
+ Delegate* delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestInterceptor);
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 #include "base/basictypes.h"
Joao da Silva 2013/02/13 15:57:15 Done.
+};
+
+} // namespace
+
+// Tests the cloud policy stack using a URLRequestJobFactory::ProtocolHandler
+// to intercept requests and produce canned responses.
+class CloudPolicyManagerTest : public InProcessBrowserTest {
+ protected:
+ CloudPolicyManagerTest() {}
+ virtual ~CloudPolicyManagerTest() {}
+
+ virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
+ CommandLine* command_line = CommandLine::ForCurrentProcess();
+ command_line->AppendSwitchASCII(switches::kDeviceManagementUrl,
+ kServiceUrl);
+ }
+
+ virtual void SetUpOnMainThread() OVERRIDE {
+ // Checks that no policies have been loaded by the other providers before
+ // setting up the cloud connection. Other policies configured in the test
+ // machine will interfere with these tests.
+ const PolicyMap& map = g_browser_process->policy_service()->GetPolicies(
+ PolicyNamespace(POLICY_DOMAIN_CHROME, std::string()));
+ if (!map.empty()) {
+ base::DictionaryValue dict;
+ for (PolicyMap::const_iterator it = map.begin(); it != map.end(); ++it)
+ dict.SetWithoutPathExpansion(it->first, it->second.value->DeepCopy());
+ ADD_FAILURE()
+ << "There are pre-existing policies in this machine that will "
+ << "interfere with these tests. Policies found: " << dict;
+ }
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 This should probably be factored out now that we h
Joao da Silva 2013/02/13 15:57:15 Done. Added policy/test_utils.h to contain helpers
+
+ interceptor_.reset(new RequestInterceptor());
+
+ BrowserPolicyConnector* connector =
+ g_browser_process->browser_policy_connector();
+ connector->ScheduleServiceInitialization(0);
+
+#if !defined(OS_CHROMEOS)
+ // Mock a signed-in user. This is used by the UserCloudPolicyStore to pass
+ // the username to the UserCloudPolicyValidator.
+ SigninManager* signin_manager =
+ SigninManagerFactory::GetForProfile(browser()->profile());
+ ASSERT_TRUE(signin_manager);
+ signin_manager->SetAuthenticatedUsername("user@example.com");
+
+ UserCloudPolicyManager* policy_manager =
+ UserCloudPolicyManagerFactory::GetForProfile(browser()->profile());
+ ASSERT_TRUE(policy_manager);
+ policy_manager->Connect(g_browser_process->local_state(),
+ UserCloudPolicyManager::CreateCloudPolicyClient(
+ connector->device_management_service()).Pass());
+#endif
Mattias Nissler (ping if slow) 2013/02/13 10:50:43 I'm wondering whether this test would be simpler t
Joao da Silva 2013/02/13 15:57:15 I'd prefer not to do this setup too. Unfortunately
+ }
+
+ virtual void CleanUpOnMainThread() OVERRIDE {
+ // Verify that all the expected requests were handled.
+ EXPECT_EQ(0u, interceptor_->GetPendingSize());
+
+ interceptor_.reset();
+ }
+
+#if defined(OS_CHROMEOS)
+ UserCloudPolicyManagerChromeOS* policy_manager() {
+ return g_browser_process->browser_policy_connector()->
+ GetUserCloudPolicyManager();
+ }
+#else
+ UserCloudPolicyManager* policy_manager() {
+ return UserCloudPolicyManagerFactory::GetForProfile(browser()->profile());
+ }
+#endif // defined(OS_CHROMEOS)
+
+ // Register the client of the policy_manager() using a bogus auth token, and
+ // returns once the registration gets a result back.
+ void Register() {
+ ASSERT_TRUE(policy_manager());
+ ASSERT_TRUE(policy_manager()->core()->client());
+
+ base::RunLoop run_loop;
+ MockCloudPolicyClientObserver observer;
+ EXPECT_CALL(observer, OnRegistrationStateChanged(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+ EXPECT_CALL(observer, OnClientError(_))
+ .Times(AnyNumber())
+ .WillRepeatedly(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit));
+ policy_manager()->core()->client()->AddObserver(&observer);
+
+ // Give a bogus OAuth token to the |policy_manager|. This should make its
+ // CloudPolicyClient fetch the DMToken.
+ policy_manager()->RegisterClient("bogus");
+ run_loop.Run();
+ Mock::VerifyAndClearExpectations(&observer);
+ policy_manager()->core()->client()->RemoveObserver(&observer);
+ }
+
+ scoped_ptr<RequestInterceptor> interceptor_;
+};
+
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, Register) {
+ // Accept one register request. The initial request should not include the
+ // reregister flag.
+ const bool expect_reregister = false;
+ interceptor_->PushJobCallback(
+ base::Bind(&RegisterJobCallback, expect_reregister));
+
+ EXPECT_FALSE(policy_manager()->core()->client()->is_registered());
+ ASSERT_NO_FATAL_FAILURE(Register());
+ EXPECT_TRUE(policy_manager()->core()->client()->is_registered());
+}
+
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterFails) {
+ // The interceptor makes all requests fail by default; this will trigger
+ // an OnClientError() call on the observer.
+ EXPECT_FALSE(policy_manager()->core()->client()->is_registered());
+ ASSERT_NO_FATAL_FAILURE(Register());
+ EXPECT_FALSE(policy_manager()->core()->client()->is_registered());
+}
+
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterFailsWithRetries) {
+ // Fail 4 times with ERR_NETWORK_CHANGED; the first 3 will trigger a retry,
+ // the last one will forward the error to the client and unblock the
+ // register process.
+ for (int i = 0; i < 4; ++i) {
+ interceptor_->PushJobCallback(base::Bind(&ErrorJobCallback,
+ net::ERR_NETWORK_CHANGED));
+ }
+
+ EXPECT_FALSE(policy_manager()->core()->client()->is_registered());
+ ASSERT_NO_FATAL_FAILURE(Register());
+ EXPECT_FALSE(policy_manager()->core()->client()->is_registered());
+}
+
+IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterWithRetry) {
+ // Accept one register request after failing once. The retry request should
+ // set the reregister flag.
+ interceptor_->PushJobCallback(base::Bind(&ErrorJobCallback,
+ net::ERR_NETWORK_CHANGED));
+ const bool expect_reregister = true;
+ interceptor_->PushJobCallback(
+ base::Bind(&RegisterJobCallback, expect_reregister));
+
+ EXPECT_FALSE(policy_manager()->core()->client()->is_registered());
+ ASSERT_NO_FATAL_FAILURE(Register());
+ EXPECT_TRUE(policy_manager()->core()->client()->is_registered());
+}
+
+} // namespace policy
« no previous file with comments | « chrome/browser/policy/cloud_policy_client_unittest.cc ('k') | chrome/browser/policy/device_management_service.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698