push_messaging_api.cc revision effb81e5f8246d0db0270817048dc992db66e9fb
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_auth_provider.h"
20#include "chrome/browser/invalidation/invalidation_service.h"
21#include "chrome/browser/invalidation/invalidation_service_factory.h"
22#include "chrome/browser/profiles/profile.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/common/extensions/api/push_messaging.h"
27#include "components/signin/core/browser/profile_oauth2_token_service.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    invalidation::InvalidationAuthProvider* auth_provider =
107        GetInvalidationAuthProvider();
108    if (interactive_ && auth_provider->ShowLoginUI()) {
109      auth_provider->GetTokenService()->AddObserver(this);
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  invalidation::InvalidationAuthProvider* auth_provider =
129      GetInvalidationAuthProvider();
130  fetcher_access_token_request_ =
131      auth_provider->GetTokenService()->StartRequest(
132          auth_provider->GetAccountId(), scopes, this);
133}
134
135void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable(
136    const std::string& account_id) {
137  GetInvalidationAuthProvider()->GetTokenService()->RemoveObserver(this);
138  DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileName();
139  StartAccessTokenFetch();
140}
141
142void PushMessagingGetChannelIdFunction::OnGetTokenSuccess(
143    const OAuth2TokenService::Request* request,
144    const std::string& access_token,
145    const base::Time& expiration_time) {
146  DCHECK_EQ(fetcher_access_token_request_.get(), request);
147  fetcher_access_token_request_.reset();
148
149  StartGaiaIdFetch(access_token);
150}
151
152void PushMessagingGetChannelIdFunction::OnGetTokenFailure(
153    const OAuth2TokenService::Request* request,
154    const GoogleServiceAuthError& error) {
155  DCHECK_EQ(fetcher_access_token_request_.get(), request);
156  fetcher_access_token_request_.reset();
157
158  // TODO(fgorski): We are currently ignoring the error passed in upon failure.
159  // It should be revisited when we are working on improving general error
160  // handling for the identity related code.
161  DVLOG(1) << "Cannot obtain access token for this user "
162           << error.error_message() << " " << error.state();
163  error_ = kUserAccessTokenFailure;
164  ReportResult(std::string(), error_);
165}
166
167void PushMessagingGetChannelIdFunction::StartGaiaIdFetch(
168    const std::string& access_token) {
169  // Start the async fetch of the Gaia Id.
170  DCHECK_CURRENTLY_ON(BrowserThread::UI);
171  net::URLRequestContextGetter* context = GetProfile()->GetRequestContext();
172  fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, access_token));
173
174  // Get the token cache and see if we have already cached a Gaia Id.
175  TokenCacheService* token_cache =
176      TokenCacheServiceFactory::GetForProfile(GetProfile());
177
178  // Check the cache, if we already have a Gaia ID, use it instead of
179  // fetching the ID over the network.
180  const std::string& gaia_id =
181      token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId);
182  if (!gaia_id.empty()) {
183    ReportResult(gaia_id, std::string());
184    return;
185  }
186
187  fetcher_->Start();
188}
189
190// Check if the user is logged in.
191bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() {
192  invalidation::InvalidationAuthProvider* auth_provider =
193      GetInvalidationAuthProvider();
194  return auth_provider->GetTokenService()->RefreshTokenIsAvailable(
195      auth_provider->GetAccountId());
196}
197
198void PushMessagingGetChannelIdFunction::ReportResult(
199    const std::string& gaia_id, const std::string& error_string) {
200  DCHECK_CURRENTLY_ON(BrowserThread::UI);
201
202  BuildAndSendResult(gaia_id, error_string);
203
204  // Cache the obfuscated ID locally. It never changes for this user,
205  // and if we call the web API too often, we get errors due to rate limiting.
206  if (!gaia_id.empty()) {
207    base::TimeDelta timeout =
208        base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays);
209    TokenCacheService* token_cache =
210        TokenCacheServiceFactory::GetForProfile(GetProfile());
211    token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id,
212                            timeout);
213  }
214
215  // Balanced in RunImpl.
216  Release();
217}
218
219void PushMessagingGetChannelIdFunction::BuildAndSendResult(
220    const std::string& gaia_id, const std::string& error_message) {
221  std::string channel_id;
222  if (!gaia_id.empty()) {
223    channel_id = gaia_id;
224    channel_id += kChannelIdSeparator;
225    channel_id += extension_id();
226  }
227
228  // TODO(petewil): It may be a good idea to further
229  // obfuscate the channel ID to prevent the user's obfuscated Gaia Id
230  // from being readily obtained.  Security review will tell us if we need to.
231
232  // Create a ChannelId results object and set the fields.
233  glue::ChannelIdResult result;
234  result.channel_id = channel_id;
235  SetError(error_message);
236  results_ = glue::GetChannelId::Results::Create(result);
237
238  bool success = error_message.empty() && !gaia_id.empty();
239  SendResponse(success);
240}
241
242void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
243    const std::string& gaia_id) {
244  ReportResult(gaia_id, std::string());
245}
246
247void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure(
248      const GoogleServiceAuthError& error) {
249  std::string error_text = error.error_message();
250  // If the error message is blank, see if we can set it from the state.
251  if (error_text.empty() &&
252      (0 != error.state())) {
253    error_text = base::IntToString(error.state());
254  }
255
256  DVLOG(1) << "GetChannelId status: '" << error_text << "'";
257
258  // If we had bad credentials, try the logon again.
259  switch (error.state()) {
260    case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS:
261    case GoogleServiceAuthError::ACCOUNT_DELETED:
262    case GoogleServiceAuthError::ACCOUNT_DISABLED: {
263      if (!interactive_ || !GetInvalidationAuthProvider()->ShowLoginUI()) {
264        ReportResult(std::string(), error_text);
265      }
266      return;
267    }
268    default:
269      // Return error to caller.
270      ReportResult(std::string(), error_text);
271      return;
272  }
273}
274
275invalidation::InvalidationAuthProvider*
276PushMessagingGetChannelIdFunction::GetInvalidationAuthProvider() {
277  return invalidation::InvalidationServiceFactory::GetForProfile(GetProfile())
278      ->GetInvalidationAuthProvider();
279}
280
281PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context)
282    : profile_(Profile::FromBrowserContext(context)) {
283  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
284                 content::Source<Profile>(profile_->GetOriginalProfile()));
285  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
286                 content::Source<Profile>(profile_->GetOriginalProfile()));
287  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED,
288                 content::Source<Profile>(profile_->GetOriginalProfile()));
289}
290
291PushMessagingAPI::~PushMessagingAPI() {
292}
293
294// static
295PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) {
296  return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context);
297}
298
299void PushMessagingAPI::Shutdown() {
300  event_router_.reset();
301  handler_.reset();
302}
303
304static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> >
305    g_factory = LAZY_INSTANCE_INITIALIZER;
306
307// static
308BrowserContextKeyedAPIFactory<PushMessagingAPI>*
309PushMessagingAPI::GetFactoryInstance() {
310  return g_factory.Pointer();
311}
312
313void PushMessagingAPI::Observe(int type,
314                               const content::NotificationSource& source,
315                               const content::NotificationDetails& details) {
316  invalidation::InvalidationService* invalidation_service =
317      invalidation::InvalidationServiceFactory::GetForProfile(profile_);
318  // This may be NULL; for example, for the ChromeOS guest user. In these cases,
319  // just return without setting up anything, since it won't work anyway.
320  if (!invalidation_service)
321    return;
322
323  if (!event_router_)
324    event_router_.reset(new PushMessagingEventRouter(profile_));
325  if (!handler_) {
326    handler_.reset(new PushMessagingInvalidationHandler(
327        invalidation_service, event_router_.get()));
328  }
329  switch (type) {
330    case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
331      const Extension* extension =
332          content::Details<const InstalledExtensionInfo>(details)->extension;
333      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
334        handler_->SuppressInitialInvalidationsForExtension(extension->id());
335      }
336      break;
337    }
338    case chrome::NOTIFICATION_EXTENSION_LOADED: {
339      const Extension* extension = content::Details<Extension>(details).ptr();
340      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
341        handler_->RegisterExtension(extension->id());
342      }
343      break;
344    }
345    case chrome::NOTIFICATION_EXTENSION_UNLOADED_DEPRECATED: {
346      const Extension* extension =
347          content::Details<UnloadedExtensionInfo>(details)->extension;
348      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
349        handler_->UnregisterExtension(extension->id());
350      }
351      break;
352    }
353    default:
354      NOTREACHED();
355  }
356}
357
358void PushMessagingAPI::SetMapperForTest(
359    scoped_ptr<PushMessagingInvalidationMapper> mapper) {
360  handler_ = mapper.Pass();
361}
362
363template <>
364void
365BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
366  DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
367  DependsOn(invalidation::InvalidationServiceFactory::GetInstance());
368}
369
370}  // namespace extensions
371