Ich möchte ein Konfigurationsprofil mit meiner iPhone-Anwendung versenden und bei Bedarf installieren.
Wir sprechen von einem Konfigurationsprofil, nicht von einem Bereitstellungsprofil.
Zunächst einmal ist eine solche Aufgabe möglich. Wenn Sie ein Konfigurationsprofil auf einer Webseite platzieren und in Safari darauf klicken, wird es installiert. Wenn Sie ein Profil per E-Mail senden und auf den Anhang klicken, wird es ebenfalls installiert. "Installiert" bedeutet in diesem Fall "Die Installationsoberfläche wird aufgerufen" - aber ich konnte nicht einmal so weit kommen.
Ich arbeitete also unter der Theorie, dass das Initiieren einer Profilinstallation das Navigieren als URL umfasst. Ich habe das Profil meinem App-Bundle hinzugefügt.
A) Zuerst habe ich [sharedApp openURL] mit der Datei: // URL in meinem Bundle ausprobiert. Kein solches Glück - nichts passiert.
B) Ich habe dann meinem Bundle eine HTML-Seite hinzugefügt, die einen Link zum Profil enthält, und diese in eine UIWebView geladen. Ein Klick auf den Link führt zu nichts. Das Laden einer identischen Seite von einem Webserver in Safari funktioniert jedoch einwandfrei - der Link ist anklickbar, das Profil wird installiert. Ich habe ein UIWebViewDelegate bereitgestellt, das auf jede Navigationsanforderung mit JA geantwortet hat - kein Unterschied.
C) Dann habe ich versucht, dieselbe Webseite aus meinem Bundle in Safari zu laden (mit [sharedApp openURL] - nichts passiert. Ich denke, Safari kann keine Dateien in meinem App-Bundle sehen.
D) Das Hochladen der Seite und des Profils auf einen Webserver ist machbar, aber ein Problem auf organisatorischer Ebene, ganz zu schweigen von einer zusätzlichen Fehlerquelle (was ist, wenn keine 3G-Abdeckung vorhanden ist? Usw.).
Meine große Frage lautet also: ** Wie installiere ich ein Profil programmgesteuert?
Und die kleinen Fragen sind: Was kann dazu führen, dass ein Link in einer UIWebView nicht anklickbar ist? Ist es möglich, eine Datei: // URL aus meinem Bundle in Safari zu laden ? Wenn nicht, gibt es einen lokalen Speicherort auf dem iPhone, an dem ich Dateien ablegen und Safari sie finden kann?
BEARBEITEN auf B): Das Problem liegt irgendwie in der Tatsache, dass wir auf ein Profil verlinken. Ich habe es von .mobileconfig in .xml umbenannt (weil es wirklich XML ist) und den Link geändert. Und der Link funktionierte in meinem UIWebView. Umbenannt zurück - das gleiche Zeug. Es sieht so aus, als würde UIWebView nur ungern anwendungsweite Aufgaben ausführen - da die Installation des Profils die App schließt. Ich habe versucht zu sagen, dass es in Ordnung ist - mittels UIWebViewDelegate - aber das hat nicht überzeugt. Gleiches Verhalten für mailto: URLs in UIWebView.
Für mailto: URLs besteht die übliche Technik darin, sie in [openURL] -Aufrufe zu übersetzen, aber das funktioniert in meinem Fall nicht ganz, siehe Szenario A.
Für itms: URLs funktioniert UIWebView jedoch wie erwartet ...
EDIT2: Es wurde versucht, eine Daten-URL über [openURL] an Safari weiterzuleiten - funktioniert nicht, siehe hier: iPhone Open DATA: Url In Safari
EDIT3: Es wurden viele Informationen darüber gefunden, wie Safari file: // URLs nicht unterstützt. UIWebView macht jedoch sehr viel. Auch Safari auf dem Simulator öffnet sie ganz gut. Das letztere ist das frustrierendste.
EDIT4: Ich habe nie eine Lösung gefunden. Stattdessen habe ich eine Zwei-Bit-Weboberfläche zusammengestellt, über die die Benutzer das per E-Mail an sie gesendete Profil bestellen können.
quelle
Antworten:
1) Installieren Sie einen lokalen Server wie RoutingHTTPServer.
2) Konfigurieren Sie den benutzerdefinierten Header:
[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"];
3) Konfigurieren Sie den lokalen Stammpfad für die mobileconfig-Datei (Dokumente):
[httpServer setDocumentRoot:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];
4) Fügen Sie Folgendes hinzu, damit der Webserver Zeit zum Senden der Datei hat:
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) Rufen Sie in Ihrem Controller die Safari mit dem Namen der in Documents gespeicherten mobileconfig auf:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]];
quelle
Die Antwort von malinois hat für mich funktioniert, ABER ich wollte eine Lösung, die automatisch zur App zurückkehrt, nachdem der Benutzer die mobileconfig installiert hat.
Ich habe 4 Stunden gebraucht, aber hier ist die Lösung, die auf der Idee von Malinois basiert, einen lokalen http-Server zu haben: Sie geben HTML an Safari zurück, das sich selbst aktualisiert. Das erste Mal, wenn der Server die mobileconfig zurückgibt, und das zweite Mal, wenn er das benutzerdefinierte URL-Schema zurückgibt, um zu Ihrer App zurückzukehren. Die UX ist das, was ich wollte: Die App ruft Safari auf, Safari öffnet mobileconfig, wenn der Benutzer auf mobileconfig auf "erledigt" klickt, lädt safari Ihre App erneut (benutzerdefiniertes URL-Schema).
- (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"]; } }
... und hier ist der Code, um dies von der App (dh Viewcontroller) aus aufzurufen:
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:8000/start/"]];
Hoffe das hilft jemandem.
quelle
Ich habe eine Klasse geschrieben, um eine mobileconfig-Datei über Safari zu installieren und dann zur App zurückzukehren. Es basiert auf der http Server Engine Swifter, die ich als gut funktionierend empfunden habe. Ich möchte meinen Code unten dafür teilen. Es ist inspiriert von mehreren Codequellen, die ich im WWW gefunden habe. Wenn Sie also Teile Ihres eigenen Codes finden, Beiträge zu Ihnen.
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 } } }
quelle
Ich denke, was Sie suchen, ist "Over the Air Enrollment" mit dem Simple Certificate Enrollment Protocol (SCEP). Lesen Sie den OTA-Registrierungsleitfaden und den Abschnitt SCEP Payload des Enterprise Deployment Guide .
Laut der Gerätekonfigurationsübersicht haben Sie nur vier Optionen:
quelle
Haben Sie versucht, dass die App dem Benutzer beim ersten Start das Konfigurationsprofil per E-Mail sendet?
Ich habe es zu einer IBAction gemacht, falls Sie es an eine Schaltfläche binden möchten, damit der Benutzer es jederzeit erneut an sich selbst senden kann. Beachten Sie, dass ich im obigen Beispiel möglicherweise nicht den richtigen MIME-Typ habe. Sie sollten dies überprüfen.
quelle
Hosten Sie die Datei einfach auf einer Website mit der Erweiterung * .mobileconfig und setzen Sie den MIME-Typ auf application / x-apple-aspen-config. Der Benutzer wird aufgefordert, aber wenn er akzeptiert, sollte das Profil installiert werden.
Sie können diese Profile nicht programmgesteuert installieren.
quelle
Auf dieser Seite wird erläutert, wie Sie Bilder aus Ihrem Bundle in einer UIWebView verwenden.
Vielleicht würde das auch für ein Konfigurationsprofil funktionieren.
quelle
Ich habe mir eine andere Möglichkeit überlegt, wie es funktionieren könnte (leider habe ich kein Konfigurationsprofil zum Testen):
quelle
Sie sind sich nicht sicher, warum Sie ein Konfigurationsprofil benötigen, können jedoch versuchen, mit diesem Delegaten aus der UIWebView zu hacken:
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if (navigationType == UIWebViewNavigationTypeLinkClicked) { //do something with link clicked return NO; } return YES; }
Andernfalls können Sie die Installation von einem sicheren Server aus aktivieren.
quelle
Dies ist ein großartiger Thread und insbesondere der oben erwähnte Blog .
Für diejenigen, die Xamarin machen, sind hier meine 2 Cent. Ich habe das Blattzertifikat als Inhalt in meine App eingebettet und es dann mit dem folgenden Code überprüft:
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
(Mann, ich liebe es, wie sauber dieser Code im Vergleich zu einer der Apple-Sprachen ist)
quelle