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