Upstream version 7.36.149.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / extensions / api / push_messaging / push_messaging_api.cc
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/extensions/api/push_messaging/push_messaging_api.h"
6
7 #include <set>
8
9 #include "base/bind.h"
10 #include "base/lazy_instance.h"
11 #include "base/logging.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/values.h"
14 #include "chrome/browser/chrome_notification_types.h"
15 #include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h"
16 #include "chrome/browser/extensions/extension_service.h"
17 #include "chrome/browser/extensions/token_cache/token_cache_service.h"
18 #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h"
19 #include "chrome/browser/invalidation/invalidation_service_factory.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h"
22 #include "chrome/browser/signin/signin_manager_factory.h"
23 #include "chrome/common/extensions/api/push_messaging.h"
24 #include "components/invalidation/invalidation_service.h"
25 #include "components/signin/core/browser/profile_oauth2_token_service.h"
26 #include "components/signin/core/browser/signin_manager.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/notification_details.h"
29 #include "content/public/browser/notification_source.h"
30 #include "extensions/browser/event_router.h"
31 #include "extensions/browser/extension_system_provider.h"
32 #include "extensions/browser/extensions_browser_client.h"
33 #include "extensions/common/extension.h"
34 #include "extensions/common/permissions/api_permission.h"
35 #include "google_apis/gaia/gaia_constants.h"
36 #include "google_apis/gaia/identity_provider.h"
37
38 using content::BrowserThread;
39
40 const char kChannelIdSeparator[] = "/";
41 const char kUserNotSignedIn[] = "The user is not signed in.";
42 const char kUserAccessTokenFailure[] =
43     "Cannot obtain access token for the user.";
44 const char kAPINotAvailableForUser[] =
45     "The API is not available for this user.";
46 const int kObfuscatedGaiaIdTimeoutInDays = 30;
47
48 namespace extensions {
49
50 namespace glue = api::push_messaging;
51
52 PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile)
53     : profile_(profile) {
54 }
55
56 PushMessagingEventRouter::~PushMessagingEventRouter() {}
57
58 void PushMessagingEventRouter::TriggerMessageForTest(
59     const std::string& extension_id,
60     int subchannel,
61     const std::string& payload) {
62   OnMessage(extension_id, subchannel, payload);
63 }
64
65 void PushMessagingEventRouter::OnMessage(const std::string& extension_id,
66                                          int subchannel,
67                                          const std::string& payload) {
68   glue::Message message;
69   message.subchannel_id = subchannel;
70   message.payload = payload;
71
72   DVLOG(2) << "PushMessagingEventRouter::OnMessage"
73            << " payload = '" << payload
74            << "' subchannel = '" << subchannel
75            << "' extension = '" << extension_id << "'";
76
77   scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message));
78   scoped_ptr<extensions::Event> event(
79       new extensions::Event(glue::OnMessage::kEventName, args.Pass()));
80   event->restrict_to_browser_context = profile_;
81   EventRouter::Get(profile_)->DispatchEventToExtension(
82       extension_id, event.Pass());
83 }
84
85 // GetChannelId class functions
86
87 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction()
88     : OAuth2TokenService::Consumer("push_messaging"),
89       interactive_(false) {}
90
91 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {}
92
93 bool PushMessagingGetChannelIdFunction::RunAsync() {
94   // Fetch the function arguments.
95   scoped_ptr<glue::GetChannelId::Params> params(
96       glue::GetChannelId::Params::Create(*args_));
97   EXTENSION_FUNCTION_VALIDATE(params.get());
98
99   if (params && params->interactive) {
100     interactive_ = *params->interactive;
101   }
102
103   // Balanced in ReportResult()
104   AddRef();
105
106   invalidation::InvalidationService* invalidation_service =
107       invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
108   if (!invalidation_service) {
109     error_ = kAPINotAvailableForUser;
110     ReportResult(std::string(), error_);
111     return false;
112   }
113
114   IdentityProvider* identity_provider =
115       invalidation_service->GetIdentityProvider();
116   if (!identity_provider->GetTokenService()->RefreshTokenIsAvailable(
117           identity_provider->GetActiveAccountId())) {
118     if (interactive_ && identity_provider->RequestLogin()) {
119       identity_provider->AddActiveAccountRefreshTokenObserver(this);
120       return true;
121     } else {
122       error_ = kUserNotSignedIn;
123       ReportResult(std::string(), error_);
124       return false;
125     }
126   }
127
128   DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName();
129
130   StartAccessTokenFetch();
131   return true;
132 }
133
134 void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() {
135   invalidation::InvalidationService* invalidation_service =
136       invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
137   CHECK(invalidation_service);
138   IdentityProvider* identity_provider =
139       invalidation_service->GetIdentityProvider();
140
141   std::vector<std::string> scope_vector =
142       extensions::ObfuscatedGaiaIdFetcher::GetScopes();
143   OAuth2TokenService::ScopeSet scopes(scope_vector.begin(), scope_vector.end());
144   fetcher_access_token_request_ =
145       identity_provider->GetTokenService()->StartRequest(
146           identity_provider->GetActiveAccountId(), scopes, this);
147 }
148
149 void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable(
150     const std::string& account_id) {
151   invalidation::InvalidationService* invalidation_service =
152       invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
153   CHECK(invalidation_service);
154   invalidation_service->GetIdentityProvider()->
155       RemoveActiveAccountRefreshTokenObserver(this);
156   DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName();
157   StartAccessTokenFetch();
158 }
159
160 void PushMessagingGetChannelIdFunction::OnGetTokenSuccess(
161     const OAuth2TokenService::Request* request,
162     const std::string& access_token,
163     const base::Time& expiration_time) {
164   DCHECK_EQ(fetcher_access_token_request_.get(), request);
165   fetcher_access_token_request_.reset();
166
167   StartGaiaIdFetch(access_token);
168 }
169
170 void PushMessagingGetChannelIdFunction::OnGetTokenFailure(
171     const OAuth2TokenService::Request* request,
172     const GoogleServiceAuthError& error) {
173   DCHECK_EQ(fetcher_access_token_request_.get(), request);
174   fetcher_access_token_request_.reset();
175
176   // TODO(fgorski): We are currently ignoring the error passed in upon failure.
177   // It should be revisited when we are working on improving general error
178   // handling for the identity related code.
179   DVLOG(1) << "Cannot obtain access token for this user "
180            << error.error_message() << " " << error.state();
181   error_ = kUserAccessTokenFailure;
182   ReportResult(std::string(), error_);
183 }
184
185 void PushMessagingGetChannelIdFunction::StartGaiaIdFetch(
186     const std::string& access_token) {
187   // Start the async fetch of the Gaia Id.
188   DCHECK_CURRENTLY_ON(BrowserThread::UI);
189   net::URLRequestContextGetter* context = GetProfile()->GetRequestContext();
190   fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token));
191
192   // Get the token cache and see if we have already cached a Gaia Id.
193   TokenCacheService* token_cache =
194       TokenCacheServiceFactory::GetForProfile(GetProfile());
195
196   // Check the cache, if we already have a Gaia ID, use it instead of
197   // fetching the ID over the network.
198   const std::string& gaia_id =
199       token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId);
200   if (!gaia_id.empty()) {
201     ReportResult(gaia_id, std::string());
202     return;
203   }
204
205   fetcher_->Start();
206 }
207
208 void PushMessagingGetChannelIdFunction::ReportResult(
209     const std::string& gaia_id, const std::string& error_string) {
210   DCHECK_CURRENTLY_ON(BrowserThread::UI);
211
212   BuildAndSendResult(gaia_id, error_string);
213
214   // Cache the obfuscated ID locally. It never changes for this user,
215   // and if we call the web API too often, we get errors due to rate limiting.
216   if (!gaia_id.empty()) {
217     base::TimeDelta timeout =
218         base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays);
219     TokenCacheService* token_cache =
220         TokenCacheServiceFactory::GetForProfile(GetProfile());
221     token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id,
222                             timeout);
223   }
224
225   // Balanced in RunAsync.
226   Release();
227 }
228
229 void PushMessagingGetChannelIdFunction::BuildAndSendResult(
230     const std::string& gaia_id, const std::string& error_message) {
231   std::string channel_id;
232   if (!gaia_id.empty()) {
233     channel_id = gaia_id;
234     channel_id += kChannelIdSeparator;
235     channel_id += extension_id();
236   }
237
238   // TODO(petewil): It may be a good idea to further
239   // obfuscate the channel ID to prevent the user's obfuscated Gaia Id
240   // from being readily obtained.  Security review will tell us if we need to.
241
242   // Create a ChannelId results object and set the fields.
243   glue::ChannelIdResult result;
244   result.channel_id = channel_id;
245   SetError(error_message);
246   results_ = glue::GetChannelId::Results::Create(result);
247
248   bool success = error_message.empty() && !gaia_id.empty();
249   SendResponse(success);
250 }
251
252 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
253     const std::string& gaia_id) {
254   ReportResult(gaia_id, std::string());
255 }
256
257 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure(
258       const GoogleServiceAuthError& error) {
259   std::string error_text = error.error_message();
260   // If the error message is blank, see if we can set it from the state.
261   if (error_text.empty() &&
262       (0 != error.state())) {
263     error_text = base::IntToString(error.state());
264   }
265
266   DVLOG(1) << "GetChannelId status: '" << error_text << "'";
267
268   // If we had bad credentials, try the logon again.
269   switch (error.state()) {
270     case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
271     case GoogleServiceAuthError::ACCOUNT_DELETED:
272     case GoogleServiceAuthError::ACCOUNT_DISABLED: {
273       invalidation::InvalidationService* invalidation_service =
274           invalidation::InvalidationServiceFactory::GetForProfile(GetProfile());
275       CHECK(invalidation_service);
276       if (!interactive_ ||
277           !invalidation_service->GetIdentityProvider()->RequestLogin()) {
278         ReportResult(std::string(), error_text);
279       }
280       return;
281     }
282     default:
283       // Return error to caller.
284       ReportResult(std::string(), error_text);
285       return;
286   }
287 }
288
289 PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context)
290     : profile_(Profile::FromBrowserContext(context)) {
291   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
292                  content::Source<Profile>(profile_->GetOriginalProfile()));
293   registrar_.Add(this,
294                  chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED,
295                  content::Source<Profile>(profile_->GetOriginalProfile()));
296   registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
297                  content::Source<Profile>(profile_->GetOriginalProfile()));
298 }
299
300 PushMessagingAPI::~PushMessagingAPI() {
301 }
302
303 // static
304 PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) {
305   return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context);
306 }
307
308 void PushMessagingAPI::Shutdown() {
309   event_router_.reset();
310   handler_.reset();
311 }
312
313 static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> >
314     g_factory = LAZY_INSTANCE_INITIALIZER;
315
316 // static
317 BrowserContextKeyedAPIFactory<PushMessagingAPI>*
318 PushMessagingAPI::GetFactoryInstance() {
319   return g_factory.Pointer();
320 }
321
322 void PushMessagingAPI::Observe(int type,
323                                const content::NotificationSource& source,
324                                const content::NotificationDetails& details) {
325   invalidation::InvalidationService* invalidation_service =
326       invalidation::InvalidationServiceFactory::GetForProfile(profile_);
327   if (!invalidation_service)
328     return;
329
330   if (!event_router_)
331     event_router_.reset(new PushMessagingEventRouter(profile_));
332   if (!handler_) {
333     handler_.reset(new PushMessagingInvalidationHandler(
334         invalidation_service, event_router_.get()));
335   }
336   switch (type) {
337     case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
338       const Extension* extension =
339           content::Details<const InstalledExtensionInfo>(details)->extension;
340       if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
341         handler_->SuppressInitialInvalidationsForExtension(extension->id());
342       }
343       break;
344     }
345     case chrome::NOTIFICATION_EXTENSION_LOADED_DEPRECATED: {
346       const Extension* extension = content::Details<Extension>(details).ptr();
347       if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
348         handler_->RegisterExtension(extension->id());
349       }
350       break;
351     }
352     case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
353       const Extension* extension =
354           content::Details<UnloadedExtensionInfo>(details)->extension;
355       if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
356         handler_->UnregisterExtension(extension->id());
357       }
358       break;
359     }
360     default:
361       NOTREACHED();
362   }
363 }
364
365 void PushMessagingAPI::SetMapperForTest(
366     scoped_ptr<PushMessagingInvalidationMapper> mapper) {
367   handler_ = mapper.Pass();
368 }
369
370 template <>
371 void
372 BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
373   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
374   DependsOn(invalidation::InvalidationServiceFactory::GetInstance());
375 }
376
377 }  // namespace extensions