it-swarm.com.de

Konfigurationsprofil auf dem iPhone installieren - programmgesteuert

Ich möchte ein Konfigurationsprofil mit meiner iPhone-Anwendung versenden und es bei Bedarf installieren. 

Wohlgemerkt, wir reden hier 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 versenden und auf den Anhang klicken, wird dieser ebenfalls installiert. "Installiert" bedeutet in diesem Fall "Die Installationsbenutzeroberfläche wird aufgerufen" - aber so weit konnte ich nicht kommen.

Ich arbeitete also mit der Theorie, dass das Initiieren einer Profilinstallation die Navigation als URL erfordert. Ich habe das Profil zu meinem App-Bundle hinzugefügt.

A) Zuerst habe ich [sharedApp openURL] mit der file: // URL in meinem Bundle ausprobiert. Kein solches Glück - nichts passiert.

B) Ich fügte meinem Bundle dann eine HTML-Seite hinzu, die einen Link zum Profil hat, und lud sie in ein UIWebView. Ein Klick auf den Link bewirkt 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 antwortete - kein Unterschied.

C) Dann habe ich versucht, dieselbe Webseite aus meinem Bundle in Safari zu laden (mit [sharedApp openURL] - es passiert nichts. Ich vermute, Safari kann keine Dateien in meinem App-Paket sehen.

D) Das Hochladen der Seite und des Profils auf einen Webserver ist machbar, aber auf organisatorischer Ebene ein Problem, ganz zu schweigen von einer zusätzlichen Fehlerquelle (was ist, wenn keine 3G-Abdeckung besteht? Usw.).

Meine große Frage ist also: Wie installiere ich ein Profil programmgesteuert?

Und die kleinen Fragen sind: Was kann einen Link innerhalb einer UIWebView nicht anklickbar machen? Ist es möglich, eine Datei: // URL aus dem Paket my in Safari zu laden? Wenn nicht, gibt es auf dem iPhone einen lokalen Ort, an dem ich Dateien ablegen kann und Safari sie finden kann?

EDIT on B): Das Problem liegt irgendwie darin, dass wir auf ein Profil verlinken. Ich habe es von .mobileconfig in .xml umbenannt (weil es wirklich XML ist), den Link geändert. Und der Link funktionierte in meiner UIWebView. Umbenannt es wieder - das gleiche Zeug. Es sieht so aus, als würde UIWebView nur ungern anwendungsübergreifend vorgehen, da durch die Installation des Profils die App geschlossen wird. Ich habe versucht zu sagen, dass es OK ist - mittels UIWebViewDelegate - aber das hat nicht überzeugt. Gleiches Verhalten für mailto: URLs in UIWebView.

Für mailto: URLs ist die übliche Technik, sie in [openURL] -Aufrufe zu übersetzen. Dies funktioniert jedoch in meinem Fall nicht ganz, siehe Szenario A.

Bei itms: URLs funktioniert UIWebView jedoch wie erwartet ...

EDIT2: hat versucht, eine Daten-URL über [openURL] an Safari zu übergeben - funktioniert nicht, siehe hier: iPhone Open DATA: URL in Safari

EDIT3: hat viele Informationen darüber gefunden, wie Safari URLs von file: // nicht unterstützt. UIWebView macht jedoch sehr viel. Außerdem öffnen Safari auf dem Simulator sie gut. Letzteres ist am frustrierendsten.


EDIT4: Ich habe nie eine Lösung gefunden. Stattdessen habe ich eine Zwei-Bit-Weboberfläche zusammengestellt, in der die Benutzer das per E-Mail versendete Profil bestellen können.

69
Seva Alekseyev

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 (Documents):

[httpServer setDocumentRoot:[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0]];

4) Um dem Webserver Zeit zu gewähren, um die Datei zu senden, fügen Sie Folgendes hinzu:

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 mobileconfig an, die in Documents gespeichert ist:

[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]];
36
malinois

Die Antwort von Malinois funktionierte für mich, ABER, ich wollte eine Lösung, die automatisch zur App kam, nachdem der Benutzer die mobileconfig installiert hatte.

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 kehren HTML zu Safari zurück, die sich selbst aktualisiert; Das erste Mal, wenn der Server die mobileconfig zurückgibt, und das zweite Mal das benutzerdefinierte URL-Schema, um zu Ihrer App zurückzukehren. Die UX ist das, was ich wollte: Die App ruft Safari an, Safari öffnet mobileconfig, wenn der Benutzer bei mobileconfig "done" drückt, 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, den Sie aus der App heraus aufrufen können (zB viewcontroller):

