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