OLD | NEW |
| (Empty) |
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_invalidati
on_handler.h" | |
15 #include "chrome/browser/extensions/extension_service.h" | |
16 #include "chrome/browser/extensions/token_cache/token_cache_service.h" | |
17 #include "chrome/browser/extensions/token_cache/token_cache_service_factory.h" | |
18 #include "chrome/browser/invalidation/profile_invalidation_provider_factory.h" | |
19 #include "chrome/browser/profiles/profile.h" | |
20 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" | |
21 #include "chrome/browser/signin/signin_manager_factory.h" | |
22 #include "chrome/common/extensions/api/push_messaging.h" | |
23 #include "components/invalidation/invalidation_service.h" | |
24 #include "components/invalidation/profile_invalidation_provider.h" | |
25 #include "components/signin/core/browser/profile_oauth2_token_service.h" | |
26 #include "components/signin/core/browser/signin_manager.h" | |
27 #include "content/public/browser/browser_thread.h" | |
28 #include "extensions/browser/event_router.h" | |
29 #include "extensions/browser/extension_registry.h" | |
30 #include "extensions/browser/extension_registry_factory.h" | |
31 #include "extensions/browser/extension_system_provider.h" | |
32 #include "extensions/browser/extensions_browser_client.h" | |
33 #include "extensions/common/extension.h" | |
34 #include "extensions/common/permissions/api_permission.h" | |
35 #include "extensions/common/permissions/permissions_data.h" | |
36 #include "google_apis/gaia/gaia_constants.h" | |
37 #include "google_apis/gaia/identity_provider.h" | |
38 | |
39 using content::BrowserThread; | |
40 | |
41 namespace extensions { | |
42 | |
43 namespace { | |
44 const char kChannelIdSeparator[] = "/"; | |
45 const char kUserNotSignedIn[] = "The user is not signed in."; | |
46 const char kUserAccessTokenFailure[] = | |
47 "Cannot obtain access token for the user."; | |
48 const char kAPINotAvailableForUser[] = | |
49 "The API is not available for this user."; | |
50 const int kObfuscatedGaiaIdTimeoutInDays = 30; | |
51 const char kDeprecationMessage[] = | |
52 "The chrome.pushMessaging API is deprecated. Use chrome.gcm API instead."; | |
53 } // namespace | |
54 | |
55 namespace glue = api::push_messaging; | |
56 | |
57 PushMessagingEventRouter::PushMessagingEventRouter( | |
58 content::BrowserContext* context) | |
59 : browser_context_(context) { | |
60 } | |
61 | |
62 PushMessagingEventRouter::~PushMessagingEventRouter() {} | |
63 | |
64 void PushMessagingEventRouter::TriggerMessageForTest( | |
65 const std::string& extension_id, | |
66 int subchannel, | |
67 const std::string& payload) { | |
68 OnMessage(extension_id, subchannel, payload); | |
69 } | |
70 | |
71 void PushMessagingEventRouter::OnMessage(const std::string& extension_id, | |
72 int subchannel, | |
73 const std::string& payload) { | |
74 glue::Message message; | |
75 message.subchannel_id = subchannel; | |
76 message.payload = payload; | |
77 | |
78 DVLOG(2) << "PushMessagingEventRouter::OnMessage" | |
79 << " payload = '" << payload | |
80 << "' subchannel = '" << subchannel | |
81 << "' extension = '" << extension_id << "'"; | |
82 | |
83 scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message)); | |
84 scoped_ptr<Event> event(new Event(glue::OnMessage::kEventName, args.Pass())); | |
85 event->restrict_to_browser_context = browser_context_; | |
86 EventRouter::Get(browser_context_) | |
87 ->DispatchEventToExtension(extension_id, event.Pass()); | |
88 } | |
89 | |
90 // GetChannelId class functions | |
91 | |
92 PushMessagingGetChannelIdFunction::PushMessagingGetChannelIdFunction() | |
93 : OAuth2TokenService::Consumer("push_messaging"), | |
94 interactive_(false) {} | |
95 | |
96 PushMessagingGetChannelIdFunction::~PushMessagingGetChannelIdFunction() {} | |
97 | |
98 bool PushMessagingGetChannelIdFunction::RunAsync() { | |
99 // Issue a deprecation message. | |
100 WriteToConsole(content::CONSOLE_MESSAGE_LEVEL_WARNING, kDeprecationMessage); | |
101 | |
102 // Fetch the function arguments. | |
103 scoped_ptr<glue::GetChannelId::Params> params( | |
104 glue::GetChannelId::Params::Create(*args_)); | |
105 EXTENSION_FUNCTION_VALIDATE(params.get()); | |
106 | |
107 if (params && params->interactive) { | |
108 interactive_ = *params->interactive; | |
109 } | |
110 | |
111 // Balanced in ReportResult() | |
112 AddRef(); | |
113 | |
114 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
115 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
116 GetProfile()); | |
117 if (!invalidation_provider) { | |
118 error_ = kAPINotAvailableForUser; | |
119 ReportResult(std::string(), error_); | |
120 return false; | |
121 } | |
122 | |
123 IdentityProvider* identity_provider = | |
124 invalidation_provider->GetInvalidationService()->GetIdentityProvider(); | |
125 if (!identity_provider->GetTokenService()->RefreshTokenIsAvailable( | |
126 identity_provider->GetActiveAccountId())) { | |
127 if (interactive_ && identity_provider->RequestLogin()) { | |
128 identity_provider->AddActiveAccountRefreshTokenObserver(this); | |
129 return true; | |
130 } else { | |
131 error_ = kUserNotSignedIn; | |
132 ReportResult(std::string(), error_); | |
133 return false; | |
134 } | |
135 } | |
136 | |
137 DVLOG(2) << "Logged in profile name: " << GetProfile()->GetProfileUserName(); | |
138 | |
139 StartAccessTokenFetch(); | |
140 return true; | |
141 } | |
142 | |
143 void PushMessagingGetChannelIdFunction::StartAccessTokenFetch() { | |
144 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
145 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
146 GetProfile()); | |
147 CHECK(invalidation_provider); | |
148 IdentityProvider* identity_provider = | |
149 invalidation_provider->GetInvalidationService()->GetIdentityProvider(); | |
150 | |
151 OAuth2TokenService::ScopeSet scopes = ObfuscatedGaiaIdFetcher::GetScopes(); | |
152 fetcher_access_token_request_ = | |
153 identity_provider->GetTokenService()->StartRequest( | |
154 identity_provider->GetActiveAccountId(), scopes, this); | |
155 } | |
156 | |
157 void PushMessagingGetChannelIdFunction::OnRefreshTokenAvailable( | |
158 const std::string& account_id) { | |
159 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
160 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
161 GetProfile()); | |
162 CHECK(invalidation_provider); | |
163 invalidation_provider->GetInvalidationService()->GetIdentityProvider()-> | |
164 RemoveActiveAccountRefreshTokenObserver(this); | |
165 DVLOG(2) << "Newly logged in: " << GetProfile()->GetProfileUserName(); | |
166 StartAccessTokenFetch(); | |
167 } | |
168 | |
169 void PushMessagingGetChannelIdFunction::OnGetTokenSuccess( | |
170 const OAuth2TokenService::Request* request, | |
171 const std::string& access_token, | |
172 const base::Time& expiration_time) { | |
173 DCHECK_EQ(fetcher_access_token_request_.get(), request); | |
174 fetcher_access_token_request_.reset(); | |
175 | |
176 StartGaiaIdFetch(access_token); | |
177 } | |
178 | |
179 void PushMessagingGetChannelIdFunction::OnGetTokenFailure( | |
180 const OAuth2TokenService::Request* request, | |
181 const GoogleServiceAuthError& error) { | |
182 DCHECK_EQ(fetcher_access_token_request_.get(), request); | |
183 fetcher_access_token_request_.reset(); | |
184 | |
185 // TODO(fgorski): We are currently ignoring the error passed in upon failure. | |
186 // It should be revisited when we are working on improving general error | |
187 // handling for the identity related code. | |
188 DVLOG(1) << "Cannot obtain access token for this user " | |
189 << error.error_message() << " " << error.state(); | |
190 error_ = kUserAccessTokenFailure; | |
191 ReportResult(std::string(), error_); | |
192 } | |
193 | |
194 void PushMessagingGetChannelIdFunction::StartGaiaIdFetch( | |
195 const std::string& access_token) { | |
196 // Start the async fetch of the Gaia Id. | |
197 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
198 fetcher_.reset(new ObfuscatedGaiaIdFetcher(this)); | |
199 | |
200 // Get the token cache and see if we have already cached a Gaia Id. | |
201 TokenCacheService* token_cache = | |
202 TokenCacheServiceFactory::GetForProfile(GetProfile()); | |
203 | |
204 // Check the cache, if we already have a Gaia ID, use it instead of | |
205 // fetching the ID over the network. | |
206 const std::string& gaia_id = | |
207 token_cache->RetrieveToken(GaiaConstants::kObfuscatedGaiaId); | |
208 if (!gaia_id.empty()) { | |
209 ReportResult(gaia_id, std::string()); | |
210 return; | |
211 } | |
212 | |
213 net::URLRequestContextGetter* context = GetProfile()->GetRequestContext(); | |
214 fetcher_->Start(context, access_token); | |
215 } | |
216 | |
217 void PushMessagingGetChannelIdFunction::ReportResult( | |
218 const std::string& gaia_id, const std::string& error_string) { | |
219 DCHECK_CURRENTLY_ON(BrowserThread::UI); | |
220 | |
221 BuildAndSendResult(gaia_id, error_string); | |
222 | |
223 // Cache the obfuscated ID locally. It never changes for this user, | |
224 // and if we call the web API too often, we get errors due to rate limiting. | |
225 if (!gaia_id.empty()) { | |
226 base::TimeDelta timeout = | |
227 base::TimeDelta::FromDays(kObfuscatedGaiaIdTimeoutInDays); | |
228 TokenCacheService* token_cache = | |
229 TokenCacheServiceFactory::GetForProfile(GetProfile()); | |
230 token_cache->StoreToken(GaiaConstants::kObfuscatedGaiaId, gaia_id, | |
231 timeout); | |
232 } | |
233 | |
234 // Balanced in RunAsync. | |
235 Release(); | |
236 } | |
237 | |
238 void PushMessagingGetChannelIdFunction::BuildAndSendResult( | |
239 const std::string& gaia_id, const std::string& error_message) { | |
240 std::string channel_id; | |
241 if (!gaia_id.empty()) { | |
242 channel_id = gaia_id; | |
243 channel_id += kChannelIdSeparator; | |
244 channel_id += extension_id(); | |
245 } | |
246 | |
247 // TODO(petewil): It may be a good idea to further | |
248 // obfuscate the channel ID to prevent the user's obfuscated Gaia Id | |
249 // from being readily obtained. Security review will tell us if we need to. | |
250 | |
251 // Create a ChannelId results object and set the fields. | |
252 glue::ChannelIdResult result; | |
253 result.channel_id = channel_id; | |
254 SetError(error_message); | |
255 results_ = glue::GetChannelId::Results::Create(result); | |
256 | |
257 bool success = error_message.empty() && !gaia_id.empty(); | |
258 SendResponse(success); | |
259 } | |
260 | |
261 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchSuccess( | |
262 const std::string& gaia_id) { | |
263 ReportResult(gaia_id, std::string()); | |
264 } | |
265 | |
266 void PushMessagingGetChannelIdFunction::OnObfuscatedGaiaIdFetchFailure( | |
267 const GoogleServiceAuthError& error) { | |
268 std::string error_text = error.error_message(); | |
269 // If the error message is blank, see if we can set it from the state. | |
270 if (error_text.empty() && | |
271 (0 != error.state())) { | |
272 error_text = base::IntToString(error.state()); | |
273 } | |
274 | |
275 DVLOG(1) << "GetChannelId status: '" << error_text << "'"; | |
276 | |
277 // If we had bad credentials, try the logon again. | |
278 switch (error.state()) { | |
279 case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: | |
280 case GoogleServiceAuthError::ACCOUNT_DELETED: | |
281 case GoogleServiceAuthError::ACCOUNT_DISABLED: { | |
282 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
283 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
284 GetProfile()); | |
285 CHECK(invalidation_provider); | |
286 if (!interactive_ || !invalidation_provider->GetInvalidationService()-> | |
287 GetIdentityProvider()->RequestLogin()) { | |
288 ReportResult(std::string(), error_text); | |
289 } | |
290 return; | |
291 } | |
292 default: | |
293 // Return error to caller. | |
294 ReportResult(std::string(), error_text); | |
295 return; | |
296 } | |
297 } | |
298 | |
299 PushMessagingAPI::PushMessagingAPI(content::BrowserContext* context) | |
300 : extension_registry_observer_(this), browser_context_(context) { | |
301 extension_registry_observer_.Add(ExtensionRegistry::Get(browser_context_)); | |
302 } | |
303 | |
304 PushMessagingAPI::~PushMessagingAPI() { | |
305 } | |
306 | |
307 // static | |
308 PushMessagingAPI* PushMessagingAPI::Get(content::BrowserContext* context) { | |
309 return BrowserContextKeyedAPIFactory<PushMessagingAPI>::Get(context); | |
310 } | |
311 | |
312 void PushMessagingAPI::Shutdown() { | |
313 event_router_.reset(); | |
314 handler_.reset(); | |
315 } | |
316 | |
317 static base::LazyInstance<BrowserContextKeyedAPIFactory<PushMessagingAPI> > | |
318 g_factory = LAZY_INSTANCE_INITIALIZER; | |
319 | |
320 // static | |
321 BrowserContextKeyedAPIFactory<PushMessagingAPI>* | |
322 PushMessagingAPI::GetFactoryInstance() { | |
323 return g_factory.Pointer(); | |
324 } | |
325 | |
326 bool PushMessagingAPI::InitEventRouterAndHandler() { | |
327 invalidation::ProfileInvalidationProvider* invalidation_provider = | |
328 invalidation::ProfileInvalidationProviderFactory::GetForProfile( | |
329 Profile::FromBrowserContext(browser_context_)); | |
330 if (!invalidation_provider) | |
331 return false; | |
332 | |
333 if (!event_router_) | |
334 event_router_.reset(new PushMessagingEventRouter(browser_context_)); | |
335 if (!handler_) { | |
336 handler_.reset(new PushMessagingInvalidationHandler( | |
337 invalidation_provider->GetInvalidationService(), | |
338 event_router_.get())); | |
339 } | |
340 | |
341 return true; | |
342 } | |
343 | |
344 void PushMessagingAPI::OnExtensionLoaded( | |
345 content::BrowserContext* browser_context, | |
346 const Extension* extension) { | |
347 if (!InitEventRouterAndHandler()) | |
348 return; | |
349 | |
350 if (extension->permissions_data()->HasAPIPermission( | |
351 APIPermission::kPushMessaging)) { | |
352 handler_->RegisterExtension(extension->id()); | |
353 } | |
354 } | |
355 | |
356 void PushMessagingAPI::OnExtensionUnloaded( | |
357 content::BrowserContext* browser_context, | |
358 const Extension* extension, | |
359 UnloadedExtensionInfo::Reason reason) { | |
360 if (!InitEventRouterAndHandler()) | |
361 return; | |
362 | |
363 if (extension->permissions_data()->HasAPIPermission( | |
364 APIPermission::kPushMessaging)) { | |
365 handler_->UnregisterExtension(extension->id()); | |
366 } | |
367 } | |
368 | |
369 void PushMessagingAPI::OnExtensionWillBeInstalled( | |
370 content::BrowserContext* browser_context, | |
371 const Extension* extension, | |
372 bool is_update, | |
373 bool from_ephemeral, | |
374 const std::string& old_name) { | |
375 if (InitEventRouterAndHandler() && | |
376 extension->permissions_data()->HasAPIPermission( | |
377 APIPermission::kPushMessaging)) { | |
378 handler_->SuppressInitialInvalidationsForExtension(extension->id()); | |
379 } | |
380 } | |
381 | |
382 void PushMessagingAPI::SetMapperForTest( | |
383 scoped_ptr<PushMessagingInvalidationMapper> mapper) { | |
384 handler_ = mapper.Pass(); | |
385 } | |
386 | |
387 template <> | |
388 void | |
389 BrowserContextKeyedAPIFactory<PushMessagingAPI>::DeclareFactoryDependencies() { | |
390 DependsOn(ExtensionRegistryFactory::GetInstance()); | |
391 DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory()); | |
392 DependsOn(invalidation::ProfileInvalidationProviderFactory::GetInstance()); | |
393 } | |
394 | |
395 } // namespace extensions | |
OLD | NEW |