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