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