Install a configuration profile on iPhone - software

I would like to send a configuration profile with my iPhone application and install it if necessary.

Remember, we are talking about a configuration profile, not a provisioning profile.

Firstly, such a task is possible. If you place a configuration profile on a web page and click on it from Safari, it will be installed. If you send an email profile and click on the attachment, it will also be installed. “Installed” in this case means “The installation user interface is initialized” - but I could not even get to this.

So, I worked on the theory that initiating a profile installation involves navigating to it as a URL. I added a profile to my application package.

A) First I tried [sharedApp openURL] with file: // URL in my package. There is no such luck - nothing happens.

B) Then I added an HTML page to my package that has a profile link and uploaded it to UIWebView. Clicking on a link does nothing. However, loading an identical page from a web server in Safari works fine - the link is clickable, the profile is set. I provided a UIWebViewDelegate, responding YES to every navigation request - no difference.

C) Then I tried to load the same web page from my package into Safari (using [sharedApp openURL] - nothing happens. I think Safari cannot see the files inside my application package.

D) Loading the page and profile on the web server is feasible, but the pain is at the organizational level, not to mention an additional source of failures (what if there is no 3G coverage?).

So my big question is: ** how to programmatically set a profile?

And small questions: what can a link do without clicks in UIWebView? Can I upload a file: // URL from my package in Safari? If not, is there a local location on the iPhone where I can place the files and Safari can find them?

EDIT on B): the problem is that we are attached to the profile. I renamed it from .mobileconfig to .xml (because it is really XML), changed the link. And the link worked in my UIWebView. Renamed it back - same thing. UIWebView seems to be reluctant to run all applications - since setting a profile closes the application. I tried to say that everything was fine - using the UIWebViewDelegate - but that did not convince. This behavior is for mailto: URLs in the UIWebView interface.

For mailto URLs: the general way is to translate them into [openURL] calls, but this doesn’t quite work for my case, see scenario A.

For itms: URL, however UIWebView works as expected ...

EDIT2: tried to submit data url to Safari via [openURL] - does not work, see here: iPhone Open DATA: Url In Safari

EDIT3: Found a lot of information on how Safari does not support files: // URL. UIWebView, however, really like it. In addition, Safari on the simulator opens them just fine. The last bit is the most frustrating.




EDIT4: I did not find a solution. Instead, I put together a two-bit web interface where users can order a profile sent to them by email.

+62
iphone cocoa-touch ios-simulator configuration-files
Feb 25 '10 at 22:07
source share
10 answers

1) Install a local server, for example RoutingHTTPServer

2) Set up custom header:

[httpServer setDefaultHeader:@"Content-Type" value:@"application/x-apple-aspen-config"]; 

3) Configure the local root path for the mobileconfig file (Documents):

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

4) To give the web server time to send the file, add the following:

 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) In your controller, call a safari with the name of the mobileconfig file stored in the documents:

 [[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]]; 
+33
Mar 24 '12 at 19:44
source share

The answer from malinois worked for me, BUT, I wanted the solution returning to the application automatically after the user installed mobileconfig.

It took me 4 hours, but here is a solution based on malinois idea of ​​having a local http server: you return the HTML to the safari, updating itself; the first time the server returns the mobileconfig file, and the second time it returns the user url to return to your application. UX is what I wanted: the application calls safari, safari opens mobileconfig when the user clicks "done" on mobileconfig, then safari download your application again (custom URL scheme).

 - (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"]; } } 

... and here is the code to call this application (i.e. viewcontroller):

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

Hope this helps someone.

+23
Jan 09 '14 at 7:39
source share

I wrote a class to install the mobileconfig file through Safari and then return to the application. It relies on the Swifter http server server, which seemed to work fine. I want to share my code below for this. It is inspired by several sources of code that I found at www. Therefore, if you find fragments of your own code, contribute to you.

 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 } } } 
+5
Oct 17 '15 at 8:07
source share

I think what you are looking for is "Over the Air Enrollment" using the Simple Certificate Enrollment Protocol (SCEP). See the OTA Registration Guide and the SCEP Payload section of the Enterprise Deployment Guide .

According to the device configuration overview , you have only four options:

  • Desktop setup via USB
  • Email (attachment)
  • Website (via Safari)
  • Billing and distribution by air
+4
Apr 25 2018-10-25T00:
source share

This page explains how to use images from your package in UIWebView.

Perhaps the same will work for the configuration profile.

0
Feb 25 '10 at 22:48
source share

Have you tried just using the application to send the user a configuration profile on first run?

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

      [email setSubject: @ "My App 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];
 }

I did this IBAction if you want to bind it to a button so that the user can resend it to themselves at any time. Please note that in the above example, I may have the wrong MIME type, you must verify this.

0
Apr 21 '10 at 15:33
source share

I have another way that it can work (unfortunately, I don't have a configuration profile for testing):

 // 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];
0
Apr 22 '10 at 13:29
source share

Not sure why a configuration profile is needed, but you can try to hack this delegate from UIWebView:

 - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{ if (navigationType == UIWebViewNavigationTypeLinkClicked) { //do something with link clicked return NO; } return YES; } 

Otherwise, you can enable installation from a secure server.

0
Jan 03 2018-11-11T00:
source share

Just place the file on a website with the extension * .mobileconfig and set the MIME type to application / x-apple-aspen-config. A user will be requested, but if they agree, a profile must be established.

You cannot programmatically install these profiles.

0
Jul 14 '11 at 20:30
source share

This is a great thread, and especially the blog mentioned above .

For those who make Xamarin, here are my added 2 cents. I included the sheet certificate in my application as Content, and then used the following code to verify it:

  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 

(Man, I like how clean this code is compared to any of the Apple languages)

0
Nov 30 '16 at 5:30
source share



All Articles