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