1// Copyright (c) 2012 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 "chrome/browser/chromeos/login/profile_auth_data.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/bind_helpers.h"
11#include "base/callback.h"
12#include "base/location.h"
13#include "base/logging.h"
14#include "base/memory/ref_counted.h"
15#include "base/message_loop/message_loop.h"
16#include "base/time/time.h"
17#include "content/public/browser/browser_context.h"
18#include "content/public/browser/browser_thread.h"
19#include "net/cookies/canonical_cookie.h"
20#include "net/cookies/cookie_monster.h"
21#include "net/cookies/cookie_store.h"
22#include "net/http/http_auth_cache.h"
23#include "net/http/http_network_session.h"
24#include "net/http/http_transaction_factory.h"
25#include "net/ssl/channel_id_service.h"
26#include "net/ssl/channel_id_store.h"
27#include "net/url_request/url_request_context.h"
28#include "net/url_request/url_request_context_getter.h"
29#include "url/gurl.h"
30
31using content::BrowserThread;
32
33namespace chromeos {
34
35namespace {
36
37const char kSAMLStartCookie[] = "google-accounts-saml-start";
38const char kSAMLEndCookie[] = "google-accounts-saml-end";
39
40class ProfileAuthDataTransferer {
41 public:
42  ProfileAuthDataTransferer(
43      content::BrowserContext* from_context,
44      content::BrowserContext* to_context,
45      bool transfer_auth_cookies_and_channel_ids_on_first_login,
46      bool transfer_saml_auth_cookies_on_subsequent_login,
47      const base::Closure& completion_callback);
48
49  void BeginTransfer();
50
51 private:
52  void BeginTransferOnIOThread();
53
54  // Transfer the proxy auth cache from |from_context_| to |to_context_|. If
55  // the user was required to authenticate with a proxy during login, this
56  // authentication information will be transferred into the user's session.
57  void TransferProxyAuthCache();
58
59  // Callback that receives the content of |to_context_|'s cookie jar. Checks
60  // whether this is the user's first login, based on the state of the cookie
61  // jar, and starts retrieval of the data that should be transfered. Calls
62  // Finish() if there is no data to transfer.
63  void OnTargetCookieJarContentsRetrieved(
64      const net::CookieList& target_cookies);
65
66  // Retrieve the contents of |from_context_|'s cookie jar. When the retrieval
67  // finishes, OnCookiesToTransferRetrieved will be called with the result.
68  void RetrieveCookiesToTransfer();
69
70  // Callback that receives the contents of |from_context_|'s cookie jar. Calls
71  // MaybeTransferCookiesAndChannelIDs() to try and perform the transfer.
72  void OnCookiesToTransferRetrieved(const net::CookieList& cookies_to_transfer);
73
74  // Retrieve |from_context_|'s channel IDs. When the retrieval finishes,
75  // OnChannelIDsToTransferRetrieved will be called with the result.
76  void RetrieveChannelIDsToTransfer();
77
78  // Callback that receives |from_context_|'s channel IDs. Calls
79  // MaybeTransferCookiesAndChannelIDs() to try and perform the transfer.
80  void OnChannelIDsToTransferRetrieved(
81      const net::ChannelIDStore::ChannelIDList& channel_ids_to_transfer);
82
83  // Given a |cookie| set during login, returns true if the cookie may have been
84  // set by GAIA. The main criterion is the |cookie|'s creation date. The points
85  // in time at which redirects from GAIA to SAML IdP and back occur are stored
86  // in |saml_start_time_| and |saml_end_time_|. If the cookie was set between
87  // these two times, it was created by the SAML IdP. Otherwise, it was created
88  // by GAIA.
89  // As an additional precaution, the cookie's domain is checked. If the domain
90  // contains "google" or "youtube", the cookie is considered to have been set
91  // by GAIA as well.
92  bool IsGAIACookie(const net::CanonicalCookie& cookie);
93
94  // If all data to be transferred has been retrieved already, transfer it to
95  // |to_context_| and call Finish().
96  void MaybeTransferCookiesAndChannelIDs();
97
98  // Post the |completion_callback_| to the UI thread and schedule destruction
99  // of |this|.
100  void Finish();
101
102  scoped_refptr<net::URLRequestContextGetter> from_context_;
103  scoped_refptr<net::URLRequestContextGetter> to_context_;
104  bool transfer_auth_cookies_and_channel_ids_on_first_login_;
105  bool transfer_saml_auth_cookies_on_subsequent_login_;
106  base::Closure completion_callback_;
107
108  net::CookieList cookies_to_transfer_;
109  net::ChannelIDStore::ChannelIDList channel_ids_to_transfer_;
110
111  // The time at which a redirect from GAIA to a SAML IdP occurred.
112  base::Time saml_start_time_;
113  // The time at which a redirect from a SAML IdP back to GAIA occurred.
114  base::Time saml_end_time_;
115
116  bool first_login_;
117  bool waiting_for_auth_cookies_;
118  bool waiting_for_channel_ids_;
119};
120
121ProfileAuthDataTransferer::ProfileAuthDataTransferer(
122    content::BrowserContext* from_context,
123    content::BrowserContext* to_context,
124    bool transfer_auth_cookies_and_channel_ids_on_first_login,
125    bool transfer_saml_auth_cookies_on_subsequent_login,
126    const base::Closure& completion_callback)
127    : from_context_(from_context->GetRequestContext()),
128      to_context_(to_context->GetRequestContext()),
129      transfer_auth_cookies_and_channel_ids_on_first_login_(
130          transfer_auth_cookies_and_channel_ids_on_first_login),
131      transfer_saml_auth_cookies_on_subsequent_login_(
132          transfer_saml_auth_cookies_on_subsequent_login),
133      completion_callback_(completion_callback),
134      first_login_(false),
135      waiting_for_auth_cookies_(false),
136      waiting_for_channel_ids_(false) {
137}
138
139void ProfileAuthDataTransferer::BeginTransfer() {
140  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
141  // If we aren't transferring auth cookies or channel IDs, post the completion
142  // callback immediately. Otherwise, it will be called when the transfer
143  // finishes.
144  if (!transfer_auth_cookies_and_channel_ids_on_first_login_ &&
145      !transfer_saml_auth_cookies_on_subsequent_login_) {
146    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, completion_callback_);
147    // Null the callback so that when Finish is called, the callback won't be
148    // called again.
149    completion_callback_.Reset();
150  }
151  BrowserThread::PostTask(
152      BrowserThread::IO, FROM_HERE,
153      base::Bind(&ProfileAuthDataTransferer::BeginTransferOnIOThread,
154                 base::Unretained(this)));
155}
156
157void ProfileAuthDataTransferer::BeginTransferOnIOThread() {
158  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
159  TransferProxyAuthCache();
160  if (transfer_auth_cookies_and_channel_ids_on_first_login_ ||
161      transfer_saml_auth_cookies_on_subsequent_login_) {
162    // Retrieve the contents of |to_context_|'s cookie jar.
163    net::CookieStore* to_store =
164        to_context_->GetURLRequestContext()->cookie_store();
165    net::CookieMonster* to_monster = to_store->GetCookieMonster();
166    to_monster->GetAllCookiesAsync(
167        base::Bind(
168            &ProfileAuthDataTransferer::OnTargetCookieJarContentsRetrieved,
169        base::Unretained(this)));
170  } else {
171    Finish();
172  }
173}
174
175void ProfileAuthDataTransferer::TransferProxyAuthCache() {
176  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
177  net::HttpAuthCache* new_cache = to_context_->GetURLRequestContext()->
178      http_transaction_factory()->GetSession()->http_auth_cache();
179  new_cache->UpdateAllFrom(*from_context_->GetURLRequestContext()->
180      http_transaction_factory()->GetSession()->http_auth_cache());
181}
182
183void ProfileAuthDataTransferer::OnTargetCookieJarContentsRetrieved(
184    const net::CookieList& target_cookies) {
185  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
186  first_login_ = target_cookies.empty();
187  if (first_login_) {
188    // On first login, transfer all auth cookies and channel IDs if
189    // |transfer_auth_cookies_and_channel_ids_on_first_login_| is true.
190    waiting_for_auth_cookies_ =
191        transfer_auth_cookies_and_channel_ids_on_first_login_;
192    waiting_for_channel_ids_ =
193        transfer_auth_cookies_and_channel_ids_on_first_login_;
194  } else {
195    // On subsequent login, transfer auth cookies set by the SAML IdP if
196    // |transfer_saml_auth_cookies_on_subsequent_login_| is true.
197    waiting_for_auth_cookies_ = transfer_saml_auth_cookies_on_subsequent_login_;
198  }
199
200  if (!waiting_for_auth_cookies_ && !waiting_for_channel_ids_) {
201    Finish();
202    return;
203  }
204
205  if (waiting_for_auth_cookies_)
206    RetrieveCookiesToTransfer();
207  if (waiting_for_channel_ids_)
208    RetrieveChannelIDsToTransfer();
209}
210
211void ProfileAuthDataTransferer::RetrieveCookiesToTransfer() {
212  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
213  net::CookieStore* from_store =
214      from_context_->GetURLRequestContext()->cookie_store();
215  net::CookieMonster* from_monster = from_store->GetCookieMonster();
216  from_monster->SetKeepExpiredCookies();
217  from_monster->GetAllCookiesAsync(
218      base::Bind(&ProfileAuthDataTransferer::OnCookiesToTransferRetrieved,
219                 base::Unretained(this)));
220}
221
222void ProfileAuthDataTransferer::OnCookiesToTransferRetrieved(
223    const net::CookieList& cookies_to_transfer) {
224  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
225  waiting_for_auth_cookies_ = false;
226  cookies_to_transfer_ = cookies_to_transfer;
227
228  // Look for cookies indicating the points in time at which redirects from GAIA
229  // to SAML IdP and back occurred. These cookies are synthesized by
230  // chrome/browser/resources/gaia_auth/background.js. If the cookies are found,
231  // their creation times are stored in |saml_start_time_| and
232  // |cookies_to_transfer_| and the cookies are deleted.
233  for (net::CookieList::iterator it = cookies_to_transfer_.begin();
234       it != cookies_to_transfer_.end(); ) {
235    if (it->Name() == kSAMLStartCookie) {
236      saml_start_time_ = it->CreationDate();
237      it = cookies_to_transfer_.erase(it);
238    } else if (it->Name() == kSAMLEndCookie) {
239      saml_end_time_ = it->CreationDate();
240      it = cookies_to_transfer_.erase(it);
241    } else {
242      ++it;
243    }
244  }
245
246  MaybeTransferCookiesAndChannelIDs();
247}
248
249void ProfileAuthDataTransferer::RetrieveChannelIDsToTransfer() {
250  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
251  net::ChannelIDService* from_service =
252      from_context_->GetURLRequestContext()->channel_id_service();
253  from_service->GetChannelIDStore()->GetAllChannelIDs(
254      base::Bind(
255          &ProfileAuthDataTransferer::OnChannelIDsToTransferRetrieved,
256          base::Unretained(this)));
257}
258
259void ProfileAuthDataTransferer::OnChannelIDsToTransferRetrieved(
260    const net::ChannelIDStore::ChannelIDList& channel_ids_to_transfer) {
261  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
262  channel_ids_to_transfer_ = channel_ids_to_transfer;
263  waiting_for_channel_ids_ = false;
264  MaybeTransferCookiesAndChannelIDs();
265}
266
267bool ProfileAuthDataTransferer::IsGAIACookie(
268    const net::CanonicalCookie& cookie) {
269  const base::Time& creation_date = cookie.CreationDate();
270  if (creation_date < saml_start_time_)
271    return true;
272  if (!saml_end_time_.is_null() && creation_date > saml_end_time_)
273    return true;
274
275  const std::string& domain = cookie.Domain();
276  return domain.find("google") != std::string::npos ||
277         domain.find("youtube") != std::string::npos;
278}
279
280void ProfileAuthDataTransferer::MaybeTransferCookiesAndChannelIDs() {
281  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
282  if (waiting_for_auth_cookies_ || waiting_for_channel_ids_)
283    return;
284
285  net::CookieStore* to_store =
286      to_context_->GetURLRequestContext()->cookie_store();
287  net::CookieMonster* to_monster = to_store->GetCookieMonster();
288  if (first_login_) {
289    to_monster->ImportCookies(cookies_to_transfer_);
290    net::ChannelIDService* to_cert_service =
291        to_context_->GetURLRequestContext()->channel_id_service();
292    to_cert_service->GetChannelIDStore()->InitializeFrom(
293        channel_ids_to_transfer_);
294  } else {
295    net::CookieList non_gaia_cookies;
296    for (net::CookieList::const_iterator it = cookies_to_transfer_.begin();
297         it != cookies_to_transfer_.end(); ++it) {
298      if (!IsGAIACookie(*it))
299        non_gaia_cookies.push_back(*it);
300    }
301    to_monster->ImportCookies(non_gaia_cookies);
302  }
303
304  Finish();
305}
306
307void ProfileAuthDataTransferer::Finish() {
308  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
309  if (!completion_callback_.is_null())
310    BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, completion_callback_);
311  base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
312}
313
314}  // namespace
315
316void ProfileAuthData::Transfer(
317    content::BrowserContext* from_context,
318    content::BrowserContext* to_context,
319    bool transfer_auth_cookies_and_channel_ids_on_first_login,
320    bool transfer_saml_auth_cookies_on_subsequent_login,
321    const base::Closure& completion_callback) {
322  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
323  (new ProfileAuthDataTransferer(
324       from_context,
325       to_context,
326       transfer_auth_cookies_and_channel_ids_on_first_login,
327       transfer_saml_auth_cookies_on_subsequent_login,
328       completion_callback))->BeginTransfer();
329}
330
331}  // namespace chromeos
332