OLD | NEW |
---|---|
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> | 7 #include <string> |
8 | 8 |
9 #include "base/bind.h" | 9 #include "base/bind.h" |
10 #include "base/bind_helpers.h" | 10 #include "base/bind_helpers.h" |
11 #include "base/callback.h" | 11 #include "base/callback.h" |
12 #include "base/file_util.h" | 12 #include "base/file_util.h" |
13 #include "base/memory/ref_counted.h" | 13 #include "base/memory/ref_counted.h" |
14 #include "base/stringprintf.h" | |
14 #include "chrome/browser/policy/proto/cloud_policy.pb.h" | 15 #include "chrome/browser/policy/proto/cloud_policy.pb.h" |
15 #include "chrome/browser/policy/proto/device_management_local.pb.h" | 16 #include "chrome/browser/policy/proto/device_management_local.pb.h" |
16 #include "chrome/browser/policy/user_policy_disk_cache.h" | 17 #include "chrome/browser/policy/user_policy_disk_cache.h" |
18 #include "chrome/browser/policy/user_policy_key.h" | |
17 #include "chrome/browser/policy/user_policy_token_loader.h" | 19 #include "chrome/browser/policy/user_policy_token_loader.h" |
20 #include "chromeos/dbus/cryptohome_client.h" | |
18 #include "chromeos/dbus/session_manager_client.h" | 21 #include "chromeos/dbus/session_manager_client.h" |
19 #include "content/public/browser/browser_thread.h" | 22 #include "content/public/browser/browser_thread.h" |
20 #include "google_apis/gaia/gaia_auth_util.h" | 23 #include "google_apis/gaia/gaia_auth_util.h" |
21 | 24 |
22 namespace em = enterprise_management; | 25 namespace em = enterprise_management; |
23 | 26 |
24 namespace policy { | 27 namespace policy { |
25 | 28 |
26 namespace { | 29 namespace { |
27 // Subdirectory in the user's profile for storing user policies. | 30 // Path within |user_policy_key_dir_| that contains the policy key. |
28 const FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Device Management"); | 31 // "%s" must be substituted with the sanitized username. |
29 // File in the above directory for stroing user policy dmtokens. | 32 const FilePath::CharType kPolicyKeyFile[] = FILE_PATH_LITERAL("%s/policy.pub"); |
30 const FilePath::CharType kTokenCacheFile[] = FILE_PATH_LITERAL("Token"); | |
31 // File in the above directory for storing user policy data. | |
32 const FilePath::CharType kPolicyCacheFile[] = FILE_PATH_LITERAL("Policy"); | |
33 } // namespace | 33 } // namespace |
34 | 34 |
35 | 35 |
36 // Helper class for loading legacy policy caches. | 36 // Helper class for loading legacy policy caches. |
37 class LegacyPolicyCacheLoader : public UserPolicyTokenLoader::Delegate, | 37 class LegacyPolicyCacheLoader : public UserPolicyTokenLoader::Delegate, |
38 public UserPolicyDiskCache::Delegate { | 38 public UserPolicyDiskCache::Delegate { |
39 public: | 39 public: |
40 typedef base::Callback<void(const std::string&, | 40 typedef base::Callback<void(const std::string&, |
41 const std::string&, | 41 const std::string&, |
42 CloudPolicyStore::Status, | 42 CloudPolicyStore::Status, |
(...skipping 96 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
139 return CloudPolicyStore::STATUS_OK; | 139 return CloudPolicyStore::STATUS_OK; |
140 case UserPolicyDiskCache::LOAD_RESULT_PARSE_ERROR: | 140 case UserPolicyDiskCache::LOAD_RESULT_PARSE_ERROR: |
141 case UserPolicyDiskCache::LOAD_RESULT_READ_ERROR: | 141 case UserPolicyDiskCache::LOAD_RESULT_READ_ERROR: |
142 return CloudPolicyStore::STATUS_LOAD_ERROR; | 142 return CloudPolicyStore::STATUS_LOAD_ERROR; |
143 } | 143 } |
144 NOTREACHED(); | 144 NOTREACHED(); |
145 return CloudPolicyStore::STATUS_OK; | 145 return CloudPolicyStore::STATUS_OK; |
146 } | 146 } |
147 | 147 |
148 UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS( | 148 UserCloudPolicyStoreChromeOS::UserCloudPolicyStoreChromeOS( |
149 chromeos::CryptohomeClient* cryptohome_client, | |
149 chromeos::SessionManagerClient* session_manager_client, | 150 chromeos::SessionManagerClient* session_manager_client, |
150 const std::string& username, | 151 const std::string& username, |
152 const FilePath& user_policy_key_dir, | |
151 const FilePath& legacy_token_cache_file, | 153 const FilePath& legacy_token_cache_file, |
152 const FilePath& legacy_policy_cache_file) | 154 const FilePath& legacy_policy_cache_file) |
153 : session_manager_client_(session_manager_client), | 155 : cryptohome_client_(cryptohome_client), |
156 session_manager_client_(session_manager_client), | |
154 username_(username), | 157 username_(username), |
158 user_policy_key_dir_(user_policy_key_dir), | |
155 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), | 159 ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)), |
156 legacy_cache_dir_(legacy_token_cache_file.DirName()), | 160 legacy_cache_dir_(legacy_token_cache_file.DirName()), |
157 legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file, | 161 legacy_loader_(new LegacyPolicyCacheLoader(legacy_token_cache_file, |
158 legacy_policy_cache_file)), | 162 legacy_policy_cache_file)), |
159 legacy_caches_loaded_(false) {} | 163 legacy_caches_loaded_(false) {} |
160 | 164 |
161 UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {} | 165 UserCloudPolicyStoreChromeOS::~UserCloudPolicyStoreChromeOS() {} |
162 | 166 |
163 void UserCloudPolicyStoreChromeOS::Store( | 167 void UserCloudPolicyStoreChromeOS::Store( |
164 const em::PolicyFetchResponse& policy) { | 168 const em::PolicyFetchResponse& policy) { |
165 // Cancel all pending requests. | 169 // Cancel all pending requests. |
166 weak_factory_.InvalidateWeakPtrs(); | 170 weak_factory_.InvalidateWeakPtrs(); |
167 Validate( | 171 scoped_ptr<em::PolicyFetchResponse> response( |
168 scoped_ptr<em::PolicyFetchResponse>(new em::PolicyFetchResponse(policy)), | 172 new em::PolicyFetchResponse(policy)); |
169 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated, | 173 EnsurePolicyKeyLoaded( |
170 weak_factory_.GetWeakPtr())); | 174 base::Bind(&UserCloudPolicyStoreChromeOS::ValidatePolicyForStore, |
175 weak_factory_.GetWeakPtr(), | |
176 base::Passed(&response))); | |
171 } | 177 } |
172 | 178 |
173 void UserCloudPolicyStoreChromeOS::Load() { | 179 void UserCloudPolicyStoreChromeOS::Load() { |
174 // Cancel all pending requests. | 180 // Cancel all pending requests. |
175 weak_factory_.InvalidateWeakPtrs(); | 181 weak_factory_.InvalidateWeakPtrs(); |
176 session_manager_client_->RetrieveUserPolicy( | 182 session_manager_client_->RetrieveUserPolicy( |
177 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyRetrieved, | 183 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyRetrieved, |
178 weak_factory_.GetWeakPtr())); | 184 weak_factory_.GetWeakPtr())); |
179 } | 185 } |
180 | 186 |
187 void UserCloudPolicyStoreChromeOS::ValidatePolicyForStore( | |
188 scoped_ptr<em::PolicyFetchResponse> policy) { | |
189 // Create and configure a validator. | |
190 scoped_ptr<UserCloudPolicyValidator> validator = | |
191 CreateValidator(policy.Pass()); | |
192 validator->ValidateUsername(username_); | |
193 if (policy_key_->key().empty()) { | |
194 validator->ValidateInitialKey(); | |
195 } else { | |
196 const bool allow_rotation = true; | |
197 validator->ValidateSignature(policy_key_->key(), allow_rotation); | |
198 } | |
199 | |
200 // Start validation. The Validator will free itself once validation is | |
Mattias Nissler (ping if slow)
2013/02/07 14:12:07
nit: s/free/delete/
Joao da Silva
2013/02/07 16:32:00
Done.
| |
201 // complete. | |
202 validator.release()->StartValidation( | |
203 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated, | |
204 weak_factory_.GetWeakPtr())); | |
205 } | |
206 | |
207 void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated( | |
208 UserCloudPolicyValidator* validator) { | |
209 validation_status_ = validator->status(); | |
210 if (!validator->success()) { | |
211 status_ = STATUS_VALIDATION_ERROR; | |
212 NotifyStoreError(); | |
213 return; | |
214 } | |
215 | |
216 std::string policy_blob; | |
217 if (!validator->policy()->SerializeToString(&policy_blob)) { | |
218 status_ = STATUS_SERIALIZE_ERROR; | |
219 NotifyStoreError(); | |
220 return; | |
221 } | |
222 | |
223 session_manager_client_->StoreUserPolicy( | |
224 policy_blob, | |
225 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored, | |
226 weak_factory_.GetWeakPtr())); | |
227 } | |
228 | |
229 void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) { | |
230 if (!success) { | |
231 status_ = STATUS_STORE_ERROR; | |
232 NotifyStoreError(); | |
233 } else { | |
234 // Load the policy right after storing it, to make sure it was accepted by | |
235 // the session manager. An additional validation is performed after the | |
236 // load; reload the key for that validation too, in case it was rotated. | |
237 // This method is the callback for a store operation, which is posted after | |
238 // validating the policy, and the validation is performed after ensuring the | |
239 // key is loaded, so |policy_key_| is valid here. | |
240 policy_key_->Load(base::Bind(&UserCloudPolicyStoreChromeOS::Load, | |
241 weak_factory_.GetWeakPtr())); | |
242 } | |
243 } | |
244 | |
181 void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved( | 245 void UserCloudPolicyStoreChromeOS::OnPolicyRetrieved( |
182 const std::string& policy_blob) { | 246 const std::string& policy_blob) { |
183 if (policy_blob.empty()) { | 247 if (policy_blob.empty()) { |
184 // Policy fetch failed. Try legacy caches if we haven't done that already. | 248 // Policy fetch failed. Try legacy caches if we haven't done that already. |
185 if (!legacy_caches_loaded_ && legacy_loader_.get()) { | 249 if (!legacy_caches_loaded_ && legacy_loader_.get()) { |
186 legacy_caches_loaded_ = true; | 250 legacy_caches_loaded_ = true; |
187 legacy_loader_->Load( | 251 legacy_loader_->Load( |
188 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished, | 252 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished, |
189 weak_factory_.GetWeakPtr())); | 253 weak_factory_.GetWeakPtr())); |
190 } else { | 254 } else { |
191 // session_manager doesn't have policy. Adjust internal state and notify | 255 // session_manager doesn't have policy. Adjust internal state and notify |
192 // the world about the policy update. | 256 // the world about the policy update. |
193 policy_.reset(); | 257 policy_.reset(); |
194 NotifyStoreLoaded(); | 258 NotifyStoreLoaded(); |
195 } | 259 } |
196 return; | 260 return; |
197 } | 261 } |
198 | 262 |
199 // Policy is supplied by session_manager. Disregard legacy data from now on. | 263 // Policy is supplied by session_manager. Disregard legacy data from now on. |
200 legacy_loader_.reset(); | 264 legacy_loader_.reset(); |
201 | 265 |
202 scoped_ptr<em::PolicyFetchResponse> policy(new em::PolicyFetchResponse()); | 266 scoped_ptr<em::PolicyFetchResponse> policy(new em::PolicyFetchResponse()); |
203 if (!policy->ParseFromString(policy_blob)) { | 267 if (!policy->ParseFromString(policy_blob)) { |
204 status_ = STATUS_PARSE_ERROR; | 268 status_ = STATUS_PARSE_ERROR; |
205 NotifyStoreError(); | 269 NotifyStoreError(); |
206 return; | 270 return; |
207 } | 271 } |
208 | 272 |
209 Validate(policy.Pass(), | 273 // Load |policy_key_| to verify the loaded policy. |
210 base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated, | 274 EnsurePolicyKeyLoaded( |
211 weak_factory_.GetWeakPtr())); | 275 base::Bind(&UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy, |
276 weak_factory_.GetWeakPtr(), | |
277 base::Passed(&policy))); | |
278 } | |
279 | |
280 void UserCloudPolicyStoreChromeOS::ValidateRetrievedPolicy( | |
281 scoped_ptr<em::PolicyFetchResponse> policy) { | |
282 // Create and configure a validator for the loaded policy. | |
283 scoped_ptr<UserCloudPolicyValidator> validator = | |
284 CreateValidator(policy.Pass()); | |
285 validator->ValidateUsername(username_); | |
286 const bool allow_rotation = false; | |
287 validator->ValidateSignature(policy_key_->key(), allow_rotation); | |
288 // Start validation. The Validator will free itself once validation is | |
Joao da Silva
2013/02/07 16:32:00
Changed here too for consistency.
| |
289 // complete. | |
290 validator.release()->StartValidation( | |
291 base::Bind(&UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated, | |
292 weak_factory_.GetWeakPtr())); | |
212 } | 293 } |
213 | 294 |
214 void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated( | 295 void UserCloudPolicyStoreChromeOS::OnRetrievedPolicyValidated( |
215 UserCloudPolicyValidator* validator) { | 296 UserCloudPolicyValidator* validator) { |
216 validation_status_ = validator->status(); | 297 validation_status_ = validator->status(); |
217 if (!validator->success()) { | 298 if (!validator->success()) { |
218 status_ = STATUS_VALIDATION_ERROR; | 299 status_ = STATUS_VALIDATION_ERROR; |
219 NotifyStoreError(); | 300 NotifyStoreError(); |
220 return; | 301 return; |
221 } | 302 } |
222 | 303 |
223 InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); | 304 InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); |
224 status_ = STATUS_OK; | 305 status_ = STATUS_OK; |
225 | 306 |
226 // Policy has been loaded successfully. This indicates that new-style policy | 307 // Policy has been loaded successfully. This indicates that new-style policy |
227 // is working, so the legacy cache directory can be removed. | 308 // is working, so the legacy cache directory can be removed. |
228 if (!legacy_cache_dir_.empty()) { | 309 if (!legacy_cache_dir_.empty()) { |
229 content::BrowserThread::PostBlockingPoolTask( | 310 content::BrowserThread::PostBlockingPoolTask( |
230 FROM_HERE, | 311 FROM_HERE, |
231 base::Bind(&UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir, | 312 base::Bind(&UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir, |
232 legacy_cache_dir_)); | 313 legacy_cache_dir_)); |
233 legacy_cache_dir_.clear(); | 314 legacy_cache_dir_.clear(); |
234 } | 315 } |
235 NotifyStoreLoaded(); | 316 NotifyStoreLoaded(); |
236 } | 317 } |
237 | 318 |
238 void UserCloudPolicyStoreChromeOS::OnPolicyToStoreValidated( | |
239 UserCloudPolicyValidator* validator) { | |
240 validation_status_ = validator->status(); | |
241 if (!validator->success()) { | |
242 status_ = STATUS_VALIDATION_ERROR; | |
243 NotifyStoreError(); | |
244 return; | |
245 } | |
246 | |
247 std::string policy_blob; | |
248 if (!validator->policy()->SerializeToString(&policy_blob)) { | |
249 status_ = STATUS_SERIALIZE_ERROR; | |
250 NotifyStoreError(); | |
251 return; | |
252 } | |
253 | |
254 session_manager_client_->StoreUserPolicy( | |
255 policy_blob, | |
256 base::Bind(&UserCloudPolicyStoreChromeOS::OnPolicyStored, | |
257 weak_factory_.GetWeakPtr())); | |
258 } | |
259 | |
260 void UserCloudPolicyStoreChromeOS::OnPolicyStored(bool success) { | |
261 if (!success) { | |
262 status_ = STATUS_STORE_ERROR; | |
263 NotifyStoreError(); | |
264 } else { | |
265 // TODO(mnissler): Once we do signature verifications, we'll have to reload | |
266 // the key at this point to account for key rotations. | |
267 Load(); | |
268 } | |
269 } | |
270 | |
271 void UserCloudPolicyStoreChromeOS::Validate( | |
272 scoped_ptr<em::PolicyFetchResponse> policy, | |
273 const UserCloudPolicyValidator::CompletionCallback& callback) { | |
274 // Configure the validator. | |
275 scoped_ptr<UserCloudPolicyValidator> validator = | |
276 CreateValidator(policy.Pass()); | |
277 validator->ValidateUsername(username_); | |
278 | |
279 // TODO(mnissler): Do a signature check here as well. The key is stored by | |
280 // session_manager in the root-owned cryptohome area, which is currently | |
281 // inaccessible to Chrome though. | |
282 | |
283 // Start validation. The Validator will free itself once validation is | |
Joao da Silva
2013/02/07 16:32:00
I think the comment was originally from here.
| |
284 // complete. | |
285 validator.release()->StartValidation(callback); | |
286 } | |
287 | |
288 void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished( | 319 void UserCloudPolicyStoreChromeOS::OnLegacyLoadFinished( |
289 const std::string& dm_token, | 320 const std::string& dm_token, |
290 const std::string& device_id, | 321 const std::string& device_id, |
291 Status status, | 322 Status status, |
292 scoped_ptr<em::PolicyFetchResponse> policy) { | 323 scoped_ptr<em::PolicyFetchResponse> policy) { |
293 status_ = status; | 324 status_ = status; |
294 if (policy.get()) { | 325 if (policy.get()) { |
295 Validate(policy.Pass(), | 326 // Create and configure a validator for the loaded legacy policy. Note that |
296 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated, | 327 // the signature on this policy is not verified. |
297 weak_factory_.GetWeakPtr(), | 328 scoped_ptr<UserCloudPolicyValidator> validator = |
298 dm_token, device_id)); | 329 CreateValidator(policy.Pass()); |
330 validator->ValidateUsername(username_); | |
331 validator.release()->StartValidation( | |
332 base::Bind(&UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated, | |
333 weak_factory_.GetWeakPtr(), | |
334 dm_token, | |
335 device_id)); | |
299 } else { | 336 } else { |
300 InstallLegacyTokens(dm_token, device_id); | 337 InstallLegacyTokens(dm_token, device_id); |
301 } | 338 } |
302 } | 339 } |
303 | 340 |
304 void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated( | 341 void UserCloudPolicyStoreChromeOS::OnLegacyPolicyValidated( |
305 const std::string& dm_token, | 342 const std::string& dm_token, |
306 const std::string& device_id, | 343 const std::string& device_id, |
307 UserCloudPolicyValidator* validator) { | 344 UserCloudPolicyValidator* validator) { |
308 validation_status_ = validator->status(); | 345 validation_status_ = validator->status(); |
309 if (validator->success()) { | 346 if (validator->success()) { |
310 status_ = STATUS_OK; | 347 status_ = STATUS_OK; |
311 InstallPolicy(validator->policy_data().Pass(), | 348 InstallPolicy(validator->policy_data().Pass(), validator->payload().Pass()); |
312 validator->payload().Pass()); | |
313 | 349 |
314 // Clear the public key version. The public key version field would | 350 // Clear the public key version. The public key version field would |
315 // otherwise indicate that we have key installed in the store when in fact | 351 // otherwise indicate that we have key installed in the store when in fact |
316 // we haven't. This may result in policy updates failing signature | 352 // we haven't. This may result in policy updates failing signature |
317 // verification. | 353 // verification. |
318 policy_->clear_public_key_version(); | 354 policy_->clear_public_key_version(); |
319 } else { | 355 } else { |
320 status_ = STATUS_VALIDATION_ERROR; | 356 status_ = STATUS_VALIDATION_ERROR; |
321 } | 357 } |
322 | 358 |
(...skipping 16 matching lines...) Expand all Loading... | |
339 // Tell the rest of the world that the policy load completed. | 375 // Tell the rest of the world that the policy load completed. |
340 NotifyStoreLoaded(); | 376 NotifyStoreLoaded(); |
341 } | 377 } |
342 | 378 |
343 // static | 379 // static |
344 void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir(const FilePath& dir) { | 380 void UserCloudPolicyStoreChromeOS::RemoveLegacyCacheDir(const FilePath& dir) { |
345 if (file_util::PathExists(dir) && !file_util::Delete(dir, true)) | 381 if (file_util::PathExists(dir) && !file_util::Delete(dir, true)) |
346 LOG(ERROR) << "Failed to remove cache dir " << dir.value(); | 382 LOG(ERROR) << "Failed to remove cache dir " << dir.value(); |
347 } | 383 } |
348 | 384 |
385 void UserCloudPolicyStoreChromeOS::EnsurePolicyKeyLoaded( | |
386 const base::Closure& callback) { | |
387 if (policy_key_) { | |
388 callback.Run(); | |
389 } else { | |
390 // Get the hashed username that's part of the key's path, to create a new | |
391 // |policy_key_| later. | |
392 cryptohome_client_->GetSanitizedUsername(username_, | |
393 base::Bind(&UserCloudPolicyStoreChromeOS::OnGetSanitizedUsername, | |
394 weak_factory_.GetWeakPtr(), | |
395 callback)); | |
396 } | |
397 } | |
398 | |
399 void UserCloudPolicyStoreChromeOS::OnGetSanitizedUsername( | |
400 const base::Closure& callback, | |
401 chromeos::DBusMethodCallStatus call_status, | |
402 const std::string& sanitized_username) { | |
403 // EnsurePolicyKeyLoaded() may have been called twice before getting a reply | |
404 // from dbus; the second request will just reload the key. | |
405 if (!policy_key_) { | |
406 // The default empty path will always yield an empty key. | |
407 FilePath path; | |
408 if (call_status == chromeos::DBUS_METHOD_CALL_SUCCESS && | |
409 !sanitized_username.empty()) { | |
410 path = user_policy_key_dir_.Append( | |
411 base::StringPrintf(kPolicyKeyFile, sanitized_username.c_str())); | |
412 } | |
413 policy_key_.reset(new UserPolicyKey(path)); | |
414 } | |
415 policy_key_->Load(callback); | |
416 } | |
417 | |
349 } // namespace policy | 418 } // namespace policy |
OLD | NEW |