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

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

Issue 12255017: Revert 182279 due to failure in CloudPolicyTest.FetchPolicyWithRotatedKey on ChromeOS Asan bots. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: Created 7 years, 10 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) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 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/user_cloud_policy_store_chromeos.h" 5 #include "chrome/browser/policy/user_cloud_policy_store_chromeos.h"
6 6
7 #include <string>
8
7 #include "base/bind.h" 9 #include "base/bind.h"
8 #include "base/bind_helpers.h" 10 #include "base/bind_helpers.h"
9 #include "base/callback.h" 11 #include "base/callback.h"
10 #include "base/file_util.h" 12 #include "base/file_util.h"
11 #include "base/logging.h"
12 #include "base/memory/ref_counted.h" 13 #include "base/memory/ref_counted.h"
13 #include "base/stl_util.h"
14 #include "base/stringprintf.h"
15 #include "chrome/browser/policy/proto/cloud_policy.pb.h" 14 #include "chrome/browser/policy/proto/cloud_policy.pb.h"
16 #include "chrome/browser/policy/proto/device_management_local.pb.h" 15 #include "chrome/browser/policy/proto/device_management_local.pb.h"
17 #include "chrome/browser/policy/user_policy_disk_cache.h" 16 #include "chrome/browser/policy/user_policy_disk_cache.h"
18 #include "chrome/browser/policy/user_policy_token_loader.h" 17 #include "chrome/browser/policy/user_policy_token_loader.h"
19 #include "chromeos/dbus/cryptohome_client.h"
20 #include "chromeos/dbus/session_manager_client.h" 18 #include "chromeos/dbus/session_manager_client.h"
21 #include "content/public/browser/browser_thread.h" 19 #include "content/public/browser/browser_thread.h"
22 #include "google_apis/gaia/gaia_auth_util.h" 20 #include "google_apis/gaia/gaia_auth_util.h"
23 21
24 namespace em = enterprise_management; 22 namespace em = enterprise_management;
25 23
26 namespace policy { 24 namespace policy {
27 25
28 namespace { 26 namespace {
27 // Subdirectory in the user's profile for storing user policies.
28 const base::FilePath::CharType kPolicyDir[] =
29 FILE_PATH_LITERAL("Device Management");
30 // File in the above directory for stroing user policy dmtokens.
31 const base::FilePath::CharType kTokenCacheFile[] = FILE_PATH_LITERAL("Token");
32 // File in the above directory for storing user policy data.
33 const base::FilePath::CharType kPolicyCacheFile[] = FILE_PATH_LITERAL("Policy");
34 } // namespace
29 35
30 // Path within |user_policy_key_dir_| that contains the policy key.
31 // "%s" must be substituted with the sanitized username.
32 const base::FilePath::CharType kPolicyKeyFile[] =
33 FILE_PATH_LITERAL("%s/policy.pub");
34
35 // Maximum key size that will be loaded, in bytes.
36 const int kKeySizeLimit = 16 * 1024;
37
38 } // namespace
39 36
40 // Helper class for loading legacy policy caches. 37 // Helper class for loading legacy policy caches.
41 class LegacyPolicyCacheLoader : public UserPolicyTokenLoader::Delegate, 38 class LegacyPolicyCacheLoader : public UserPolicyTokenLoader::Delegate,
42 public UserPolicyDiskCache::Delegate { 39 public UserPolicyDiskCache::Delegate {
43 public: 40 public:
44 typedef base::Callback<void(const std::string&, 41 typedef base::Callback<void(const std::string&,
45 const std::string&, 42 const std::string&,
46 CloudPolicyStore::Status, 43 CloudPolicyStore::Status,
47 scoped_ptr<em::PolicyFetchResponse>)> Callback; 44 scoped_ptr<em::PolicyFetchResponse>)> Callback;
48 45
(...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after
143 return CloudPolicyStore::STATUS_OK; 140 return CloudPolicyStore::STATUS_OK;
144 case UserPolicyDiskCache::LOAD_RESULT_PARSE_ERROR: 141 case UserPolicyDiskCache::LOAD_RESULT_PARSE_ERROR:
145 case UserPolicyDiskCache::LOAD_RESULT_READ_ERROR: 142 case UserPolicyDiskCache::LOAD_RESULT_READ_ERROR:
146 return CloudPolicyStore::STATUS_LOAD_ERROR; 143 return CloudPolicyStore::STATUS_LOAD_ERROR;
147 } 144 }
148 NOTREACHED(); 145 NOTREACHED();
149 return CloudPolicyStore::STATUS_OK; 146 return CloudPolicyStore::STATUS_OK;
150 } 147 }
151 148
152 UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS( 149 UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS(
153 chromeos::CryptohomeClient* cryptohome_client,
154 chromeos::SessionManagerClient* session_manager_client, 150 chromeos::SessionManagerClient* session_manager_client,
155 const std::string& username, 151 const std::string& username,
156 const base::FilePath& user_policy_key_dir,
157 const base::FilePath& legacy_token_cache_file, 152 const base::FilePath& legacy_token_cache_file,
158 const base::FilePath& legacy_policy_cache_file) 153 const base::FilePath& legacy_policy_cache_file)
159 : cryptohome_client_(cryptohome_client), 154 : session_manager_client_(session_manager_client),
160 session_manager_client_(session_manager_client),
161 username_(username), 155 username_(username),
162 user_policy_key_dir_(user_policy_key_dir),
163 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), 156 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
164 legacy_cache_dir_(legacy_token_cache_file.DirName()), 157 legacy_cache_dir_(legacy_token_cache_file.DirName()),
165 legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file, 158 legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file,
166 legacy_policy_cache_file)), 159 legacy_policy_cache_file)),
167 legacy_caches_loaded_(false), 160 legacy_caches_loaded_(false) {}
168 policy_key_loaded_(false) {}
169 161
170 UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {} 162 UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {}
171 163
172 void UserCloudPolicyStoreChromeOS::Store( 164 void UserCloudPolicyStoreChromeOS::Store(
173 const em::PolicyFetchResponse& policy) { 165 const em::PolicyFetchResponse& policy) {
174 // Cancel all pending requests. 166 // Cancel all pending requests.
175 weak_factory_.InvalidateWeakPtrs(); 167 weak_factory_.InvalidateWeakPtrs();
176 scoped_ptr<em::PolicyFetchResponse> response( 168 Validate(
177 new em::PolicyFetchResponse(policy)); 169 scoped_ptr<em::PolicyFetchResponse>(new em::PolicyFetchResponse(policy)),
178 EnsurePolicyKeyLoaded( 170 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated,
179 base::Bind(&UserCloudPolicyStoreChromeOS::ValidatePolicyForStore, 171 weak_factory_.GetWeakPtr()));
180 weak_factory_.GetWeakPtr(),
181 base::Passed(&response)));
182 } 172 }
183 173
184 void UserCloudPolicyStoreChromeOS::Load() { 174 void UserCloudPolicyStoreChromeOS::Load() {
185 // Cancel all pending requests. 175 // Cancel all pending requests.
186 weak_factory_.InvalidateWeakPtrs(); 176 weak_factory_.InvalidateWeakPtrs();
187 session_manager_client_->RetrieveUserPolicy( 177 session_manager_client_->RetrieveUserPolicy(
188 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyRetrieved, 178 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyRetrieved,
189 weak_factory_.GetWeakPtr())); 179 weak_factory_.GetWeakPtr()));
190 } 180 }
191 181
192 void UserCloudPolicyStoreChromeOS::ValidatePolicyForStore(
193 scoped_ptr<em::PolicyFetchResponse> policy) {
194 // Create and configure a validator.
195 scoped_ptr<UserCloudPolicyValidator> validator =
196 CreateValidator(policy.Pass());
197 validator->ValidateUsername(username_);
198 if (policy_key_.empty()) {
199 validator->ValidateInitialKey();
200 } else {
201 const bool allow_rotation = true;
202 validator->ValidateSignature(policy_key_, allow_rotation);
203 }
204
205 // Start validation. The Validator will delete itself once validation is
206 // complete.
207 validator.release()->StartValidation(
208 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated,
209 weak_factory_.GetWeakPtr()));
210 }
211
212 void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated(
213 UserCloudPolicyValidator* validator) {
214 validation_status_ = validator->status();
215 if (!validator->success()) {
216 status_ = STATUS_VALIDATION_ERROR;
217 NotifyStoreError();
218 return;
219 }
220
221 std::string policy_blob;
222 if (!validator->policy()->SerializeToString(&policy_blob)) {
223 status_ = STATUS_SERIALIZE_ERROR;
224 NotifyStoreError();
225 return;
226 }
227
228 session_manager_client_->StoreUserPolicy(
229 policy_blob,
230 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored,
231 weak_factory_.GetWeakPtr()));
232 }
233
234 void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) {
235 if (!success) {
236 status_ = STATUS_STORE_ERROR;
237 NotifyStoreError();
238 } else {
239 // Load the policy right after storing it, to make sure it was accepted by
240 // the session manager. An additional validation is performed after the
241 // load; reload the key for that validation too, in case it was rotated.
242 ReloadPolicyKey(base::Bind(&UserCloudPolicyStoreChromeOS::Load,
243 weak_factory_.GetWeakPtr()));
244 }
245 }
246
247 void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved( 182 void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved(
248 const std::string& policy_blob) { 183 const std::string& policy_blob) {
249 if (policy_blob.empty()) { 184 if (policy_blob.empty()) {
250 // Policy fetch failed. Try legacy caches if we haven't done that already. 185 // Policy fetch failed. Try legacy caches if we haven't done that already.
251 if (!legacy_caches_loaded_ && legacy_loader_.get()) { 186 if (!legacy_caches_loaded_ && legacy_loader_.get()) {
252 legacy_caches_loaded_ = true; 187 legacy_caches_loaded_ = true;
253 legacy_loader_->Load( 188 legacy_loader_->Load(
254 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished, 189 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished,
255 weak_factory_.GetWeakPtr())); 190 weak_factory_.GetWeakPtr()));
256 } else { 191 } else {
257 // session_manager doesn't have policy. Adjust internal state and notify 192 // session_manager doesn't have policy. Adjust internal state and notify
258 // the world about the policy update. 193 // the world about the policy update.
259 policy_.reset(); 194 policy_.reset();
260 NotifyStoreLoaded(); 195 NotifyStoreLoaded();
261 } 196 }
262 return; 197 return;
263 } 198 }
264 199
265 // Policy is supplied by session_manager. Disregard legacy data from now on. 200 // Policy is supplied by session_manager. Disregard legacy data from now on.
266 legacy_loader_.reset(); 201 legacy_loader_.reset();
267 202
268 scoped_ptr<em::PolicyFetchResponse> policy(new em::PolicyFetchResponse()); 203 scoped_ptr<em::PolicyFetchResponse> policy(new em::PolicyFetchResponse());
269 if (!policy->ParseFromString(policy_blob)) { 204 if (!policy->ParseFromString(policy_blob)) {
270 status_ = STATUS_PARSE_ERROR; 205 status_ = STATUS_PARSE_ERROR;
271 NotifyStoreError(); 206 NotifyStoreError();
272 return; 207 return;
273 } 208 }
274 209
275 // Load |policy_key_| to verify the loaded policy. 210 Validate(policy.Pass(),
276 EnsurePolicyKeyLoaded( 211 base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated,
277 base::Bind(&UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy, 212 weak_factory_.GetWeakPtr()));
278 weak_factory_.GetWeakPtr(),
279 base::Passed(&policy)));
280 }
281
282 void UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy(
283 scoped_ptr<em::PolicyFetchResponse> policy) {
284 // Create and configure a validator for the loaded policy.
285 scoped_ptr<UserCloudPolicyValidator> validator =
286 CreateValidator(policy.Pass());
287 validator->ValidateUsername(username_);
288 const bool allow_rotation = false;
289 validator->ValidateSignature(policy_key_, allow_rotation);
290 // Start validation. The Validator will delete itself once validation is
291 // complete.
292 validator.release()->StartValidation(
293 base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated,
294 weak_factory_.GetWeakPtr()));
295 } 213 }
296 214
297 void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated( 215 void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated(
298 UserCloudPolicyValidator* validator) { 216 UserCloudPolicyValidator* validator) {
299 validation_status_ = validator->status(); 217 validation_status_ = validator->status();
300 if (!validator->success()) { 218 if (!validator->success()) {
301 status_ = STATUS_VALIDATION_ERROR; 219 status_ = STATUS_VALIDATION_ERROR;
302 NotifyStoreError(); 220 NotifyStoreError();
303 return; 221 return;
304 } 222 }
305 223
306 InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); 224 InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass());
307 status_ = STATUS_OK; 225 status_ = STATUS_OK;
308 226
309 // Policy has been loaded successfully. This indicates that new-style policy 227 // Policy has been loaded successfully. This indicates that new-style policy
310 // is working, so the legacy cache directory can be removed. 228 // is working, so the legacy cache directory can be removed.
311 if (!legacy_cache_dir_.empty()) { 229 if (!legacy_cache_dir_.empty()) {
312 content::BrowserThread::PostBlockingPoolTask( 230 content::BrowserThread::PostBlockingPoolTask(
313 FROM_HERE, 231 FROM_HERE,
314 base::Bind(&UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir, 232 base::Bind(&UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir,
315 legacy_cache_dir_)); 233 legacy_cache_dir_));
316 legacy_cache_dir_.clear(); 234 legacy_cache_dir_.clear();
317 } 235 }
318 NotifyStoreLoaded(); 236 NotifyStoreLoaded();
319 } 237 }
320 238
239 void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated(
240 UserCloudPolicyValidator* validator) {
241 validation_status_ = validator->status();
242 if (!validator->success()) {
243 status_ = STATUS_VALIDATION_ERROR;
244 NotifyStoreError();
245 return;
246 }
247
248 std::string policy_blob;
249 if (!validator->policy()->SerializeToString(&policy_blob)) {
250 status_ = STATUS_SERIALIZE_ERROR;
251 NotifyStoreError();
252 return;
253 }
254
255 session_manager_client_->StoreUserPolicy(
256 policy_blob,
257 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored,
258 weak_factory_.GetWeakPtr()));
259 }
260
261 void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) {
262 if (!success) {
263 status_ = STATUS_STORE_ERROR;
264 NotifyStoreError();
265 } else {
266 // TODO(mnissler): Once we do signature verifications, we'll have to reload
267 // the key at this point to account for key rotations.
268 Load();
269 }
270 }
271
272 void UserCloudPolicyStoreChromeOS::Validate(
273 scoped_ptr<em::PolicyFetchResponse> policy,
274 const UserCloudPolicyValidator::CompletionCallback& callback) {
275 // Configure the validator.
276 scoped_ptr<UserCloudPolicyValidator> validator =
277 CreateValidator(policy.Pass());
278 validator->ValidateUsername(username_);
279
280 // TODO(mnissler): Do a signature check here as well. The key is stored by
281 // session_manager in the root-owned cryptohome area, which is currently
282 // inaccessible to Chrome though.
283
284 // Start validation. The Validator will free itself once validation is
285 // complete.
286 validator.release()->StartValidation(callback);
287 }
288
321 void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished( 289 void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished(
322 const std::string& dm_token, 290 const std::string& dm_token,
323 const std::string& device_id, 291 const std::string& device_id,
324 Status status, 292 Status status,
325 scoped_ptr<em::PolicyFetchResponse> policy) { 293 scoped_ptr<em::PolicyFetchResponse> policy) {
326 status_ = status; 294 status_ = status;
327 if (policy.get()) { 295 if (policy.get()) {
328 // Create and configure a validator for the loaded legacy policy. Note that 296 Validate(policy.Pass(),
329 // the signature on this policy is not verified. 297 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated,
330 scoped_ptr<UserCloudPolicyValidator> validator = 298 weak_factory_.GetWeakPtr(),
331 CreateValidator(policy.Pass()); 299 dm_token, device_id));
332 validator->ValidateUsername(username_);
333 validator.release()->StartValidation(
334 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated,
335 weak_factory_.GetWeakPtr(),
336 dm_token,
337 device_id));
338 } else { 300 } else {
339 InstallLegacyTokens(dm_token, device_id); 301 InstallLegacyTokens(dm_token, device_id);
340 } 302 }
341 } 303 }
342 304
343 void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated( 305 void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated(
344 const std::string& dm_token, 306 const std::string& dm_token,
345 const std::string& device_id, 307 const std::string& device_id,
346 UserCloudPolicyValidator* validator) { 308 UserCloudPolicyValidator* validator) {
347 validation_status_ = validator->status(); 309 validation_status_ = validator->status();
348 if (validator->success()) { 310 if (validator->success()) {
349 status_ = STATUS_OK; 311 status_ = STATUS_OK;
350 InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); 312 InstallPolicy(validator->policy_data().Pass(),
313 validator->payload().Pass());
351 314
352 // Clear the public key version. The public key version field would 315 // Clear the public key version. The public key version field would
353 // otherwise indicate that we have key installed in the store when in fact 316 // otherwise indicate that we have key installed in the store when in fact
354 // we haven't. This may result in policy updates failing signature 317 // we haven't. This may result in policy updates failing signature
355 // verification. 318 // verification.
356 policy_->clear_public_key_version(); 319 policy_->clear_public_key_version();
357 } else { 320 } else {
358 status_ = STATUS_VALIDATION_ERROR; 321 status_ = STATUS_VALIDATION_ERROR;
359 } 322 }
360 323
(...skipping 17 matching lines...) Expand all
378 NotifyStoreLoaded(); 341 NotifyStoreLoaded();
379 } 342 }
380 343
381 // static 344 // static
382 void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir( 345 void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir(
383 const base::FilePath& dir) { 346 const base::FilePath& dir) {
384 if (file_util::PathExists(dir) && !file_util::Delete(dir, true)) 347 if (file_util::PathExists(dir) && !file_util::Delete(dir, true))
385 LOG(ERROR) << "Failed to remove cache dir " << dir.value(); 348 LOG(ERROR) << "Failed to remove cache dir " << dir.value();
386 } 349 }
387 350
388 void UserCloudPolicyStoreChromeOS::ReloadPolicyKey(
389 const base::Closure& callback) {
390 std::vector<uint8>* key = new std::vector<uint8>();
391 content::BrowserThread::PostBlockingPoolTaskAndReply(
392 FROM_HERE,
393 base::Bind(&UserCloudPolicyStoreChromeOS::LoadPolicyKey,
394 policy_key_path_,
395 key),
396 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyKeyReloaded,
397 weak_factory_.GetWeakPtr(),
398 base::Owned(key),
399 callback));
400 }
401
402 // static
403 void UserCloudPolicyStoreChromeOS::LoadPolicyKey(const base::FilePath& path,
404 std::vector<uint8>* key) {
405 if (!file_util::PathExists(path)) {
406 VLOG(1) << "No key at " << path.value();
407 return;
408 }
409
410 int64 size;
411 if (!file_util::GetFileSize(path, &size)) {
412 LOG(ERROR) << "Could not get size of " << path.value();
413 } else if (size == 0 || size > kKeySizeLimit) {
414 LOG(ERROR) << "Key at " << path.value() << " has bad size " << size;
415 } else {
416 key->resize(size);
417 int read_size = file_util::ReadFile(
418 path, reinterpret_cast<char*>(vector_as_array(key)), size);
419 if (read_size != size) {
420 LOG(ERROR) << "Failed to read key at " << path.value();
421 key->clear();
422 }
423 }
424 }
425
426 void UserCloudPolicyStoreChromeOS::OnPolicyKeyReloaded(
427 std::vector<uint8>* key,
428 const base::Closure& callback) {
429 policy_key_.swap(*key);
430 policy_key_loaded_ = true;
431 callback.Run();
432 }
433
434 void UserCloudPolicyStoreChromeOS::EnsurePolicyKeyLoaded(
435 const base::Closure& callback) {
436 if (policy_key_loaded_) {
437 callback.Run();
438 } else {
439 // Get the hashed username that's part of the key's path, to determine
440 // |policy_key_path_|.
441 cryptohome_client_->GetSanitizedUsername(username_,
442 base::Bind(&UserCloudPolicyStoreChromeOS::OnGetSanitizedUsername,
443 weak_factory_.GetWeakPtr(),
444 callback));
445 }
446 }
447
448 void UserCloudPolicyStoreChromeOS::OnGetSanitizedUsername(
449 const base::Closure& callback,
450 chromeos::DBusMethodCallStatus call_status,
451 const std::string& sanitized_username) {
452 // The default empty path will always yield an empty key.
453 if (call_status == chromeos::DBUS_METHOD_CALL_SUCCESS &&
454 !sanitized_username.empty()) {
455 policy_key_path_ = user_policy_key_dir_.Append(
456 base::StringPrintf(kPolicyKeyFile, sanitized_username.c_str()));
457 }
458 ReloadPolicyKey(callback);
459 }
460
461 } // namespace policy 351 } // namespace policy
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698