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 3 // found in the LICENSE file. 4 5 #include "chrome/browser/policy/policy_loader_mac.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/callback.h" 10 #include "base/file_util.h" 11 #include "base/mac/foundation_util.h" 12 #include "base/mac/scoped_cftyperef.h" 13 #include "base/path_service.h" 14 #include "base/platform_file.h" 15 #include "base/strings/sys_string_conversions.h" 16 #include "base/values.h" 17 #include "chrome/browser/policy/external_data_fetcher.h" 18 #include "chrome/browser/policy/policy_bundle.h" 19 #include "chrome/browser/policy/policy_domain_descriptor.h" 20 #include "chrome/browser/policy/policy_load_status.h" 21 #include "chrome/browser/policy/policy_map.h" 22 #include "chrome/browser/policy/preferences_mac.h" 23 #include "chrome/common/chrome_paths.h" 24 #include "chrome/common/policy/policy_schema.h" 25 #include "policy/policy_constants.h" 26 27 using base::mac::CFCast; 28 using base::ScopedCFTypeRef; 29 30 namespace policy { 31 32 namespace { 33 34 base::FilePath GetManagedPolicyPath() { 35 // This constructs the path to the plist file in which Mac OS X stores the 36 // managed preference for the application. This is undocumented and therefore 37 // fragile, but if it doesn't work out, AsyncPolicyLoader has a task that 38 // polls periodically in order to reload managed preferences later even if we 39 // missed the change. 40 base::FilePath path; 41 if (!PathService::Get(chrome::DIR_MANAGED_PREFS, &path)) 42 return base::FilePath(); 43 44 CFBundleRef bundle(CFBundleGetMainBundle()); 45 if (!bundle) 46 return base::FilePath(); 47 48 CFStringRef bundle_id = CFBundleGetIdentifier(bundle); 49 if (!bundle_id) 50 return base::FilePath(); 51 52 return path.Append(base::SysCFStringRefToUTF8(bundle_id) + ".plist"); 53 } 54 55 // Callback function for CFDictionaryApplyFunction. |key| and |value| are an 56 // entry of the CFDictionary that should be converted into an equivalent entry 57 // in the DictionaryValue in |context|. 58 void DictionaryEntryToValue(const void* key, const void* value, void* context) { 59 if (CFStringRef cf_key = CFCast<CFStringRef>(key)) { 60 base::Value* converted = 61 PolicyLoaderMac::CreateValueFromProperty( 62 static_cast<CFPropertyListRef>(value)); 63 if (converted) { 64 const std::string string = base::SysCFStringRefToUTF8(cf_key); 65 static_cast<base::DictionaryValue *>(context)->Set(string, converted); 66 } 67 } 68 } 69 70 // Callback function for CFArrayApplyFunction. |value| is an entry of the 71 // CFArray that should be converted into an equivalent entry in the ListValue 72 // in |context|. 73 void ArrayEntryToValue(const void* value, void* context) { 74 base::Value* converted = 75 PolicyLoaderMac::CreateValueFromProperty( 76 static_cast<CFPropertyListRef>(value)); 77 if (converted) 78 static_cast<base::ListValue *>(context)->Append(converted); 79 } 80 81 } // namespace 82 83 PolicyLoaderMac::PolicyLoaderMac(const PolicyDefinitionList* policy_list, 84 MacPreferences* preferences) 85 : policy_list_(policy_list), 86 preferences_(preferences), 87 managed_policy_path_(GetManagedPolicyPath()) {} 88 89 PolicyLoaderMac::~PolicyLoaderMac() {} 90 91 void PolicyLoaderMac::InitOnFile() { 92 if (!managed_policy_path_.empty()) { 93 watcher_.Watch( 94 managed_policy_path_, false, 95 base::Bind(&PolicyLoaderMac::OnFileUpdated, base::Unretained(this))); 96 } 97 } 98 99 scoped_ptr<PolicyBundle> PolicyLoaderMac::Load() { 100 preferences_->AppSynchronize(kCFPreferencesCurrentApplication); 101 scoped_ptr<PolicyBundle> bundle(new PolicyBundle()); 102 103 // Load Chrome's policy. 104 // TODO(joaodasilva): use a schema for Chrome once it's generated and 105 // available from a PolicyDomainDescriptor. 106 PolicyMap& chrome_policy = 107 bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, std::string())); 108 109 PolicyLoadStatusSample status; 110 bool policy_present = false; 111 const PolicyDefinitionList::Entry* current; 112 for (current = policy_list_->begin; current != policy_list_->end; ++current) { 113 base::ScopedCFTypeRef<CFStringRef> name( 114 base::SysUTF8ToCFStringRef(current->name)); 115 base::ScopedCFTypeRef<CFPropertyListRef> value( 116 preferences_->CopyAppValue(name, kCFPreferencesCurrentApplication)); 117 if (!value.get()) 118 continue; 119 policy_present = true; 120 bool forced = 121 preferences_->AppValueIsForced(name, kCFPreferencesCurrentApplication); 122 PolicyLevel level = forced ? POLICY_LEVEL_MANDATORY : 123 POLICY_LEVEL_RECOMMENDED; 124 // TODO(joaodasilva): figure the policy scope. 125 base::Value* policy = CreateValueFromProperty(value); 126 if (policy) 127 chrome_policy.Set(current->name, level, POLICY_SCOPE_USER, policy, NULL); 128 else 129 status.Add(POLICY_LOAD_STATUS_PARSE_ERROR); 130 } 131 132 if (!policy_present) 133 status.Add(POLICY_LOAD_STATUS_NO_POLICY); 134 135 // Load policy for the registered components. 136 static const struct { 137 PolicyDomain domain; 138 const char* domain_name; 139 } kSupportedDomains[] = { 140 { POLICY_DOMAIN_EXTENSIONS, "extensions" }, 141 }; 142 for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSupportedDomains); ++i) { 143 DescriptorMap::const_iterator it = 144 descriptor_map().find(kSupportedDomains[i].domain); 145 if (it != descriptor_map().end()) { 146 LoadPolicyForDomain( 147 it->second, kSupportedDomains[i].domain_name, bundle.get()); 148 } 149 } 150 151 return bundle.Pass(); 152 } 153 154 base::Time PolicyLoaderMac::LastModificationTime() { 155 base::PlatformFileInfo file_info; 156 if (!file_util::GetFileInfo(managed_policy_path_, &file_info) || 157 file_info.is_directory) { 158 return base::Time(); 159 } 160 161 return file_info.last_modified; 162 } 163 164 // static 165 base::Value* PolicyLoaderMac::CreateValueFromProperty( 166 CFPropertyListRef property) { 167 if (CFCast<CFNullRef>(property)) 168 return base::Value::CreateNullValue(); 169 170 if (CFBooleanRef boolean = CFCast<CFBooleanRef>(property)) 171 return base::Value::CreateBooleanValue(CFBooleanGetValue(boolean)); 172 173 if (CFNumberRef number = CFCast<CFNumberRef>(property)) { 174 // CFNumberGetValue() converts values implicitly when the conversion is not 175 // lossy. Check the type before trying to convert. 176 if (CFNumberIsFloatType(number)) { 177 double double_value; 178 if (CFNumberGetValue(number, kCFNumberDoubleType, &double_value)) 179 return base::Value::CreateDoubleValue(double_value); 180 } else { 181 int int_value; 182 if (CFNumberGetValue(number, kCFNumberIntType, &int_value)) 183 return base::Value::CreateIntegerValue(int_value); 184 } 185 } 186 187 if (CFStringRef string = CFCast<CFStringRef>(property)) 188 return base::Value::CreateStringValue(base::SysCFStringRefToUTF8(string)); 189 190 if (CFDictionaryRef dict = CFCast<CFDictionaryRef>(property)) { 191 base::DictionaryValue* dict_value = new base::DictionaryValue(); 192 CFDictionaryApplyFunction(dict, DictionaryEntryToValue, dict_value); 193 return dict_value; 194 } 195 196 if (CFArrayRef array = CFCast<CFArrayRef>(property)) { 197 base::ListValue* list_value = new base::ListValue(); 198 CFArrayApplyFunction(array, 199 CFRangeMake(0, CFArrayGetCount(array)), 200 ArrayEntryToValue, 201 list_value); 202 return list_value; 203 } 204 205 return NULL; 206 } 207 208 void PolicyLoaderMac::LoadPolicyForDomain( 209 scoped_refptr<const PolicyDomainDescriptor> descriptor, 210 const std::string& domain_name, 211 PolicyBundle* bundle) { 212 std::string id_prefix(base::mac::BaseBundleID()); 213 id_prefix.append(".").append(domain_name).append("."); 214 215 for (PolicyDomainDescriptor::SchemaMap::const_iterator it_schema = 216 descriptor->components().begin(); 217 it_schema != descriptor->components().end(); ++it_schema) { 218 PolicyMap policy; 219 LoadPolicyForComponent( 220 id_prefix + it_schema->first, it_schema->second, &policy); 221 if (!policy.empty()) { 222 bundle->Get(PolicyNamespace(descriptor->domain(), it_schema->first)) 223 .Swap(&policy); 224 } 225 } 226 } 227 228 void PolicyLoaderMac::LoadPolicyForComponent( 229 const std::string& bundle_id_string, 230 const PolicySchema* schema, 231 PolicyMap* policy) { 232 // TODO(joaodasilva): extensions may be registered in a PolicyDomainDescriptor 233 // without a PolicySchema, to allow a graceful update of the Legacy Browser 234 // Support extension on Windows. Remove this temporary check once that support 235 // is removed. 236 if (!schema) 237 return; 238 239 base::ScopedCFTypeRef<CFStringRef> bundle_id( 240 base::SysUTF8ToCFStringRef(bundle_id_string)); 241 preferences_->AppSynchronize(bundle_id); 242 243 const PolicySchemaMap* map = schema->GetProperties(); 244 if (!map) { 245 NOTREACHED(); 246 return; 247 } 248 249 for (PolicySchemaMap::const_iterator it = map->begin(); 250 it != map->end(); ++it) { 251 base::ScopedCFTypeRef<CFStringRef> pref_name( 252 base::SysUTF8ToCFStringRef(it->first)); 253 base::ScopedCFTypeRef<CFPropertyListRef> value( 254 preferences_->CopyAppValue(pref_name, bundle_id)); 255 if (!value.get()) 256 continue; 257 bool forced = 258 preferences_->AppValueIsForced(pref_name, bundle_id); 259 PolicyLevel level = forced ? POLICY_LEVEL_MANDATORY : 260 POLICY_LEVEL_RECOMMENDED; 261 scoped_ptr<base::Value> policy_value(CreateValueFromProperty(value)); 262 if (policy_value) 263 policy->Set(it->first, level, POLICY_SCOPE_USER, 264 policy_value.release(), NULL); 265 } 266 } 267 268 void PolicyLoaderMac::OnFileUpdated(const base::FilePath& path, bool error) { 269 if (!error) 270 Reload(false); 271 } 272 273 } // namespace policy 274