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