1// Copyright 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/basictypes.h"
6#include "base/bind.h"
7#include "base/callback.h"
8#include "base/command_line.h"
9#include "base/compiler_specific.h"
10#include "base/memory/scoped_ptr.h"
11#include "base/message_loop/message_loop.h"
12#include "chrome/browser/local_discovery/test_service_discovery_client.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
15#include "chrome/browser/signin/signin_manager_factory.h"
16#include "chrome/browser/ui/browser.h"
17#include "chrome/browser/ui/webui/local_discovery/local_discovery_ui_handler.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/common/url_constants.h"
20#include "chrome/test/base/ui_test_utils.h"
21#include "chrome/test/base/web_ui_browser_test.h"
22#include "components/signin/core/browser/profile_oauth2_token_service.h"
23#include "components/signin/core/browser/signin_manager.h"
24#include "components/signin/core/browser/signin_manager_base.h"
25#include "google_apis/gaia/gaia_urls.h"
26#include "net/http/http_status_code.h"
27#include "net/url_request/test_url_fetcher_factory.h"
28#include "net/url_request/url_request_status.h"
29#include "net/url_request/url_request_test_util.h"
30
31#if defined(OS_CHROMEOS)
32#include "base/prefs/pref_service.h"
33#include "chrome/common/pref_names.h"
34#endif
35
36using testing::InvokeWithoutArgs;
37using testing::Return;
38using testing::AtLeast;
39using testing::DoDefault;
40using testing::DoAll;
41using testing::InSequence;
42using testing::StrictMock;
43using testing::AnyNumber;
44
45using testing::InvokeWithoutArgs;
46using testing::Return;
47using testing::AtLeast;
48
49namespace local_discovery {
50
51namespace {
52
53const uint8 kQueryData[] = {
54  // Header
55  0x00, 0x00,
56  0x00, 0x00,               // Flags not set.
57  0x00, 0x01,               // Set QDCOUNT (question count) to 1, all the
58  // rest are 0 for a query.
59  0x00, 0x00,
60  0x00, 0x00,
61  0x00, 0x00,
62
63  // Question
64  0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
65  0x04, '_', 't', 'c', 'p',
66  0x05, 'l', 'o', 'c', 'a', 'l',
67  0x00,
68
69  0x00, 0x0c,               // QTYPE: A query.
70  0x00, 0x01,               // QCLASS: IN class. Unicast bit not set.
71};
72
73const uint8 kAnnouncePacket[] = {
74  // Header
75  0x00, 0x00,               // ID is zeroed out
76  0x80, 0x00,               // Standard query response, no error
77  0x00, 0x00,               // No questions (for simplicity)
78  0x00, 0x05,               // 5 RR (answers)
79  0x00, 0x00,               // 0 authority RRs
80  0x00, 0x00,               // 0 additional RRs
81
82  0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
83  0x04, '_', 't', 'c', 'p',
84  0x05, 'l', 'o', 'c', 'a', 'l',
85  0x00,
86  0x00, 0x0c,        // TYPE is PTR.
87  0x00, 0x01,        // CLASS is IN.
88  0x00, 0x00,        // TTL (4 bytes) is 32768 second.
89  0x10, 0x00,
90  0x00, 0x0c,        // RDLENGTH is 12 bytes.
91  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
92  0xc0, 0x0c,
93
94  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
95  0xc0, 0x0c,
96  0x00, 0x10,        // TYPE is TXT.
97  0x00, 0x01,        // CLASS is IN.
98  0x00, 0x00,        // TTL (4 bytes) is 32768 seconds.
99  0x01, 0x00,
100  0x00, 0x41,        // RDLENGTH is 69 bytes.
101  0x03, 'i', 'd', '=',
102  0x10, 't', 'y', '=', 'S', 'a', 'm', 'p', 'l', 'e', ' ',
103        'd', 'e', 'v', 'i', 'c', 'e',
104  0x1e, 'n', 'o', 't', 'e', '=',
105        'S', 'a', 'm', 'p', 'l', 'e', ' ', 'd', 'e', 'v', 'i', 'c', 'e', ' ',
106        'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'i', 'o', 'n',
107  0x0c, 't', 'y', 'p', 'e', '=', 'p', 'r', 'i', 'n', 't', 'e', 'r',
108
109  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
110  0xc0, 0x0c,
111  0x00, 0x21,        // Type is SRV
112  0x00, 0x01,        // CLASS is IN
113  0x00, 0x00,        // TTL (4 bytes) is 32768 second.
114  0x10, 0x00,
115  0x00, 0x17,        // RDLENGTH is 23
116  0x00, 0x00,
117  0x00, 0x00,
118  0x22, 0xb8,        // port 8888
119  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
120  0x05, 'l', 'o', 'c', 'a', 'l',
121  0x00,
122
123  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
124  0x05, 'l', 'o', 'c', 'a', 'l',
125  0x00,
126  0x00, 0x01,        // Type is A
127  0x00, 0x01,        // CLASS is IN
128  0x00, 0x00,        // TTL (4 bytes) is 32768 second.
129  0x10, 0x00,
130  0x00, 0x04,        // RDLENGTH is 4
131  0x01, 0x02, 0x03, 0x04,  // 1.2.3.4
132
133  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
134  0x05, 'l', 'o', 'c', 'a', 'l',
135  0x00,
136  0x00, 0x1C,        // Type is AAAA
137  0x00, 0x01,        // CLASS is IN
138  0x00, 0x00,        // TTL (4 bytes) is 32768 second.
139  0x10, 0x00,
140  0x00, 0x10,        // RDLENGTH is 16
141  0x01, 0x02, 0x03, 0x04,  // 1.2.3.4
142  0x01, 0x02, 0x03, 0x04,
143  0x01, 0x02, 0x03, 0x04,
144  0x01, 0x02, 0x03, 0x04,
145};
146
147
148const uint8 kGoodbyePacket[] = {
149  // Header
150  0x00, 0x00,               // ID is zeroed out
151  0x80, 0x00,               // Standard query response, RA, no error
152  0x00, 0x00,               // No questions (for simplicity)
153  0x00, 0x02,               // 1 RR (answers)
154  0x00, 0x00,               // 0 authority RRs
155  0x00, 0x00,               // 0 additional RRs
156
157  0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
158  0x04, '_', 't', 'c', 'p',
159  0x05, 'l', 'o', 'c', 'a', 'l',
160  0x00,
161  0x00, 0x0c,        // TYPE is PTR.
162  0x00, 0x01,        // CLASS is IN.
163  0x00, 0x00,        // TTL (4 bytes) is 0 seconds.
164  0x00, 0x00,
165  0x00, 0x0c,        // RDLENGTH is 12 bytes.
166  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
167  0xc0, 0x0c,
168
169
170  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
171  0xc0, 0x0c,
172  0x00, 0x21,        // Type is SRV
173  0x00, 0x01,        // CLASS is IN
174  0x00, 0x00,        // TTL (4 bytes) is 0 seconds.
175  0x00, 0x00,
176  0x00, 0x17,        // RDLENGTH is 23
177  0x00, 0x00,
178  0x00, 0x00,
179  0x22, 0xb8,        // port 8888
180  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
181  0x05, 'l', 'o', 'c', 'a', 'l',
182  0x00,
183};
184
185const uint8 kAnnouncePacketRegistered[] = {
186  // Header
187  0x00, 0x00,               // ID is zeroed out
188  0x80, 0x00,               // Standard query response, RA, no error
189  0x00, 0x00,               // No questions (for simplicity)
190  0x00, 0x01,               // 1 RR (answers)
191  0x00, 0x00,               // 0 authority RRs
192  0x00, 0x00,               // 0 additional RRs
193
194  0x09, 'm', 'y', 'S', 'e', 'r', 'v', 'i', 'c', 'e',
195  0x07, '_', 'p', 'r', 'i', 'v', 'e', 't',
196  0x04, '_', 't', 'c', 'p',
197  0x05, 'l', 'o', 'c', 'a', 'l',
198  0x00,
199  0x00, 0x10,        // TYPE is TXT.
200  0x00, 0x01,        // CLASS is IN.
201  0x00, 0x00,        // TTL (4 bytes) is 32768 seconds.
202  0x01, 0x00,
203  0x00, 0x3b,        // RDLENGTH is 76 bytes.
204  0x0a, 'i', 'd', '=', 's', 'o', 'm', 'e', '_', 'i', 'd',
205  0x10, 't', 'y', '=', 'S', 'a', 'm', 'p', 'l', 'e', ' ',
206        'd', 'e', 'v', 'i', 'c', 'e',
207  0x1e, 'n', 'o', 't', 'e', '=',
208        'S', 'a', 'm', 'p', 'l', 'e', ' ', 'd', 'e', 'v', 'i', 'c', 'e', ' ',
209        'd', 'e', 's', 'c', 'r', 'i', 'p', 't', 'i', 'o', 'n',
210};
211
212const char kResponseInfo[] = "{"
213    "     \"x-privet-token\" : \"MyPrivetToken\""
214    "}";
215
216const char kResponseInfoWithID[] = "{"
217    "     \"x-privet-token\" : \"MyPrivetToken\","
218    "     \"id\" : \"my_id\""
219    "}";
220
221const char kResponseRegisterStart[] = "{"
222    "     \"action\": \"start\","
223    "     \"user\": \"user@host.com\""
224    "}";
225
226const char kResponseRegisterClaimTokenNoConfirm[] = "{"
227    "    \"action\": \"getClaimToken\","
228    "    \"user\": \"user@host.com\","
229    "    \"error\": \"pending_user_action\","
230    "    \"timeout\": 1"
231    "}";
232
233const char kResponseRegisterClaimTokenConfirm[] = "{"
234    "    \"action\": \"getClaimToken\","
235    "    \"user\": \"user@host.com\","
236    "    \"token\": \"MySampleToken\","
237    "    \"claim_url\": \"http://someurl.com/\""
238    "}";
239
240const char kResponseCloudPrintConfirm[] = "{ \"success\": true }";
241
242const char kResponseRegisterComplete[] = "{"
243    "    \"action\": \"complete\","
244    "    \"user\": \"user@host.com\","
245    "    \"device_id\": \"my_id\""
246    "}";
247
248const char kResponseGaiaToken[] = "{"
249    "  \"access_token\": \"at1\","
250    "  \"expires_in\": 3600,"
251    "  \"token_type\": \"Bearer\""
252    "}";
253
254const char kResponseGaiaId[] = "{"
255    "  \"id\": \"12345\""
256    "}";
257
258const char kURLInfo[] = "http://1.2.3.4:8888/privet/info";
259
260const char kURLRegisterStart[] =
261    "http://1.2.3.4:8888/privet/register?action=start&user=user%40host.com";
262
263const char kURLRegisterClaimToken[] =
264    "http://1.2.3.4:8888/privet/register?action=getClaimToken&"
265    "user=user%40host.com";
266
267const char kURLCloudPrintConfirm[] =
268    "https://www.google.com/cloudprint/confirm?token=MySampleToken";
269
270const char kURLRegisterComplete[] =
271    "http://1.2.3.4:8888/privet/register?action=complete&user=user%40host.com";
272
273const char kURLGaiaToken[] =
274    "https://accounts.google.com/o/oauth2/token";
275
276const char kSampleUser[] = "user@host.com";
277
278class TestMessageLoopCondition {
279 public:
280  TestMessageLoopCondition() : signaled_(false),
281                               waiting_(false) {
282  }
283
284  ~TestMessageLoopCondition() {
285  }
286
287  // Signal a waiting method that it can continue executing.
288  void Signal() {
289    signaled_ = true;
290    if (waiting_)
291      base::MessageLoop::current()->Quit();
292  }
293
294  // Pause execution and recursively run the message loop until |Signal()| is
295  // called. Do not pause if |Signal()| has already been called.
296  void Wait() {
297    while (!signaled_) {
298      waiting_ = true;
299      base::MessageLoop::current()->Run();
300      waiting_ = false;
301    }
302    signaled_ = false;
303  }
304
305 private:
306  bool signaled_;
307  bool waiting_;
308
309  DISALLOW_COPY_AND_ASSIGN(TestMessageLoopCondition);
310};
311
312class MockableFakeURLFetcherCreator {
313 public:
314  MockableFakeURLFetcherCreator() {
315  }
316
317  ~MockableFakeURLFetcherCreator() {
318  }
319
320  MOCK_METHOD1(OnCreateFakeURLFetcher, void(const std::string& url));
321
322  scoped_ptr<net::FakeURLFetcher> CreateFakeURLFetcher(
323      const GURL& url,
324      net::URLFetcherDelegate* delegate,
325      const std::string& response_data,
326      net::HttpStatusCode response_code,
327      net::URLRequestStatus::Status status) {
328    OnCreateFakeURLFetcher(url.spec());
329    return scoped_ptr<net::FakeURLFetcher>(new net::FakeURLFetcher(
330        url, delegate, response_data, response_code, status));
331  }
332
333  net::FakeURLFetcherFactory::FakeURLFetcherCreator callback() {
334    return base::Bind(&MockableFakeURLFetcherCreator::CreateFakeURLFetcher,
335                      base::Unretained(this));
336  }
337};
338
339class LocalDiscoveryUITest : public WebUIBrowserTest {
340 public:
341  LocalDiscoveryUITest() : fake_fetcher_factory_(
342      &fetcher_impl_factory_,
343      fake_url_fetcher_creator_.callback()) {
344  }
345  virtual ~LocalDiscoveryUITest() {
346  }
347
348  virtual void SetUpOnMainThread() OVERRIDE {
349    WebUIBrowserTest::SetUpOnMainThread();
350
351    test_service_discovery_client_ = new TestServiceDiscoveryClient();
352    test_service_discovery_client_->Start();
353    EXPECT_CALL(
354        *test_service_discovery_client_.get(),
355        OnSendTo(std::string((const char*)kQueryData, sizeof(kQueryData))))
356        .Times(AtLeast(2))
357        .WillOnce(InvokeWithoutArgs(&condition_devices_listed_,
358                                    &TestMessageLoopCondition::Signal))
359        .WillRepeatedly(Return());
360
361    SigninManagerBase* signin_manager =
362        SigninManagerFactory::GetForProfile(browser()->profile());
363
364#if defined(OS_CHROMEOS)
365    // Chrome OS initializes prefs::kGoogleServicesUsername to "stub user" so
366    // we need to override it as well.
367    browser()->profile()->GetPrefs()->
368        SetString(prefs::kGoogleServicesUsername, kSampleUser);
369#endif
370    DCHECK(signin_manager);
371    signin_manager->SetAuthenticatedUsername(kSampleUser);
372
373    fake_fetcher_factory().SetFakeResponse(
374        GURL(kURLInfo),
375        kResponseInfo,
376        net::HTTP_OK,
377        net::URLRequestStatus::SUCCESS);
378
379    fake_fetcher_factory().SetFakeResponse(
380        GURL(kURLRegisterStart),
381        kResponseRegisterStart,
382        net::HTTP_OK,
383        net::URLRequestStatus::SUCCESS);
384
385    fake_fetcher_factory().SetFakeResponse(
386        GURL(kURLRegisterClaimToken),
387        kResponseRegisterClaimTokenNoConfirm,
388        net::HTTP_OK,
389        net::URLRequestStatus::SUCCESS);
390
391    fake_fetcher_factory().SetFakeResponse(
392        GURL(kURLCloudPrintConfirm),
393        kResponseCloudPrintConfirm,
394        net::HTTP_OK,
395        net::URLRequestStatus::SUCCESS);
396
397    fake_fetcher_factory().SetFakeResponse(
398        GURL(kURLRegisterComplete),
399        kResponseRegisterComplete,
400        net::HTTP_OK,
401        net::URLRequestStatus::SUCCESS);
402
403    fake_fetcher_factory().SetFakeResponse(
404        GURL(kURLGaiaToken),
405        kResponseGaiaToken,
406        net::HTTP_OK,
407        net::URLRequestStatus::SUCCESS);
408
409    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
410        kURLGaiaToken))
411        .Times(AnyNumber());
412
413    fake_fetcher_factory().SetFakeResponse(
414        GaiaUrls::GetInstance()->oauth_user_info_url(),
415        kResponseGaiaId,
416        net::HTTP_OK,
417        net::URLRequestStatus::SUCCESS);
418
419    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
420        GaiaUrls::GetInstance()->oauth_user_info_url().spec()))
421        .Times(AnyNumber());
422
423    ProfileOAuth2TokenService* token_service =
424        ProfileOAuth2TokenServiceFactory::GetForProfile(browser()->profile());
425
426    token_service->UpdateCredentials("user@host.com", "MyFakeToken");
427
428    AddLibrary(base::FilePath(FILE_PATH_LITERAL("local_discovery_ui_test.js")));
429  }
430
431  virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
432    WebUIBrowserTest::SetUpCommandLine(command_line);
433  }
434
435  void RunFor(base::TimeDelta time_period) {
436    base::CancelableCallback<void()> callback(base::Bind(
437        &base::MessageLoop::Quit, base::Unretained(
438            base::MessageLoop::current())));
439    base::MessageLoop::current()->PostDelayedTask(
440        FROM_HERE, callback.callback(), time_period);
441
442    base::MessageLoop::current()->Run();
443    callback.Cancel();
444  }
445
446  TestServiceDiscoveryClient* test_service_discovery_client() {
447    return test_service_discovery_client_.get();
448  }
449
450  TestMessageLoopCondition& condition_devices_listed() {
451    return condition_devices_listed_;
452  }
453
454  net::FakeURLFetcherFactory& fake_fetcher_factory() {
455    return fake_fetcher_factory_;
456  }
457
458  MockableFakeURLFetcherCreator& fake_url_fetcher_creator() {
459    return fake_url_fetcher_creator_;
460  }
461
462 private:
463  scoped_refptr<TestServiceDiscoveryClient> test_service_discovery_client_;
464  TestMessageLoopCondition condition_devices_listed_;
465
466  net::URLFetcherImplFactory fetcher_impl_factory_;
467  StrictMock<MockableFakeURLFetcherCreator> fake_url_fetcher_creator_;
468  net::FakeURLFetcherFactory fake_fetcher_factory_;
469
470  DISALLOW_COPY_AND_ASSIGN(LocalDiscoveryUITest);
471};
472
473IN_PROC_BROWSER_TEST_F(LocalDiscoveryUITest, EmptyTest) {
474  ui_test_utils::NavigateToURL(browser(), GURL(
475      chrome::kChromeUIDevicesURL));
476  condition_devices_listed().Wait();
477  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkNoDevices"));
478}
479
480IN_PROC_BROWSER_TEST_F(LocalDiscoveryUITest, AddRowTest) {
481  ui_test_utils::NavigateToURL(browser(), GURL(
482      chrome::kChromeUIDevicesURL));
483  condition_devices_listed().Wait();
484
485  test_service_discovery_client()->SimulateReceive(
486      kAnnouncePacket, sizeof(kAnnouncePacket));
487
488  base::MessageLoop::current()->RunUntilIdle();
489
490  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkOneDevice"));
491
492  test_service_discovery_client()->SimulateReceive(
493      kGoodbyePacket, sizeof(kGoodbyePacket));
494
495  RunFor(base::TimeDelta::FromMilliseconds(1100));
496
497  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkNoDevices"));
498}
499
500
501IN_PROC_BROWSER_TEST_F(LocalDiscoveryUITest, RegisterTest) {
502  TestMessageLoopCondition condition_token_claimed;
503
504  ui_test_utils::NavigateToURL(browser(), GURL(
505      chrome::kChromeUIDevicesURL));
506  condition_devices_listed().Wait();
507
508  test_service_discovery_client()->SimulateReceive(
509      kAnnouncePacket, sizeof(kAnnouncePacket));
510
511  base::MessageLoop::current()->RunUntilIdle();
512
513  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("checkOneDevice"));
514
515  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("registerShowOverlay"));
516
517  {
518    InSequence s;
519    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(kURLInfo));
520    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
521        kURLRegisterStart));
522    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
523        kURLRegisterClaimToken))
524        .WillOnce(InvokeWithoutArgs(&condition_token_claimed,
525                                    &TestMessageLoopCondition::Signal));
526  }
527
528  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("registerBegin"));
529
530  condition_token_claimed.Wait();
531
532  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("expectPageAdding1"));
533
534  fake_fetcher_factory().SetFakeResponse(
535      GURL(kURLRegisterClaimToken),
536      kResponseRegisterClaimTokenConfirm,
537      net::HTTP_OK,
538      net::URLRequestStatus::SUCCESS);
539
540  fake_fetcher_factory().SetFakeResponse(
541      GURL(kURLInfo),
542      kResponseInfoWithID,
543      net::HTTP_OK,
544      net::URLRequestStatus::SUCCESS);
545
546  {
547    InSequence s;
548    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
549        kURLRegisterClaimToken));
550    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
551        kURLCloudPrintConfirm));
552    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(
553        kURLRegisterComplete));
554    EXPECT_CALL(fake_url_fetcher_creator(), OnCreateFakeURLFetcher(kURLInfo))
555        .WillOnce(InvokeWithoutArgs(&condition_token_claimed,
556                                    &TestMessageLoopCondition::Signal));
557  }
558
559  condition_token_claimed.Wait();
560
561  test_service_discovery_client()->SimulateReceive(
562      kAnnouncePacketRegistered, sizeof(kAnnouncePacketRegistered));
563
564  base::MessageLoop::current()->RunUntilIdle();
565
566  EXPECT_TRUE(WebUIBrowserTest::RunJavascriptTest("expectRegisterDone"));
567}
568
569}  // namespace
570
571}  // namespace local_discovery
572