Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(72)

Side by Side Diff: chrome/browser/ui/webui/signin/inline_login_handler_impl.cc

Issue 2478173003: Lock profile before sign in when force sign in is enabled. (Closed)
Patch Set: rogerta's comments Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "chrome/browser/ui/webui/signin/inline_login_handler_impl.h" 5 #include "chrome/browser/ui/webui/signin/inline_login_handler_impl.h"
6 6
7 #include <stddef.h> 7 #include <stddef.h>
8 8
9 #include <string> 9 #include <string>
10 #include <vector> 10 #include <vector>
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
227 } 227 }
228 228
229 void CloseModalSigninIfNeeded(InlineLoginHandlerImpl* handler) { 229 void CloseModalSigninIfNeeded(InlineLoginHandlerImpl* handler) {
230 if (handler && switches::UsePasswordSeparatedSigninFlow()) { 230 if (handler && switches::UsePasswordSeparatedSigninFlow()) {
231 Browser* browser = handler->GetDesktopBrowser(); 231 Browser* browser = handler->GetDesktopBrowser();
232 if (browser) 232 if (browser)
233 browser->CloseModalSigninWindow(); 233 browser->CloseModalSigninWindow();
234 } 234 }
235 } 235 }
236 236
237 void UnlockProfileAndHideLoginUI(const base::FilePath profile_path,
238 InlineLoginHandlerImpl* handler) {
239 ProfileManager* profile_manager = g_browser_process->profile_manager();
240 if (profile_manager) {
241 ProfileAttributesEntry* entry;
242 if (profile_manager->GetProfileAttributesStorage()
243 .GetProfileAttributesWithPath(profile_path, &entry)) {
244 entry->SetIsSigninRequired(false);
245 }
246 }
247 if (handler)
248 handler->web_ui()->CallJavascriptFunctionUnsafe("inline.login.closeDialog");
249 UserManager::Hide();
250 }
251
252 bool IsCrossAccountError(Profile* profile,
253 const std::string& email,
254 const std::string& gaia_id) {
255 InvestigatorDependencyProvider provider(profile);
256 InvestigatedScenario scenario =
257 SigninInvestigator(email, gaia_id, &provider).Investigate();
258
259 return scenario == InvestigatedScenario::DIFFERENT_ACCOUNT;
260 }
261
237 } // namespace 262 } // namespace
238 263
239 InlineSigninHelper::InlineSigninHelper( 264 InlineSigninHelper::InlineSigninHelper(
240 base::WeakPtr<InlineLoginHandlerImpl> handler, 265 base::WeakPtr<InlineLoginHandlerImpl> handler,
241 net::URLRequestContextGetter* getter, 266 net::URLRequestContextGetter* getter,
242 Profile* profile, 267 Profile* profile,
268 Profile::CreateStatus create_status,
243 const GURL& current_url, 269 const GURL& current_url,
244 const std::string& email, 270 const std::string& email,
245 const std::string& gaia_id, 271 const std::string& gaia_id,
246 const std::string& password, 272 const std::string& password,
247 const std::string& session_index, 273 const std::string& session_index,
248 const std::string& auth_code, 274 const std::string& auth_code,
249 const std::string& signin_scoped_device_id, 275 const std::string& signin_scoped_device_id,
250 bool choose_what_to_sync, 276 bool choose_what_to_sync,
251 bool confirm_untrusted_signin) 277 bool confirm_untrusted_signin)
252 : gaia_auth_fetcher_(this, GaiaConstants::kChromeSource, getter), 278 : gaia_auth_fetcher_(this, GaiaConstants::kChromeSource, getter),
253 handler_(handler), 279 handler_(handler),
254 profile_(profile), 280 profile_(profile),
281 create_status_(create_status),
255 current_url_(current_url), 282 current_url_(current_url),
256 email_(email), 283 email_(email),
257 gaia_id_(gaia_id), 284 gaia_id_(gaia_id),
258 password_(password), 285 password_(password),
259 session_index_(session_index), 286 session_index_(session_index),
260 auth_code_(auth_code), 287 auth_code_(auth_code),
261 choose_what_to_sync_(choose_what_to_sync), 288 choose_what_to_sync_(choose_what_to_sync),
262 confirm_untrusted_signin_(confirm_untrusted_signin) { 289 confirm_untrusted_signin_(confirm_untrusted_signin) {
263 DCHECK(profile_); 290 DCHECK(profile_);
264 DCHECK(!email_.empty()); 291 DCHECK(!email_.empty());
265 if (!auth_code_.empty()) { 292 if (!auth_code_.empty()) {
266 gaia_auth_fetcher_.StartAuthCodeForOAuth2TokenExchangeWithDeviceId( 293 gaia_auth_fetcher_.StartAuthCodeForOAuth2TokenExchangeWithDeviceId(
267 auth_code, signin_scoped_device_id); 294 auth_code, signin_scoped_device_id);
268 } else { 295 } else {
269 DCHECK(!session_index_.empty()); 296 DCHECK(!session_index_.empty());
270 gaia_auth_fetcher_.StartCookieForOAuthLoginTokenExchangeWithDeviceId( 297 gaia_auth_fetcher_.StartCookieForOAuthLoginTokenExchangeWithDeviceId(
271 session_index_, signin_scoped_device_id); 298 session_index_, signin_scoped_device_id);
272 } 299 }
273 } 300 }
274 301
275 InlineSigninHelper::~InlineSigninHelper() {} 302 InlineSigninHelper::~InlineSigninHelper() {}
276 303
277 void InlineSigninHelper::OnClientOAuthSuccess(const ClientOAuthResult& result) { 304 void InlineSigninHelper::OnClientOAuthSuccess(const ClientOAuthResult& result) {
305 if (signin::IsForceSigninEnabled()) {
306 // With force sign in enabled, the browser window won't be opened until now.
307 profiles::OpenBrowserWindowForProfile(
308 base::Bind(&InlineSigninHelper::OnClientOAuthSuccessAndBrowserOpened,
309 base::Unretained(this), result),
310 true, false, profile_, create_status_);
311 } else {
312 OnClientOAuthSuccessAndBrowserOpened(result, profile_, create_status_);
313 }
314 }
315
316 void InlineSigninHelper::OnClientOAuthSuccessAndBrowserOpened(
317 const ClientOAuthResult& result,
318 Profile* profile,
319 Profile::CreateStatus status) {
320 UnlockProfileAndHideLoginUI(profile_->GetPath(), handler_.get());
278 content::WebContents* contents = NULL; 321 content::WebContents* contents = NULL;
279 Browser* browser = NULL; 322 Browser* browser = NULL;
280 if (handler_) { 323 if (handler_) {
281 contents = handler_->web_ui()->GetWebContents(); 324 contents = handler_->web_ui()->GetWebContents();
282 browser = handler_->GetDesktopBrowser(); 325 browser = handler_->GetDesktopBrowser();
283 } 326 }
284 327
285 AboutSigninInternals* about_signin_internals = 328 AboutSigninInternals* about_signin_internals =
286 AboutSigninInternalsFactory::GetForProfile(profile_); 329 AboutSigninInternalsFactory::GetForProfile(profile_);
287 about_signin_internals->OnRefreshTokenReceived("Successful"); 330 about_signin_internals->OnRefreshTokenReceived("Successful");
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
374 new OneClickSigninSyncStarter( 417 new OneClickSigninSyncStarter(
375 profile_, browser, gaia_id_, email_, password_, refresh_token, start_mode, 418 profile_, browser, gaia_id_, email_, password_, refresh_token, start_mode,
376 contents, confirmation_required, current_url, continue_url, 419 contents, confirmation_required, current_url, continue_url,
377 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_)); 420 base::Bind(&InlineLoginHandlerImpl::SyncStarterCallback, handler_));
378 } 421 }
379 422
380 bool InlineSigninHelper::HandleCrossAccountError( 423 bool InlineSigninHelper::HandleCrossAccountError(
381 const std::string& refresh_token, 424 const std::string& refresh_token,
382 OneClickSigninSyncStarter::ConfirmationRequired confirmation_required, 425 OneClickSigninSyncStarter::ConfirmationRequired confirmation_required,
383 OneClickSigninSyncStarter::StartSyncMode start_mode) { 426 OneClickSigninSyncStarter::StartSyncMode start_mode) {
427 // With force sign in enabled, cross account sign in will be rejected in the
428 // early stage so there is no need to show the warning page here.
429 if (signin::IsForceSigninEnabled())
430 return false;
431
384 std::string last_email = 432 std::string last_email =
385 profile_->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername); 433 profile_->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
386 434
387 InvestigatorDependencyProvider provider(profile_);
388 InvestigatedScenario scenario =
389 SigninInvestigator(email_, gaia_id_, &provider).Investigate();
390
391 // TODO(skym): Warn for high risk upgrade scenario, crbug.com/572754. 435 // TODO(skym): Warn for high risk upgrade scenario, crbug.com/572754.
392 if (scenario != InvestigatedScenario::DIFFERENT_ACCOUNT) { 436 if (!IsCrossAccountError(profile_, email_, gaia_id_))
393 return false; 437 return false;
394 }
395 438
396 Browser* browser = chrome::FindLastActiveWithProfile(profile_); 439 Browser* browser = chrome::FindLastActiveWithProfile(profile_);
397 content::WebContents* web_contents = 440 content::WebContents* web_contents =
398 browser->tab_strip_model()->GetActiveWebContents(); 441 browser->tab_strip_model()->GetActiveWebContents();
399 442
400 ConfirmEmailDialogDelegate::AskForConfirmation( 443 ConfirmEmailDialogDelegate::AskForConfirmation(
401 web_contents, 444 web_contents,
402 last_email, 445 last_email,
403 email_, 446 email_,
404 base::Bind(&InlineSigninHelper::ConfirmEmailAction, 447 base::Bind(&InlineSigninHelper::ConfirmEmailAction,
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after
567 gaia::AreEmailsSame(email, profile_email)) { 610 gaia::AreEmailsSame(email, profile_email)) {
568 if (error_message) { 611 if (error_message) {
569 error_message->assign( 612 error_message->assign(
570 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR)); 613 l10n_util::GetStringUTF8(IDS_SYNC_USER_NAME_IN_USE_ERROR));
571 } 614 }
572 return false; 615 return false;
573 } 616 }
574 } 617 }
575 } 618 }
576 } 619 }
620
621 // With force sign in enabled, cross account sign in is not allowed.
622 if (signin::IsForceSigninEnabled() &&
623 IsCrossAccountError(profile, email, gaia_id)) {
624 if (error_message) {
625 std::string last_email =
626 profile->GetPrefs()->GetString(prefs::kGoogleServicesLastUsername);
627 error_message->assign(l10n_util::GetStringFUTF8(
628 IDS_SYNC_USED_PROFILE_ERROR, base::UTF8ToUTF16(last_email)));
629 }
630 return false;
631 }
577 } 632 }
578 633
579 return true; 634 return true;
580 } 635 }
581 636
582 void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) { 637 void InlineLoginHandlerImpl::SetExtraInitParams(base::DictionaryValue& params) {
583 params.SetString("service", "chromiumsync"); 638 params.SetString("service", "chromiumsync");
584 639
585 // If this was called from the user manager to reauthenticate the profile, 640 // If this was called from the user manager to reauthenticate the profile,
586 // make sure the webui is aware. 641 // make sure the webui is aware.
(...skipping 97 matching lines...) Expand 10 before | Expand all | Expand 10 after
684 contents->GetBrowserContext(), signin::GetSigninPartitionURL()); 739 contents->GetBrowserContext(), signin::GetSigninPartitionURL());
685 740
686 // If this was called from the user manager to reauthenticate the profile, 741 // If this was called from the user manager to reauthenticate the profile,
687 // the current profile is the system profile. In this case, use the email to 742 // the current profile is the system profile. In this case, use the email to
688 // find the right profile to reauthenticate. Otherwise the profile can be 743 // find the right profile to reauthenticate. Otherwise the profile can be
689 // taken from web_ui(). 744 // taken from web_ui().
690 Profile* profile = Profile::FromWebUI(web_ui()); 745 Profile* profile = Profile::FromWebUI(web_ui());
691 if (IsSystemProfile(profile)) { 746 if (IsSystemProfile(profile)) {
692 ProfileManager* manager = g_browser_process->profile_manager(); 747 ProfileManager* manager = g_browser_process->profile_manager();
693 base::FilePath path = profiles::GetPathOfProfileWithEmail(manager, email); 748 base::FilePath path = profiles::GetPathOfProfileWithEmail(manager, email);
749 if (path.empty()) {
750 path = UserManager::GetSigninProfilePath();
751 }
694 if (!path.empty()) { 752 if (!path.empty()) {
695 signin_metrics::Reason reason = 753 signin_metrics::Reason reason =
696 signin::GetSigninReasonForPromoURL(current_url); 754 signin::GetSigninReasonForPromoURL(current_url);
697 // If we are only reauthenticating a profile in the user manager (and not 755 // If we are only reauthenticating a profile in the user manager (and not
698 // unlocking it), load the profile and finish the login. 756 // unlocking it), load the profile and finish the login.
699 if (reason == signin_metrics::Reason::REASON_REAUTHENTICATION) { 757 if (reason == signin_metrics::Reason::REASON_REAUTHENTICATION) {
700 FinishCompleteLoginParams params( 758 FinishCompleteLoginParams params(
701 this, partition, current_url, base::FilePath(), 759 this, partition, current_url, base::FilePath(),
702 confirm_untrusted_signin_, email, gaia_id, password, session_index, 760 confirm_untrusted_signin_, email, gaia_id, password, session_index,
703 auth_code, choose_what_to_sync); 761 auth_code, choose_what_to_sync);
704 ProfileManager::CreateCallback callback = 762 ProfileManager::CreateCallback callback =
705 base::Bind(&InlineLoginHandlerImpl::FinishCompleteLogin, params); 763 base::Bind(&InlineLoginHandlerImpl::FinishCompleteLogin, params);
706 profiles::LoadProfileAsync(path, callback); 764 profiles::LoadProfileAsync(path, callback);
707 } else { 765 } else {
708 // Otherwise, switch to the profile and finish the login. Pass the 766 // Otherwise, switch to the profile and finish the login. Pass the
709 // profile path so it can be marked as unlocked. Don't pass a handler 767 // profile path so it can be marked as unlocked. Don't pass a handler
710 // pointer since it will be destroyed before the callback runs. 768 // pointer since it will be destroyed before the callback runs.
711 FinishCompleteLoginParams params(nullptr, partition, current_url, path, 769 bool is_force_signin_enabled = signin::IsForceSigninEnabled();
770 InlineLoginHandlerImpl* handler = nullptr;
771 if (is_force_signin_enabled)
772 handler = this;
773 FinishCompleteLoginParams params(handler, partition, current_url, path,
712 confirm_untrusted_signin_, email, 774 confirm_untrusted_signin_, email,
713 gaia_id, password, session_index, 775 gaia_id, password, session_index,
714 auth_code, choose_what_to_sync); 776 auth_code, choose_what_to_sync);
715 ProfileManager::CreateCallback callback = 777 ProfileManager::CreateCallback callback =
716 base::Bind(&InlineLoginHandlerImpl::FinishCompleteLogin, params); 778 base::Bind(&InlineLoginHandlerImpl::FinishCompleteLogin, params);
717 profiles::SwitchToProfile(path, true, callback, 779 if (is_force_signin_enabled) {
718 ProfileMetrics::SWITCH_PROFILE_UNLOCK); 780 // Browser window will be opened after ClientOAuthSuccess.
781 profiles::LoadProfileAsync(path, callback);
782 } else {
783 profiles::SwitchToProfile(path, true, callback,
784 ProfileMetrics::SWITCH_PROFILE_UNLOCK);
785 }
719 } 786 }
720 } 787 }
721 } else { 788 } else {
722 FinishCompleteLogin( 789 FinishCompleteLogin(
723 FinishCompleteLoginParams(this, partition, current_url, 790 FinishCompleteLoginParams(this, partition, current_url,
724 base::FilePath(), confirm_untrusted_signin_, 791 base::FilePath(), confirm_untrusted_signin_,
725 email, gaia_id, password, session_index, 792 email, gaia_id, password, session_index,
726 auth_code, choose_what_to_sync), 793 auth_code, choose_what_to_sync),
727 profile, 794 profile,
728 Profile::CREATE_STATUS_CREATED); 795 Profile::CREATE_STATUS_CREATED);
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
766 Profile::CreateStatus status) { 833 Profile::CreateStatus status) {
767 // When doing a SAML sign in, this email check may result in a false 834 // When doing a SAML sign in, this email check may result in a false
768 // positive. This happens when the user types one email address in the 835 // positive. This happens when the user types one email address in the
769 // gaia sign in page, but signs in to a different account in the SAML sign in 836 // gaia sign in page, but signs in to a different account in the SAML sign in
770 // page. 837 // page.
771 std::string default_email; 838 std::string default_email;
772 std::string validate_email; 839 std::string validate_email;
773 if (net::GetValueForKeyInQuery(params.url, "email", &default_email) && 840 if (net::GetValueForKeyInQuery(params.url, "email", &default_email) &&
774 net::GetValueForKeyInQuery(params.url, "validateEmail", 841 net::GetValueForKeyInQuery(params.url, "validateEmail",
775 &validate_email) && 842 &validate_email) &&
776 validate_email == "1") { 843 validate_email == "1" && !default_email.empty()) {
777 if (!gaia::AreEmailsSame(params.email, default_email)) { 844 if (!gaia::AreEmailsSame(params.email, default_email)) {
778 if (params.handler) { 845 if (params.handler) {
779 params.handler->HandleLoginError( 846 params.handler->HandleLoginError(
780 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL, 847 l10n_util::GetStringFUTF8(IDS_SYNC_WRONG_EMAIL,
781 base::UTF8ToUTF16(default_email)), 848 base::UTF8ToUTF16(default_email)),
782 base::UTF8ToUTF16(params.email)); 849 base::UTF8ToUTF16(params.email));
783 } 850 }
784 return; 851 return;
785 } 852 }
786 } 853 }
(...skipping 47 matching lines...) Expand 10 before | Expand all | Expand 10 after
834 901
835 SigninClient* signin_client = 902 SigninClient* signin_client =
836 ChromeSigninClientFactory::GetForProfile(profile); 903 ChromeSigninClientFactory::GetForProfile(profile);
837 std::string signin_scoped_device_id = 904 std::string signin_scoped_device_id =
838 signin_client->GetSigninScopedDeviceId(); 905 signin_client->GetSigninScopedDeviceId();
839 base::WeakPtr<InlineLoginHandlerImpl> handler_weak_ptr; 906 base::WeakPtr<InlineLoginHandlerImpl> handler_weak_ptr;
840 if (params.handler) 907 if (params.handler)
841 handler_weak_ptr = params.handler->GetWeakPtr(); 908 handler_weak_ptr = params.handler->GetWeakPtr();
842 909
843 // InlineSigninHelper will delete itself. 910 // InlineSigninHelper will delete itself.
844 new InlineSigninHelper(handler_weak_ptr, 911 new InlineSigninHelper(
845 params.partition->GetURLRequestContext(), profile, 912 handler_weak_ptr, params.partition->GetURLRequestContext(), profile,
846 params.url, 913 status, params.url, params.email, params.gaia_id, params.password,
847 params.email, params.gaia_id, params.password, 914 params.session_index, params.auth_code, signin_scoped_device_id,
848 params.session_index, params.auth_code, 915 params.choose_what_to_sync, params.confirm_untrusted_signin);
849 signin_scoped_device_id,
850 params.choose_what_to_sync,
851 params.confirm_untrusted_signin);
852 916
853 // If opened from user manager to unlock a profile, make sure the user manager 917 // If opened from user manager to unlock a profile, make sure the user manager
854 // is closed and that the profile is marked as unlocked. 918 // is closed and that the profile is marked as unlocked.
855 if (!params.profile_path.empty()) { 919 if (!params.profile_path.empty() && !signin::IsForceSigninEnabled()) {
856 UserManager::Hide(); 920 UnlockProfileAndHideLoginUI(params.profile_path, params.handler);
857 ProfileManager* profile_manager = g_browser_process->profile_manager();
858 if (profile_manager) {
859 ProfileAttributesEntry* entry;
860 if (profile_manager->GetProfileAttributesStorage()
861 .GetProfileAttributesWithPath(params.profile_path, &entry)) {
862 entry->SetIsSigninRequired(false);
863 }
864 }
865 } 921 }
866
867 if (params.handler)
868 params.handler->web_ui()->CallJavascriptFunctionUnsafe(
869 "inline.login.closeDialog");
870 } 922 }
871 923
872 void InlineLoginHandlerImpl::HandleLoginError(const std::string& error_msg, 924 void InlineLoginHandlerImpl::HandleLoginError(const std::string& error_msg,
873 const base::string16& email) { 925 const base::string16& email) {
874 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE); 926 SyncStarterCallback(OneClickSigninSyncStarter::SYNC_SETUP_FAILURE);
875 Browser* browser = GetDesktopBrowser(); 927 Browser* browser = GetDesktopBrowser();
876 Profile* profile = Profile::FromWebUI(web_ui()); 928 Profile* profile = Profile::FromWebUI(web_ui());
877 929
930 if (IsSystemProfile(profile))
931 profile = g_browser_process->profile_manager()->GetProfileByPath(
932 UserManager::GetSigninProfilePath());
878 CloseModalSigninIfNeeded(this); 933 CloseModalSigninIfNeeded(this);
879 if (browser && !error_msg.empty()) { 934 if (!error_msg.empty()) {
880 LoginUIServiceFactory::GetForProfile(profile)->DisplayLoginResult( 935 LoginUIServiceFactory::GetForProfile(profile)->DisplayLoginResult(
881 browser, base::UTF8ToUTF16(error_msg), email); 936 browser, base::UTF8ToUTF16(error_msg), email);
882 } 937 }
883 } 938 }
884 939
885 Browser* InlineLoginHandlerImpl::GetDesktopBrowser() { 940 Browser* InlineLoginHandlerImpl::GetDesktopBrowser() {
886 Browser* browser = chrome::FindBrowserWithWebContents( 941 Browser* browser = chrome::FindBrowserWithWebContents(
887 web_ui()->GetWebContents()); 942 web_ui()->GetWebContents());
888 if (!browser) 943 if (!browser)
889 browser = chrome::FindLastActiveWithProfile(Profile::FromWebUI(web_ui())); 944 browser = chrome::FindLastActiveWithProfile(Profile::FromWebUI(web_ui()));
(...skipping 43 matching lines...) Expand 10 before | Expand all | Expand 10 after
933 } 988 }
934 989
935 if (show_account_management) { 990 if (show_account_management) {
936 browser->window()->ShowAvatarBubbleFromAvatarButton( 991 browser->window()->ShowAvatarBubbleFromAvatarButton(
937 BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT, 992 BrowserWindow::AVATAR_BUBBLE_MODE_ACCOUNT_MANAGEMENT,
938 signin::ManageAccountsParams(), 993 signin::ManageAccountsParams(),
939 signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN); 994 signin_metrics::AccessPoint::ACCESS_POINT_AVATAR_BUBBLE_SIGN_IN);
940 } 995 }
941 } 996 }
942 } 997 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698