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