push_messaging_api.cc revision eb525c5499e34cc9c4b825d6d9e75bb07cc06ace
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/event_names.h"
16#include "chrome/browser/extensions/event_router.h"
17#include "chrome/browser/extensions/extension_service.h"
18#include "chrome/browser/extensions/extension_system.h"
19#include "chrome/browser/extensions/extension_system_factory.h"
20#include "chrome/browser/extensions/token_cache/token_cache_service.h"
21#include "chrome/browser/extensions/token_cache/token_cache_service_factory.h"
22#include "chrome/browser/invalidation/invalidation_service.h"
23#include "chrome/browser/invalidation/invalidation_service_factory.h"
24#include "chrome/browser/profiles/profile.h"
25#include "chrome/browser/signin/token_service.h"
26#include "chrome/browser/signin/token_service_factory.h"
27#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
28#include "chrome/common/chrome_notification_types.h"
29#include "chrome/common/extensions/api/push_messaging.h"
30#include "chrome/common/extensions/extension.h"
31#include "chrome/common/extensions/extension_set.h"
32#include "chrome/common/extensions/permissions/api_permission.h"
33#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/notification_details.h"
35#include "content/public/browser/notification_source.h"
36#include "google_apis/gaia/gaia_constants.h"
37#include "googleurl/src/gurl.h"
38
39using content::BrowserThread;
40
41namespace {
42const char kChannelIdSeparator[] = "/";
43const char kUserNotSignedIn[] = "The user is not signed in.";
44const char kTokenServiceNotAvailable[] = "Failed to get token service.";
45const int kObfuscatedGaiaIdTimeoutInDays = 30;
46}
47
48namespace extensions {
49
50namespace glue = api::push_messaging;
51
52PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile)
53    : profile_(profile) {
54}
55
56PushMessagingEventRouter::~PushMessagingEventRouter() {}
57
58void PushMessagingEventRouter::TriggerMessageForTest(
59    const std::string& extension_id,
60    int subchannel,
61    const std::string& payload) {
62  OnMessage(extension_id, subchannel, payload);
63}
64
65void 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(new extensions::Event(
79      event_names::kOnPushMessage, args.Pass()));
80  event->restrict_to_profile = profile_;
81  ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
82      extension_id, event.Pass());
83}
84
85// GetChannelId class functions
86
87PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction()
88    : interactive_(false) {}
89
90PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {}
91
92bool 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      LoginUIService* login_ui_service =
108          LoginUIServiceFactory::GetForProfile(profile());
109      TokenService* token_service = TokenServiceFactory::GetForProfile(
110          profile());
111      // Register for token available so we can tell when the logon is done.
112      // Observe() will be called if token is issued.
113      registrar_.Add(this,
114                     chrome::NOTIFICATION_TOKEN_AVAILABLE,
115                     content::Source<TokenService>(token_service));
116      login_ui_service->ShowLoginPopup();
117      return true;
118    } else {
119      error_ = kUserNotSignedIn;
120      ReportResult(std::string(), error_);
121      return false;
122    }
123  }
124
125  return StartGaiaIdFetch();
126}
127
128// If we are in an interactive login and it succeeds, start token fetch.
129void PushMessagingGetChannelIdFunction::Observe(
130    int type,
131    const content::NotificationSource& source,
132    const content::NotificationDetails& details) {
133  DCHECK_EQ(chrome::NOTIFICATION_TOKEN_AVAILABLE, type);
134
135  TokenService::TokenAvailableDetails* token_details =
136      content::Details<TokenService::TokenAvailableDetails>(details).ptr();
137  if (token_details->service() == GaiaConstants::kGaiaOAuth2LoginRefreshToken) {
138    TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
139    registrar_.Remove(this,
140                      chrome::NOTIFICATION_TOKEN_AVAILABLE,
141                      content::Source<TokenService>(token_service));
142    // If we got a token, the logon succeeded, continue fetching Obfuscated
143    // Gaia Id.
144    if (!StartGaiaIdFetch())
145      SendResponse(false);
146  }
147}
148
149bool PushMessagingGetChannelIdFunction::StartGaiaIdFetch() {
150  // Start the async fetch of the Gaia Id.
151  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
152  net::URLRequestContextGetter* context = profile()->GetRequestContext();
153  TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
154  if (!token_service) {
155    ReportResult(std::string(), std::string(kTokenServiceNotAvailable));
156    return false;
157  }
158  const std::string& refresh_token =
159      token_service->GetOAuth2LoginRefreshToken();
160  fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, refresh_token));
161
162  // Get the token cache and see if we have already cached a Gaia Id.
163  TokenCacheService* token_cache =
164      TokenCacheServiceFactory::GetForProfile(profile());
165
166  // Check the cache, if we already have a Gaia ID, use it instead of
167  // fetching the ID over the network.
168  const std::string& gaia_id =
169      token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId);
170  if (!gaia_id.empty()) {
171    ReportResult(gaia_id, std::string());
172    return true;
173  }
174
175  fetcher_->Start();
176
177  // Will finish asynchronously.
178  return true;
179}
180
181// Check if the user is logged in.
182bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() const {
183  TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
184  if (!token_service)
185    return false;
186  return token_service->HasOAuthLoginToken();
187}
188
189void PushMessagingGetChannelIdFunction::ReportResult(
190    const std::string& gaia_id, const std::string& error_string) {
191  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
192
193  BuildAndSendResult(gaia_id, error_string);
194
195  // Cache the obfuscated ID locally. It never changes for this user,
196  // and if we call the web API too often, we get errors due to rate limiting.
197  if (!gaia_id.empty()) {
198    base::TimeDelta timeout =
199        base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays);
200    TokenCacheService* token_cache =
201        TokenCacheServiceFactory::GetForProfile(profile());
202    token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id,
203                            timeout);
204  }
205
206  // Balanced in RunImpl.
207  Release();
208}
209
210void PushMessagingGetChannelIdFunction::BuildAndSendResult(
211    const std::string& gaia_id, const std::string& error_message) {
212  std::string channel_id;
213  if (!gaia_id.empty()) {
214    channel_id = gaia_id;
215    channel_id += kChannelIdSeparator;
216    channel_id += extension_id();
217  }
218
219  // TODO(petewil): It may be a good idea to further
220  // obfuscate the channel ID to prevent the user's obfuscated Gaia Id
221  // from being readily obtained.  Security review will tell us if we need to.
222
223  // Create a ChannelId results object and set the fields.
224  glue::ChannelIdResult result;
225  result.channel_id = channel_id;
226  SetError(error_message);
227  results_ = glue::GetChannelId::Results::Create(result);
228
229  bool success = error_message.empty() && !gaia_id.empty();
230  SendResponse(success);
231}
232
233void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
234    const std::string& gaia_id) {
235  ReportResult(gaia_id, std::string());
236}
237
238void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure(
239      const GoogleServiceAuthError& error) {
240  std::string error_text = error.error_message();
241  // If the error message is blank, see if we can set it from the state.
242  if (error_text.empty() &&
243      (0 != error.state())) {
244    error_text = base::IntToString(error.state());
245  }
246
247  DVLOG(1) << "GetChannelId status: '" << error_text << "'";
248
249  // If we had bad credentials, try the logon again.
250  switch (error.state()) {
251    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
252    case GoogleServiceAuthError::ACCOUNT_DELETED:
253    case GoogleServiceAuthError::ACCOUNT_DISABLED: {
254      if (interactive_) {
255        LoginUIService* login_ui_service =
256            LoginUIServiceFactory::GetForProfile(profile());
257        // content::NotificationObserver will be called if token is issued.
258        login_ui_service->ShowLoginPopup();
259      } else {
260        ReportResult(std::string(), error_text);
261      }
262      return;
263    }
264    default:
265      // Return error to caller.
266      ReportResult(std::string(), error_text);
267      return;
268  }
269}
270
271PushMessagingAPI::PushMessagingAPI(Profile* profile) : profile_(profile) {
272  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
273                 content::Source<Profile>(profile_->GetOriginalProfile()));
274  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
275                 content::Source<Profile>(profile_->GetOriginalProfile()));
276  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
277                 content::Source<Profile>(profile_->GetOriginalProfile()));
278}
279
280PushMessagingAPI::~PushMessagingAPI() {
281}
282
283// static
284PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) {
285  return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile);
286}
287
288void PushMessagingAPI::Shutdown() {
289  event_router_.reset();
290  handler_.reset();
291}
292
293static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> >
294g_factory = LAZY_INSTANCE_INITIALIZER;
295
296// static
297ProfileKeyedAPIFactory<PushMessagingAPI>*
298PushMessagingAPI::GetFactoryInstance() {
299  return &g_factory.Get();
300}
301
302void PushMessagingAPI::Observe(int type,
303                               const content::NotificationSource& source,
304                               const content::NotificationDetails& details) {
305  invalidation::InvalidationService* invalidation_service =
306      invalidation::InvalidationServiceFactory::GetForProfile(profile_);
307  // This may be NULL; for example, for the ChromeOS guest user. In these cases,
308  // just return without setting up anything, since it won't work anyway.
309  if (!invalidation_service)
310    return;
311
312  if (!event_router_)
313    event_router_.reset(new PushMessagingEventRouter(profile_));
314  if (!handler_) {
315    handler_.reset(new PushMessagingInvalidationHandler(
316        invalidation_service, event_router_.get()));
317  }
318  switch (type) {
319    case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
320      const Extension* extension =
321          content::Details<const InstalledExtensionInfo>(details)->extension;
322      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
323        handler_->SuppressInitialInvalidationsForExtension(extension->id());
324      }
325      break;
326    }
327    case chrome::NOTIFICATION_EXTENSION_LOADED: {
328      const Extension* extension = content::Details<Extension>(details).ptr();
329      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
330        handler_->RegisterExtension(extension->id());
331      }
332      break;
333    }
334    case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
335      const Extension* extension =
336          content::Details<UnloadedExtensionInfo>(details)->extension;
337      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
338        handler_->UnregisterExtension(extension->id());
339      }
340      break;
341    }
342    default:
343      NOTREACHED();
344  }
345}
346
347void PushMessagingAPI::SetMapperForTest(
348    scoped_ptr<PushMessagingInvalidationMapper> mapper) {
349  handler_ = mapper.Pass();
350}
351
352template <>
353void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
354  DependsOn(ExtensionSystemFactory::GetInstance());
355  DependsOn(invalidation::InvalidationServiceFactory::GetInstance());
356}
357
358}  // namespace extensions
359