[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:8000/start/"]];

Hoffe das hilft jemandem.

27
xaphod

Ich habe eine Klasse geschrieben, in der Sie eine mobileconfig-Datei über Safari installieren und dann zur App zurückkehren können. Es basiert auf der http-Server-Engine Swifter , für die ich herausfand, dass sie gut funktioniert. Ich möchte meinen Code hier unten freigeben. Es ist von mehreren Codequellen inspiriert, die ich im Internet gefunden habe. Wenn du also Teile deines eigenen Codes findest, Beiträge an dich.

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
        }
    }
}
6
freshking

Ich denke, was Sie suchen, ist "Over the Air Enrollment" unter Verwendung des Simple Certificate Enrollment Protocol (SCEP). Sehen Sie sich den OTA Enrollment Guide und den Abschnitt SCEP Payload des Enterprise Deployment Guide an.

Entsprechend der Gerätekonfigurationsübersicht haben Sie nur vier Möglichkeiten:

  • Desktop-Installation über USB
  • Email Anhang)
  • Website (über Safari)
  • Over-the-Air-Registrierung und -Verteilung
4
slf

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, das Profil zu installieren, wenn er damit einverstanden ist.

Sie können diese Profile nicht programmgesteuert installieren.

0
Brandon

Dies ist ein großartiger Thread und vor allem der Blog oben erwähnt .

Für diejenigen, die Xamarin machen, hier sind meine zusätzlichen 2 Cent. Ich habe das leaf cert in meine App als Content eingebettet und dann den folgenden Code verwendet, um es zu überprüfen:

        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 der Code ist, verglichen mit den Sprachen von Apple)

0
Eliot Gillum

Sie sind sich nicht sicher, warum Sie ein Konfigurationsprofil benötigen, aber Sie können versuchen, mit diesem Delegierten von der UIWebView aus 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.

0
Hoang Pham

Ich habe zwar eine andere Möglichkeit, wie es funktionieren könnte (leider habe ich kein Konfigurationsprofil zum Testen):

 // Erstellen Sie einen UIViewController, der eine UIWebView enthält 
- (void) viewDidLoad {
 [super viewDidLoad]; 
 // Weist das WebView an, das Konfigurationsprofil zu laden 
 [self.webView loadRequest: [NSURLRequest requestWithURL: self.cpUrl]]; 
} 

 // Wenn Sie in Ihrem Code sehen, dass das Profil nicht installiert wurde: 
 ConfigProfileViewController * cpVC = 
 [[ConfigProfileViewController-Zuordnung] initWithNibName: @ "MobileConfigView" 
 bundle: nil]; 
 NSString * cpPath = [[NSBundle mainBundle] pathForResource: @ "configProfileName" 
 ofType: @ ". mobileconfig"]; 
 cpVC.cpURL = [NSURL URLWithString: cpPath]; 
 Wenn Ihre App über einen Nav-Controller verfügt, können Sie einfach die Ansicht 
 // und dann drücken wird Ihre mobile Konfiguration laden (die installiert werden sollte) 
 [self.navigationController pushViewController: Controller animated: YES]; 
 [cpVC release]; 
0
smountcastle

Haben Sie gerade versucht, dass die App dem Benutzer beim ersten Start das Konfigurationsprofil sendet?

- (IBAction) mailConfigProfile {
 MFMailComposeViewController * email = [[Zuweisung von MFMailComposeViewController] init]; 
 email.mailComposeDelegate = self; 

 [email setSubject: @ "Konfigurationsprofil meiner App"]; 

 NSString * filePath = [[NSBundle mainBundle] pathForResource: @ "MyAppConfig" ofType: @ "mobileconfig"]; 
 NSData * configData = [NSData dataWithContentsOfFile: filePath]; 
 [email addAttachmentData: configData mimeType: @ "application/x-Apple-aspen-config" Dateiname: @ "MyAppConfig.mobileconfig"]; 

 NSString * emailBody = @ "Bitte tippen Sie auf den Anhang, um das Konfigurationsprofil für Meine App zu installieren."; 
 [email setMessageBody: emailBody isHTML: YES]; 

 [self presentModalViewController: E-Mail animiert: JA]; 
 [E-Mail-Freigabe]; 
} 

Ich habe es zu einer IBAction gemacht, falls Sie es an einen Button binden möchten, damit der Benutzer es jederzeit wieder 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.

0
smountcastle

Diese Seite erläutert die Verwendung von Bildern aus Ihrem Bundle in einer UIWebView.

Möglicherweise funktioniert das gleiche auch für ein Konfigurationsprofil.

0
Jon-Eric