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