Chromium Code Reviews| OLD | NEW |
|---|---|
| 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/chromeos/login/gaia_screen_handler.h" | 5 #include "chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.h" |
| 6 | 6 |
| 7 #include "ash/common/system/chromeos/devicetype_utils.h" | 7 #include "ash/common/system/chromeos/devicetype_utils.h" |
| 8 #include "base/bind.h" | 8 #include "base/bind.h" |
| 9 #include "base/callback.h" | 9 #include "base/callback.h" |
| 10 #include "base/guid.h" | 10 #include "base/guid.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/metrics/histogram_macros.h" | 12 #include "base/metrics/histogram_macros.h" |
| 13 #include "base/stl_util.h" | 13 #include "base/stl_util.h" |
| 14 #include "base/strings/utf_string_conversions.h" | 14 #include "base/strings/utf_string_conversions.h" |
| 15 #include "base/values.h" | 15 #include "base/values.h" |
| 16 #include "chrome/browser/browser_process.h" | 16 #include "chrome/browser/browser_process.h" |
| 17 #include "chrome/browser/browser_shutdown.h" | 17 #include "chrome/browser/browser_shutdown.h" |
| 18 #include "chrome/browser/chromeos/input_method/input_method_util.h" | 18 #include "chrome/browser/chromeos/input_method/input_method_util.h" |
| 19 #include "chrome/browser/chromeos/language_preferences.h" | 19 #include "chrome/browser/chromeos/language_preferences.h" |
| 20 #include "chrome/browser/chromeos/login/helper.h" | |
| 20 #include "chrome/browser/chromeos/login/screens/network_error.h" | 21 #include "chrome/browser/chromeos/login/screens/network_error.h" |
| 21 #include "chrome/browser/chromeos/login/ui/user_adding_screen.h" | 22 #include "chrome/browser/chromeos/login/ui/user_adding_screen.h" |
| 22 #include "chrome/browser/chromeos/login/users/chrome_user_manager.h" | 23 #include "chrome/browser/chromeos/login/users/chrome_user_manager.h" |
| 23 #include "chrome/browser/chromeos/net/network_portal_detector_impl.h" | 24 #include "chrome/browser/chromeos/net/network_portal_detector_impl.h" |
| 24 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" | 25 #include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h" |
| 25 #include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h" | 26 #include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h" |
| 26 #include "chrome/browser/chromeos/profiles/profile_helper.h" | 27 #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| 27 #include "chrome/browser/chromeos/settings/cros_settings.h" | 28 #include "chrome/browser/chromeos/settings/cros_settings.h" |
| 28 #include "chrome/browser/io_thread.h" | 29 #include "chrome/browser/io_thread.h" |
| 29 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" | 30 #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" |
| 30 #include "chrome/browser/ui/webui/signin/signin_utils.h" | 31 #include "chrome/browser/ui/webui/signin/signin_utils.h" |
| 31 #include "chrome/common/channel_info.h" | 32 #include "chrome/common/channel_info.h" |
| 32 #include "chrome/common/pref_names.h" | 33 #include "chrome/common/pref_names.h" |
| 33 #include "chrome/grit/generated_resources.h" | 34 #include "chrome/grit/generated_resources.h" |
| 34 #include "chromeos/chromeos_switches.h" | 35 #include "chromeos/chromeos_switches.h" |
| 36 #include "chromeos/dbus/auth_policy_client.h" | |
| 37 #include "chromeos/dbus/dbus_thread_manager.h" | |
| 35 #include "chromeos/login/auth/user_context.h" | 38 #include "chromeos/login/auth/user_context.h" |
| 36 #include "chromeos/settings/cros_settings_names.h" | 39 #include "chromeos/settings/cros_settings_names.h" |
| 37 #include "chromeos/system/devicetype.h" | 40 #include "chromeos/system/devicetype.h" |
| 38 #include "chromeos/system/version_loader.h" | 41 #include "chromeos/system/version_loader.h" |
| 39 #include "components/login/localized_values_builder.h" | 42 #include "components/login/localized_values_builder.h" |
| 40 #include "components/prefs/pref_service.h" | 43 #include "components/prefs/pref_service.h" |
| 41 #include "components/strings/grit/components_strings.h" | 44 #include "components/strings/grit/components_strings.h" |
| 42 #include "components/user_manager/known_user.h" | 45 #include "components/user_manager/known_user.h" |
| 43 #include "components/user_manager/user_manager.h" | 46 #include "components/user_manager/user_manager.h" |
| 44 #include "components/version_info/version_info.h" | 47 #include "components/version_info/version_info.h" |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 65 // The possible modes that the Gaia signin screen can be in. | 68 // The possible modes that the Gaia signin screen can be in. |
| 66 enum GaiaScreenMode { | 69 enum GaiaScreenMode { |
| 67 // Default Gaia authentication will be used. | 70 // Default Gaia authentication will be used. |
| 68 GAIA_SCREEN_MODE_DEFAULT = 0, | 71 GAIA_SCREEN_MODE_DEFAULT = 0, |
| 69 | 72 |
| 70 // Gaia offline mode will be used. | 73 // Gaia offline mode will be used. |
| 71 GAIA_SCREEN_MODE_OFFLINE = 1, | 74 GAIA_SCREEN_MODE_OFFLINE = 1, |
| 72 | 75 |
| 73 // An interstitial page will be used before SAML redirection. | 76 // An interstitial page will be used before SAML redirection. |
| 74 GAIA_SCREEN_MODE_SAML_INTERSTITIAL = 2, | 77 GAIA_SCREEN_MODE_SAML_INTERSTITIAL = 2, |
| 78 | |
| 79 // Offline UI for Active Directory authentication. | |
| 80 GAIA_SCREEN_MODE_AD = 3, | |
| 75 }; | 81 }; |
| 76 | 82 |
| 83 policy::DeviceMode GetDeviceMode() { | |
| 84 policy::BrowserPolicyConnectorChromeOS* connector = | |
| 85 g_browser_process->platform_part()->browser_policy_connector_chromeos(); | |
| 86 return connector->GetDeviceMode(); | |
| 87 } | |
| 88 | |
| 77 GaiaScreenMode GetGaiaScreenMode(const std::string& email, bool use_offline) { | 89 GaiaScreenMode GetGaiaScreenMode(const std::string& email, bool use_offline) { |
| 90 if (GetDeviceMode() == policy::DEVICE_MODE_ENTERPRISE_AD) | |
| 91 return GAIA_SCREEN_MODE_AD; | |
| 92 | |
| 78 if (use_offline) | 93 if (use_offline) |
| 79 return GAIA_SCREEN_MODE_OFFLINE; | 94 return GAIA_SCREEN_MODE_OFFLINE; |
| 80 | 95 |
| 81 int authentication_behavior = 0; | 96 int authentication_behavior = 0; |
| 82 CrosSettings::Get()->GetInteger(kLoginAuthenticationBehavior, | 97 CrosSettings::Get()->GetInteger(kLoginAuthenticationBehavior, |
| 83 &authentication_behavior); | 98 &authentication_behavior); |
| 84 if (authentication_behavior == | 99 if (authentication_behavior == |
| 85 em::LoginAuthenticationBehaviorProto::SAML_INTERSTITIAL) { | 100 em::LoginAuthenticationBehaviorProto::SAML_INTERSTITIAL) { |
| 86 if (email.empty()) | 101 if (email.empty()) |
| 87 return GAIA_SCREEN_MODE_SAML_INTERSTITIAL; | 102 return GAIA_SCREEN_MODE_SAML_INTERSTITIAL; |
| (...skipping 10 matching lines...) Expand all Loading... | |
| 98 | 113 |
| 99 return GAIA_SCREEN_MODE_DEFAULT; | 114 return GAIA_SCREEN_MODE_DEFAULT; |
| 100 } | 115 } |
| 101 | 116 |
| 102 std::string GetEnterpriseDomain() { | 117 std::string GetEnterpriseDomain() { |
| 103 policy::BrowserPolicyConnectorChromeOS* connector = | 118 policy::BrowserPolicyConnectorChromeOS* connector = |
| 104 g_browser_process->platform_part()->browser_policy_connector_chromeos(); | 119 g_browser_process->platform_part()->browser_policy_connector_chromeos(); |
| 105 return connector->GetEnterpriseDomain(); | 120 return connector->GetEnterpriseDomain(); |
| 106 } | 121 } |
| 107 | 122 |
| 123 std::string GetRealm() { | |
| 124 policy::BrowserPolicyConnectorChromeOS* connector = | |
| 125 g_browser_process->platform_part()->browser_policy_connector_chromeos(); | |
| 126 return connector->GetRealm(); | |
| 127 } | |
| 128 | |
| 108 std::string GetChromeType() { | 129 std::string GetChromeType() { |
| 109 switch (chromeos::GetDeviceType()) { | 130 switch (chromeos::GetDeviceType()) { |
| 110 case chromeos::DeviceType::kChromebox: | 131 case chromeos::DeviceType::kChromebox: |
| 111 return "chromebox"; | 132 return "chromebox"; |
| 112 case chromeos::DeviceType::kChromebase: | 133 case chromeos::DeviceType::kChromebase: |
| 113 return "chromebase"; | 134 return "chromebase"; |
| 114 case chromeos::DeviceType::kChromebit: | 135 case chromeos::DeviceType::kChromebit: |
| 115 return "chromebit"; | 136 return "chromebit"; |
| 116 case chromeos::DeviceType::kChromebook: | 137 case chromeos::DeviceType::kChromebook: |
| 117 return "chromebook"; | 138 return "chromebook"; |
| (...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 241 | 262 |
| 242 GaiaScreenMode screen_mode = GetGaiaScreenMode(context.email, | 263 GaiaScreenMode screen_mode = GetGaiaScreenMode(context.email, |
| 243 context.use_offline); | 264 context.use_offline); |
| 244 params.SetInteger("screenMode", screen_mode); | 265 params.SetInteger("screenMode", screen_mode); |
| 245 if (screen_mode != GAIA_SCREEN_MODE_OFFLINE) { | 266 if (screen_mode != GAIA_SCREEN_MODE_OFFLINE) { |
| 246 const std::string app_locale = g_browser_process->GetApplicationLocale(); | 267 const std::string app_locale = g_browser_process->GetApplicationLocale(); |
| 247 if (!app_locale.empty()) | 268 if (!app_locale.empty()) |
| 248 params.SetString("hl", app_locale); | 269 params.SetString("hl", app_locale); |
| 249 } | 270 } |
| 250 | 271 |
| 272 std::string realm(GetRealm()); | |
| 273 if (!realm.empty()) { | |
| 274 params.SetString("realm", realm); | |
| 275 } | |
| 276 | |
| 251 std::string enterprise_domain(GetEnterpriseDomain()); | 277 std::string enterprise_domain(GetEnterpriseDomain()); |
| 252 if (!enterprise_domain.empty()) | 278 if (!enterprise_domain.empty()) |
| 253 params.SetString("enterpriseDomain", enterprise_domain); | 279 params.SetString("enterpriseDomain", enterprise_domain); |
| 254 | 280 |
| 255 params.SetString("chromeType", GetChromeType()); | 281 params.SetString("chromeType", GetChromeType()); |
| 256 params.SetString("clientId", | 282 params.SetString("clientId", |
| 257 GaiaUrls::GetInstance()->oauth2_chrome_client_id()); | 283 GaiaUrls::GetInstance()->oauth2_chrome_client_id()); |
| 258 params.SetString("clientVersion", version_info::GetVersionNumber()); | 284 params.SetString("clientVersion", version_info::GetVersionNumber()); |
| 259 if (!platform_version.empty()) | 285 if (!platform_version.empty()) |
| 260 params.SetString("platformVersion", platform_version); | 286 params.SetString("platformVersion", platform_version); |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 351 builder->Add("offlineLoginForgotPasswordDlg", | 377 builder->Add("offlineLoginForgotPasswordDlg", |
| 352 IDS_OFFLINE_LOGIN_FORGOT_PASSWORD_DIALOG_TEXT); | 378 IDS_OFFLINE_LOGIN_FORGOT_PASSWORD_DIALOG_TEXT); |
| 353 builder->Add("offlineLoginCloseBtn", IDS_OFFLINE_LOGIN_CLOSE_BUTTON_TEXT); | 379 builder->Add("offlineLoginCloseBtn", IDS_OFFLINE_LOGIN_CLOSE_BUTTON_TEXT); |
| 354 builder->Add("enterpriseInfoMessage", IDS_LOGIN_DEVICE_MANAGED_BY_NOTICE); | 380 builder->Add("enterpriseInfoMessage", IDS_LOGIN_DEVICE_MANAGED_BY_NOTICE); |
| 355 builder->Add("samlInterstitialMessage", | 381 builder->Add("samlInterstitialMessage", |
| 356 IDS_LOGIN_SAML_INTERSTITIAL_MESSAGE); | 382 IDS_LOGIN_SAML_INTERSTITIAL_MESSAGE); |
| 357 builder->Add("samlInterstitialChangeAccountLink", | 383 builder->Add("samlInterstitialChangeAccountLink", |
| 358 IDS_LOGIN_SAML_INTERSTITIAL_CHANGE_ACCOUNT_LINK_TEXT); | 384 IDS_LOGIN_SAML_INTERSTITIAL_CHANGE_ACCOUNT_LINK_TEXT); |
| 359 builder->Add("samlInterstitialNextBtn", | 385 builder->Add("samlInterstitialNextBtn", |
| 360 IDS_LOGIN_SAML_INTERSTITIAL_NEXT_BUTTON_TEXT); | 386 IDS_LOGIN_SAML_INTERSTITIAL_NEXT_BUTTON_TEXT); |
| 387 | |
| 388 builder->Add("adAuthWelcomeMessage", IDS_AD_DOMAIN_AUTH_WELCOME_MESSAGE); | |
| 389 builder->Add("adLoginUser", IDS_AD_LOGIN_USER); | |
| 390 builder->Add("adLoginPassword", IDS_AD_LOGIN_PASSWORD); | |
| 361 } | 391 } |
| 362 | 392 |
| 363 void GaiaScreenHandler::Initialize() { | 393 void GaiaScreenHandler::Initialize() { |
| 364 } | 394 } |
| 365 | 395 |
| 366 void GaiaScreenHandler::RegisterMessages() { | 396 void GaiaScreenHandler::RegisterMessages() { |
| 367 AddCallback("webviewLoadAborted", | 397 AddCallback("webviewLoadAborted", |
| 368 &GaiaScreenHandler::HandleWebviewLoadAborted); | 398 &GaiaScreenHandler::HandleWebviewLoadAborted); |
| 369 AddCallback("completeLogin", &GaiaScreenHandler::HandleCompleteLogin); | 399 AddCallback("completeLogin", &GaiaScreenHandler::HandleCompleteLogin); |
| 370 AddCallback("completeAuthentication", | 400 AddCallback("completeAuthentication", |
| 371 &GaiaScreenHandler::HandleCompleteAuthentication); | 401 &GaiaScreenHandler::HandleCompleteAuthentication); |
| 372 AddCallback("completeAuthenticationAuthCodeOnly", | 402 AddCallback("completeAuthenticationAuthCodeOnly", |
| 373 &GaiaScreenHandler::HandleCompleteAuthenticationAuthCodeOnly); | 403 &GaiaScreenHandler::HandleCompleteAuthenticationAuthCodeOnly); |
| 374 AddCallback("usingSAMLAPI", &GaiaScreenHandler::HandleUsingSAMLAPI); | 404 AddCallback("usingSAMLAPI", &GaiaScreenHandler::HandleUsingSAMLAPI); |
| 375 AddCallback("scrapedPasswordCount", | 405 AddCallback("scrapedPasswordCount", |
| 376 &GaiaScreenHandler::HandleScrapedPasswordCount); | 406 &GaiaScreenHandler::HandleScrapedPasswordCount); |
| 377 AddCallback("scrapedPasswordVerificationFailed", | 407 AddCallback("scrapedPasswordVerificationFailed", |
| 378 &GaiaScreenHandler::HandleScrapedPasswordVerificationFailed); | 408 &GaiaScreenHandler::HandleScrapedPasswordVerificationFailed); |
| 379 AddCallback("loginWebuiReady", &GaiaScreenHandler::HandleGaiaUIReady); | 409 AddCallback("loginWebuiReady", &GaiaScreenHandler::HandleGaiaUIReady); |
| 380 AddCallback("toggleEasyBootstrap", | 410 AddCallback("toggleEasyBootstrap", |
| 381 &GaiaScreenHandler::HandleToggleEasyBootstrap); | 411 &GaiaScreenHandler::HandleToggleEasyBootstrap); |
| 382 AddCallback("identifierEntered", &GaiaScreenHandler::HandleIdentifierEntered); | 412 AddCallback("identifierEntered", &GaiaScreenHandler::HandleIdentifierEntered); |
| 383 AddCallback("updateOfflineLogin", | 413 AddCallback("updateOfflineLogin", |
| 384 &GaiaScreenHandler::set_offline_login_is_active); | 414 &GaiaScreenHandler::set_offline_login_is_active); |
| 385 AddCallback("authExtensionLoaded", | 415 AddCallback("authExtensionLoaded", |
| 386 &GaiaScreenHandler::HandleAuthExtensionLoaded); | 416 &GaiaScreenHandler::HandleAuthExtensionLoaded); |
| 417 AddCallback("completeAdAuthentication", | |
| 418 &GaiaScreenHandler::HandleCompleteAdAuthentication); | |
| 387 } | 419 } |
| 388 | 420 |
| 389 void GaiaScreenHandler::OnPortalDetectionCompleted( | 421 void GaiaScreenHandler::OnPortalDetectionCompleted( |
| 390 const NetworkState* network, | 422 const NetworkState* network, |
| 391 const NetworkPortalDetector::CaptivePortalState& state) { | 423 const NetworkPortalDetector::CaptivePortalState& state) { |
| 392 VLOG(1) << "OnPortalDetectionCompleted " | 424 VLOG(1) << "OnPortalDetectionCompleted " |
| 393 << NetworkPortalDetector::CaptivePortalStatusString(state.status); | 425 << NetworkPortalDetector::CaptivePortalStatusString(state.status); |
| 394 | 426 |
| 395 const NetworkPortalDetector::CaptivePortalStatus previous_status = | 427 const NetworkPortalDetector::CaptivePortalStatus previous_status = |
| 396 captive_portal_status_; | 428 captive_portal_status_; |
| (...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 460 authenticated_email, id, account_type); | 492 authenticated_email, id, account_type); |
| 461 | 493 |
| 462 if (account_id.GetUserEmail() != canonicalized_email) { | 494 if (account_id.GetUserEmail() != canonicalized_email) { |
| 463 LOG(WARNING) << "Existing user '" << account_id.GetUserEmail() | 495 LOG(WARNING) << "Existing user '" << account_id.GetUserEmail() |
| 464 << "' authenticated by alias '" << canonicalized_email << "'."; | 496 << "' authenticated by alias '" << canonicalized_email << "'."; |
| 465 } | 497 } |
| 466 | 498 |
| 467 return account_id; | 499 return account_id; |
| 468 } | 500 } |
| 469 | 501 |
| 502 void GaiaScreenHandler::HandleAdAuth(const std::string& username, | |
|
Alexander Alekseev
2016/12/21 15:03:13
This does not handle any JS->chrome message.
Could
Roman Sorokin (ftl)
2016/12/23 13:30:22
Done.
| |
| 503 const Key& key, | |
| 504 int code, | |
| 505 const std::string& uid) { | |
| 506 if (code == 0 && !uid.empty()) { | |
| 507 const AccountId account_id( | |
| 508 GetAccountId(username, uid, AccountType::ACTIVE_DIRECTORY)); | |
| 509 UserContext user_context(account_id); | |
| 510 user_context.SetKey(key); | |
| 511 user_context.SetAuthFlow(UserContext::AUTH_FLOW_ACTIVE_DIRECTORY); | |
| 512 user_context.SetIsUsingOAuth(false); | |
| 513 user_context.SetUserType( | |
| 514 user_manager::UserType::USER_TYPE_ACTIVE_DIRECTORY); | |
| 515 Delegate()->CompleteLogin(user_context); | |
| 516 } else { | |
| 517 // TODO(rsorokin): Proper error handling. | |
| 518 DLOG(ERROR) << "Failed to auth " << username << ", code " << code; | |
| 519 LoadAuthExtension(true, false /* offline */); | |
| 520 } | |
| 521 } | |
| 522 | |
| 523 void GaiaScreenHandler::HandleCompleteAdAuthentication( | |
| 524 const std::string& user_name, | |
| 525 const std::string& password) { | |
| 526 Delegate()->SetDisplayEmail(user_name); | |
| 527 set_populated_email(user_name); | |
| 528 | |
| 529 login::GetPipeReadEnd( | |
| 530 password, | |
| 531 base::Bind(&GaiaScreenHandler::OnPasswordPipeReady, | |
| 532 weak_factory_.GetWeakPtr(), user_name, Key(password))); | |
| 533 } | |
| 534 | |
| 535 void GaiaScreenHandler::OnPasswordPipeReady(const std::string& user_name, | |
| 536 const Key& key, | |
| 537 base::ScopedFD password_fd) { | |
| 538 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); | |
| 539 if (!password_fd.is_valid()) { | |
| 540 DLOG(ERROR) << "Got invalid password_fd"; | |
| 541 return; | |
| 542 } | |
| 543 chromeos::AuthPolicyClient* client = | |
| 544 chromeos::DBusThreadManager::Get()->GetAuthPolicyClient(); | |
| 545 client->AuthenticateUser( | |
| 546 user_name, password_fd.get(), | |
| 547 base::Bind(&GaiaScreenHandler::HandleAdAuth, weak_factory_.GetWeakPtr(), | |
| 548 user_name, key)); | |
| 549 } | |
| 550 | |
| 470 void GaiaScreenHandler::HandleCompleteAuthentication( | 551 void GaiaScreenHandler::HandleCompleteAuthentication( |
| 471 const std::string& gaia_id, | 552 const std::string& gaia_id, |
| 472 const std::string& email, | 553 const std::string& email, |
| 473 const std::string& password, | 554 const std::string& password, |
| 474 const std::string& auth_code, | 555 const std::string& auth_code, |
| 475 bool using_saml, | 556 bool using_saml, |
| 476 const std::string& gaps_cookie) { | 557 const std::string& gaps_cookie) { |
| 477 if (!Delegate()) | 558 if (!Delegate()) |
| 478 return; | 559 return; |
| 479 | 560 |
| (...skipping 363 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 843 bool GaiaScreenHandler::IsRestrictiveProxy() const { | 924 bool GaiaScreenHandler::IsRestrictiveProxy() const { |
| 844 return !disable_restrictive_proxy_check_for_test_ && | 925 return !disable_restrictive_proxy_check_for_test_ && |
| 845 !IsOnline(captive_portal_status_); | 926 !IsOnline(captive_portal_status_); |
| 846 } | 927 } |
| 847 | 928 |
| 848 void GaiaScreenHandler::DisableRestrictiveProxyCheckForTest() { | 929 void GaiaScreenHandler::DisableRestrictiveProxyCheckForTest() { |
| 849 disable_restrictive_proxy_check_for_test_ = true; | 930 disable_restrictive_proxy_check_for_test_ = true; |
| 850 } | 931 } |
| 851 | 932 |
| 852 } // namespace chromeos | 933 } // namespace chromeos |
| OLD | NEW |