1 // Copyright 2018 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 "google_apis/gaia/oauth_multilogin_result.h"
6 
7 #include <string>
8 #include <vector>
9 
10 #include "base/json/json_reader.h"
11 #include "base/time/time.h"
12 #include "net/cookies/canonical_cookie.h"
13 #include "testing/gmock/include/gmock/gmock-matchers.h"
14 #include "testing/gtest/include/gtest/gtest.h"
15 
16 using ::net::CanonicalCookie;
17 using ::testing::DoubleNear;
18 using ::testing::ElementsAre;
19 using ::testing::Eq;
20 using ::testing::Property;
21 using ::testing::_;
22 
TEST(OAuthMultiloginResultTest,TryParseCookiesFromValue)23 TEST(OAuthMultiloginResultTest, TryParseCookiesFromValue) {
24   OAuthMultiloginResult result("");
25   // SID: typical response for a domain cookie
26   // SAPISID: typical response for a host cookie
27   // SSID: not canonical cookie because of the wrong path, should not be added
28   // HSID: canonical but not valid because of the wrong host value, still will
29   // be parsed but domain_ field will be empty. Also it is expired.
30   std::string data =
31       R"({
32           "cookies":[
33             {
34               "name":"SID",
35               "value":"vAlUe1",
36               "domain":".google.ru",
37               "path":"/",
38               "isSecure":true,
39               "isHttpOnly":false,
40               "priority":"HIGH",
41               "maxAge":63070000
42             },
43             {
44               "name":"SAPISID",
45               "value":"vAlUe2",
46               "host":"google.com",
47               "path":"/",
48               "isSecure":false,
49               "isHttpOnly":true,
50               "priority":"HIGH",
51               "maxAge":63070000,
52               "sameSite":"Lax"
53             },
54             {
55               "name":"SSID",
56               "value":"vAlUe3",
57               "domain":".google.de",
58               "path":"path",
59               "isSecure":true,
60               "isHttpOnly":false,
61               "priority":"HIGH",
62               "maxAge":63070000
63             },
64             {
65               "name":"HSID",
66               "value":"vAlUe4",
67               "host":".google.fr",
68               "path":"/",
69               "priority":"HIGH",
70               "maxAge":0,
71               "sameSite":"Strict"
72             }
73           ]
74         }
75       )";
76 
77   std::unique_ptr<base::DictionaryValue> dictionary_value =
78       base::DictionaryValue::From(base::JSONReader::ReadDeprecated(data));
79   result.TryParseCookiesFromValue(dictionary_value.get());
80 
81   base::Time time_now = base::Time::Now();
82   base::Time expiration_time =
83       (time_now + base::TimeDelta::FromSecondsD(63070000.));
84   double now = time_now.ToDoubleT();
85   double expiration = expiration_time.ToDoubleT();
86   const std::vector<CanonicalCookie> cookies = {
87       CanonicalCookie("SID", "vAlUe1", ".google.ru", "/", time_now, time_now,
88                       expiration_time, /*is_secure=*/true,
89                       /*is_http_only=*/false, net::CookieSameSite::UNSPECIFIED,
90                       net::CookiePriority::COOKIE_PRIORITY_HIGH),
91       CanonicalCookie("SAPISID", "vAlUe2", "google.com", "/", time_now,
92                       time_now, expiration_time, /*is_secure=*/false,
93                       /*is_http_only=*/true, net::CookieSameSite::LAX_MODE,
94                       net::CookiePriority::COOKIE_PRIORITY_HIGH),
95       CanonicalCookie("HSID", "vAlUe4", "", "/", time_now, time_now, time_now,
96                       /*is_secure=*/true, /*is_http_only=*/true,
97                       net::CookieSameSite::STRICT_MODE,
98                       net::CookiePriority::COOKIE_PRIORITY_HIGH)};
99 
100   EXPECT_EQ((int)result.cookies().size(), 3);
101 
102   EXPECT_TRUE(result.cookies()[0].IsEquivalent(cookies[0]));
103   EXPECT_TRUE(result.cookies()[1].IsEquivalent(cookies[1]));
104   EXPECT_TRUE(result.cookies()[2].IsEquivalent(cookies[2]));
105 
106   EXPECT_FALSE(result.cookies()[0].IsExpired(base::Time::Now()));
107   EXPECT_FALSE(result.cookies()[1].IsExpired(base::Time::Now()));
108   EXPECT_TRUE(result.cookies()[2].IsExpired(base::Time::Now()));
109 
110   EXPECT_THAT(
111       result.cookies(),
112       ElementsAre(Property(&CanonicalCookie::IsDomainCookie, Eq(true)),
113                   Property(&CanonicalCookie::IsHostCookie, Eq(true)),
114                   Property(&CanonicalCookie::IsDomainCookie, Eq(false))));
115   EXPECT_THAT(result.cookies(),
116               ElementsAre(Property(&CanonicalCookie::IsCanonical, Eq(true)),
117                           Property(&CanonicalCookie::IsCanonical, Eq(true)),
118                           Property(&CanonicalCookie::IsCanonical, Eq(true))));
119   EXPECT_THAT(result.cookies(),
120               ElementsAre(Property(&CanonicalCookie::IsHttpOnly, Eq(false)),
121                           Property(&CanonicalCookie::IsHttpOnly, Eq(true)),
122                           Property(&CanonicalCookie::IsHttpOnly, Eq(true))));
123   EXPECT_THAT(result.cookies(),
124               ElementsAre(Property(&CanonicalCookie::IsSecure, Eq(true)),
125                           Property(&CanonicalCookie::IsSecure, Eq(false)),
126                           Property(&CanonicalCookie::IsSecure, Eq(true))));
127   EXPECT_THAT(result.cookies(),
128               ElementsAre(Property(&CanonicalCookie::SameSite,
129                                    Eq(net::CookieSameSite::UNSPECIFIED)),
130                           Property(&CanonicalCookie::SameSite,
131                                    Eq(net::CookieSameSite::LAX_MODE)),
132                           Property(&CanonicalCookie::SameSite,
133                                    Eq(net::CookieSameSite::STRICT_MODE))));
134   EXPECT_THAT(
135       result.cookies(),
136       ElementsAre(Property(&CanonicalCookie::Priority,
137                            Eq(net::CookiePriority::COOKIE_PRIORITY_HIGH)),
138                   Property(&CanonicalCookie::Priority,
139                            Eq(net::CookiePriority::COOKIE_PRIORITY_HIGH)),
140                   Property(&CanonicalCookie::Priority,
141                            Eq(net::CookiePriority::COOKIE_PRIORITY_HIGH))));
142 
143   EXPECT_THAT(result.cookies()[0].CreationDate().ToDoubleT(),
144               DoubleNear(now, 0.5));
145   EXPECT_THAT(result.cookies()[0].LastAccessDate().ToDoubleT(),
146               DoubleNear(now, 0.5));
147   EXPECT_THAT(result.cookies()[0].ExpiryDate().ToDoubleT(),
148               DoubleNear(expiration, 0.5));
149 }
150 
TEST(OAuthMultiloginResultTest,CreateOAuthMultiloginResultFromString)151 TEST(OAuthMultiloginResultTest, CreateOAuthMultiloginResultFromString) {
152   OAuthMultiloginResult result1(R"()]}'
153         {
154           "status": "OK",
155           "cookies":[
156             {
157               "name":"SID",
158               "value":"vAlUe1",
159               "domain":".google.ru",
160               "path":"/",
161               "isSecure":true,
162               "isHttpOnly":false,
163               "priority":"HIGH",
164               "maxAge":63070000
165             }
166           ]
167         }
168       )");
169   EXPECT_EQ(result1.status(), OAuthMultiloginResponseStatus::kOk);
170   EXPECT_FALSE(result1.cookies().empty());
171 
172   OAuthMultiloginResult result2(R"(many_random_characters_before_newline
173         {
174           "status": "OK",
175           "cookies":[
176             {
177               "name":"SID",
178               "value":"vAlUe1",
179               "domain":".google.ru",
180               "path":"/",
181               "isSecure":true,
182               "isHttpOnly":false,
183               "priority":"HIGH",
184               "maxAge":63070000
185             }
186           ]
187         }
188       )");
189   EXPECT_EQ(result2.status(), OAuthMultiloginResponseStatus::kOk);
190   EXPECT_FALSE(result2.cookies().empty());
191 
192   OAuthMultiloginResult result3(R"())]}\'\n)]}'\n{
193           "status": "OK",
194           "cookies":[
195             {
196               "name":"SID",
197               "value":"vAlUe1",
198               "domain":".google.ru",
199               "path":"/",
200               "isSecure":true,
201               "isHttpOnly":false,
202               "priority":"HIGH",
203               "maxAge":63070000
204             }
205           ]
206         }
207       )");
208   EXPECT_EQ(result3.status(), OAuthMultiloginResponseStatus::kUnknownStatus);
209 }
210 
TEST(OAuthMultiloginResultTest,ProduceErrorFromResponseStatus)211 TEST(OAuthMultiloginResultTest, ProduceErrorFromResponseStatus) {
212   std::string data_error_none =
213       R"()]}'
214         {
215           "status": "OK",
216           "cookies":[
217             {
218               "name":"SID",
219               "value":"vAlUe1",
220               "domain":".google.ru",
221               "path":"/",
222               "isSecure":true,
223               "isHttpOnly":false,
224               "priority":"HIGH",
225               "maxAge":63070000
226             }
227           ]
228         }
229       )";
230   OAuthMultiloginResult result1(data_error_none);
231   EXPECT_EQ(result1.status(), OAuthMultiloginResponseStatus::kOk);
232 
233   std::string data_error_transient =
234       R"(()]}'
235         {
236           "status": "RETRY",
237           "cookies":[
238             {
239               "name":"SID",
240               "value":"vAlUe1",
241               "domain":".google.ru",
242               "path":"/",
243               "isSecure":true,
244               "isHttpOnly":false,
245               "priority":"HIGH",
246               "maxAge":63070000
247             }
248           ]
249         }
250       )";
251   OAuthMultiloginResult result2(data_error_transient);
252   EXPECT_EQ(result2.status(), OAuthMultiloginResponseStatus::kRetry);
253 
254   // "ERROR" is a real response status that Gaia sends. This is a persistent
255   // error.
256   std::string data_error_persistent =
257       R"(()]}'
258         {
259           "status": "ERROR",
260           "cookies":[
261             {
262               "name":"SID",
263               "value":"vAlUe1",
264               "domain":".google.ru",
265               "path":"/",
266               "isSecure":true,
267               "isHttpOnly":false,
268               "priority":"HIGH",
269               "maxAge":63070000
270             }
271           ]
272         }
273       )";
274   OAuthMultiloginResult result3(data_error_persistent);
275   EXPECT_EQ(result3.status(), OAuthMultiloginResponseStatus::kError);
276 
277   std::string data_error_invalid_credentials =
278       R"()]}'
279         {
280           "status": "INVALID_TOKENS",
281           "failed_accounts": [
282             {
283               "status": "RECOVERABLE",
284               "obfuscated_id": "account1"
285             },
286             {
287               "status": "OK",
288               "obfuscated_id": "account2"
289             }
290           ],
291           "cookies":[
292             {
293               "name":"SID",
294               "value":"vAlUe1",
295               "domain":".google.ru",
296               "path":"/",
297               "isSecure":true,
298               "isHttpOnly":false,
299               "priority":"HIGH",
300               "maxAge":63070000
301             }
302           ]
303         }
304       )";
305   OAuthMultiloginResult result4(data_error_invalid_credentials);
306   EXPECT_EQ(result4.status(), OAuthMultiloginResponseStatus::kInvalidTokens);
307   EXPECT_THAT(result4.failed_gaia_ids(), ElementsAre(Eq("account1")));
308 
309   // Unknown status.
310   OAuthMultiloginResult unknown_status(R"()]}'
311         {
312           "status": "Foo",
313           "cookies":[
314             {
315               "name":"SID",
316               "value":"vAlUe1",
317               "domain":".google.ru",
318               "path":"/",
319               "isSecure":true,
320               "isHttpOnly":false,
321               "priority":"HIGH",
322               "maxAge":63070000
323             }
324           ]
325         }
326       )");
327   EXPECT_EQ(unknown_status.status(),
328             OAuthMultiloginResponseStatus::kUnknownStatus);
329   EXPECT_TRUE(unknown_status.cookies().empty());
330 }
331 
TEST(OAuthMultiloginResultTest,ParseResponseStatus)332 TEST(OAuthMultiloginResultTest, ParseResponseStatus) {
333   struct TestCase {
334     std::string status_string;
335     OAuthMultiloginResponseStatus expected_status;
336   };
337 
338   std::vector<TestCase> test_cases = {
339       {"FOO", OAuthMultiloginResponseStatus::kUnknownStatus},
340       {"OK", OAuthMultiloginResponseStatus::kOk},
341       {"RETRY", OAuthMultiloginResponseStatus::kRetry},
342       {"INVALID_INPUT", OAuthMultiloginResponseStatus::kInvalidInput},
343       {"INVALID_TOKENS", OAuthMultiloginResponseStatus::kInvalidTokens},
344       {"ERROR", OAuthMultiloginResponseStatus::kError}};
345 
346   for (const auto& test_case : test_cases) {
347     EXPECT_EQ(test_case.expected_status,
348               ParseOAuthMultiloginResponseStatus(test_case.status_string));
349   }
350 }
351