iPhone에 구성 프로파일 설치-프로그래밍 방식
내 iPhone 응용 프로그램과 함께 구성 프로파일을 제공하고 필요한 경우 설치하고 싶습니다.
우리는 프로비저닝 프로파일이 아니라 구성 프로파일에 대해 이야기하고 있습니다.
우선 이러한 작업이 가능합니다. 웹 페이지에 구성 프로파일을 배치하고 Safari에서 클릭하면 설치됩니다. 프로필을 이메일로 보내고 첨부 파일을 클릭하면 설치됩니다. 이 경우 "설치됨"은 "설치 UI가 호출 됨"을 의미합니다.하지만 그 정도까지는 갈 수 없었습니다.
그래서 저는 프로필 설치를 시작하는 데 URL로 이동하는 것이 포함된다는 이론에 따라 작업했습니다. 내 앱 번들에 프로필을 추가했습니다.
A) 먼저 내 번들에 file : // URL로 [sharedApp openURL]을 시도했습니다. 그런 행운이 없습니다-아무 일도 일어나지 않습니다.
B) 그런 다음 프로필에 대한 링크가있는 내 번들에 HTML 페이지를 추가하고 UIWebView에로드했습니다. 링크를 클릭해도 아무 효과가 없습니다. 그러나 Safari의 웹 서버에서 동일한 페이지를로드하는 것은 정상적으로 작동합니다. 링크를 클릭 할 수 있고 프로필이 설치됩니다. UIWebViewDelegate를 제공하여 모든 탐색 요청에 대해 YES라고 대답했습니다. 차이는 없습니다.
C) 그런 다음 Safari의 번들에서 동일한 웹 페이지를로드하려고했습니다 ([sharedApp openURL] 사용-아무 일도 일어나지 않습니다. Safari가 내 앱 번들 내의 파일을 볼 수없는 것 같습니다.
D) 웹 서버에 페이지와 프로필을 업로드하는 것은 가능하지만 조직 수준에서 고통을 겪습니다. 추가 실패 원인은 말할 것도 없습니다 (3G 서비스 범위가 없다면 어떨까요? 등).
그래서 내 큰 질문은 ** 프로그래밍 방식으로 프로필을 어떻게 설치합니까?
그리고 작은 질문은 다음과 같습니다. UIWebView 내에서 링크를 클릭 할 수 없게 만드는 것은 무엇입니까? // URL을에서 : 그것은 파일을로드 할 수 있습니다 내 Safari에서 번들? 그렇지 않은 경우 iPhone에 파일을 저장할 수 있고 Safari에서 찾을 수있는 로컬 위치가 있습니까?
편집 B) : 문제는 우리가 프로필에 연결되어 있다는 사실에 있습니다. .mobileconfig에서 .xml로 이름을 바꾸고 (실제로 XML이기 때문에) 링크를 변경했습니다. 그리고 링크는 내 UIWebView에서 작동했습니다. 다시 이름을 변경했습니다. 프로필을 설치하면 앱이 닫히기 때문에 UIWebView가 응용 프로그램 전체의 작업을 꺼리는 것처럼 보입니다. UIWebViewDelegate를 통해 괜찮다고 말했지만 확신하지 못했습니다. mailto : UIWebView 내의 URL에 대해 동일한 동작.
mailto : URL의 경우 일반적인 기술은 [openURL] 호출로 변환하는 것이지만 제 경우에는 작동하지 않습니다. 시나리오 A를 참조하십시오.
itms : URL, 그러나 UIWebView는 예상대로 작동합니다.
EDIT2 : [openURL]을 통해 Safari에 데이터 URL을 공급하려고했습니다 . 작동하지 않습니다. 여기를 참조하십시오. iPhone Open DATA : Safari의 Url
EDIT3 : Safari가 file : // URL을 지원하지 않는 방법에 대한 많은 정보를 찾았습니다. 그러나 UIWebView는 매우 많습니다. 또한 시뮬레이터의 Safari는 정상적으로 열립니다. 후자의 비트가 가장 실망 스럽습니다.
EDIT4 : 해결책을 찾지 못했습니다. 대신 사용자가 이메일로받은 프로필을 주문할 수있는 2 비트 웹 인터페이스를 구성했습니다.
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) 컨트롤러에서 Documents에 저장된 mobileconfig 이름으로 safari를 호출하십시오.
[[UIApplication sharedApplication] openURL:[NSURL URLWithString: @"http://localhost:12345/MyProfile.mobileconfig"]];
malinois의 대답은 저에게 효과적이지만 사용자가 mobileconfig를 설치 한 후 자동으로 앱으로 돌아 오는 솔루션을 원했습니다.
4 시간이 걸렸지 만 여기에 로컬 http 서버가 있다는 malinois의 아이디어를 기반으로 구축 된 솔루션이 있습니다. HTML을 사파리로 반환하여 자체적으로 새로 고쳐집니다. 서버가 처음으로 mobileconfig를 반환하고 두 번째로 사용자 지정 url-scheme을 반환하여 앱으로 돌아갑니다. 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/"]];
이것이 누군가를 돕기를 바랍니다.
Safari를 통해 mobileconfig 파일을 설치 한 다음 앱으로 돌아가는 클래스를 작성했습니다. 그것은 내가 잘 작동하는 것으로 밝혀진 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 (Simple Certificate Enrollment Protocol)를 사용하는 "Over the Air Enrollment"라고 생각합니다. OTA 등록 가이드 및 엔터프라이즈 배포 가이드 의 SCEP 페이로드 섹션을 살펴보십시오 .
According to the Device Config Overview you only have four options:
- Desktop installation via USB
- Email (attachment)
- Website (via Safari)
- Over-the-Air Enrollment and Distribution
This page explains how to use images from your bundle in a UIWebView.
Perhaps the same would work for a configuration profile as well.
Have you tried just having the app mail the user the config profile the first time it starts up?
-(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]; }
I made it an IBAction in case you want to tie it to a button so the user can re-send it to themselves at any time. Note that I may not have the correct MIME type in the example above, you should verify that.
I've though of another way in which it might work (unfortunately I don't have a configuration profile to test out with):
// 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];
Not sure why you need a configuration profile, but you can try to hack with this delegate from the 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 may consider enable the installation from a secure server.
Just host the file on a website with the extension *.mobileconfig and set the MIME type to application/x-apple-aspen-config. The user will be prompted, but if they accept the profile should be installed.
You cannot install these profiles programmatically.
This is a great thread, and especially the blog mentioned above.
For those doing Xamarin, here's my added 2 cents. I embedded the leaf cert in my app as Content, then used the following code to check 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 love how clean that code is, vs. either of Apple's languages)
'Nice programing' 카테고리의 다른 글
Python : TypeError : 해시 할 수없는 유형 : 'list' (0) | 2020.10.27 |
---|---|
UUID 형식 : 8-4-4-4-12-왜? (0) | 2020.10.27 |
iPhone : WWDR 중간 인증서 란 무엇입니까? (0) | 2020.10.27 |
스톱워치 벤치마킹이 허용됩니까? (0) | 2020.10.27 |
Visual Studio : LINK : 치명적인 오류 LNK1181 : 입력 파일을 열 수 없습니다. (0) | 2020.10.27 |