- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / signin / profile_oauth2_token_service.cc
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 "chrome/browser/signin/profile_oauth2_token_service.h"
6
7 #include "base/bind.h"
8 #include "base/message_loop/message_loop.h"
9 #include "base/stl_util.h"
10 #include "base/time/time.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/profiles/profile.h"
13 #include "chrome/browser/signin/signin_global_error.h"
14 #include "chrome/browser/signin/signin_manager.h"
15 #include "chrome/browser/signin/signin_manager_factory.h"
16 #include "chrome/browser/signin/token_service.h"
17 #include "chrome/browser/signin/token_service_factory.h"
18 #include "chrome/browser/ui/global_error/global_error_service.h"
19 #include "chrome/browser/ui/global_error/global_error_service_factory.h"
20 #include "chrome/browser/webdata/token_web_data.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_details.h"
23 #include "content/public/browser/notification_source.h"
24 #include "google_apis/gaia/gaia_constants.h"
25 #include "google_apis/gaia/google_service_auth_error.h"
26 #include "net/url_request/url_request_context_getter.h"
27
28 namespace {
29
30 const char kAccountIdPrefix[] = "AccountId-";
31 const size_t kAccountIdPrefixLength = 10;
32
33 bool IsLegacyServiceId(const std::string& account_id) {
34   return account_id.compare(0u, kAccountIdPrefixLength, kAccountIdPrefix) != 0;
35 }
36
37 bool IsLegacyRefreshTokenId(const std::string& service_id) {
38   return service_id == GaiaConstants::kGaiaOAuth2LoginRefreshToken;
39 }
40
41 std::string ApplyAccountIdPrefix(const std::string& account_id) {
42   return kAccountIdPrefix + account_id;
43 }
44
45 std::string RemoveAccountIdPrefix(const std::string& prefixed_account_id) {
46   return prefixed_account_id.substr(kAccountIdPrefixLength);
47 }
48
49 }  // namespace
50
51
52 ProfileOAuth2TokenService::AccountInfo::AccountInfo(
53     ProfileOAuth2TokenService* token_service,
54     const std::string& account_id,
55     const std::string& refresh_token)
56   : token_service_(token_service),
57     account_id_(account_id),
58     refresh_token_(refresh_token),
59     last_auth_error_(GoogleServiceAuthError::NONE) {
60   DCHECK(token_service_);
61   DCHECK(!account_id_.empty());
62   token_service_->signin_global_error()->AddProvider(this);
63 }
64
65 ProfileOAuth2TokenService::AccountInfo::~AccountInfo() {
66   token_service_->signin_global_error()->RemoveProvider(this);
67 }
68
69 void ProfileOAuth2TokenService::AccountInfo::SetLastAuthError(
70     const GoogleServiceAuthError& error) {
71   if (error.state() != last_auth_error_.state()) {
72     last_auth_error_ = error;
73     token_service_->signin_global_error()->AuthStatusChanged();
74   }
75 }
76
77 std::string ProfileOAuth2TokenService::AccountInfo::GetAccountId() const {
78   return account_id_;
79 }
80
81 GoogleServiceAuthError
82 ProfileOAuth2TokenService::AccountInfo::GetAuthStatus() const {
83   return last_auth_error_;
84 }
85
86 ProfileOAuth2TokenService::ProfileOAuth2TokenService()
87     : profile_(NULL),
88       web_data_service_request_(0) {
89 }
90
91 ProfileOAuth2TokenService::~ProfileOAuth2TokenService() {
92   DCHECK(!signin_global_error_.get()) <<
93       "ProfileOAuth2TokenService::Initialize called but not "
94       "ProfileOAuth2TokenService::Shutdown";
95 }
96
97 void ProfileOAuth2TokenService::Initialize(Profile* profile) {
98   DCHECK(profile);
99   DCHECK(!profile_);
100   profile_ = profile;
101
102   signin_global_error_.reset(new SigninGlobalError(profile));
103   GlobalErrorServiceFactory::GetForProfile(profile_)->AddGlobalError(
104       signin_global_error_.get());
105
106   content::Source<TokenService> token_service_source(
107       TokenServiceFactory::GetForProfile(profile));
108   registrar_.Add(this,
109                  chrome::NOTIFICATION_TOKENS_CLEARED,
110                  token_service_source);
111   registrar_.Add(this,
112                  chrome::NOTIFICATION_TOKEN_AVAILABLE,
113                  token_service_source);
114   registrar_.Add(this,
115                  chrome::NOTIFICATION_TOKEN_LOADING_FINISHED,
116                  token_service_source);
117 }
118
119 void ProfileOAuth2TokenService::Shutdown() {
120   DCHECK(profile_) << "Shutdown() called without matching call to Initialize()";
121   CancelAllRequests();
122   refresh_tokens_.clear();
123   GlobalErrorServiceFactory::GetForProfile(profile_)->RemoveGlobalError(
124       signin_global_error_.get());
125   signin_global_error_.reset();
126 }
127
128 std::string ProfileOAuth2TokenService::GetRefreshToken(
129     const std::string& account_id) {
130   AccountInfoMap::const_iterator iter = refresh_tokens_.find(account_id);
131   if (iter != refresh_tokens_.end())
132     return iter->second->refresh_token();
133   return std::string();
134 }
135
136 net::URLRequestContextGetter* ProfileOAuth2TokenService::GetRequestContext() {
137   return profile_->GetRequestContext();
138 }
139
140 void ProfileOAuth2TokenService::UpdateAuthError(
141     const std::string& account_id,
142     const GoogleServiceAuthError& error) {
143   // Do not report connection errors as these are not actually auth errors.
144   // We also want to avoid masking a "real" auth error just because we
145   // subsequently get a transient network error.
146   if (error.state() == GoogleServiceAuthError::CONNECTION_FAILED)
147     return;
148
149   DCHECK_GT(refresh_tokens_.count(account_id), 0u);
150   refresh_tokens_[account_id]->SetLastAuthError(error);
151 }
152
153 void ProfileOAuth2TokenService::Observe(
154     int type,
155     const content::NotificationSource& source,
156     const content::NotificationDetails& details) {
157   const std::string& account_id = GetPrimaryAccountId();
158   switch (type) {
159     case chrome::NOTIFICATION_TOKEN_AVAILABLE: {
160       TokenService::TokenAvailableDetails* tok_details =
161           content::Details<TokenService::TokenAvailableDetails>(details).ptr();
162       if (tok_details->service() ==
163           GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
164         // TODO(fgorski): Work on removing this code altogether in favor of the
165         // upgrade steps invoked by Initialize.
166         // TODO(fgorski): Refresh token received that way is not persisted in
167         // the token DB.
168         CancelRequestsForAccount(account_id);
169         ClearCacheForAccount(account_id);
170         refresh_tokens_[account_id].reset(
171             new AccountInfo(this, account_id, tok_details->token()));
172         UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
173         FireRefreshTokenAvailable(account_id);
174       }
175       break;
176     }
177     case chrome::NOTIFICATION_TOKENS_CLEARED: {
178       CancelAllRequests();
179       ClearCache();
180       if (!account_id.empty())
181         UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
182       break;
183     }
184     case chrome::NOTIFICATION_TOKEN_LOADING_FINISHED:
185       // During startup, if the user is signed in and the OAuth2 refresh token
186       // is empty, flag it as an error by badging the menu. Otherwise, if the
187       // user goes on to set up sync, they will have to make two attempts:
188       // One to surface the OAuth2 error, and a second one after signing in.
189       // See crbug.com/276650.
190
191       // If |account_id| is not empty, make sure that we have an entry in the
192       // map for it.  The entry could be missing if there is a corruption in
193       // the token DB while this profile is connected to an account.
194       if (!account_id.empty() && refresh_tokens_.count(account_id) == 0) {
195         refresh_tokens_[account_id].reset(new AccountInfo(
196             this, account_id, std::string()));
197       }
198
199       if (!account_id.empty() && !RefreshTokenIsAvailable(account_id)) {
200         UpdateAuthError(account_id, GoogleServiceAuthError(
201             GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
202       }
203       FireRefreshTokensLoaded();
204       break;
205     default:
206       NOTREACHED() << "Invalid notification type=" << type;
207       break;
208   }
209 }
210
211 std::string ProfileOAuth2TokenService::GetPrimaryAccountId() {
212   if (profile_->IsManaged())
213     return std::string("SupervisedUser");
214
215   SigninManagerBase* signin_manager =
216       SigninManagerFactory::GetForProfileIfExists(profile_);
217   // TODO(fgorski): DCHECK(signin_manager) here - it may require update to test
218   // code and the line above (SigninManager might not exist yet).
219   return signin_manager ? signin_manager->GetAuthenticatedUsername()
220       : std::string();
221 }
222
223 std::vector<std::string> ProfileOAuth2TokenService::GetAccounts() {
224   std::vector<std::string> account_ids;
225   for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
226            iter != refresh_tokens_.end(); ++iter) {
227     account_ids.push_back(iter->first);
228   }
229   return account_ids;
230 }
231
232 void ProfileOAuth2TokenService::UpdateCredentials(
233     const std::string& account_id,
234     const std::string& refresh_token) {
235   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
236   DCHECK(!account_id.empty());
237   DCHECK(!refresh_token.empty());
238
239   bool refresh_token_present = refresh_tokens_.count(account_id) > 0;
240   if (!refresh_token_present ||
241       refresh_tokens_[account_id]->refresh_token() != refresh_token) {
242     // If token present, and different from the new one, cancel its requests,
243     // and clear the entries in cache related to that account.
244     if (refresh_token_present) {
245       CancelRequestsForAccount(account_id);
246       ClearCacheForAccount(account_id);
247       refresh_tokens_[account_id]->set_refresh_token(refresh_token);
248     } else {
249       refresh_tokens_[account_id].reset(
250           new AccountInfo(this, account_id, refresh_token));
251     }
252
253     // Save the token in memory and in persistent store.
254     PersistCredentials(account_id, refresh_token);
255
256     UpdateAuthError(account_id, GoogleServiceAuthError::AuthErrorNone());
257     FireRefreshTokenAvailable(account_id);
258     // TODO(fgorski): Notify diagnostic observers.
259   }
260 }
261
262 void ProfileOAuth2TokenService::RevokeCredentials(
263     const std::string& account_id) {
264   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
265
266   if (refresh_tokens_.count(account_id) > 0) {
267     CancelRequestsForAccount(account_id);
268     ClearCacheForAccount(account_id);
269     refresh_tokens_.erase(account_id);
270     ClearPersistedCredentials(account_id);
271     FireRefreshTokenRevoked(account_id);
272
273     // TODO(fgorski): Notify diagnostic observers.
274   }
275 }
276
277 void ProfileOAuth2TokenService::PersistCredentials(
278     const std::string& account_id,
279     const std::string& refresh_token) {
280   scoped_refptr<TokenWebData> token_web_data =
281       TokenWebData::FromBrowserContext(profile_);
282   if (token_web_data.get()) {
283     token_web_data->SetTokenForService(ApplyAccountIdPrefix(account_id),
284                                        refresh_token);
285   }
286 }
287
288 void ProfileOAuth2TokenService::ClearPersistedCredentials(
289     const std::string& account_id) {
290   scoped_refptr<TokenWebData> token_web_data =
291       TokenWebData::FromBrowserContext(profile_);
292   if (token_web_data.get())
293     token_web_data->RemoveTokenForService(ApplyAccountIdPrefix(account_id));
294 }
295
296 void ProfileOAuth2TokenService::RevokeAllCredentials() {
297   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
298
299   CancelAllRequests();
300   for (AccountInfoMap::const_iterator iter = refresh_tokens_.begin();
301            iter != refresh_tokens_.end(); ++iter) {
302     FireRefreshTokenRevoked(iter->first);
303   }
304   refresh_tokens_.clear();
305
306   scoped_refptr<TokenWebData> token_web_data =
307       TokenWebData::FromBrowserContext(profile_);
308   if (token_web_data.get())
309     token_web_data->RemoveAllTokens();
310
311   // TODO(fgorski): Notify diagnostic observers.
312 }
313
314 void ProfileOAuth2TokenService::LoadCredentials() {
315   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
316   DCHECK_EQ(0, web_data_service_request_);
317
318   CancelAllRequests();
319   refresh_tokens_.clear();
320   scoped_refptr<TokenWebData> token_web_data =
321       TokenWebData::FromBrowserContext(profile_);
322   if (token_web_data.get())
323     web_data_service_request_ = token_web_data->GetAllTokens(this);
324 }
325
326 void ProfileOAuth2TokenService::OnWebDataServiceRequestDone(
327     WebDataServiceBase::Handle handle,
328     const WDTypedResult* result) {
329   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
330   DCHECK_EQ(web_data_service_request_, handle);
331   web_data_service_request_ = 0;
332
333   if (result) {
334     DCHECK(result->GetType() == TOKEN_RESULT);
335     const WDResult<std::map<std::string, std::string> > * token_result =
336         static_cast<const WDResult<std::map<std::string, std::string> > * > (
337             result);
338     LoadAllCredentialsIntoMemory(token_result->GetValue());
339   }
340 }
341
342 void ProfileOAuth2TokenService::LoadAllCredentialsIntoMemory(
343     const std::map<std::string, std::string>& db_tokens) {
344   std::string old_login_token;
345
346   for (std::map<std::string, std::string>::const_iterator iter =
347            db_tokens.begin();
348        iter != db_tokens.end();
349        ++iter) {
350     std::string prefixed_account_id = iter->first;
351     std::string refresh_token = iter->second;
352
353     if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty())
354       old_login_token = refresh_token;
355
356     if (IsLegacyServiceId(prefixed_account_id)) {
357       scoped_refptr<TokenWebData> token_web_data =
358           TokenWebData::FromBrowserContext(profile_);
359       if (token_web_data.get())
360         token_web_data->RemoveTokenForService(prefixed_account_id);
361     } else {
362       DCHECK(!refresh_token.empty());
363       std::string account_id = RemoveAccountIdPrefix(prefixed_account_id);
364       refresh_tokens_[account_id].reset(
365           new AccountInfo(this, account_id, refresh_token));
366       FireRefreshTokenAvailable(account_id);
367       // TODO(fgorski): Notify diagnostic observers.
368     }
369   }
370
371   if (!old_login_token.empty() &&
372       refresh_tokens_.count(GetPrimaryAccountId()) == 0) {
373     UpdateCredentials(GetPrimaryAccountId(), old_login_token);
374   }
375
376   FireRefreshTokensLoaded();
377 }