Установка профиля конфигурации на iPhone - программно
Я хотел бы отправить профиль конфигурации с моим приложением для iPhone и установить его при необходимости.
Имейте в виду, мы говорим о профиле конфигурации, а не о профиле обеспечения.
Во-первых, такая задача возможна. Если вы разместите профиль конфигурации на веб-странице и щелкните по нему в Safari, он будет установлен. Если вы отправите профиль по электронной почте и нажмете вложение, оно также будет установлено. "Установлено" в данном случае означает "Пользовательский интерфейс установки вызывается" - но я даже не смог зайти так далеко.
Поэтому я работал в соответствии с теорией, согласно которой установка профиля требует перехода к нему в виде URL-адреса. Я добавил профиль в свой пакет приложений.
A) Сначала я попробовал [sharedApp openURL] с URL-адресом file: // в моем комплекте. Нет такой удачи - ничего не происходит.
Б) Затем я добавил HTML-страницу в свой пакет, которая имеет ссылку на профиль, и загрузил ее в UIWebView. Нажатие на ссылку ничего не делает. Загрузка идентичной страницы с веб-сервера в Safari, однако, работает нормально - ссылка кликабельна, профиль устанавливается. Я предоставил UIWebViewDelegate, отвечая ДА на каждый запрос навигации - без разницы.
C) Затем я попытался загрузить ту же самую веб-страницу из моего пакета в Safari (используя [sharedApp openURL] - ничего не происходит. Я думаю, Safari не может видеть файлы в моем комплекте приложений.
D) Загрузка страницы и профиля на веб-сервер выполнима, но на организационном уровне это боль, не говоря уже о дополнительном источнике сбоев (что если нет покрытия 3G? И т. Д.).
Итак, мой большой вопрос: ** как мне установить профиль программно?
И маленькие вопросы: что может сделать ссылку недоступной для клика в UIWebView? Можно ли загрузить файл:// URL из моего пакета в Safari? Если нет, есть ли локальное местоположение на iPhone, где я могу разместить файлы, и Safari может их найти?
РЕДАКТИРОВАТЬ на B): проблема заключается в том, что мы ссылаемся на профиль. Я переименовал его из.mobileconfig в.xml (потому что это действительно XML), изменил ссылку. И ссылка работала в моем UIWebView. Переименовал обратно - тоже самое. Похоже, что UIWebView неохотно делает вещи для всего приложения - так как установка профиля закрывает приложение. Я пытался сказать, что все в порядке - с помощью UIWebViewDelegate - но это не убедило. Такое же поведение для mailto: URL в UIWebView.
Для адресов mailto: URL общепринятым методом является перевод их в вызовы [openURL], но это не совсем подходит для моего случая, см. Сценарий А.
Для itms: URL, однако, UIWebView работает как положено...
EDIT2: попытался передать URL-адрес данных в Safari через [openURL] - не работает, см. Здесь: iPhone Открыть DATA: URL в Safari
EDIT3: нашел много информации о том, как Safari не поддерживает file:// URL. UIWebView, однако, очень много делает. Также в Safari на симуляторе их открывают просто отлично. Последний бит является самым расстраивающим.
EDIT4: я так и не нашел решение. Вместо этого я собрал двухбитный веб-интерфейс, где пользователи могут заказать профиль, отправленный им по электронной почте.
10 ответов
1) Установите локальный сервер, например RoutingHTTPServer
2) Настройте пользовательский заголовок:
[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"];
3) Настройте локальный корневой путь для файла mobileconfig (Documents):
[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, хранящимся в Documents:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]];
Ответ от Malinois работал для меня, НО, я хотел решение, которое вернулось к приложению автоматически после того, как пользователь установил mobileconfig.
Это заняло у меня 4 часа, но вот решение, основанное на идее малинуа о наличии локального http-сервера: вы возвращаете HTML в safari, который обновляется; первый раз, когда сервер возвращает mobileconfig, и второй раз, когда он возвращает пользовательскую схему URL, чтобы вернуться в ваше приложение. UX - это то, что я хотел: приложение вызывает safari, safari открывает mobileconfig, когда пользователь нажимает "done" на 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-сервера Swifter. Я хочу поделиться своим кодом ниже для этого. Он вдохновлен множественными источниками кода, которые я нашел плавающими на 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)
- Регистрация по воздуху и распределение
Пытались ли вы, просто чтобы приложение отправляло пользователю по почте профиль конфигурации при первом запуске?
- (IBAction) mailConfigProfile { MFMailComposeViewController * email = [[MFMailComposeViewController alloc] init]; email.mailComposeDelegate = self; [email setSubject: @ "Профиль конфигурации моего приложения"]; 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 = @"Пожалуйста, нажмите на вложение, чтобы установить профиль конфигурации для Моего приложения."; [email setMessageBody:emailBody isHTML:YES]; [self presentModalViewController: анимированная электронная почта: ДА]; [релиз по электронной почте]; }
Я сделал это IBAction на тот случай, если вы захотите привязать его к кнопке, чтобы пользователь мог отправить его себе в любое время. Обратите внимание, что в приведенном выше примере у меня может быть неправильный тип MIME, вам следует это проверить.
Просто разместите файл на веб-сайте с расширением *.mobileconfig и установите тип MIME для application/x-apple-aspen-config. Пользователю будет предложено, но если они принимают профиль должен быть установлен.
Вы не можете установить эти профили программно.
У меня есть еще один способ, которым он может работать (к сожалению, у меня нет профиля конфигурации для тестирования):
// Создаем UIViewController, который содержит UIWebView - (void)viewDidLoad { [super viewDidLoad]; // Сообщает webView загрузить профиль конфигурации [self.webView loadRequest:[NSURLRequest requestWithURL:self.cpUrl]]; } // Затем в вашем коде, когда вы видите, что профиль не был установлен: ConfigProfileViewController *cpVC = [[ConfigProfileViewController alloc] initWithNibName:@"MobileConfigView" расслоение: ноль]; NSString *cpPath = [[NSBundle mainBundle] pathForResource:@"configProfileName" OfType:@"mobileconfig"]; cpVC.cpURL = [NSURL URLWithString:cpPath]; // Затем, если в вашем приложении есть навигационный контроллер, вы можете просто нажать на представление // и он загрузит ваш мобильный конфиг (который должен его установить). [self.navigationController pushViewController: контроллер анимирован: ДА]; [релиз cpVC];
Не знаю, зачем вам нужен профиль конфигурации, но вы можете попробовать взломать этот делегат из UIWebView:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{
if (navigationType == UIWebViewNavigationTypeLinkClicked) {
//do something with link clicked
return NO;
}
return YES;
}
В противном случае вы можете включить установку с безопасного сервера.
Это отличная тема, особенно блог, упомянутый выше.
Для тех, кто делает Xamarin, вот мои добавленные 2 цента. Я встроил листовой сертификат в свое приложение как Контент, а затем использовал следующий код для его проверки:
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)
На этой странице объясняется, как использовать изображения из вашего пакета в UIWebView.
Возможно, то же самое будет работать и для профиля конфигурации.