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.
5 #include "chrome/browser/extensions/api/push_messaging/push_messaging_api.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"
39 using content::BrowserThread;
41 const char kChannelIdSeparator[] = "/";
42 const char kUserNotSignedIn[] = "The user is not signed in.";
43 const char kUserAccessTokenFailure[] =
44 "Cannot obtain access token for the user.";
45 const int kObfuscatedGaiaIdTimeoutInDays = 30;
47 namespace extensions {
49 namespace glue = api::push_messaging;
51 PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile)
55 PushMessagingEventRouter::~PushMessagingEventRouter() {}
57 void PushMessagingEventRouter::TriggerMessageForTest(
58 const std::string& extension_id,
60 const std::string& payload) {
61 OnMessage(extension_id, subchannel, payload);
64 void PushMessagingEventRouter::OnMessage(const std::string& extension_id,
66 const std::string& payload) {
67 glue::Message message;
68 message.subchannel_id = subchannel;
69 message.payload = payload;
71 DVLOG(2) << "PushMessagingEventRouter::OnMessage"
72 << " payload = '" << payload
73 << "' subchannel = '" << subchannel
74 << "' extension = '" << extension_id << "'";
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());
84 // GetChannelId class functions
86 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction()
87 : OAuth2TokenService::Consumer("push_messaging"),
88 interactive_(false) {}
90 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {}
92 bool 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());
98 if (params && params->interactive) {
99 interactive_ = *params->interactive;
102 // Balanced in ReportResult()
105 if (!IsUserLoggedIn()) {
107 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile())
109 LoginUIServiceFactory::GetForProfile(GetProfile())->ShowLoginPopup();
112 error_ = kUserNotSignedIn;
113 ReportResult(std::string(), error_);
118 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileName();
120 StartAccessTokenFetch();
124 void 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);
136 void 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();
144 void 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();
151 StartGaiaIdFetch(access_token);
154 void 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();
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_);
169 void 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));
176 // Get the token cache and see if we have already cached a Gaia Id.
177 TokenCacheService* token_cache =
178 TokenCacheServiceFactory::GetForProfile(GetProfile());
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());
192 // Check if the user is logged in.
193 bool 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());
202 void PushMessagingGetChannelIdFunction::ReportResult(
203 const std::string& gaia_id, const std::string& error_string) {
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 BuildAndSendResult(gaia_id, error_string);
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,
219 // Balanced in RunImpl.
223 void 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();
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.
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);
242 bool success = error_message.empty() && !gaia_id.empty();
243 SendResponse(success);
246 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess(
247 const std::string& gaia_id) {
248 ReportResult(gaia_id, std::string());
251 void 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());
260 DVLOG(1) << "GetChannelId status: '" << error_text << "'";
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: {
268 LoginUIService* login_ui_service =
269 LoginUIServiceFactory::GetForProfile(GetProfile());
270 // content::NotificationObserver will be called if token is issued.
271 login_ui_service->ShowLoginPopup();
273 ReportResult(std::string(), error_text);
278 // Return error to caller.
279 ReportResult(std::string(), error_text);
284 PushMessagingAPI::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()));
293 PushMessagingAPI::~PushMessagingAPI() {
297 PushMessagingAPI* PushMessagingAPI::Get(Profile* profile) {
298 return ProfileKeyedAPIFactory<PushMessagingAPI>::GetForProfile(profile);
301 void PushMessagingAPI::Shutdown() {
302 event_router_.reset();
306 static base::LazyInstance<ProfileKeyedAPIFactory<PushMessagingAPI> >
307 g_factory = LAZY_INSTANCE_INITIALIZER;
310 ProfileKeyedAPIFactory<PushMessagingAPI>*
311 PushMessagingAPI::GetFactoryInstance() {
312 return g_factory.Pointer();
315 void 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)
326 event_router_.reset(new PushMessagingEventRouter(profile_));
328 handler_.reset(new PushMessagingInvalidationHandler(
329 invalidation_service, event_router_.get()));
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());
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());
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());
360 void PushMessagingAPI::SetMapperForTest(
361 scoped_ptr<PushMessagingInvalidationMapper> mapper) {
362 handler_ = mapper.Pass();
366 void ProfileKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() {
367 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
368 DependsOn(invalidation::InvalidationServiceFactory::GetInstance());
371 } // namespace extensions