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