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