Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "base/bind.h" | |
| 6 #include "base/bind_helpers.h" | |
| 7 #include "base/command_line.h" | |
| 8 #include "base/memory/ref_counted.h" | |
| 9 #include "base/memory/scoped_ptr.h" | |
| 10 #include "base/run_loop.h" | |
| 11 #include "base/values.h" | |
| 12 #include "chrome/browser/browser_process.h" | |
| 13 #include "chrome/browser/policy/browser_policy_connector.h" | |
| 14 #include "chrome/browser/policy/cloud_policy_client.h" | |
| 15 #include "chrome/browser/policy/cloud_policy_constants.h" | |
| 16 #include "chrome/browser/policy/policy_map.h" | |
| 17 #include "chrome/browser/policy/policy_service.h" | |
| 18 #include "chrome/browser/policy/proto/device_management_backend.pb.h" | |
| 19 #include "chrome/browser/profiles/profile.h" | |
| 20 #include "chrome/browser/ui/browser.h" | |
| 21 #include "chrome/common/chrome_switches.h" | |
| 22 #include "chrome/test/base/in_process_browser_test.h" | |
| 23 #include "content/public/browser/browser_thread.h" | |
| 24 #include "googleurl/src/gurl.h" | |
| 25 #include "net/base/io_buffer.h" | |
| 26 #include "net/base/net_errors.h" | |
| 27 #include "net/base/upload_data_stream.h" | |
| 28 #include "net/url_request/url_request_error_job.h" | |
| 29 #include "net/url_request/url_request_filter.h" | |
| 30 #include "net/url_request/url_request_job_factory.h" | |
| 31 #include "net/url_request/url_request_test_job.h" | |
| 32 #include "policy/policy_constants.h" | |
| 33 #include "testing/gmock/include/gmock/gmock.h" | |
| 34 #include "testing/gtest/include/gtest/gtest.h" | |
| 35 | |
| 36 #if defined(OS_CHROMEOS) | |
| 37 #include "chrome/browser/chromeos/login/user_manager.h" | |
| 38 #include "chrome/browser/policy/user_cloud_policy_manager_chromeos.h" | |
| 39 #else | |
| 40 #include "chrome/browser/policy/user_cloud_policy_manager.h" | |
| 41 #include "chrome/browser/policy/user_cloud_policy_manager_factory.h" | |
| 42 #include "chrome/browser/signin/signin_manager.h" | |
| 43 #include "chrome/browser/signin/signin_manager_factory.h" | |
| 44 #endif | |
| 45 | |
| 46 using testing::AnyNumber; | |
| 47 using testing::InvokeWithoutArgs; | |
| 48 using testing::Mock; | |
| 49 using testing::_; | |
| 50 | |
| 51 namespace em = enterprise_management; | |
| 52 | |
| 53 namespace policy { | |
| 54 | |
| 55 namespace { | |
| 56 | |
| 57 // Dummy service URL for testing with request interception enabled. | |
| 58 const char kServiceUrl[] = "http://dmserver.com/device_management"; | |
| 59 | |
| 60 // HTTP headers for replies simulating a bad request response. | |
| 61 const char kBadHeaders[] = | |
| 62 "HTTP/1.1 400 Bad request\0" | |
| 63 "Content-type: application/protobuf\0" | |
| 64 "\0"; | |
| 65 | |
| 66 // HTTP headers for replies containing good responses. | |
| 67 const char kGoodHeaders[] = | |
| 68 "HTTP/1.1 200 OK\0" | |
| 69 "Content-type: application/protobuf\0" | |
| 70 "\0"; | |
| 71 | |
| 72 // A callback that returns a new URLRequestJob given a URLRequest. | |
| 73 // This is used to queue callbacks that will handle expected requests. | |
| 74 typedef base::Callback<net::URLRequestJob*(net::URLRequest*, | |
| 75 net::NetworkDelegate*)> JobCallback; | |
| 76 | |
| 77 // Helper callback for jobs that should fail with a network |error|. | |
| 78 net::URLRequestJob* ErrorJobCallback(int error, | |
| 79 net::URLRequest* request, | |
| 80 net::NetworkDelegate* network_delegate) { | |
| 81 return new net::URLRequestErrorJob(request, network_delegate, error); | |
| 82 } | |
| 83 | |
| 84 // Helper callback for jobs that should fail with a 400 HTTP error. | |
| 85 net::URLRequestJob* BadRequestJobCallback( | |
| 86 net::URLRequest* request, | |
| 87 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.
| |
| 88 std::string headers(kBadHeaders, arraysize(kBadHeaders)); | |
| 89 return new net::URLRequestTestJob( | |
| 90 request, network_delegate, headers, std::string(), true); | |
| 91 } | |
| 92 | |
| 93 // Parses the upload data in |request| into |request_msg|, and validates the | |
| 94 // request. The query string in the URL must contain the |expected_type| for | |
| 95 // the "request" parameter. Returns true if all checks succeeded, and the | |
| 96 // request data has been parsed into |request_msg|. | |
| 97 bool ValidRequest(net::URLRequest* request, | |
| 98 const std::string& expected_type, | |
| 99 em::DeviceManagementRequest* request_msg) { | |
| 100 if (request->method() != "POST") | |
| 101 return false; | |
| 102 std::string spec = request->url().spec(); | |
| 103 if (spec.find("request=" + expected_type) == std::string::npos) | |
| 104 return false; | |
| 105 | |
| 106 // This destroys the UploadDataStream to read its data, but that's OK since | |
| 107 // the request isn't going anywhere. | |
| 108 net::UploadDataStream* stream = | |
| 109 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
| |
| 110 if (!stream) | |
| 111 return false; | |
| 112 stream->Init(net::CompletionCallback()); | |
| 113 scoped_refptr<net::IOBuffer> buffer(new net::IOBuffer(1024)); | |
| 114 int size = stream->Read(buffer.get(), 1024, net::CompletionCallback()); | |
| 115 std::string data(buffer->data(), size); | |
| 116 if (!request_msg->ParseFromString(data)) | |
| 117 return false; | |
| 118 | |
| 119 return true; | |
| 120 } | |
| 121 | |
| 122 // Helper callback for register jobs that should suceed. Validates the request | |
| 123 // parameters and returns an appropriate response job. If |expect_reregister| | |
| 124 // is true then the reregister flag must be set in the DeviceRegisterRequest | |
| 125 // protobuf. | |
| 126 net::URLRequestJob* RegisterJobCallback( | |
| 127 bool expect_reregister, | |
| 128 net::URLRequest* request, | |
| 129 net::NetworkDelegate* network_delegate) { | |
| 130 em::DeviceManagementRequest request_msg; | |
| 131 if (!ValidRequest(request, "register", &request_msg)) | |
| 132 return BadRequestJobCallback(request, network_delegate); | |
| 133 | |
| 134 if (!request_msg.has_register_request() || | |
| 135 request_msg.has_unregister_request() || | |
| 136 request_msg.has_policy_request() || | |
| 137 request_msg.has_device_status_report_request() || | |
| 138 request_msg.has_session_status_report_request() || | |
| 139 request_msg.has_auto_enrollment_request()) { | |
| 140 return BadRequestJobCallback(request, network_delegate); | |
| 141 } | |
| 142 | |
| 143 const em::DeviceRegisterRequest& register_request = | |
| 144 request_msg.register_request(); | |
| 145 if (expect_reregister && | |
| 146 (!register_request.has_reregister() || !register_request.reregister())) { | |
| 147 return BadRequestJobCallback(request, network_delegate); | |
| 148 } else if (!expect_reregister && | |
| 149 register_request.has_reregister() && | |
| 150 register_request.reregister()) { | |
| 151 return BadRequestJobCallback(request, network_delegate); | |
| 152 } | |
| 153 | |
| 154 em::DeviceRegisterRequest::Type expected_type = | |
| 155 #if defined(OS_CHROMEOS) | |
| 156 em::DeviceRegisterRequest::USER; | |
| 157 #else | |
| 158 em::DeviceRegisterRequest::BROWSER; | |
| 159 #endif | |
| 160 if (!register_request.has_type() || register_request.type() != expected_type) | |
| 161 return BadRequestJobCallback(request, network_delegate); | |
| 162 | |
| 163 em::DeviceManagementResponse response; | |
| 164 em::DeviceRegisterResponse* register_response = | |
| 165 response.mutable_register_response(); | |
| 166 register_response->set_device_management_token("s3cr3t70k3n"); | |
| 167 std::string data; | |
| 168 response.SerializeToString(&data); | |
| 169 | |
| 170 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.
| |
| 171 return new net::URLRequestTestJob( | |
| 172 request, network_delegate, headers, data, true); | |
| 173 } | |
| 174 | |
| 175 class MockCloudPolicyClientObserver : public CloudPolicyClient::Observer { | |
| 176 public: | |
| 177 MockCloudPolicyClientObserver() {} | |
| 178 virtual ~MockCloudPolicyClientObserver() {} | |
| 179 | |
| 180 MOCK_METHOD1(OnPolicyFetched, void(CloudPolicyClient*)); | |
| 181 MOCK_METHOD1(OnRegistrationStateChanged, void(CloudPolicyClient*)); | |
| 182 MOCK_METHOD1(OnClientError, void(CloudPolicyClient*)); | |
| 183 }; | |
| 184 | |
| 185 // 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.
| |
| 186 // destroyed while the IO thread is valid. | |
| 187 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
| |
| 188 public: | |
| 189 RequestInterceptor() { | |
| 190 delegate_ = new Delegate(); | |
| 191 scoped_ptr<net::URLRequestJobFactory::ProtocolHandler> handler(delegate_); | |
| 192 PostToIOAndWait( | |
| 193 base::Bind(&net::URLRequestFilter::AddHostnameProtocolHandler, | |
| 194 base::Unretained(net::URLRequestFilter::GetInstance()), | |
| 195 "http", "dmserver.com", base::Passed(&handler))); | |
| 196 } | |
| 197 | |
| 198 ~RequestInterceptor() { | |
| 199 // RemoveHostnameHandler() destroys the |delegate_|, which is owned by | |
| 200 // the URLRequestFilter. | |
| 201 delegate_ = NULL; | |
| 202 PostToIOAndWait( | |
| 203 base::Bind(&net::URLRequestFilter::RemoveHostnameHandler, | |
| 204 base::Unretained(net::URLRequestFilter::GetInstance()), | |
| 205 "http", "dmserver.com")); | |
| 206 } | |
| 207 | |
| 208 // Returns the number of pending callback jobs that haven't been used yet. | |
| 209 size_t GetPendingSize() { | |
| 210 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.
| |
| 211 PostToIOAndWait(base::Bind(&Delegate::GetPendingSize, | |
| 212 base::Unretained(delegate_), | |
| 213 &pending_size)); | |
| 214 return pending_size; | |
| 215 } | |
| 216 | |
| 217 // Queues |callback| to handle a request to "dmserver.com". Each callback is | |
| 218 // used only once, and in the order that they're pushed. | |
| 219 void PushJobCallback(const JobCallback& callback) { | |
| 220 PostToIOAndWait(base::Bind(&Delegate::PushJobCallback, | |
| 221 base::Unretained(delegate_), | |
| 222 callback)); | |
| 223 } | |
| 224 | |
| 225 private: | |
| 226 class Delegate : public net::URLRequestJobFactory::ProtocolHandler { | |
| 227 public: | |
| 228 virtual net::URLRequestJob* MaybeCreateJob( | |
| 229 net::URLRequest* request, | |
| 230 net::NetworkDelegate* network_delegate) const OVERRIDE { | |
| 231 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
| 232 | |
| 233 if (request->url().host() != "dmserver.com") { | |
| 234 // Reject requests to other servers. | |
| 235 return ErrorJobCallback( | |
| 236 net::ERR_CONNECTION_REFUSED, request, network_delegate); | |
| 237 } | |
| 238 | |
| 239 if (pending_job_callbacks_.empty()) { | |
| 240 // Reject dmserver requests by default. | |
| 241 return BadRequestJobCallback(request, network_delegate); | |
| 242 } | |
| 243 | |
| 244 JobCallback callback = pending_job_callbacks_.front(); | |
| 245 pending_job_callbacks_.pop(); | |
| 246 return callback.Run(request, network_delegate); | |
| 247 } | |
| 248 | |
| 249 void GetPendingSize(size_t* pending_size) const { | |
| 250 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
| 251 *pending_size = pending_job_callbacks_.size(); | |
| 252 } | |
| 253 | |
| 254 void PushJobCallback(const JobCallback& callback) { | |
| 255 CHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); | |
| 256 pending_job_callbacks_.push(callback); | |
| 257 } | |
| 258 | |
| 259 private: | |
| 260 // The queue of pending callbacks. 'mutable' because MaybeCreateJob() is a | |
| 261 // const method; it can't reenter though, because it runs exclusively on | |
| 262 // the IO thread. | |
| 263 mutable std::queue<JobCallback> pending_job_callbacks_; | |
| 264 }; | |
| 265 | |
| 266 // Helper to execute a |task| on IO, and return only after it has completed. | |
| 267 void PostToIOAndWait(const base::Closure& task) { | |
| 268 base::RunLoop run_loop; | |
| 269 content::BrowserThread::PostTaskAndReply( | |
| 270 content::BrowserThread::IO, FROM_HERE, task, run_loop.QuitClosure()); | |
| 271 run_loop.Run(); | |
| 272 } | |
| 273 | |
| 274 // Owned by URLRequestFilter. This handle is valid on IO and only while the | |
| 275 // interceptor is valid. | |
| 276 Delegate* delegate_; | |
| 277 | |
| 278 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.
| |
| 279 }; | |
| 280 | |
| 281 } // namespace | |
| 282 | |
| 283 // Tests the cloud policy stack using a URLRequestJobFactory::ProtocolHandler | |
| 284 // to intercept requests and produce canned responses. | |
| 285 class CloudPolicyManagerTest : public InProcessBrowserTest { | |
| 286 protected: | |
| 287 CloudPolicyManagerTest() {} | |
| 288 virtual ~CloudPolicyManagerTest() {} | |
| 289 | |
| 290 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { | |
| 291 CommandLine* command_line = CommandLine::ForCurrentProcess(); | |
| 292 command_line->AppendSwitchASCII(switches::kDeviceManagementUrl, | |
| 293 kServiceUrl); | |
| 294 } | |
| 295 | |
| 296 virtual void SetUpOnMainThread() OVERRIDE { | |
| 297 // Checks that no policies have been loaded by the other providers before | |
| 298 // setting up the cloud connection. Other policies configured in the test | |
| 299 // machine will interfere with these tests. | |
| 300 const PolicyMap& map = g_browser_process->policy_service()->GetPolicies( | |
| 301 PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())); | |
| 302 if (!map.empty()) { | |
| 303 base::DictionaryValue dict; | |
| 304 for (PolicyMap::const_iterator it = map.begin(); it != map.end(); ++it) | |
| 305 dict.SetWithoutPathExpansion(it->first, it->second.value->DeepCopy()); | |
| 306 ADD_FAILURE() | |
| 307 << "There are pre-existing policies in this machine that will " | |
| 308 << "interfere with these tests. Policies found: " << dict; | |
| 309 } | |
|
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
| |
| 310 | |
| 311 interceptor_.reset(new RequestInterceptor()); | |
| 312 | |
| 313 BrowserPolicyConnector* connector = | |
| 314 g_browser_process->browser_policy_connector(); | |
| 315 connector->ScheduleServiceInitialization(0); | |
| 316 | |
| 317 #if !defined(OS_CHROMEOS) | |
| 318 // Mock a signed-in user. This is used by the UserCloudPolicyStore to pass | |
| 319 // the username to the UserCloudPolicyValidator. | |
| 320 SigninManager* signin_manager = | |
| 321 SigninManagerFactory::GetForProfile(browser()->profile()); | |
| 322 ASSERT_TRUE(signin_manager); | |
| 323 signin_manager->SetAuthenticatedUsername("user@example.com"); | |
| 324 | |
| 325 UserCloudPolicyManager* policy_manager = | |
| 326 UserCloudPolicyManagerFactory::GetForProfile(browser()->profile()); | |
| 327 ASSERT_TRUE(policy_manager); | |
| 328 policy_manager->Connect(g_browser_process->local_state(), | |
| 329 UserCloudPolicyManager::CreateCloudPolicyClient( | |
| 330 connector->device_management_service()).Pass()); | |
| 331 #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
| |
| 332 } | |
| 333 | |
| 334 virtual void CleanUpOnMainThread() OVERRIDE { | |
| 335 // Verify that all the expected requests were handled. | |
| 336 EXPECT_EQ(0u, interceptor_->GetPendingSize()); | |
| 337 | |
| 338 interceptor_.reset(); | |
| 339 } | |
| 340 | |
| 341 #if defined(OS_CHROMEOS) | |
| 342 UserCloudPolicyManagerChromeOS* policy_manager() { | |
| 343 return g_browser_process->browser_policy_connector()-> | |
| 344 GetUserCloudPolicyManager(); | |
| 345 } | |
| 346 #else | |
| 347 UserCloudPolicyManager* policy_manager() { | |
| 348 return UserCloudPolicyManagerFactory::GetForProfile(browser()->profile()); | |
| 349 } | |
| 350 #endif // defined(OS_CHROMEOS) | |
| 351 | |
| 352 // Register the client of the policy_manager() using a bogus auth token, and | |
| 353 // returns once the registration gets a result back. | |
| 354 void Register() { | |
| 355 ASSERT_TRUE(policy_manager()); | |
| 356 ASSERT_TRUE(policy_manager()->core()->client()); | |
| 357 | |
| 358 base::RunLoop run_loop; | |
| 359 MockCloudPolicyClientObserver observer; | |
| 360 EXPECT_CALL(observer, OnRegistrationStateChanged(_)) | |
| 361 .Times(AnyNumber()) | |
| 362 .WillRepeatedly(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); | |
| 363 EXPECT_CALL(observer, OnClientError(_)) | |
| 364 .Times(AnyNumber()) | |
| 365 .WillRepeatedly(InvokeWithoutArgs(&run_loop, &base::RunLoop::Quit)); | |
| 366 policy_manager()->core()->client()->AddObserver(&observer); | |
| 367 | |
| 368 // Give a bogus OAuth token to the |policy_manager|. This should make its | |
| 369 // CloudPolicyClient fetch the DMToken. | |
| 370 policy_manager()->RegisterClient("bogus"); | |
| 371 run_loop.Run(); | |
| 372 Mock::VerifyAndClearExpectations(&observer); | |
| 373 policy_manager()->core()->client()->RemoveObserver(&observer); | |
| 374 } | |
| 375 | |
| 376 scoped_ptr<RequestInterceptor> interceptor_; | |
| 377 }; | |
| 378 | |
| 379 IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, Register) { | |
| 380 // Accept one register request. The initial request should not include the | |
| 381 // reregister flag. | |
| 382 const bool expect_reregister = false; | |
| 383 interceptor_->PushJobCallback( | |
| 384 base::Bind(&RegisterJobCallback, expect_reregister)); | |
| 385 | |
| 386 EXPECT_FALSE(policy_manager()->core()->client()->is_registered()); | |
| 387 ASSERT_NO_FATAL_FAILURE(Register()); | |
| 388 EXPECT_TRUE(policy_manager()->core()->client()->is_registered()); | |
| 389 } | |
| 390 | |
| 391 IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterFails) { | |
| 392 // The interceptor makes all requests fail by default; this will trigger | |
| 393 // an OnClientError() call on the observer. | |
| 394 EXPECT_FALSE(policy_manager()->core()->client()->is_registered()); | |
| 395 ASSERT_NO_FATAL_FAILURE(Register()); | |
| 396 EXPECT_FALSE(policy_manager()->core()->client()->is_registered()); | |
| 397 } | |
| 398 | |
| 399 IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterFailsWithRetries) { | |
| 400 // Fail 4 times with ERR_NETWORK_CHANGED; the first 3 will trigger a retry, | |
| 401 // the last one will forward the error to the client and unblock the | |
| 402 // register process. | |
| 403 for (int i = 0; i < 4; ++i) { | |
| 404 interceptor_->PushJobCallback(base::Bind(&ErrorJobCallback, | |
| 405 net::ERR_NETWORK_CHANGED)); | |
| 406 } | |
| 407 | |
| 408 EXPECT_FALSE(policy_manager()->core()->client()->is_registered()); | |
| 409 ASSERT_NO_FATAL_FAILURE(Register()); | |
| 410 EXPECT_FALSE(policy_manager()->core()->client()->is_registered()); | |
| 411 } | |
| 412 | |
| 413 IN_PROC_BROWSER_TEST_F(CloudPolicyManagerTest, RegisterWithRetry) { | |
| 414 // Accept one register request after failing once. The retry request should | |
| 415 // set the reregister flag. | |
| 416 interceptor_->PushJobCallback(base::Bind(&ErrorJobCallback, | |
| 417 net::ERR_NETWORK_CHANGED)); | |
| 418 const bool expect_reregister = true; | |
| 419 interceptor_->PushJobCallback( | |
| 420 base::Bind(&RegisterJobCallback, expect_reregister)); | |
| 421 | |
| 422 EXPECT_FALSE(policy_manager()->core()->client()->is_registered()); | |
| 423 ASSERT_NO_FATAL_FAILURE(Register()); | |
| 424 EXPECT_TRUE(policy_manager()->core()->client()->is_registered()); | |
| 425 } | |
| 426 | |
| 427 } // namespace policy | |
| OLD | NEW |