| OLD | NEW |
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 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 | 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 <map> | 5 #include <map> |
| 6 #include <string> | 6 #include <string> |
| 7 | 7 |
| 8 #include "base/basictypes.h" | 8 #include "base/basictypes.h" |
| 9 #include "base/bind.h" | 9 #include "base/bind.h" |
| 10 #include "base/callback.h" | 10 #include "base/callback.h" |
| (...skipping 25 matching lines...) Expand all Loading... |
| 36 #include "chrome/browser/ui/browser_finder.h" | 36 #include "chrome/browser/ui/browser_finder.h" |
| 37 #include "chrome/browser/ui/host_desktop.h" | 37 #include "chrome/browser/ui/host_desktop.h" |
| 38 #include "chrome/browser/ui/tabs/tab_strip_model.h" | 38 #include "chrome/browser/ui/tabs/tab_strip_model.h" |
| 39 #include "chrome/common/chrome_notification_types.h" | 39 #include "chrome/common/chrome_notification_types.h" |
| 40 #include "chrome/common/chrome_paths.h" | 40 #include "chrome/common/chrome_paths.h" |
| 41 #include "chrome/common/chrome_switches.h" | 41 #include "chrome/common/chrome_switches.h" |
| 42 #include "chrome/test/base/in_process_browser_test.h" | 42 #include "chrome/test/base/in_process_browser_test.h" |
| 43 #include "chromeos/dbus/cryptohome_client.h" | 43 #include "chromeos/dbus/cryptohome_client.h" |
| 44 #include "chromeos/dbus/dbus_method_call_status.h" | 44 #include "chromeos/dbus/dbus_method_call_status.h" |
| 45 #include "chromeos/dbus/dbus_thread_manager.h" | 45 #include "chromeos/dbus/dbus_thread_manager.h" |
| 46 #include "chromeos/dbus/fake_session_manager_client.h" |
| 46 #include "chromeos/dbus/mock_dbus_thread_manager.h" | 47 #include "chromeos/dbus/mock_dbus_thread_manager.h" |
| 47 #include "chromeos/dbus/session_manager_client.h" | |
| 48 #include "content/public/browser/notification_observer.h" | 48 #include "content/public/browser/notification_observer.h" |
| 49 #include "content/public/browser/notification_registrar.h" | 49 #include "content/public/browser/notification_registrar.h" |
| 50 #include "content/public/browser/notification_service.h" | 50 #include "content/public/browser/notification_service.h" |
| 51 #include "content/public/browser/notification_watcher.h" |
| 51 #include "content/public/browser/web_contents.h" | 52 #include "content/public/browser/web_contents.h" |
| 52 #include "testing/gmock/include/gmock/gmock.h" | 53 #include "testing/gmock/include/gmock/gmock.h" |
| 53 #include "third_party/cros_system_api/dbus/service_constants.h" | 54 #include "third_party/cros_system_api/dbus/service_constants.h" |
| 54 | 55 |
| 55 namespace em = enterprise_management; | 56 namespace em = enterprise_management; |
| 56 | 57 |
| 57 using testing::Return; | 58 using testing::Return; |
| 58 | 59 |
| 59 namespace policy { | 60 namespace policy { |
| 60 | 61 |
| 61 namespace { | 62 namespace { |
| 62 | 63 |
| 63 const char kAccountId1[] = "dla1@example.com"; | 64 const char kAccountId1[] = "dla1@example.com"; |
| 64 const char kAccountId2[] = "dla2@example.com"; | 65 const char kAccountId2[] = "dla2@example.com"; |
| 65 const char kDisplayName1[] = "display name for account 1"; | 66 const char kDisplayName1[] = "display name for account 1"; |
| 66 const char kDisplayName2[] = "display name for account 2"; | 67 const char kDisplayName2[] = "display name for account 2"; |
| 67 const char* kStartupURLs[] = { | 68 const char* kStartupURLs[] = { |
| 68 "chrome://policy", | 69 "chrome://policy", |
| 69 "chrome://about", | 70 "chrome://about", |
| 70 }; | 71 }; |
| 71 | 72 |
| 72 // Observes a specific notification type and quits the message loop once a | |
| 73 // condition holds. | |
| 74 class NotificationWatcher : public content::NotificationObserver { | |
| 75 public: | |
| 76 // Callback invoked on notifications. Should return true when the condition | |
| 77 // that the caller is waiting for is satisfied. | |
| 78 typedef base::Callback<bool(void)> ConditionTestCallback; | |
| 79 | |
| 80 explicit NotificationWatcher(int notification_type, | |
| 81 const ConditionTestCallback& callback) | |
| 82 : type_(notification_type), | |
| 83 callback_(callback) {} | |
| 84 | |
| 85 void Run() { | |
| 86 if (callback_.Run()) | |
| 87 return; | |
| 88 | |
| 89 content::NotificationRegistrar registrar; | |
| 90 registrar.Add(this, type_, content::NotificationService::AllSources()); | |
| 91 run_loop_.Run(); | |
| 92 } | |
| 93 | |
| 94 // content::NotificationObserver: | |
| 95 virtual void Observe(int type, | |
| 96 const content::NotificationSource& source, | |
| 97 const content::NotificationDetails& details) OVERRIDE { | |
| 98 if (callback_.Run()) | |
| 99 run_loop_.Quit(); | |
| 100 } | |
| 101 | |
| 102 private: | |
| 103 int type_; | |
| 104 ConditionTestCallback callback_; | |
| 105 base::RunLoop run_loop_; | |
| 106 | |
| 107 DISALLOW_COPY_AND_ASSIGN(NotificationWatcher); | |
| 108 }; | |
| 109 | |
| 110 // A fake implementation of session_manager. Accepts policy blobs to be set and | |
| 111 // returns them unmodified. | |
| 112 class FakeSessionManagerClient : public chromeos::SessionManagerClient { | |
| 113 public: | |
| 114 FakeSessionManagerClient() {} | |
| 115 virtual ~FakeSessionManagerClient() {} | |
| 116 | |
| 117 // SessionManagerClient: | |
| 118 virtual void AddObserver(Observer* observer) OVERRIDE {} | |
| 119 virtual void RemoveObserver(Observer* observer) OVERRIDE {} | |
| 120 virtual bool HasObserver(Observer* observer) OVERRIDE { return false; } | |
| 121 virtual void EmitLoginPromptReady() OVERRIDE {} | |
| 122 virtual void EmitLoginPromptVisible() OVERRIDE {} | |
| 123 virtual void RestartJob(int pid, const std::string& command_line) OVERRIDE {} | |
| 124 virtual void RestartEntd() OVERRIDE {} | |
| 125 virtual void StartSession(const std::string& user_email) OVERRIDE {} | |
| 126 virtual void StopSession() OVERRIDE {} | |
| 127 virtual void StartDeviceWipe() OVERRIDE {} | |
| 128 virtual void RequestLockScreen() OVERRIDE {} | |
| 129 virtual void NotifyLockScreenShown() OVERRIDE {} | |
| 130 virtual void RequestUnlockScreen() OVERRIDE {} | |
| 131 virtual void NotifyLockScreenDismissed() OVERRIDE {} | |
| 132 virtual void RetrieveDevicePolicy( | |
| 133 const RetrievePolicyCallback& callback) OVERRIDE { | |
| 134 MessageLoop::current()->PostTask(FROM_HERE, | |
| 135 base::Bind(callback, device_policy_)); | |
| 136 } | |
| 137 virtual void RetrieveUserPolicy( | |
| 138 const RetrievePolicyCallback& callback) OVERRIDE { | |
| 139 MessageLoop::current()->PostTask(FROM_HERE, | |
| 140 base::Bind(callback, user_policy_)); | |
| 141 } | |
| 142 virtual void RetrieveDeviceLocalAccountPolicy( | |
| 143 const std::string& account_id, | |
| 144 const RetrievePolicyCallback& callback) OVERRIDE { | |
| 145 MessageLoop::current()->PostTask( | |
| 146 FROM_HERE, | |
| 147 base::Bind(callback, device_local_account_policy_[account_id])); | |
| 148 } | |
| 149 virtual void StoreDevicePolicy(const std::string& policy_blob, | |
| 150 const StorePolicyCallback& callback) OVERRIDE { | |
| 151 device_policy_ = policy_blob; | |
| 152 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, true)); | |
| 153 } | |
| 154 virtual void StoreUserPolicy(const std::string& policy_blob, | |
| 155 const StorePolicyCallback& callback) OVERRIDE { | |
| 156 user_policy_ = policy_blob; | |
| 157 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, true)); | |
| 158 } | |
| 159 virtual void StoreDeviceLocalAccountPolicy( | |
| 160 const std::string& account_id, | |
| 161 const std::string& policy_blob, | |
| 162 const StorePolicyCallback& callback) OVERRIDE { | |
| 163 device_local_account_policy_[account_id] = policy_blob; | |
| 164 MessageLoop::current()->PostTask(FROM_HERE, base::Bind(callback, true)); | |
| 165 } | |
| 166 | |
| 167 const std::string& device_policy() const { | |
| 168 return device_policy_; | |
| 169 } | |
| 170 void set_device_policy(const std::string& policy_blob) { | |
| 171 device_policy_ = policy_blob; | |
| 172 } | |
| 173 | |
| 174 const std::string& user_policy() const { | |
| 175 return user_policy_; | |
| 176 } | |
| 177 void set_user_policy(const std::string& policy_blob) { | |
| 178 user_policy_ = policy_blob; | |
| 179 } | |
| 180 | |
| 181 const std::string& device_local_account_policy( | |
| 182 const std::string& account_id) const { | |
| 183 std::map<std::string, std::string>::const_iterator entry = | |
| 184 device_local_account_policy_.find(account_id); | |
| 185 return entry != device_local_account_policy_.end() ? entry->second | |
| 186 : EmptyString(); | |
| 187 } | |
| 188 void set_device_local_account_policy(const std::string& account_id, | |
| 189 const std::string& policy_blob) { | |
| 190 device_local_account_policy_[account_id] = policy_blob; | |
| 191 } | |
| 192 | |
| 193 private: | |
| 194 std::string device_policy_; | |
| 195 std::string user_policy_; | |
| 196 std::map<std::string, std::string> device_local_account_policy_; | |
| 197 | |
| 198 DISALLOW_COPY_AND_ASSIGN(FakeSessionManagerClient); | |
| 199 }; | |
| 200 | |
| 201 class FakeCryptohomeClient : public chromeos::CryptohomeClient { | 73 class FakeCryptohomeClient : public chromeos::CryptohomeClient { |
| 202 public: | 74 public: |
| 203 using chromeos::CryptohomeClient::AsyncMethodCallback; | 75 using chromeos::CryptohomeClient::AsyncMethodCallback; |
| 204 using chromeos::CryptohomeClient::AsyncCallStatusHandler; | 76 using chromeos::CryptohomeClient::AsyncCallStatusHandler; |
| 205 using chromeos::CryptohomeClient::AsyncCallStatusWithDataHandler; | 77 using chromeos::CryptohomeClient::AsyncCallStatusWithDataHandler; |
| 206 | 78 |
| 207 FakeCryptohomeClient() {} | 79 FakeCryptohomeClient() {} |
| 208 virtual ~FakeCryptohomeClient() {} | 80 virtual ~FakeCryptohomeClient() {} |
| 209 | 81 |
| 210 virtual void SetAsyncCallStatusHandlers( | 82 virtual void SetAsyncCallStatusHandlers( |
| (...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 477 void CheckPublicSessionPresent(const std::string& id) { | 349 void CheckPublicSessionPresent(const std::string& id) { |
| 478 const chromeos::User* user = chromeos::UserManager::Get()->FindUser(id); | 350 const chromeos::User* user = chromeos::UserManager::Get()->FindUser(id); |
| 479 ASSERT_TRUE(user); | 351 ASSERT_TRUE(user); |
| 480 EXPECT_EQ(id, user->email()); | 352 EXPECT_EQ(id, user->email()); |
| 481 EXPECT_EQ(chromeos::User::USER_TYPE_PUBLIC_ACCOUNT, user->GetType()); | 353 EXPECT_EQ(chromeos::User::USER_TYPE_PUBLIC_ACCOUNT, user->GetType()); |
| 482 } | 354 } |
| 483 | 355 |
| 484 LocalPolicyTestServer test_server_; | 356 LocalPolicyTestServer test_server_; |
| 485 base::ScopedTempDir temp_dir_; | 357 base::ScopedTempDir temp_dir_; |
| 486 | 358 |
| 487 FakeSessionManagerClient session_manager_client_; | 359 chromeos::FakeSessionManagerClient session_manager_client_; |
| 488 FakeCryptohomeClient cryptohome_client_; | 360 FakeCryptohomeClient cryptohome_client_; |
| 489 }; | 361 }; |
| 490 | 362 |
| 491 static bool IsKnownUser(const std::string& account_id) { | 363 static bool IsKnownUser(const std::string& account_id) { |
| 492 return chromeos::UserManager::Get()->IsKnownUser(account_id); | 364 return chromeos::UserManager::Get()->IsKnownUser(account_id); |
| 493 } | 365 } |
| 494 | 366 |
| 495 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, LoginScreen) { | 367 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, LoginScreen) { |
| 496 NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, | 368 content::NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 497 base::Bind(&IsKnownUser, kAccountId1)).Run(); | 369 base::Bind(&IsKnownUser, kAccountId1)).Run(); |
| 498 NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, | 370 content::NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 499 base::Bind(&IsKnownUser, kAccountId2)).Run(); | 371 base::Bind(&IsKnownUser, kAccountId2)).Run(); |
| 500 | 372 |
| 501 CheckPublicSessionPresent(kAccountId1); | 373 CheckPublicSessionPresent(kAccountId1); |
| 502 CheckPublicSessionPresent(kAccountId2); | 374 CheckPublicSessionPresent(kAccountId2); |
| 503 } | 375 } |
| 504 | 376 |
| 505 static bool DisplayNameMatches(const std::string& account_id, | 377 static bool DisplayNameMatches(const std::string& account_id, |
| 506 const std::string& display_name) { | 378 const std::string& display_name) { |
| 507 const chromeos::User* user = | 379 const chromeos::User* user = |
| 508 chromeos::UserManager::Get()->FindUser(account_id); | 380 chromeos::UserManager::Get()->FindUser(account_id); |
| 509 if (!user || user->display_name().empty()) | 381 if (!user || user->display_name().empty()) |
| 510 return false; | 382 return false; |
| 511 EXPECT_EQ(UTF8ToUTF16(display_name), user->display_name()); | 383 EXPECT_EQ(UTF8ToUTF16(display_name), user->display_name()); |
| 512 return true; | 384 return true; |
| 513 } | 385 } |
| 514 | 386 |
| 515 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, DisplayName) { | 387 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, DisplayName) { |
| 516 NotificationWatcher( | 388 content::NotificationWatcher( |
| 517 chrome::NOTIFICATION_USER_LIST_CHANGED, | 389 chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 518 base::Bind(&DisplayNameMatches, kAccountId1, kDisplayName1)).Run(); | 390 base::Bind(&DisplayNameMatches, kAccountId1, kDisplayName1)).Run(); |
| 519 } | 391 } |
| 520 | 392 |
| 521 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, PolicyDownload) { | 393 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, PolicyDownload) { |
| 522 // Policy for kAccountId2 is not installed in session_manager_client, make | 394 // Policy for kAccountId2 is not installed in session_manager_client, make |
| 523 // sure it gets fetched from the server. Note that the test setup doesn't set | 395 // sure it gets fetched from the server. Note that the test setup doesn't set |
| 524 // up policy for kAccountId2, so the presence of the display name can be used | 396 // up policy for kAccountId2, so the presence of the display name can be used |
| 525 // as signal to indicate successful policy download. | 397 // as signal to indicate successful policy download. |
| 526 NotificationWatcher( | 398 ASSERT_TRUE(session_manager_client_.device_local_account_policy( |
| 399 kAccountId2).empty()); |
| 400 content::NotificationWatcher( |
| 527 chrome::NOTIFICATION_USER_LIST_CHANGED, | 401 chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 528 base::Bind(&DisplayNameMatches, kAccountId2, kDisplayName2)).Run(); | 402 base::Bind(&DisplayNameMatches, kAccountId2, kDisplayName2)).Run(); |
| 529 | 403 |
| 530 // Sanity check: The policy should be present now. | 404 // Sanity check: The policy should be present now. |
| 531 ASSERT_FALSE(session_manager_client_.device_local_account_policy( | 405 ASSERT_FALSE(session_manager_client_.device_local_account_policy( |
| 532 kAccountId2).empty()); | 406 kAccountId2).empty()); |
| 533 } | 407 } |
| 534 | 408 |
| 535 static bool IsNotKnownUser(const std::string& account_id) { | 409 static bool IsNotKnownUser(const std::string& account_id) { |
| 536 return !IsKnownUser(account_id); | 410 return !IsKnownUser(account_id); |
| 537 } | 411 } |
| 538 | 412 |
| 539 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, DevicePolicyChange) { | 413 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, DevicePolicyChange) { |
| 540 // Wait until the login screen is up. | 414 // Wait until the login screen is up. |
| 541 NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, | 415 content::NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 542 base::Bind(&IsKnownUser, kAccountId1)).Run(); | 416 base::Bind(&IsKnownUser, kAccountId1)).Run(); |
| 543 NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, | 417 content::NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 544 base::Bind(&IsKnownUser, kAccountId2)).Run(); | 418 base::Bind(&IsKnownUser, kAccountId2)).Run(); |
| 545 | 419 |
| 546 // Update policy to remove kAccountId2. | 420 // Update policy to remove kAccountId2. |
| 547 em::ChromeDeviceSettingsProto policy; | 421 em::ChromeDeviceSettingsProto policy; |
| 548 policy.mutable_show_user_names()->set_show_user_names(true); | 422 policy.mutable_show_user_names()->set_show_user_names(true); |
| 549 policy.mutable_device_local_accounts()->add_account()->set_id(kAccountId1); | 423 policy.mutable_device_local_accounts()->add_account()->set_id(kAccountId1); |
| 550 | 424 |
| 551 test_server_.UpdatePolicy(dm_protocol::kChromeDevicePolicyType, std::string(), | 425 test_server_.UpdatePolicy(dm_protocol::kChromeDevicePolicyType, std::string(), |
| 552 policy.SerializeAsString()); | 426 policy.SerializeAsString()); |
| 553 g_browser_process->policy_service()->RefreshPolicies(base::Closure()); | 427 g_browser_process->policy_service()->RefreshPolicies(base::Closure()); |
| 554 | 428 |
| 555 // Make sure the second device-local account disappears. | 429 // Make sure the second device-local account disappears. |
| 556 NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, | 430 content::NotificationWatcher(chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 557 base::Bind(&IsNotKnownUser, kAccountId2)).Run(); | 431 base::Bind(&IsNotKnownUser, kAccountId2)).Run(); |
| 558 } | 432 } |
| 559 | 433 |
| 560 static bool IsSessionStarted() { | 434 static bool IsSessionStarted() { |
| 561 return chromeos::UserManager::Get()->IsSessionStarted(); | 435 return chromeos::UserManager::Get()->IsSessionStarted(); |
| 562 } | 436 } |
| 563 | 437 |
| 564 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, StartSession) { | 438 IN_PROC_BROWSER_TEST_F(DeviceLocalAccountTest, StartSession) { |
| 565 // This observes the display name becoming available as this indicates | 439 // This observes the display name becoming available as this indicates |
| 566 // device-local account policy is fully loaded, which is a prerequisite for | 440 // device-local account policy is fully loaded, which is a prerequisite for |
| 567 // successful login. | 441 // successful login. |
| 568 NotificationWatcher( | 442 content::NotificationWatcher( |
| 569 chrome::NOTIFICATION_USER_LIST_CHANGED, | 443 chrome::NOTIFICATION_USER_LIST_CHANGED, |
| 570 base::Bind(&DisplayNameMatches, kAccountId1, kDisplayName1)).Run(); | 444 base::Bind(&DisplayNameMatches, kAccountId1, kDisplayName1)).Run(); |
| 571 | 445 |
| 572 chromeos::ExistingUserController* controller = | 446 chromeos::ExistingUserController* controller = |
| 573 chromeos::ExistingUserController::current_controller(); | 447 chromeos::ExistingUserController::current_controller(); |
| 574 ASSERT_TRUE(controller); | 448 ASSERT_TRUE(controller); |
| 575 controller->LoginAsPublicAccount(kAccountId1); | 449 controller->LoginAsPublicAccount(kAccountId1); |
| 576 | 450 |
| 577 // Wait for the session to start. | 451 // Wait for the session to start. |
| 578 NotificationWatcher(chrome::NOTIFICATION_SESSION_STARTED, | 452 content::NotificationWatcher(chrome::NOTIFICATION_SESSION_STARTED, |
| 579 base::Bind(IsSessionStarted)).Run(); | 453 base::Bind(IsSessionStarted)).Run(); |
| 580 | 454 |
| 581 // Check that the startup pages specified in policy were opened. | 455 // Check that the startup pages specified in policy were opened. |
| 582 EXPECT_EQ(1U, chrome::GetTotalBrowserCount()); | 456 EXPECT_EQ(1U, chrome::GetTotalBrowserCount()); |
| 583 Browser* browser = | 457 Browser* browser = |
| 584 chrome::FindLastActiveWithHostDesktopType(chrome::HOST_DESKTOP_TYPE_ASH); | 458 chrome::FindLastActiveWithHostDesktopType(chrome::HOST_DESKTOP_TYPE_ASH); |
| 585 ASSERT_TRUE(browser); | 459 ASSERT_TRUE(browser); |
| 586 | 460 |
| 587 TabStripModel* tabs = browser->tab_strip_model(); | 461 TabStripModel* tabs = browser->tab_strip_model(); |
| 588 ASSERT_TRUE(tabs); | 462 ASSERT_TRUE(tabs); |
| 589 int expected_tab_count = static_cast<int>(arraysize(kStartupURLs)); | 463 int expected_tab_count = static_cast<int>(arraysize(kStartupURLs)); |
| 590 EXPECT_EQ(expected_tab_count, tabs->count()); | 464 EXPECT_EQ(expected_tab_count, tabs->count()); |
| 591 for (int i = 0; i < expected_tab_count && i < tabs->count(); ++i) | 465 for (int i = 0; i < expected_tab_count && i < tabs->count(); ++i) |
| 592 EXPECT_EQ(GURL(kStartupURLs[i]), tabs->GetWebContentsAt(i)->GetURL()); | 466 EXPECT_EQ(GURL(kStartupURLs[i]), tabs->GetWebContentsAt(i)->GetURL()); |
| 593 } | 467 } |
| 594 | 468 |
| 595 } // namespace policy | 469 } // namespace policy |
| OLD | NEW |