1// Copyright 2014 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 "components/policy/core/common/policy_loader_ios.h" 6 7#import <Foundation/Foundation.h> 8#import <UIKit/UIKit.h> 9 10#include "base/bind.h" 11#include "base/location.h" 12#include "base/logging.h" 13#include "base/mac/scoped_nsobject.h" 14#include "base/metrics/histogram.h" 15#include "base/sequenced_task_runner.h" 16#include "components/policy/core/common/mac_util.h" 17#include "components/policy/core/common/policy_bundle.h" 18#include "components/policy/core/common/policy_map.h" 19#include "components/policy/core/common/policy_namespace.h" 20#include "policy/policy_constants.h" 21 22// This policy loader loads a managed app configuration from the NSUserDefaults. 23// For example code from Apple see: 24// https://developer.apple.com/library/ios/samplecode/sc2279/Introduction/Intro.html 25// For an introduction to the API see session 301 from WWDC 2013, 26// "Extending Your Apps for Enterprise and Education Use": 27// https://developer.apple.com/videos/wwdc/2013/?id=301 28 29namespace { 30 31// Key in the NSUserDefaults that contains the managed app configuration. 32NSString* const kConfigurationKey = @"com.apple.configuration.managed"; 33 34// Key in the managed app configuration that contains the Chrome policy. 35NSString* const kChromePolicyKey = @"ChromePolicy"; 36 37// Key in the managed app configuration that contains the encoded Chrome policy. 38// This is a serialized Property List, encoded in base 64. 39NSString* const kEncodedChromePolicyKey = @"EncodedChromePolicy"; 40 41} // namespace 42 43// Helper that observes notifications for NSUserDefaults and triggers an update 44// at the loader on the right thread. 45@interface PolicyNotificationObserver : NSObject { 46 base::Closure callback_; 47 scoped_refptr<base::SequencedTaskRunner> taskRunner_; 48} 49 50// Designated initializer. |callback| will be posted to |taskRunner| whenever 51// the NSUserDefaults change. 52- (id)initWithCallback:(const base::Closure&)callback 53 taskRunner:(scoped_refptr<base::SequencedTaskRunner>)taskRunner; 54 55// Invoked when the NSUserDefaults change. 56- (void)userDefaultsChanged:(NSNotification*)notification; 57 58- (void)dealloc; 59 60@end 61 62@implementation PolicyNotificationObserver 63 64- (id)initWithCallback:(const base::Closure&)callback 65 taskRunner:(scoped_refptr<base::SequencedTaskRunner>)taskRunner { 66 if ((self = [super init])) { 67 callback_ = callback; 68 taskRunner_ = taskRunner; 69 [[NSNotificationCenter defaultCenter] 70 addObserver:self 71 selector:@selector(userDefaultsChanged:) 72 name:NSUserDefaultsDidChangeNotification 73 object:nil]; 74 } 75 return self; 76} 77 78- (void)userDefaultsChanged:(NSNotification*)notification { 79 // This may be invoked on any thread. Post the |callback_| to the loader's 80 // |taskRunner_| to make sure it Reloads() on the right thread. 81 taskRunner_->PostTask(FROM_HERE, callback_); 82} 83 84- (void)dealloc { 85 [[NSNotificationCenter defaultCenter] removeObserver:self]; 86 [super dealloc]; 87} 88 89@end 90 91namespace policy { 92 93PolicyLoaderIOS::PolicyLoaderIOS( 94 scoped_refptr<base::SequencedTaskRunner> task_runner) 95 : AsyncPolicyLoader(task_runner), 96 weak_factory_(this) {} 97 98PolicyLoaderIOS::~PolicyLoaderIOS() { 99 DCHECK(task_runner()->RunsTasksOnCurrentThread()); 100} 101 102void PolicyLoaderIOS::InitOnBackgroundThread() { 103 DCHECK(task_runner()->RunsTasksOnCurrentThread()); 104 base::Closure callback = base::Bind(&PolicyLoaderIOS::UserDefaultsChanged, 105 weak_factory_.GetWeakPtr()); 106 notification_observer_.reset( 107 [[PolicyNotificationObserver alloc] initWithCallback:callback 108 taskRunner:task_runner()]); 109} 110 111scoped_ptr<PolicyBundle> PolicyLoaderIOS::Load() { 112 scoped_ptr<PolicyBundle> bundle(new PolicyBundle()); 113 NSDictionary* configuration = [[NSUserDefaults standardUserDefaults] 114 dictionaryForKey:kConfigurationKey]; 115 id chromePolicy = configuration[kChromePolicyKey]; 116 id encodedChromePolicy = configuration[kEncodedChromePolicyKey]; 117 118 if (chromePolicy && [chromePolicy isKindOfClass:[NSDictionary class]]) { 119 LoadNSDictionaryToPolicyBundle(chromePolicy, bundle.get()); 120 121 if (encodedChromePolicy) 122 NSLog(@"Ignoring EncodedChromePolicy because ChromePolicy is present."); 123 } else if (encodedChromePolicy && 124 [encodedChromePolicy isKindOfClass:[NSString class]]) { 125 base::scoped_nsobject<NSData> data( 126 [[NSData alloc] initWithBase64EncodedString:encodedChromePolicy 127 options:0]); 128 if (!data) { 129 NSLog(@"Invalid Base64 encoding of EncodedChromePolicy"); 130 } else { 131 NSError* error = nil; 132 NSDictionary* properties = [NSPropertyListSerialization 133 propertyListWithData:data.get() 134 options:NSPropertyListImmutable 135 format:NULL 136 error:&error]; 137 if (error) { 138 NSLog(@"Invalid property list in EncodedChromePolicy: %@", error); 139 } else if (!properties) { 140 NSLog(@"Failed to deserialize a valid Property List"); 141 } else if (![properties isKindOfClass:[NSDictionary class]]) { 142 NSLog(@"Invalid property list in EncodedChromePolicy: expected an " 143 "NSDictionary but found %@", [properties class]); 144 } else { 145 LoadNSDictionaryToPolicyBundle(properties, bundle.get()); 146 } 147 } 148 } 149 150 const PolicyNamespace chrome_ns(POLICY_DOMAIN_CHROME, std::string()); 151 size_t count = bundle->Get(chrome_ns).size(); 152 UMA_HISTOGRAM_COUNTS_100("Enterprise.IOSPolicies", count); 153 154 return bundle.Pass(); 155} 156 157base::Time PolicyLoaderIOS::LastModificationTime() { 158 return last_notification_time_; 159} 160 161void PolicyLoaderIOS::UserDefaultsChanged() { 162 // The base class coalesces multiple Reload() calls into a single Load() if 163 // the LastModificationTime() has a small delta between Reload() calls. 164 // This coalesces the multiple notifications sent during startup into a single 165 // Load() call. 166 last_notification_time_ = base::Time::Now(); 167 Reload(false); 168} 169 170// static 171void PolicyLoaderIOS::LoadNSDictionaryToPolicyBundle(NSDictionary* dictionary, 172 PolicyBundle* bundle) { 173 // NSDictionary is toll-free bridged to CFDictionaryRef, which is a 174 // CFPropertyListRef. 175 scoped_ptr<base::Value> value = 176 PropertyToValue(static_cast<CFPropertyListRef>(dictionary)); 177 base::DictionaryValue* dict = NULL; 178 if (value && value->GetAsDictionary(&dict)) { 179 PolicyMap& map = bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, "")); 180 map.LoadFrom(dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE); 181 } 182} 183 184} // namespace policy 185