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