push_messaging_api.cc revision 2a99a7e74a7f215066514fe81d2bfa6639d9eddd
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/event_names.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/profiles/profile.h"
23#include "chrome/browser/signin/token_service.h"
24#include "chrome/browser/signin/token_service_factory.h"
25#include "chrome/browser/sync/profile_sync_service.h"
26#include "chrome/browser/sync/profile_sync_service_factory.h"
27#include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
28#include "chrome/common/chrome_notification_types.h"
29#include "chrome/common/extensions/api/push_messaging.h"
30#include "chrome/common/extensions/extension.h"
31#include "chrome/common/extensions/extension_set.h"
32#include "chrome/common/extensions/permissions/api_permission.h"
33#include "content/public/browser/browser_thread.h"
34#include "content/public/browser/notification_details.h"
35#include "content/public/browser/notification_source.h"
36#include "google_apis/gaia/gaia_constants.h"
37#include "googleurl/src/gurl.h"
38
39using content::BrowserThread;
40
41namespace {
42const char kChannelIdSeparator[] = "/";
43const char kUserNotSignedIn[] = "The user is not signed in.";
44const char kTokenServiceNotAvailable[] = "Failed to get token service.";
45const int kObfuscatedGaiaIdTimeoutInDays = 30;
46}
47
48namespace extensions {
49
50namespace glue = api::push_messaging;
51
52PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile)
53    : profile_(profile) {
54}
55
56PushMessagingEventRouter::~PushMessagingEventRouter() {}
57
58void PushMessagingEventRouter::TriggerMessageForTest(
59    const std::string& extension_id,
60    int subchannel,
61    const std::string& payload) {
62  OnMessage(extension_id, subchannel, payload);
63}
64
65void PushMessagingEventRouter::OnMessage(const std::string& extension_id,
66                                         int subchannel,
67                                         const std::string& payload) {
68  glue::Message message;
69  message.subchannel_id = subchannel;
70  message.payload = payload;
71
72  DVLOG(2) << "PushMessagingEventRouter::OnMessage"
73           << " payload = '" << payload
74           << "' subchannel = '" << subchannel
75           << "' extension = '" << extension_id << "'";
76
77  scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message));
78  scoped_ptr<extensions::Event> event(new extensions::Event(
79      event_names::kOnPushMessage, args.Pass()));
80  event->restrict_to_profile = profile_;
81  ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
82      extension_id, event.Pass());
83}
84
85// GetChannelId class functions
86
87PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction()
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      LoginUIService* login_ui_service =
108          LoginUIServiceFactory::GetForProfile(profile());
109      login_ui_service->AddObserver(this);
110      // OnLoginUICLosed will be called when UI is closed.
111      login_ui_service->ShowLoginPopup();
112      return true;
113    } else {
114      error_ = kUserNotSignedIn;
115      ReportResult(std::string(), error_);
116      return false;
117    }
118  }
119
120  return StartGaiaIdFetch();
121}
122
123bool PushMessagingGetChannelIdFunction::StartGaiaIdFetch() {
124  // Start the async fetch of the GAIA ID.
125  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
126  net::URLRequestContextGetter* context = profile()->GetRequestContext();
127  TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
128  if (!token_service) {
129    ReportResult(std::string(), std::string(kTokenServiceNotAvailable));
130    return false;
131  }
132  const std::string& refresh_token =
133      token_service->GetOAuth2LoginRefreshToken();
134  fetcher_.reset(new ObfuscatedGaiaIdFetcher(context, this, refresh_token));
135
136  // Get the token cache and see if we have already cached a Gaia Id.
137  TokenCacheService* token_cache =
138      TokenCacheServiceFactory::GetForProfile(profile());
139
140  // Check the cache, if we already have a gaia ID, use it instead of
141  // fetching the ID over the network.
142  const std::string& gaia_id =
143      token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId);
144  if (!gaia_id.empty()) {
145    ReportResult(gaia_id, std::string());
146    return true;
147  }
148
149  fetcher_->Start();
150
151  // Will finish asynchronously.
152  return true;
153}
154
155// Check if the user is logged in.
156bool PushMessagingGetChannelIdFunction::IsUserLoggedIn() const {
157  TokenService* token_service = TokenServiceFactory::GetForProfile(profile());
158  if (!token_service)
159    return false;
160  return token_service->HasOAuthLoginToken();
161}
162
163void PushMessagingGetChannelIdFunction::OnLoginUIShown(
164    LoginUIService::LoginUI* ui) {
165  // Do nothing when login ui is shown.
166}
167
168// If the login succeeds, continue with our logic to fetch the ChannelId.
169void PushMessagingGetChannelIdFunction::OnLoginUIClosed(
170    LoginUIService::LoginUI* ui) {
171  LoginUIService* login_ui_service =
172      LoginUIServiceFactory::GetForProfile(profile());
173  login_ui_service->RemoveObserver(this);
174  if (!StartGaiaIdFetch()) {
175    SendResponse(false);
176  }
177}
178
179void PushMessagingGetChannelIdFunction::ReportResult(
180    const std::string& gaia_id, const std::string& error_string) {
181  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
182
183  BuildAndSendResult(gaia_id, error_string);
184
185  // Cache the obfuscated ID locally. It never changes for this user,
186  // and if we call the web API too often, we get errors due to rate limiting.
187  if (!gaia_id.empty()) {
188    base::TimeDelta timeout =
189        base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays);
190    TokenCacheService* token_cache =
191        TokenCacheServiceFactory::GetForProfile(profile());
192    token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id,
193                            timeout);
194  }
195
196  // Balanced in RunImpl.
197  Release();
198}
199
200void PushMessagingGetChannelIdFunction::BuildAndSendResult(
201    const std::string& gaia_id, const std::string& error_message) {
202  std::string channel_id;
203  if (!gaia_id.empty()) {
204    channel_id = gaia_id;
205    channel_id += kChannelIdSeparator;
206    channel_id += extension_id();
207  }
208
209  // TODO(petewil): It may be a good idea to further
210  // obfuscate the channel ID to prevent the user's obfuscated GAIA ID
211  // from being readily obtained.  Security review will tell us if we need to.
212
213  // Create a ChannelId results object and set the fields.
214  glue::ChannelIdResult result;
215  result.channel_id = channel_id;
216  SetError(error_message);
217  results_ = glue::GetChannelId::Results::Create(result);
218
219  bool success = error_message.empty() && !gaia_id.empty();
220  SendResponse(success);
221}
222
223void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
224    const std::string& gaia_id) {
225  ReportResult(gaia_id, std::string());
226}
227
228void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure(
229      const GoogleServiceAuthError& error) {
230  std::string error_text = error.error_message();
231  // If the error message is blank, see if we can set it from the state.
232  if (error_text.empty() &&
233      (0 != error.state())) {
234    error_text = base::IntToString(error.state());
235  }
236
237  ReportResult(std::string(), error_text);
238  DVLOG(1) << "GetChannelId status: '" << error_text << "'";
239}
240
241PushMessagingAPI::PushMessagingAPI(Profile* profile) : profile_(profile) {
242  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_INSTALLED,
243                 content::Source<Profile>(profile_->GetOriginalProfile()));
244  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
245                 content::Source<Profile>(profile_->GetOriginalProfile()));
246  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
247                 content::Source<Profile>(profile_->GetOriginalProfile()));
248}
249
250PushMessagingAPI::~PushMessagingAPI() {
251}
252
253// static
254PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) {
255  return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile);
256}
257
258void PushMessagingAPI::Shutdown() {
259  event_router_.reset();
260  handler_.reset();
261}
262
263static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> >
264g_factory = LAZY_INSTANCE_INITIALIZER;
265
266// static
267ProfileKeyedAPIFactory<PushMessagingAPI>*
268PushMessagingAPI::GetFactoryInstance() {
269  return &g_factory.Get();
270}
271
272void PushMessagingAPI::Observe(int type,
273                               const content::NotificationSource& source,
274                               const content::NotificationDetails& details) {
275  ProfileSyncService* pss = ProfileSyncServiceFactory::GetForProfile(profile_);
276  // This may be NULL; for example, for the ChromeOS guest user. In these cases,
277  // just return without setting up anything, since it won't work anyway.
278  if (!pss)
279    return;
280
281  if (!event_router_)
282    event_router_.reset(new PushMessagingEventRouter(profile_));
283  if (!handler_) {
284    handler_.reset(new PushMessagingInvalidationHandler(
285        pss, event_router_.get()));
286  }
287  switch (type) {
288    case chrome::NOTIFICATION_EXTENSION_INSTALLED: {
289      const Extension* extension = content::Details<Extension>(details).ptr();
290      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
291        handler_->SuppressInitialInvalidationsForExtension(extension->id());
292      }
293      break;
294    }
295    case chrome::NOTIFICATION_EXTENSION_LOADED: {
296      const Extension* extension = content::Details<Extension>(details).ptr();
297      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
298        handler_->RegisterExtension(extension->id());
299      }
300      break;
301    }
302    case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
303      const Extension* extension =
304          content::Details<UnloadedExtensionInfo>(details)->extension;
305      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
306        handler_->UnregisterExtension(extension->id());
307      }
308      break;
309    }
310    default:
311      NOTREACHED();
312  }
313}
314
315void PushMessagingAPI::SetMapperForTest(
316    scoped_ptr<PushMessagingInvalidationMapper> mapper) {
317  handler_ = mapper.Pass();
318}
319
320template <>
321void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
322  DependsOn(ExtensionSystemFactory::GetInstance());
323  DependsOn(ProfileSyncServiceFactory::GetInstance());
324}
325
326}  // namespace extensions
327