Home | History | Annotate | Download | only in common
      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 
     29 namespace {
     30 
     31 // Key in the NSUserDefaults that contains the managed app configuration.
     32 NSString* const kConfigurationKey = @"com.apple.configuration.managed";
     33 
     34 // Key in the managed app configuration that contains the Chrome policy.
     35 NSString* 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.
     39 NSString* 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 
     91 namespace policy {
     92 
     93 PolicyLoaderIOS::PolicyLoaderIOS(
     94     scoped_refptr<base::SequencedTaskRunner> task_runner)
     95     : AsyncPolicyLoader(task_runner),
     96       weak_factory_(this) {}
     97 
     98 PolicyLoaderIOS::~PolicyLoaderIOS() {
     99   DCHECK(task_runner()->RunsTasksOnCurrentThread());
    100 }
    101 
    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()]);
    109 }
    110 
    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];
    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 
    157 base::Time PolicyLoaderIOS::LastModificationTime() {
    158   return last_notification_time_;
    159 }
    160 
    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);
    168 }
    169 
    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   }
    182 }
    183 
    184 }  // namespace policy
    185