Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / components / policy / core / common / policy_loader_ios.mm
blobc728a258ed9b5816437176170e48c2ef9567f4c6
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/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"
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
29 namespace {
31 // Key in the NSUserDefaults that contains the managed app configuration.
32 NSString* const kConfigurationKey = @"com.apple.configuration.managed";
34 // Key in the managed app configuration that contains the Chrome policy.
35 NSString* const kChromePolicyKey = @"ChromePolicy";
37 // Key in the managed app configuration that contains the encoded Chrome policy.
38 // This is a serialized Property List, encoded in base 64.
39 NSString* const kEncodedChromePolicyKey = @"EncodedChromePolicy";
41 }  // namespace
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_;
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;
55 // Invoked when the NSUserDefaults change.
56 - (void)userDefaultsChanged:(NSNotification*)notification;
58 - (void)dealloc;
60 @end
62 @implementation PolicyNotificationObserver
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;
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_);
84 - (void)dealloc {
85   [[NSNotificationCenter defaultCenter] removeObserver:self];
86   [super dealloc];
89 @end
91 namespace policy {
93 PolicyLoaderIOS::PolicyLoaderIOS(
94     scoped_refptr<base::SequencedTaskRunner> task_runner)
95     : AsyncPolicyLoader(task_runner),
96       weak_factory_(this) {}
98 PolicyLoaderIOS::~PolicyLoaderIOS() {
99   DCHECK(task_runner()->RunsTasksOnCurrentThread());
102 void 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()]);
111 scoped_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];
118   if (chromePolicy && [chromePolicy isKindOfClass:[NSDictionary class]]) {
119     LoadNSDictionaryToPolicyBundle(chromePolicy, bundle.get());
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   }
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);
154   return bundle.Pass();
157 base::Time PolicyLoaderIOS::LastModificationTime() {
158   return last_notification_time_;
161 void 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);
170 // static
171 void 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                  POLICY_SOURCE_PLATFORM);
182   }
185 }  // namespace policy