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.
5 #include "components/policy/core/common/policy_loader_ios.h"
7 #import <Foundation/Foundation.h>
8 #import <UIKit/UIKit.h>
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/sequenced_task_runner.h"
15 #include "components/policy/core/common/mac_util.h"
16 #include "components/policy/core/common/policy_bundle.h"
17 #include "components/policy/core/common/policy_map.h"
18 #include "components/policy/core/common/policy_namespace.h"
19 #include "policy/policy_constants.h"
21 // This policy loader loads a managed app configuration from the NSUserDefaults.
22 // For example code from Apple see:
23 // https://developer.apple.com/library/ios/samplecode/sc2279/Introduction/Intro.html
24 // For an introduction to the API see session 301 from WWDC 2013,
25 // "Extending Your Apps for Enterprise and Education Use":
26 // https://developer.apple.com/videos/wwdc/2013/?id=301
30 // Key in the NSUserDefaults that contains the managed app configuration.
31 NSString* const kConfigurationKey = @"com.apple.configuration.managed";
33 // Key in the managed app configuration that contains the Chrome policy.
34 NSString* const kChromePolicyKey = @"ChromePolicy";
36 // Key in the managed app configuration that contains the encoded Chrome policy.
37 // This is a serialized Property List, encoded in base 64.
38 NSString* const kEncodedChromePolicyKey = @"EncodedChromePolicy";
42 // Helper that observes notifications for NSUserDefaults and triggers an update
43 // at the loader on the right thread.
44 @interface PolicyNotificationObserver : NSObject {
45 base::Closure callback_;
46 scoped_refptr<base::SequencedTaskRunner> taskRunner_;
49 // Designated initializer. |callback| will be posted to |taskRunner| whenever
50 // the NSUserDefaults change.
51 - (id)initWithCallback:(const base::Closure&)callback
52 taskRunner:(scoped_refptr<base::SequencedTaskRunner>)taskRunner;
54 // Invoked when the NSUserDefaults change.
55 - (void)userDefaultsChanged:(NSNotification*)notification;
61 @implementation PolicyNotificationObserver
63 - (id)initWithCallback:(const base::Closure&)callback
64 taskRunner:(scoped_refptr<base::SequencedTaskRunner>)taskRunner {
65 if ((self = [super init])) {
67 taskRunner_ = taskRunner;
68 [[NSNotificationCenter defaultCenter]
70 selector:@selector(userDefaultsChanged:)
71 name:NSUserDefaultsDidChangeNotification
77 - (void)userDefaultsChanged:(NSNotification*)notification {
78 // This may be invoked on any thread. Post the |callback_| to the loader's
79 // |taskRunner_| to make sure it Reloads() on the right thread.
80 taskRunner_->PostTask(FROM_HERE, callback_);
84 [[NSNotificationCenter defaultCenter] removeObserver:self];
92 PolicyLoaderIOS::PolicyLoaderIOS(
93 scoped_refptr<base::SequencedTaskRunner> task_runner)
94 : AsyncPolicyLoader(task_runner),
95 weak_factory_(this) {}
97 PolicyLoaderIOS::~PolicyLoaderIOS() {
98 DCHECK(task_runner()->RunsTasksOnCurrentThread());
101 void PolicyLoaderIOS::InitOnBackgroundThread() {
102 DCHECK(task_runner()->RunsTasksOnCurrentThread());
103 base::Closure callback = base::Bind(&PolicyLoaderIOS::UserDefaultsChanged,
104 weak_factory_.GetWeakPtr());
105 notification_observer_.reset(
106 [[PolicyNotificationObserver alloc] initWithCallback:callback
107 taskRunner:task_runner()]);
110 scoped_ptr<PolicyBundle> PolicyLoaderIOS::Load() {
111 scoped_ptr<PolicyBundle> bundle(new PolicyBundle());
112 NSDictionary* configuration = [[NSUserDefaults standardUserDefaults]
113 dictionaryForKey:kConfigurationKey];
114 id chromePolicy = configuration[kChromePolicyKey];
115 id encodedChromePolicy = configuration[kEncodedChromePolicyKey];
117 if (chromePolicy && [chromePolicy isKindOfClass:[NSDictionary class]]) {
118 LoadNSDictionaryToPolicyBundle(chromePolicy, bundle.get());
120 if (encodedChromePolicy)
121 NSLog(@"Ignoring EncodedChromePolicy because ChromePolicy is present.");
122 } else if (encodedChromePolicy &&
123 [encodedChromePolicy isKindOfClass:[NSString class]]) {
124 base::scoped_nsobject<NSData> data(
125 [[NSData alloc] initWithBase64EncodedString:encodedChromePolicy
128 NSLog(@"Invalid Base64 encoding of EncodedChromePolicy");
130 NSError* error = nil;
131 NSDictionary* properties = [NSPropertyListSerialization
132 propertyListWithData:data.get()
133 options:NSPropertyListImmutable
137 NSLog(@"Invalid property list in EncodedChromePolicy: %@", error);
138 } else if (!properties) {
139 NSLog(@"Failed to deserialize a valid Property List");
140 } else if (![properties isKindOfClass:[NSDictionary class]]) {
141 NSLog(@"Invalid property list in EncodedChromePolicy: expected an "
142 "NSDictionary but found %@", [properties class]);
144 LoadNSDictionaryToPolicyBundle(properties, bundle.get());
149 return bundle.Pass();
152 base::Time PolicyLoaderIOS::LastModificationTime() {
153 return last_notification_time_;
156 void PolicyLoaderIOS::UserDefaultsChanged() {
157 // The base class coalesces multiple Reload() calls into a single Load() if
158 // the LastModificationTime() has a small delta between Reload() calls.
159 // This coalesces the multiple notifications sent during startup into a single
161 last_notification_time_ = base::Time::Now();
166 void PolicyLoaderIOS::LoadNSDictionaryToPolicyBundle(NSDictionary* dictionary,
167 PolicyBundle* bundle) {
168 // NSDictionary is toll-free bridged to CFDictionaryRef, which is a
169 // CFPropertyListRef.
170 scoped_ptr<base::Value> value =
171 PropertyToValue(static_cast<CFPropertyListRef>(dictionary));
172 base::DictionaryValue* dict = NULL;
173 if (value && value->GetAsDictionary(&dict)) {
174 PolicyMap& map = bundle->Get(PolicyNamespace(POLICY_DOMAIN_CHROME, ""));
175 map.LoadFrom(dict, POLICY_LEVEL_MANDATORY, POLICY_SCOPE_MACHINE);
179 } // namespace policy