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

Side by Side Diff: chrome/browser/policy/cloud_policy_controller_unittest.cc

Issue 7298012: Consolidate data storage and notifications in the cloud policy subsystem (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: " Created 9 years, 5 months 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 | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 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/policy/cloud_policy_controller.h" 5 #include "chrome/browser/policy/cloud_policy_controller.h"
6 6
7 #include "base/message_loop.h" 7 #include "base/message_loop.h"
8 #include "base/scoped_temp_dir.h" 8 #include "base/scoped_temp_dir.h"
9 #include "chrome/browser/policy/cloud_policy_data.h"
pastarmovj 2011/07/06 12:11:57 I thought you had this already included in the hea
gfeher 2011/07/06 15:14:20 This unittest doesn't have a header!
9 #include "chrome/browser/policy/device_management_service.h" 10 #include "chrome/browser/policy/device_management_service.h"
Joao da Silva 2011/07/06 16:45:14 Nit: device_management_service.h not used
gfeher 2011/07/07 13:51:00 Done.
10 #include "chrome/browser/policy/device_token_fetcher.h" 11 #include "chrome/browser/policy/device_token_fetcher.h"
11 #include "chrome/browser/policy/logging_work_scheduler.h" 12 #include "chrome/browser/policy/logging_work_scheduler.h"
12 #include "chrome/browser/policy/mock_configuration_policy_store.h" 13 #include "chrome/browser/policy/mock_configuration_policy_store.h"
Joao da Silva 2011/07/06 16:45:14 Nit: mock_configuration_policy_store.h not used
gfeher 2011/07/07 13:51:00 Done.
13 #include "chrome/browser/policy/mock_device_management_backend.h" 14 #include "chrome/browser/policy/mock_device_management_backend.h"
14 #include "chrome/browser/policy/mock_device_management_service.h" 15 #include "chrome/browser/policy/mock_device_management_service.h"
15 #include "chrome/browser/policy/policy_notifier.h" 16 #include "chrome/browser/policy/policy_notifier.h"
16 #include "chrome/browser/policy/proto/device_management_backend.pb.h" 17 #include "chrome/browser/policy/proto/device_management_backend.pb.h"
Joao da Silva 2011/07/06 16:45:14 Nit: device_management_backend.pb.h not used
gfeher 2011/07/07 13:51:00 Done.
17 #include "chrome/browser/policy/user_policy_cache.h" 18 #include "chrome/browser/policy/user_policy_cache.h"
18 #include "content/browser/browser_thread.h" 19 #include "content/browser/browser_thread.h"
19 #include "policy/policy_constants.h" 20 #include "policy/policy_constants.h"
20 #include "testing/gmock/include/gmock/gmock.h" 21 #include "testing/gmock/include/gmock/gmock.h"
21 #include "testing/gtest/include/gtest/gtest.h" 22 #include "testing/gtest/include/gtest/gtest.h"
22 23
23 const char kTestToken[] = "cloud_policy_controller_test_auth_token"; 24 const char kTestToken[] = "cloud_policy_controller_test_auth_token";
24 25
25 namespace policy { 26 namespace policy {
26 27
27 namespace em = enterprise_management; 28 namespace em = enterprise_management;
Joao da Silva 2011/07/06 16:45:14 Nit: not used
gfeher 2011/07/07 13:51:00 Done.
28 29
29 using ::testing::_; 30 using ::testing::_;
30 using ::testing::AtLeast; 31 using ::testing::AtLeast;
31 using ::testing::InSequence; 32 using ::testing::InSequence;
32 using ::testing::Mock; 33 using ::testing::Mock;
33 using ::testing::Return; 34 using ::testing::Return;
34 35
35 class MockCloudPolicyIdentityStrategy : public CloudPolicyIdentityStrategy {
36 public:
37 MockCloudPolicyIdentityStrategy() {}
38 virtual ~MockCloudPolicyIdentityStrategy() {}
39
40 MOCK_METHOD0(GetDeviceToken, std::string());
41 MOCK_METHOD0(GetDeviceID, std::string());
42 MOCK_METHOD0(GetMachineID, std::string());
43 MOCK_METHOD0(GetMachineModel, std::string());
44 MOCK_METHOD0(GetPolicyType, std::string());
45 MOCK_METHOD0(GetPolicyRegisterType, em::DeviceRegisterRequest_Type());
46
47 MOCK_METHOD2(GetCredentials, bool(std::string*, std::string*));
48 virtual void OnDeviceTokenAvailable(const std::string&) {}
49
50 private:
51 DISALLOW_COPY_AND_ASSIGN(MockCloudPolicyIdentityStrategy);
52 };
53
54 ACTION_P2(MockCloudPolicyIdentityStrategyGetCredentials, username, auth_token) {
55 *arg0 = username;
56 *arg1 = auth_token;
57 return true;
58 }
59
60 class MockDeviceTokenFetcher : public DeviceTokenFetcher { 36 class MockDeviceTokenFetcher : public DeviceTokenFetcher {
61 public: 37 public:
62 explicit MockDeviceTokenFetcher(CloudPolicyCacheBase* cache) 38 explicit MockDeviceTokenFetcher(CloudPolicyCacheBase* cache)
63 : DeviceTokenFetcher(NULL, cache, NULL) {} 39 : DeviceTokenFetcher(NULL, cache, NULL, NULL) {}
64 virtual ~MockDeviceTokenFetcher() {} 40 virtual ~MockDeviceTokenFetcher() {}
65 41
66 MOCK_METHOD0(GetDeviceToken, const std::string&()); 42 MOCK_METHOD0(FetchToken, void());
67 MOCK_METHOD5(FetchToken,
68 void(const std::string&, const std::string&,
69 em::DeviceRegisterRequest_Type,
70 const std::string&, const std::string&));
71 MOCK_METHOD0(SetUnmanagedState, void()); 43 MOCK_METHOD0(SetUnmanagedState, void());
72 44
73 private: 45 private:
74 DISALLOW_COPY_AND_ASSIGN(MockDeviceTokenFetcher); 46 DISALLOW_COPY_AND_ASSIGN(MockDeviceTokenFetcher);
75 }; 47 };
76 48
77 class CloudPolicyControllerTest : public testing::Test { 49 class CloudPolicyControllerTest : public testing::Test {
78 public: 50 public:
79 CloudPolicyControllerTest() 51 CloudPolicyControllerTest()
80 : ui_thread_(BrowserThread::UI, &loop_), 52 : ui_thread_(BrowserThread::UI, &loop_),
81 file_thread_(BrowserThread::FILE, &loop_) {} 53 file_thread_(BrowserThread::FILE, &loop_) {}
82 54
83 virtual ~CloudPolicyControllerTest() {} 55 virtual ~CloudPolicyControllerTest() {}
84 56
85 virtual void SetUp() { 57 virtual void SetUp() {
86 ASSERT_TRUE(temp_user_data_dir_.CreateUniqueTempDir()); 58 ASSERT_TRUE(temp_user_data_dir_.CreateUniqueTempDir());
87 cache_.reset(new UserPolicyCache( 59 cache_.reset(new UserPolicyCache(
88 temp_user_data_dir_.path().AppendASCII("CloudPolicyControllerTest"))); 60 temp_user_data_dir_.path().AppendASCII("CloudPolicyControllerTest")));
89 token_fetcher_.reset(new MockDeviceTokenFetcher(cache_.get())); 61 token_fetcher_.reset(new MockDeviceTokenFetcher(cache_.get()));
90 service_.set_backend(&backend_); 62 service_.set_backend(&backend_);
63 policy_data_.reset(CloudPolicyData::CreateForUserPolicies());
91 } 64 }
92 65
93 virtual void TearDown() { 66 virtual void TearDown() {
94 controller_.reset(); // Unregisters observers. 67 controller_.reset(); // Unregisters observers.
68 policy_data_.reset();
95 } 69 }
96 70
97 void CreateNewController() { 71 void CreateNewController() {
98 controller_.reset(new CloudPolicyController( 72 controller_.reset(new CloudPolicyController(
99 &service_, cache_.get(), token_fetcher_.get(), &identity_strategy_, 73 &service_, cache_.get(), token_fetcher_.get(), policy_data_.get(),
100 &notifier_, new DummyWorkScheduler)); 74 &notifier_, new DummyWorkScheduler));
101 } 75 }
102 76
103 void ExpectHasSpdyPolicy() { 77 void ExpectHasSpdyPolicy() {
104 FundamentalValue expected(true); 78 FundamentalValue expected(true);
105 const PolicyMap* policy_map = cache_->policy( 79 const PolicyMap* policy_map = cache_->policy(
106 CloudPolicyCacheBase::POLICY_LEVEL_MANDATORY); 80 CloudPolicyCacheBase::POLICY_LEVEL_MANDATORY);
107 ASSERT_TRUE(Value::Equals(&expected, policy_map->Get(kPolicyDisableSpdy))); 81 ASSERT_TRUE(Value::Equals(&expected, policy_map->Get(kPolicyDisableSpdy)));
108 } 82 }
109 83
110 void SetupIdentityStrategy(
111 const std::string& device_token,
112 const std::string& device_id,
113 const std::string& machine_id,
114 const std::string& machine_model,
115 const std::string& policy_type,
116 const em::DeviceRegisterRequest_Type& policy_register_type,
117 const std::string& user_name,
118 const std::string& auth_token) {
119 EXPECT_CALL(identity_strategy_, GetDeviceToken()).WillRepeatedly(
120 Return(device_token));
121 EXPECT_CALL(identity_strategy_, GetDeviceID()).WillRepeatedly(
122 Return(device_id));
123 EXPECT_CALL(identity_strategy_, GetMachineID()).WillRepeatedly(
124 Return(machine_id));
125 EXPECT_CALL(identity_strategy_, GetMachineModel()).WillRepeatedly(
126 Return(machine_model));
127 EXPECT_CALL(identity_strategy_, GetPolicyType()).WillRepeatedly(
128 Return(policy_type));
129 EXPECT_CALL(identity_strategy_, GetPolicyRegisterType()).WillRepeatedly(
130 Return(policy_register_type));
131 if (!user_name.empty()) {
132 EXPECT_CALL(identity_strategy_, GetCredentials(_, _)).WillRepeatedly(
133 MockCloudPolicyIdentityStrategyGetCredentials(user_name, auth_token));
134 }
135 }
136
137 void StopMessageLoop() { 84 void StopMessageLoop() {
138 loop_.QuitNow(); 85 loop_.QuitNow();
139 } 86 }
140 87
141 protected: 88 protected:
142 scoped_ptr<CloudPolicyCacheBase> cache_; 89 scoped_ptr<CloudPolicyCacheBase> cache_;
143 scoped_ptr<CloudPolicyController> controller_; 90 scoped_ptr<CloudPolicyController> controller_;
144 scoped_ptr<MockDeviceTokenFetcher> token_fetcher_; 91 scoped_ptr<MockDeviceTokenFetcher> token_fetcher_;
145 MockCloudPolicyIdentityStrategy identity_strategy_; 92 scoped_ptr<CloudPolicyData> policy_data_;
146 MockDeviceManagementBackend backend_; 93 MockDeviceManagementBackend backend_;
147 MockDeviceManagementService service_; 94 MockDeviceManagementService service_;
148 PolicyNotifier notifier_; 95 PolicyNotifier notifier_;
149 ScopedTempDir temp_user_data_dir_; 96 ScopedTempDir temp_user_data_dir_;
150 MessageLoop loop_; 97 MessageLoop loop_;
151 98
152 private: 99 private:
153 BrowserThread ui_thread_; 100 BrowserThread ui_thread_;
154 BrowserThread file_thread_; 101 BrowserThread file_thread_;
155 102
156 DISALLOW_COPY_AND_ASSIGN(CloudPolicyControllerTest); 103 DISALLOW_COPY_AND_ASSIGN(CloudPolicyControllerTest);
157 }; 104 };
158 105
159 // If a device token is present when the controller starts up, it should 106 // If a device token is present when the controller starts up, it should
160 // fetch and apply policy. 107 // fetch and apply policy.
161 TEST_F(CloudPolicyControllerTest, StartupWithDeviceToken) { 108 TEST_F(CloudPolicyControllerTest, StartupWithDeviceToken) {
162 SetupIdentityStrategy("fake_device_token", "device_id", "machine_id", 109 policy_data_->SetupForTesting("fake_device_token", "device_id", "", "",
163 "machine_model", "google/chromeos/user", 110 true);
164 em::DeviceRegisterRequest::USER, "", "");
165 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(DoAll( 111 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(DoAll(
166 InvokeWithoutArgs(this, &CloudPolicyControllerTest::StopMessageLoop), 112 InvokeWithoutArgs(this, &CloudPolicyControllerTest::StopMessageLoop),
167 MockDeviceManagementBackendSucceedSpdyCloudPolicy())); 113 MockDeviceManagementBackendSucceedSpdyCloudPolicy()));
168 CreateNewController(); 114 CreateNewController();
169 loop_.RunAllPending(); 115 loop_.RunAllPending();
170 ExpectHasSpdyPolicy(); 116 ExpectHasSpdyPolicy();
171 } 117 }
172 118
173 // If no device token is present when the controller starts up, it should 119 // If no device token is present when the controller starts up, it should
174 // instruct the token_fetcher_ to fetch one. 120 // instruct the token_fetcher_ to fetch one.
175 TEST_F(CloudPolicyControllerTest, StartupWithoutDeviceToken) { 121 TEST_F(CloudPolicyControllerTest, StartupWithoutDeviceToken) {
176 SetupIdentityStrategy("", "device_id", "machine_id", "machine_model", 122 policy_data_->SetupForTesting("", "device_id", "a@b.com", "auth_token",
177 "google/chromeos/user", em::DeviceRegisterRequest::USER, 123 true);
178 "a@b.com", "auth_token"); 124 EXPECT_CALL(*token_fetcher_.get(), FetchToken()).Times(1);
179 EXPECT_CALL(*token_fetcher_.get(), FetchToken(_, _, _, _, _)).Times(1);
180 CreateNewController(); 125 CreateNewController();
181 loop_.RunAllPending(); 126 loop_.RunAllPending();
182 } 127 }
183 128
184 // If the current user belongs to a known non-managed domain, no token fetch 129 // If the current user belongs to a known non-managed domain, no token fetch
185 // should be initiated. 130 // should be initiated.
186 TEST_F(CloudPolicyControllerTest, StartupUnmanagedUser) { 131 TEST_F(CloudPolicyControllerTest, StartupUnmanagedUser) {
187 SetupIdentityStrategy("", "device_id", "machine_id", "machine_mode", 132 policy_data_->SetupForTesting("", "device_id", "DannoHelper@gmail.com",
188 "google/chromeos/user", em::DeviceRegisterRequest::USER, 133 "auth_token", true);
189 "DannoHelper@gmail.com", "auth_token"); 134 EXPECT_CALL(*token_fetcher_.get(), FetchToken()).Times(0);
190 EXPECT_CALL(*token_fetcher_.get(), FetchToken(_, _, _, _, _)).Times(0);
191 CreateNewController(); 135 CreateNewController();
192 loop_.RunAllPending(); 136 loop_.RunAllPending();
193 } 137 }
194 138
195 // After policy has been fetched successfully, a new fetch should be triggered 139 // After policy has been fetched successfully, a new fetch should be triggered
196 // after the refresh interval has timed out. 140 // after the refresh interval has timed out.
197 TEST_F(CloudPolicyControllerTest, RefreshAfterSuccessfulPolicy) { 141 TEST_F(CloudPolicyControllerTest, RefreshAfterSuccessfulPolicy) {
198 SetupIdentityStrategy("device_token", "device_id", "machine_id", 142 policy_data_->SetupForTesting("device_token", "device_id",
199 "machine_model", "google/chromeos/user", 143 "DannoHelperDelegate@b.com",
200 em::DeviceRegisterRequest::USER, 144 "auth_token", true);
201 "DannoHelperDelegate@b.com", "auth_token");
202 { 145 {
203 InSequence s; 146 InSequence s;
204 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce( 147 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(
205 MockDeviceManagementBackendSucceedSpdyCloudPolicy()); 148 MockDeviceManagementBackendSucceedSpdyCloudPolicy());
206 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(DoAll( 149 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(DoAll(
207 InvokeWithoutArgs(this, &CloudPolicyControllerTest::StopMessageLoop), 150 InvokeWithoutArgs(this, &CloudPolicyControllerTest::StopMessageLoop),
208 MockDeviceManagementBackendFailPolicy( 151 MockDeviceManagementBackendFailPolicy(
209 DeviceManagementBackend::kErrorRequestFailed))); 152 DeviceManagementBackend::kErrorRequestFailed)));
210 } 153 }
211 CreateNewController(); 154 CreateNewController();
212 loop_.RunAllPending(); 155 loop_.RunAllPending();
213 ExpectHasSpdyPolicy(); 156 ExpectHasSpdyPolicy();
214 } 157 }
215 158
216 // If policy fetching failed, it should be retried. 159 // If policy fetching failed, it should be retried.
217 TEST_F(CloudPolicyControllerTest, RefreshAfterError) { 160 TEST_F(CloudPolicyControllerTest, RefreshAfterError) {
218 SetupIdentityStrategy("device_token", "device_id", "machine_id", 161 policy_data_->SetupForTesting("device_token", "device_id",
219 "machine_model", "google/chromeos/user", 162 "DannoHelperDelegateImpl@b.com",
220 em::DeviceRegisterRequest::USER, 163 "auth_token", true);
221 "DannoHelperDelegateImpl@b.com", "auth_token");
222 { 164 {
223 InSequence s; 165 InSequence s;
224 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce( 166 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(
225 MockDeviceManagementBackendFailPolicy( 167 MockDeviceManagementBackendFailPolicy(
226 DeviceManagementBackend::kErrorRequestFailed)); 168 DeviceManagementBackend::kErrorRequestFailed));
227 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(DoAll( 169 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(DoAll(
228 InvokeWithoutArgs(this, 170 InvokeWithoutArgs(this,
229 &CloudPolicyControllerTest::StopMessageLoop), 171 &CloudPolicyControllerTest::StopMessageLoop),
230 MockDeviceManagementBackendSucceedSpdyCloudPolicy())); 172 MockDeviceManagementBackendSucceedSpdyCloudPolicy()));
231 } 173 }
232 CreateNewController(); 174 CreateNewController();
233 loop_.RunAllPending(); 175 loop_.RunAllPending();
234 ExpectHasSpdyPolicy(); 176 ExpectHasSpdyPolicy();
235 } 177 }
236 178
237 // If the backend reports that the device token was invalid, the controller 179 // If the backend reports that the device token was invalid, the controller
238 // should instruct the token fetcher to fetch a new token. 180 // should instruct the token fetcher to fetch a new token.
239 TEST_F(CloudPolicyControllerTest, InvalidToken) { 181 TEST_F(CloudPolicyControllerTest, InvalidToken) {
240 SetupIdentityStrategy("device_token", "device_id", "machine_id", 182 policy_data_->SetupForTesting("device_token", "device_id",
241 "machine_model", "google/chromeos/user", 183 "standup@ten.am", "auth", true);
242 em::DeviceRegisterRequest::USER,
243 "standup@ten.am", "auth");
244 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce( 184 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(
245 MockDeviceManagementBackendFailPolicy( 185 MockDeviceManagementBackendFailPolicy(
246 DeviceManagementBackend::kErrorServiceManagementTokenInvalid)); 186 DeviceManagementBackend::kErrorServiceManagementTokenInvalid));
247 EXPECT_CALL(*token_fetcher_.get(), FetchToken(_, _, _, _, _)).Times(1); 187 EXPECT_CALL(*token_fetcher_.get(), FetchToken()).Times(1);
248 CreateNewController(); 188 CreateNewController();
249 loop_.RunAllPending(); 189 loop_.RunAllPending();
250 } 190 }
251 191
252 // If the backend reports that the device is unknown to the server, the 192 // If the backend reports that the device is unknown to the server, the
253 // controller should instruct the token fetcher to fetch a new token. 193 // controller should instruct the token fetcher to fetch a new token.
254 TEST_F(CloudPolicyControllerTest, DeviceNotFound) { 194 TEST_F(CloudPolicyControllerTest, DeviceNotFound) {
255 SetupIdentityStrategy("device_token", "device_id", "machine_id", 195 policy_data_->SetupForTesting("device_token", "device_id",
256 "machine_model", "google/chromeos/user", 196 "me@you.com", "auth", true);
257 em::DeviceRegisterRequest::USER,
258 "me@you.com", "auth");
259 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce( 197 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(
260 MockDeviceManagementBackendFailPolicy( 198 MockDeviceManagementBackendFailPolicy(
261 DeviceManagementBackend::kErrorServiceDeviceNotFound)); 199 DeviceManagementBackend::kErrorServiceDeviceNotFound));
262 EXPECT_CALL(*token_fetcher_.get(), FetchToken(_, _, _, _, _)).Times(1); 200 EXPECT_CALL(*token_fetcher_.get(), FetchToken()).Times(1);
263 CreateNewController(); 201 CreateNewController();
264 loop_.RunAllPending(); 202 loop_.RunAllPending();
265 } 203 }
266 204
267 // If the backend reports that the device is no longer managed, the controller 205 // If the backend reports that the device is no longer managed, the controller
268 // should instruct the token fetcher to fetch a new token (which will in turn 206 // should instruct the token fetcher to fetch a new token (which will in turn
269 // set and persist the correct 'unmanaged' state). 207 // set and persist the correct 'unmanaged' state).
270 TEST_F(CloudPolicyControllerTest, NoLongerManaged) { 208 TEST_F(CloudPolicyControllerTest, NoLongerManaged) {
271 SetupIdentityStrategy("device_token", "device_id", "machine_id", 209 policy_data_->SetupForTesting("device_token", "device_id",
272 "machine_model", "google/chromeos/user", 210 "who@what.com", "auth", true);
273 em::DeviceRegisterRequest::USER,
274 "who@what.com", "auth");
275 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce( 211 EXPECT_CALL(backend_, ProcessPolicyRequest(_, _, _, _)).WillOnce(
276 MockDeviceManagementBackendFailPolicy( 212 MockDeviceManagementBackendFailPolicy(
277 DeviceManagementBackend::kErrorServiceManagementNotSupported)); 213 DeviceManagementBackend::kErrorServiceManagementNotSupported));
278 EXPECT_CALL(*token_fetcher_.get(), SetUnmanagedState()).Times(1); 214 EXPECT_CALL(*token_fetcher_.get(), SetUnmanagedState()).Times(1);
279 CreateNewController(); 215 CreateNewController();
280 loop_.RunAllPending(); 216 loop_.RunAllPending();
281 } 217 }
282 218
283 } // namespace policy 219 } // namespace policy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698