返回

iOS Action Extension 如何获取 MDM 管理配置?

IOS

MDM 管理配置在 iOS Action Extension 中的访问

iOS 设备管理(MDM)系统为应用程序提供了一种集中管理配置的方法,这些配置通过 com.apple.configuration.managed 键存储在 UserDefaults 中。然而,当应用程序包含 Action Extension 时,访问此配置就并非如同在主应用中那样直接。该文将探讨在 Action Extension 中获取 MDM 管理配置的几种方式,并分析为何会出现此种情况。

问题分析

MDM 管理配置通过 UserDefaultsstandard 实例存储,这种方式对于主应用而言可以方便快捷地访问配置数据。Action Extension 与其宿主应用运行在不同的进程空间内。出于安全性和隔离考虑,每个 Extension 都有独立的 UserDefaults 实例,其与宿主应用的 UserDefaults 不共享数据,即便使用相同 Bundle ID 也无法直接访问主应用的 UserDefaults 数据,更不可能直接读取到 com.apple.configuration.managed 这个特殊的配置键。

尝试通过共享 UserDefaults(suiteName:) 也不能解决问题,因为这只会创建一个 App Group 的 UserDefaults, 而非访问 MDM 管理配置,所以直接访问共享的 user default 并不会包含该配置信息。这也就是开发者尝试 UserDefaults.standard 和指定 suiteName 后仍然找不到配置的原因。

解决方法

针对这一问题,开发者通常需要使用 App Group 或通过IPC(进程间通信) 在宿主应用和扩展程序之间进行通信,从而间接获取配置信息。

1. App Group 数据共享

App Group 允许同一个开发团队的应用(包括主应用及其扩展)访问共享的存储空间,通过 App Group 创建的 UserDefaults 可以传递部分信息,当然这个信息是不包括 com.apple.configuration.managed 的,但可以在主程序获取到这个配置信息后写入到 App Group UserDefaults,Extension 中再去获取,这样也能实现获取配置数据的目的,但是务必需要关注写入时机。

实现步骤:

  1. 在 App 的 Target 和 Extension 的 Target 配置中,Signing & Capabilities 中启用 App Groups 。选择一个现有的 App Group 或创建一个新的 App Group 。

  2. 主应用(宿主 App)代码中,获取 MDM 配置,并将其复制到共享的 UserDefaults 中:

    import Foundation
    
    func getAndShareManagedConfiguration() {
         if let managedConfig = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed") {
            let sharedDefaults = UserDefaults(suiteName: "group.your.appgroup")!  // 请替换为实际的 App Group Identifier
            sharedDefaults.set(managedConfig, forKey: "sharedManagedConfig")
        }
    }
    
    

确保这个 getAndShareManagedConfiguration 在适当的时机(例如应用启动时)被调用,并注意在数据更新时,及时同步 UserDefaults

  1. 在 Extension 代码中,读取 App Group 共享的 UserDefaults:

    import Foundation
    func loadManagedConfigurationFromGroup() -> [String: Any]?{
        let sharedDefaults = UserDefaults(suiteName: "group.your.appgroup")
        guard let managedConfig = sharedDefaults?.dictionary(forKey: "sharedManagedConfig")  else {
          return nil
        }
         return managedConfig
    }
    

安全建议:

  • 不要将敏感的 MDM 配置信息直接存储在 App Group 中,可对配置进行必要的处理或筛选。
  • 对于数据共享,做好数据的过期处理和数据有效性检查,及时清除过期数据。

2. 使用 IPC (进程间通信)

除了通过 App Group 的 UserDefaults 共享,也可以利用 IPC 机制,直接让扩展程序请求主程序中的配置信息,这里我们可以使用 Open URL传递文件(ShareSheet Extension) 、 或者使用 Background Fetch/Task 结合本地存储 的方法来实现, 这里介绍Open URL 的方法。

实现步骤:

  1. 主应用程序 (宿主 App) 实现一个自定义 URL scheme。在 info.plist 中, 添加一个新的URL types , 设定IdentifierURL Schemes ,

    URL Scheme: yourAppSchema.

  2. 主应用程序(宿主 App)的代码, 需要在 AppDelegate 或场景代理中实现 URL 接收的逻辑:

    import UIKit
    
    func handleConfigurationRequest(url:URL){
    
            if let configData = UserDefaults.standard.dictionary(forKey: "com.apple.configuration.managed") {
            do{
              let data = try JSONSerialization.data(withJSONObject: configData)
    
               let pageUrl = URL(string: "youAppSchema://data="+data.base64EncodedString() )!
    
             UIApplication.shared.open(pageUrl, options: [:]) { (success) in
    
                   // callback,可以不用处理。
             }
            }catch let error{
    
             print("handle error : ", error)
    
           }
    
         }
    
    
    }
    
    
     // AppDelegate.swift 或 SceneDelegate.swift
    
     func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
    
    
    
       if url.scheme == "youAppSchema" { // check url
    
       }
    
      return true
    }
    
  3. Action Extension 通过 context.openURL(_:completionHandler:) 传递 URL,并将主程序处理完的数据在回掉处理:

import UIKit
import  UniformTypeIdentifiers

class ActionViewController: UIViewController {


  override func viewDidLoad() {
    super.viewDidLoad()

  
    let mySchemeURL = URL(string:"yourAppSchema://getMDMData")
   
     extensionContext?.open(mySchemeURL!,completionHandler: {success in

        if let config = getManageConfiguration(){
          print("Config is",config);
        } else {

          print("Failed get configuration ")
        }

        self.done()
     })


    }



  func getManageConfiguration() ->[String: Any]? {
    guard let item = self.extensionContext?.inputItems.first as? NSExtensionItem , let attachment =  item.attachments?.first else { return nil;
      }
       
      var result :[String:Any]?  = nil
          if  attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier){
                attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { item , error in
                             
                    guard let pageUrl = item as? URL  else {
                         return;
                    }
                                        
                   
                   if pageUrl.scheme == "youAppSchema"{
                       
                      let configBase64String =  pageUrl.query!.split(separator: "=").last

                     if let data = Data(base64Encoded:String( configBase64String!)  ),  let jsonData =  try?  JSONSerialization.jsonObject(with: data) as? [String: Any]{
                             
                          result =  jsonData;
                        }else {

                           result =  nil;

                         }


                       }

                  }
       }

        return  result

     }


  @IBAction func done() {
     self.extensionContext!.completeRequest(returningItems: self.extensionContext!.inputItems, completionHandler: nil)
  }

}
此示例通过 Base64 JSON 编码的数据嵌入到 URL 中进行传输,开发者需要选择合适的编码和数据处理方式,注意传输的数据大小限制,避免过度消耗系统资源。

安全建议:

  • 注意在应用主程序中,对 URL 中的参数进行验证,避免恶意请求,验证 url 的调用者。
  • 在数据传输过程中,使用安全传输方式或加密手段保护数据安全,例如 HTTPS, 对数据加密。

总结

Action Extension 直接访问 MDM 配置是一个安全风险点。开发人员应该结合 App Group 或 IPC 等方法,谨慎地将配置传递给扩展程序,始终将用户数据安全放在第一位。选择合适的方案,确保既能满足业务需求,又不会对用户隐私和安全构成威胁。