1 // Copyright (c) 2013 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/profiles/profile_downloader.h" 6 7 #include <string> 8 #include <vector> 9 10 #include "base/json/json_reader.h" 11 #include "base/logging.h" 12 #include "base/message_loop/message_loop.h" 13 #include "base/strings/string_split.h" 14 #include "base/strings/string_util.h" 15 #include "base/strings/stringprintf.h" 16 #include "base/values.h" 17 #include "chrome/browser/profiles/profile.h" 18 #include "chrome/browser/profiles/profile_downloader_delegate.h" 19 #include "chrome/browser/profiles/profile_manager.h" 20 #include "chrome/browser/signin/profile_oauth2_token_service_factory.h" 21 #include "chrome/browser/signin/signin_manager_factory.h" 22 #include "components/signin/core/browser/profile_oauth2_token_service.h" 23 #include "components/signin/core/browser/signin_manager.h" 24 #include "content/public/browser/browser_thread.h" 25 #include "google_apis/gaia/gaia_constants.h" 26 #include "google_apis/gaia/gaia_urls.h" 27 #include "net/base/load_flags.h" 28 #include "net/url_request/url_fetcher.h" 29 #include "net/url_request/url_request_status.h" 30 #include "skia/ext/image_operations.h" 31 #include "url/gurl.h" 32 33 using content::BrowserThread; 34 35 namespace { 36 37 // Template for optional authorization header when using an OAuth access token. 38 const char kAuthorizationHeader[] = 39 "Authorization: Bearer %s"; 40 41 // URL requesting user info. 42 const char kUserEntryURL[] = 43 "https://www.googleapis.com/oauth2/v1/userinfo?alt=json"; 44 45 // OAuth scope for the user info API. 46 // For more info, see https://developers.google.com/accounts/docs/OAuth2LoginV1. 47 const char kAPIScope[] = "https://www.googleapis.com/auth/userinfo.profile"; 48 49 // Path in JSON dictionary to user's photo thumbnail URL. 50 const char kPhotoThumbnailURLPath[] = "picture"; 51 52 // From the user info API, this field corresponds to the full name of the user. 53 const char kFullNamePath[] = "name"; 54 55 const char kGivenNamePath[] = "given_name"; 56 57 // Path in JSON dictionary to user's preferred locale. 58 const char kLocalePath[] = "locale"; 59 60 // Path format for specifying thumbnail's size. 61 const char kThumbnailSizeFormat[] = "s%d-c"; 62 // Default thumbnail size. 63 const int kDefaultThumbnailSize = 64; 64 65 // Separator of URL path components. 66 const char kURLPathSeparator = '/'; 67 68 // Photo ID of the Picasa Web Albums profile picture (base64 of 0). 69 const char kPicasaPhotoId[] = "AAAAAAAAAAA"; 70 71 // Photo version of the default PWA profile picture (base64 of 1). 72 const char kDefaultPicasaPhotoVersion[] = "AAAAAAAAAAE"; 73 74 // The minimum number of path components in profile picture URL. 75 const size_t kProfileImageURLPathComponentsCount = 6; 76 77 // Index of path component with photo ID. 78 const int kPhotoIdPathComponentIndex = 2; 79 80 // Index of path component with photo version. 81 const int kPhotoVersionPathComponentIndex = 3; 82 83 // Given an image URL this function builds a new URL set to |size|. 84 // For example, if |size| was set to 256 and |old_url| was either: 85 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/photo.jpg 86 // or 87 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s64-c/photo.jpg 88 // then return value in |new_url| would be: 89 // https://example.com/--Abc/AAAAAAAAAAI/AAAAAAAAACQ/Efg/s256-c/photo.jpg 90 bool GetImageURLWithSize(const GURL& old_url, int size, GURL* new_url) { 91 DCHECK(new_url); 92 std::vector<std::string> components; 93 base::SplitString(old_url.path(), kURLPathSeparator, &components); 94 if (components.size() == 0) 95 return false; 96 97 const std::string& old_spec = old_url.spec(); 98 std::string default_size_component( 99 base::StringPrintf(kThumbnailSizeFormat, kDefaultThumbnailSize)); 100 std::string new_size_component( 101 base::StringPrintf(kThumbnailSizeFormat, size)); 102 103 size_t pos = old_spec.find(default_size_component); 104 size_t end = std::string::npos; 105 if (pos != std::string::npos) { 106 // The default size is already specified in the URL so it needs to be 107 // replaced with the new size. 108 end = pos + default_size_component.size(); 109 } else { 110 // The default size is not in the URL so try to insert it before the last 111 // component. 112 const std::string& file_name = old_url.ExtractFileName(); 113 if (!file_name.empty()) { 114 pos = old_spec.find(file_name); 115 end = pos - 1; 116 } 117 } 118 119 if (pos != std::string::npos) { 120 std::string new_spec = old_spec.substr(0, pos) + new_size_component + 121 old_spec.substr(end); 122 *new_url = GURL(new_spec); 123 return new_url->is_valid(); 124 } 125 126 // We can't set the image size, just use the default size. 127 *new_url = old_url; 128 return true; 129 } 130 131 } // namespace 132 133 // Parses the entry response and gets the name and profile image URL. 134 // |data| should be the JSON formatted data return by the response. 135 // Returns false to indicate a parsing error. 136 bool ProfileDownloader::ParseProfileJSON(const std::string& data, 137 base::string16* full_name, 138 base::string16* given_name, 139 std::string* url, 140 int image_size, 141 std::string* profile_locale) { 142 DCHECK(full_name); 143 DCHECK(given_name); 144 DCHECK(url); 145 DCHECK(profile_locale); 146 147 *full_name = base::string16(); 148 *given_name = base::string16(); 149 *url = std::string(); 150 *profile_locale = std::string(); 151 152 int error_code = -1; 153 std::string error_message; 154 scoped_ptr<base::Value> root_value(base::JSONReader::ReadAndReturnError( 155 data, base::JSON_PARSE_RFC, &error_code, &error_message)); 156 if (!root_value) { 157 LOG(ERROR) << "Error while parsing user entry response: " 158 << error_message; 159 return false; 160 } 161 if (!root_value->IsType(base::Value::TYPE_DICTIONARY)) { 162 LOG(ERROR) << "JSON root is not a dictionary: " 163 << root_value->GetType(); 164 return false; 165 } 166 base::DictionaryValue* root_dictionary = 167 static_cast<base::DictionaryValue*>(root_value.get()); 168 169 root_dictionary->GetString(kFullNamePath, full_name); 170 root_dictionary->GetString(kGivenNamePath, given_name); 171 root_dictionary->GetString(kLocalePath, profile_locale); 172 173 std::string url_string; 174 if (root_dictionary->GetString(kPhotoThumbnailURLPath, &url_string)) { 175 GURL new_url; 176 if (!GetImageURLWithSize(GURL(url_string), image_size, &new_url)) { 177 LOG(ERROR) << "GetImageURLWithSize failed for url: " << url_string; 178 return false; 179 } 180 *url = new_url.spec(); 181 } 182 183 // The profile data is considered valid as long as it has a name or a picture. 184 return !full_name->empty() || !url->empty(); 185 } 186 187 // static 188 bool ProfileDownloader::IsDefaultProfileImageURL(const std::string& url) { 189 if (url.empty()) 190 return true; 191 192 GURL image_url_object(url); 193 DCHECK(image_url_object.is_valid()); 194 VLOG(1) << "URL to check for default image: " << image_url_object.spec(); 195 std::vector<std::string> path_components; 196 base::SplitString(image_url_object.path(), 197 kURLPathSeparator, 198 &path_components); 199 200 if (path_components.size() < kProfileImageURLPathComponentsCount) 201 return false; 202 203 const std::string& photo_id = path_components[kPhotoIdPathComponentIndex]; 204 const std::string& photo_version = 205 path_components[kPhotoVersionPathComponentIndex]; 206 207 // Check that the ID and version match the default Picasa profile photo. 208 return photo_id == kPicasaPhotoId && 209 photo_version == kDefaultPicasaPhotoVersion; 210 } 211 212 ProfileDownloader::ProfileDownloader(ProfileDownloaderDelegate* delegate) 213 : OAuth2TokenService::Consumer("profile_downloader"), 214 delegate_(delegate), 215 picture_status_(PICTURE_FAILED) { 216 DCHECK(delegate_); 217 } 218 219 void ProfileDownloader::Start() { 220 StartForAccount(std::string()); 221 } 222 223 void ProfileDownloader::StartForAccount(const std::string& account_id) { 224 VLOG(1) << "Starting profile downloader..."; 225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 226 227 ProfileOAuth2TokenService* service = 228 ProfileOAuth2TokenServiceFactory::GetForProfile( 229 delegate_->GetBrowserProfile()); 230 if (!service) { 231 // This can happen in some test paths. 232 LOG(WARNING) << "User has no token service"; 233 delegate_->OnProfileDownloadFailure( 234 this, ProfileDownloaderDelegate::TOKEN_ERROR); 235 return; 236 } 237 238 SigninManagerBase* signin_manager = 239 SigninManagerFactory::GetForProfile(delegate_->GetBrowserProfile()); 240 account_id_ = 241 account_id.empty() ? 242 signin_manager->GetAuthenticatedAccountId() : account_id; 243 if (service->RefreshTokenIsAvailable(account_id_)) { 244 StartFetchingOAuth2AccessToken(); 245 } else { 246 service->AddObserver(this); 247 } 248 } 249 250 base::string16 ProfileDownloader::GetProfileFullName() const { 251 return profile_full_name_; 252 } 253 254 base::string16 ProfileDownloader::GetProfileGivenName() const { 255 return profile_given_name_; 256 } 257 258 std::string ProfileDownloader::GetProfileLocale() const { 259 return profile_locale_; 260 } 261 262 SkBitmap ProfileDownloader::GetProfilePicture() const { 263 return profile_picture_; 264 } 265 266 ProfileDownloader::PictureStatus ProfileDownloader::GetProfilePictureStatus() 267 const { 268 return picture_status_; 269 } 270 271 std::string ProfileDownloader::GetProfilePictureURL() const { 272 return picture_url_; 273 } 274 275 void ProfileDownloader::StartFetchingImage() { 276 VLOG(1) << "Fetching user entry with token: " << auth_token_; 277 user_entry_fetcher_.reset(net::URLFetcher::Create( 278 GURL(kUserEntryURL), net::URLFetcher::GET, this)); 279 user_entry_fetcher_->SetRequestContext( 280 delegate_->GetBrowserProfile()->GetRequestContext()); 281 user_entry_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 282 net::LOAD_DO_NOT_SAVE_COOKIES); 283 if (!auth_token_.empty()) { 284 user_entry_fetcher_->SetExtraRequestHeaders( 285 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); 286 } 287 user_entry_fetcher_->Start(); 288 } 289 290 void ProfileDownloader::StartFetchingOAuth2AccessToken() { 291 Profile* profile = delegate_->GetBrowserProfile(); 292 OAuth2TokenService::ScopeSet scopes; 293 scopes.insert(kAPIScope); 294 ProfileOAuth2TokenService* token_service = 295 ProfileOAuth2TokenServiceFactory::GetForProfile(profile); 296 oauth2_access_token_request_ = token_service->StartRequest( 297 account_id_, scopes, this); 298 } 299 300 ProfileDownloader::~ProfileDownloader() { 301 // Ensures PO2TS observation is cleared when ProfileDownloader is destructed 302 // before refresh token is available. 303 ProfileOAuth2TokenService* service = 304 ProfileOAuth2TokenServiceFactory::GetForProfile( 305 delegate_->GetBrowserProfile()); 306 if (service) 307 service->RemoveObserver(this); 308 } 309 310 void ProfileDownloader::OnURLFetchComplete(const net::URLFetcher* source) { 311 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 312 std::string data; 313 source->GetResponseAsString(&data); 314 bool network_error = 315 source->GetStatus().status() != net::URLRequestStatus::SUCCESS; 316 if (network_error || source->GetResponseCode() != 200) { 317 LOG(WARNING) << "Fetching profile data failed"; 318 DVLOG(1) << " Status: " << source->GetStatus().status(); 319 DVLOG(1) << " Error: " << source->GetStatus().error(); 320 DVLOG(1) << " Response code: " << source->GetResponseCode(); 321 DVLOG(1) << " Url: " << source->GetURL().spec(); 322 delegate_->OnProfileDownloadFailure(this, network_error ? 323 ProfileDownloaderDelegate::NETWORK_ERROR : 324 ProfileDownloaderDelegate::SERVICE_ERROR); 325 return; 326 } 327 328 if (source == user_entry_fetcher_.get()) { 329 std::string image_url; 330 if (!ParseProfileJSON(data, 331 &profile_full_name_, 332 &profile_given_name_, 333 &image_url, 334 delegate_->GetDesiredImageSideLength(), 335 &profile_locale_)) { 336 delegate_->OnProfileDownloadFailure( 337 this, ProfileDownloaderDelegate::SERVICE_ERROR); 338 return; 339 } 340 if (!delegate_->NeedsProfilePicture()) { 341 VLOG(1) << "Skipping profile picture download"; 342 delegate_->OnProfileDownloadSuccess(this); 343 return; 344 } 345 if (IsDefaultProfileImageURL(image_url)) { 346 VLOG(1) << "User has default profile picture"; 347 picture_status_ = PICTURE_DEFAULT; 348 delegate_->OnProfileDownloadSuccess(this); 349 return; 350 } 351 if (!image_url.empty() && image_url == delegate_->GetCachedPictureURL()) { 352 VLOG(1) << "Picture URL matches cached picture URL"; 353 picture_status_ = PICTURE_CACHED; 354 delegate_->OnProfileDownloadSuccess(this); 355 return; 356 } 357 VLOG(1) << "Fetching profile image from " << image_url; 358 picture_url_ = image_url; 359 profile_image_fetcher_.reset(net::URLFetcher::Create( 360 GURL(image_url), net::URLFetcher::GET, this)); 361 profile_image_fetcher_->SetRequestContext( 362 delegate_->GetBrowserProfile()->GetRequestContext()); 363 profile_image_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 364 net::LOAD_DO_NOT_SAVE_COOKIES); 365 if (!auth_token_.empty()) { 366 profile_image_fetcher_->SetExtraRequestHeaders( 367 base::StringPrintf(kAuthorizationHeader, auth_token_.c_str())); 368 } 369 profile_image_fetcher_->Start(); 370 } else if (source == profile_image_fetcher_.get()) { 371 VLOG(1) << "Decoding the image..."; 372 scoped_refptr<ImageDecoder> image_decoder = new ImageDecoder( 373 this, data, ImageDecoder::DEFAULT_CODEC); 374 scoped_refptr<base::MessageLoopProxy> task_runner = 375 BrowserThread::GetMessageLoopProxyForThread(BrowserThread::UI); 376 image_decoder->Start(task_runner); 377 } 378 } 379 380 void ProfileDownloader::OnImageDecoded(const ImageDecoder* decoder, 381 const SkBitmap& decoded_image) { 382 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 383 int image_size = delegate_->GetDesiredImageSideLength(); 384 profile_picture_ = skia::ImageOperations::Resize( 385 decoded_image, 386 skia::ImageOperations::RESIZE_BEST, 387 image_size, 388 image_size); 389 picture_status_ = PICTURE_SUCCESS; 390 delegate_->OnProfileDownloadSuccess(this); 391 } 392 393 void ProfileDownloader::OnDecodeImageFailed(const ImageDecoder* decoder) { 394 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 395 delegate_->OnProfileDownloadFailure( 396 this, ProfileDownloaderDelegate::IMAGE_DECODE_FAILED); 397 } 398 399 void ProfileDownloader::OnRefreshTokenAvailable(const std::string& account_id) { 400 ProfileOAuth2TokenService* service = 401 ProfileOAuth2TokenServiceFactory::GetForProfile( 402 delegate_->GetBrowserProfile()); 403 if (account_id != account_id_) 404 return; 405 406 service->RemoveObserver(this); 407 StartFetchingOAuth2AccessToken(); 408 } 409 410 // Callback for OAuth2TokenService::Request on success. |access_token| is the 411 // token used to start fetching user data. 412 void ProfileDownloader::OnGetTokenSuccess( 413 const OAuth2TokenService::Request* request, 414 const std::string& access_token, 415 const base::Time& expiration_time) { 416 DCHECK_EQ(request, oauth2_access_token_request_.get()); 417 oauth2_access_token_request_.reset(); 418 auth_token_ = access_token; 419 StartFetchingImage(); 420 } 421 422 // Callback for OAuth2TokenService::Request on failure. 423 void ProfileDownloader::OnGetTokenFailure( 424 const OAuth2TokenService::Request* request, 425 const GoogleServiceAuthError& error) { 426 DCHECK_EQ(request, oauth2_access_token_request_.get()); 427 oauth2_access_token_request_.reset(); 428 LOG(WARNING) << "ProfileDownloader: token request using refresh token failed:" 429 << error.ToString(); 430 delegate_->OnProfileDownloadFailure( 431 this, ProfileDownloaderDelegate::TOKEN_ERROR); 432 } 433