Установка профиля конфигурации на iPhone-программно
Я хотел бы отправить профиль конфигурации с моим приложением iPhone и установить его, если это необходимо.
имейте в виду, что мы говорим о профиле конфигурации, а не о профиле подготовки.
во-первых, такая задача возможна. Если вы разместите профиль конфигурации на веб-странице и нажмете на него из Safari, он будет установлен. Если вы отправите профиль по электронной почте и щелкните вложение, оно также будет установлено. "Установлен "в этом случае означает" пользовательский интерфейс установки вызвал " - но я даже не смог зайти так далеко.
поэтому я работал в соответствии с теорией, что инициирование установки профиля включает в себя переход к нему в качестве URL-адреса. Я добавил профиль в свой пакет приложений.
A) во-первых, я попытался [sharedApp openURL] с файлом:// URL в мой пакет. Нет такой удачи-ничего не происходит.
B) затем я добавил HTML-страницу в свой пакет, который имеет ссылку на профиль, и загрузил его в UIWebView. Нажатие на ссылку ничего не делает. Загрузка идентичной страницы с веб-сервера в Safari, однако, работает нормально-ссылка кликабельна, профиль устанавливается. Я предоставил UIWebViewDelegate, отвечая да на каждый запрос навигации - никакой разницы.
C) затем я попытался загрузить ту же веб - страницу из моего пакета в Safari (используя [sharedApp openURL] - ничего не происходит. Я думаю, Safari не может видеть файлы внутри моего пакета приложений.
D) загрузки страница и профиль на веб-сервере выполнимы, но боль на организационном уровне, не говоря уже о дополнительном источнике сбоев (что делать, если нет покрытия 3G? так далее.).
Итак, мой большой вопрос: * * Как установить профиль программно?
и маленькие вопросы: Что может сделать ссылку не кликабельной в UIWebView? Можно ли загрузить файл: / / URL из мой bundle в Safari? Если нет, есть ли локальное расположение на iPhone где я могу разместить файлы и Safari может найти их?
редактировать на B): проблема-то в том, что мы связываем с профилем. Я переименовал его из .mobileconfig в .xml (потому что это действительно XML), изменил ссылку. И ссылку в UIWebView. Переименовал его обратно-то же самое. Похоже, что UIWebView неохотно делает вещи для всего приложения-поскольку установка профиля закрывает приложение. Я попытался сказать ему, что все в порядке - с помощью UIWebViewDelegate - но это не убедило. Такое же поведение для mailto: URL-адреса в UIWebView.
для mailto: URL-адреса общий метод заключается в том, чтобы перевести их в вызовы [openURL], но это не совсем работает для моего случая, см. сценарий A.
для itms: url, однако, UIWebView работает так, как ожидалось...
EDIT2: пробовал подавать URL-адрес данных в Safari через [openURL] - не работает, см. здесь: iPhone Open DATA: Url In Сафари
EDIT3: найдено много информации о том, как Safari не поддерживает file:// URLs. UIWebView, однако, очень многое делает. Кроме того, Safari на симуляторе открывает их просто отлично. Последний бит является самым неприятным.
EDIT4: Я так и не нашел решения. Вместо этого я собрал двухбитный веб-интерфейс, в котором пользователи могут заказать профиль, отправленный им по электронной почте.
10 ответов:
1) Установите локальный сервер, как RoutingHTTPServer
2) настроить пользовательский заголовок :[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"];
3) Настройка локального корневого пути для файла mobileconfig (документы):
[httpServer setDocumentRoot:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
4) чтобы дать время веб-серверу отправить файл, добавьте следующее :
Appdelegate.h UIBackgroundTaskIdentifier bgTask; Appdelegate.m - (void)applicationDidEnterBackground:(UIApplication *)application { NSAssert(self->bgTask == UIBackgroundTaskInvalid, nil); bgTask = [application beginBackgroundTaskWithExpirationHandler: ^{ dispatch_async(dispatch_get_main_queue(), ^{ [application endBackgroundTask:self->bgTask]; self->bgTask = UIBackgroundTaskInvalid; }); }]; }
5) в вашем контроллере вызовите safari с именем mobileconfig, хранящимся в документах :
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]];
ответ от malinois работал для меня, но, я хотел решение, которое вернулось в приложение автоматически после того, как пользователь установил mobileconfig.
это заняло у меня 4 часа, но вот решение, построенное на идее малинуа о наличии локального http-сервера: вы возвращаете HTML в safari, который обновляет себя; в первый раз сервер возвращает mobileconfig, а во второй раз он возвращает пользовательскую url-схему, чтобы вернуться к вашему приложению. UX - это то, что я хотел: приложение вызывает safari, safari открывает mobileconfig, когда пользователь нажимает "готово" на mobileconfig, затем safari снова загружает ваше приложение (пользовательская схема url).
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. _httpServer = [[RoutingHTTPServer alloc] init]; [_httpServer setPort:8000]; // TODO: make sure this port isn't already in use _firstTime = TRUE; [_httpServer handleMethod:@"GET" withPath:@"/start" target:self selector:@selector(handleMobileconfigRootRequest:withResponse:)]; [_httpServer handleMethod:@"GET" withPath:@"/load" target:self selector:@selector(handleMobileconfigLoadRequest:withResponse:)]; NSMutableString* path = [NSMutableString stringWithString:[[NSBundle mainBundle] bundlePath]]; [path appendString:@"/your.mobileconfig"]; _mobileconfigData = [NSData dataWithContentsOfFile:path]; [_httpServer start:NULL]; return YES; } - (void)handleMobileconfigRootRequest:(RouteRequest *)request withResponse:(RouteResponse *)response { NSLog(@"handleMobileconfigRootRequest"); [response respondWithString:@"<HTML><HEAD><title>Profile Install</title>\ </HEAD><script> \ function load() { window.location.href='http://localhost:8000/load/'; } \ var int=self.setInterval(function(){load()},400); \ </script><BODY></BODY></HTML>"]; } - (void)handleMobileconfigLoadRequest:(RouteRequest *)request withResponse:(RouteResponse *)response { if( _firstTime ) { NSLog(@"handleMobileconfigLoadRequest, first time"); _firstTime = FALSE; [response setHeader:@"Content-Type" value:@"application/x-apple-aspen-config"]; [response respondWithData:_mobileconfigData]; } else { NSLog(@"handleMobileconfigLoadRequest, NOT first time"); [response setStatusCode:302]; // or 301 [response setHeader:@"Location" value:@"yourapp://custom/scheme"]; } }
... и вот код для вызова этого из приложения (т. е. viewcontroller):
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:8000/start/"]];
надеюсь, это кому-то поможет.
Я написал класс для установки файла mobileconfig через Safari, а затем вернуться к приложению. Он полагается на движок http-сервера быстрее который я обнаружил, что работает хорошо. Я хочу поделиться своим кодом ниже для этого. Он вдохновлен несколькими источниками кода, которые я нашел плавающими в www. Поэтому, если вы найдете кусочки своего собственного кода, вклады в вас.
class ConfigServer: NSObject { //TODO: Don't foget to add your custom app url scheme to info.plist if you have one! private enum ConfigState: Int { case Stopped, Ready, InstalledConfig, BackToApp } internal let listeningPort: in_port_t! = 8080 internal var configName: String! = "Profile install" private var localServer: HttpServer! private var returnURL: String! private var configData: NSData! private var serverState: ConfigState = .Stopped private var startTime: NSDate! private var registeredForNotifications = false private var backgroundTask = UIBackgroundTaskInvalid deinit { unregisterFromNotifications() } init(configData: NSData, returnURL: String) { super.init() self.returnURL = returnURL self.configData = configData localServer = HttpServer() self.setupHandlers() } //MARK:- Control functions internal func start() -> Bool { let page = self.baseURL("start/") let url: NSURL = NSURL(string: page)! if UIApplication.sharedApplication().canOpenURL(url) { var error: NSError? localServer.start(listeningPort, error: &error) if error == nil { startTime = NSDate() serverState = .Ready registerForNotifications() UIApplication.sharedApplication().openURL(url) return true } else { self.stop() } } return false } internal func stop() { if serverState != .Stopped { serverState = .Stopped unregisterFromNotifications() } } //MARK:- Private functions private func setupHandlers() { localServer["/start"] = { request in if self.serverState == .Ready { let page = self.basePage("install/") return .OK(.HTML(page)) } else { return .NotFound } } localServer["/install"] = { request in switch self.serverState { case .Stopped: return .NotFound case .Ready: self.serverState = .InstalledConfig return HttpResponse.RAW(200, "OK", ["Content-Type": "application/x-apple-aspen-config"], self.configData!) case .InstalledConfig: return .MovedPermanently(self.returnURL) case .BackToApp: let page = self.basePage(nil) return .OK(.HTML(page)) } } } private func baseURL(pathComponent: String?) -> String { var page = "http://localhost:\(listeningPort)" if let component = pathComponent { page += "/\(component)" } return page } private func basePage(pathComponent: String?) -> String { var page = "<!doctype html><html>" + "<head><meta charset='utf-8'><title>\(self.configName)</title></head>" if let component = pathComponent { let script = "function load() { window.location.href='\(self.baseURL(component))'; }window.setInterval(load, 600);" page += "<script>\(script)</script>" } page += "<body></body></html>" return page } private func returnedToApp() { if serverState != .Stopped { serverState = .BackToApp localServer.stop() } // Do whatever else you need to to } private func registerForNotifications() { if !registeredForNotifications { let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.addObserver(self, selector: "didEnterBackground:", name: UIApplicationDidEnterBackgroundNotification, object: nil) notificationCenter.addObserver(self, selector: "willEnterForeground:", name: UIApplicationWillEnterForegroundNotification, object: nil) registeredForNotifications = true } } private func unregisterFromNotifications() { if registeredForNotifications { let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.removeObserver(self, name: UIApplicationDidEnterBackgroundNotification, object: nil) notificationCenter.removeObserver(self, name: UIApplicationWillEnterForegroundNotification, object: nil) registeredForNotifications = false } } internal func didEnterBackground(notification: NSNotification) { if serverState != .Stopped { startBackgroundTask() } } internal func willEnterForeground(notification: NSNotification) { if backgroundTask != UIBackgroundTaskInvalid { stopBackgroundTask() returnedToApp() } } private func startBackgroundTask() { let application = UIApplication.sharedApplication() backgroundTask = application.beginBackgroundTaskWithExpirationHandler() { dispatch_async(dispatch_get_main_queue()) { self.stopBackgroundTask() } } } private func stopBackgroundTask() { if backgroundTask != UIBackgroundTaskInvalid { UIApplication.sharedApplication().endBackgroundTask(self.backgroundTask) backgroundTask = UIBackgroundTaskInvalid } } }
Я думаю, что вы ищете "по воздуху регистрации" с помощью простого протокола регистрации сертификатов (SCEP). Взгляните на руководство по регистрации OTA и раздел полезной нагрузки SCEP Руководство По Развертыванию Предприятия.
по словам Обзор Конфигурации Устройства у вас есть только четыре варианта:
- настольная установка через USB
- электронная почта (вложение)
- сайт (через Safari)
- регистрация и распределение по воздуху
на этой странице объясняет, как использовать изображения из вашего пакета в UIWebView.
возможно, то же самое будет работать и для профиля конфигурации.
вы пробовали просто отправить приложение пользователю профиль конфигурации при первом запуске?
-(IBAction)mailConfigProfile { MFMailComposeViewController *email = [[MFMailComposeViewController alloc] init]; email.mailComposeDelegate = self; [email setSubject:@"My App's Configuration Profile"]; NSString *filePath = [[NSBundle mainBundle] pathForResource:@"MyAppConfig" ofType:@"mobileconfig"]; NSData *configData = [NSData dataWithContentsOfFile:filePath]; [email addAttachmentData:configData mimeType:@"application/x-apple-aspen-config" fileName:@"MyAppConfig.mobileconfig"]; NSString *emailBody = @"Please tap the attachment to install the configuration profile for My App."; [email setMessageBody:emailBody isHTML:YES]; [self presentModalViewController:email animated:YES]; [email release]; }Я сделал это IBAction в случае, если вы хотите привязать его к кнопке, чтобы пользователь мог повторно отправить его себе в любое время. Обратите внимание, что я не могу иметь правильный тип MIME в примере выше, вы должны проверить это.
У меня есть другой способ, которым он может работать (к сожалению, у меня нет профиля конфигурации для тестирования):
// Create a UIViewController which contains a UIWebView - (void)viewDidLoad { [super viewDidLoad]; // Tells the webView to load the config profile [self.webView loadRequest:[NSURLRequest requestWithURL:self.cpUrl]]; } // Then in your code when you see that the profile hasn't been installed: ConfigProfileViewController *cpVC = [[ConfigProfileViewController alloc] initWithNibName:@"MobileConfigView" bundle:nil]; NSString *cpPath = [[NSBundle mainBundle] pathForResource:@"configProfileName" ofType:@".mobileconfig"]; cpVC.cpURL = [NSURL URLWithString:cpPath]; // Then if your app has a nav controller you can just push the view // on and it will load your mobile config (which should install it). [self.navigationController pushViewController:controller animated:YES]; [cpVC release];
Не уверен, зачем вам нужен профиль конфигурации, но вы можете попробовать взломать этот делегат из UIWebView:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if (navigationType == UIWebViewNavigationTypeLinkClicked) { //do something with link clicked return NO; } return YES; }
в противном случае можно включить установку с защищенного сервера.
просто разместите файл на веб-сайте с расширением *.mobileconfig и установите тип MIME в application / x-apple-aspen-config. Пользователю будет предложено, но если они принимают профиль должен быть установлен.
вы не можете установить эти профили программно.
Это отличная тема, и особенно блог упомянутые выше.
для тех, кто делает Xamarin, вот мои добавленные 2 цента. Я встроил сертификат leaf в свое приложение в качестве контента, а затем использовал следующий код для его проверки:
using Foundation; using Security; NSData data = NSData.FromFile("Leaf.cer"); SecCertificate cert = new SecCertificate(data); SecPolicy policy = SecPolicy.CreateBasicX509Policy(); SecTrust trust = new SecTrust(cert, policy); SecTrustResult result = trust.Evaluate(); return SecTrustResult.Unspecified == result; // true if installed
(человек, я люблю, как чистый этот код, против любого из языков Apple)