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 |