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