Can't create IPSEC connection using NEVPNManager on iOS - objective-c

I'm trying to create an IPSEC VPN connection in my iOS app.
My code for setting up the configuration looks like this:
-(void)setUpConfig
NEVPNManager *manager = [NEVPNManager sharedManager];
int status = manager.connection.status;
if (status == NEVPNStatusConnected) {
manager.connection stopVPNTunnel];
} else {
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
NSError *startError;
if (error) {
NSLog(#"Load config failed [%#]", error.localizedDescription);
return;
}
NEVPNProtocolIPSec *p = (NEVPNProtocolIPSec *)self.manager.protocol;
if (!p) {
p = [[NEVPNProtocolIPSec alloc] init];
}
NSString *filePath = [[NSBundle mainBundle] pathForResource:#"base64_encoded_cert" ofType:#"txt"];
NSString *certBase64String = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:NULL];
NSString *certPassword = #"cert_import_password";
NSString *vpnUsername = #"myUsername";
NSString *vpnPassword = #"myPassword";
NSString *url = #"my.server.address";
// This saves my credentials to the keychain and returns a persistent keychain reference
NSData *passRef = [self addVPNCredentialsToKeychain:vpnUsername withPassword:vpnPassword];
p.username = vpnUsername;
p.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
p.serverAddress = url;
p.passwordReference = passRef;
p.identityData = [NSData dataWithBase64EncodedString:certBase64String];
p.identityDataPassword = certPassword;
p.disconnectOnSleep = NO;
p.useExtendedAuthentication = YES;
[manager setProtocol:p];
[manager setOnDemandEnabled:NO];
[manager setLocalizedDescription:#"My VPN"];
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if(error) {
NSLog(#"Save error: %#", error);
} else {
NSLog(#"Saved!");
}
}];
}];
}
}
-(NSData*)addVPNCredentialsToKeychain:(NSString*)username withPassword:(NSString*)password
{
NSMutableDictionary *keychainItem = [NSMutableDictionary dictionary];
NSData *encodedIdentifier = [username dataUsingEncoding:NSUTF8StringEncoding];
keychainItem[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword;
keychainItem[(__bridge id)kSecAttrDescription] = #"A password used to authenticate on a VPN server";
keychainItem[(__bridge id)kSecAttrGeneric] = encodedIdentifier;
keychainItem[(__bridge id)kSecAttrAccount] = encodedIdentifier;
keychainItem[(__bridge id)kSecAttrService] = [[NSBundle mainBundle] bundleIdentifier];
keychainItem[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitOne;
keychainItem[(__bridge id)kSecReturnPersistentRef] = #YES;
CFTypeRef typeResult = nil;
OSStatus sts = SecItemCopyMatching((__bridge CFDictionaryRef)keychainItem, &typeResult);
NSLog(#"Error Code: %d", (int)sts);
if(sts == noErr) {
NSData *theReference = (__bridge NSData *)typeResult;
return theReference;
} else {
keychainItem[(__bridge id)kSecValueData] = [password dataUsingEncoding:NSUTF8StringEncoding]; //Our password
OSStatus sts = SecItemAdd((__bridge CFDictionaryRef)keychainItem, &typeResult);
NSLog(#"Error Code: %d", (int)sts);
NSData *theReference = (__bridge NSData *)(typeResult);
return theReference;
}
return nil;
}
I then attempt to open a VPN connection like so:
-(void)connect
NEVPNManager *manager = [NEVPNManager sharedManager];
[manager loadFromPreferencesWithCompletionHandler:^(NSError *error) {
NSError *startError;
[manager.connection startVPNTunnelAndReturnError:&startError];
if(startError) {
NSLog(#"Start error: %#", startError.localizedDescription);
}
}];
}
And this does work under most conditions. The problem I am experiencing is that after factory restoring a device (on iOS 8, of course), and attempting to go through this setup and connection, my profile installs just fine, but the VPN fails to connect. In fact, my interpretation is that it is failing to attempt to connect.
After factory restoring a device and attempting to connect using my method, the following shows up in the device logs:
<Notice>: NESMLegacySession[MyVPN:BB73C098-B22E-46D3-9491-2A6D9F559F8F]: Received a start command from VPNApp[256], but start was rejected
Going into the Settings app and attempting to manually toggle the VPN using the switch under "Bluetooth" results in the switch turning on for a split second and then immediately going by to off. In this case, the following log is produced:
<Warning>: -[VPNBundleController _vpnNetworkingIsDisabled]: Airplane mode: 0, WiFi Enabled: 1
In both cases, no error dialog is produced when the VPN fails to start connecting - just the logs.
I can get around this problem by navigating to Settings > General > VPN. Once having just gone to that page (i.e. Not toggling VPN there), I can then control VPN just fine. Even going to that page before a VPN configuration is even installed results in me being able to connect just fine after installing a configuration.
My goal is to be able to start the VPN connection without having to first go to that VPN page in Settings. Can anyone shed some light on the situation? It seems to me like I'm missing something to first enable VPN connections.

This appears because VPN configuration stayed disabled by default for initial VPN connection.
You must enable VPN before saveToPreferencesWithCompletionHandler.
[[NEVPNManager sharedManager] setEnabled:YES];
Example:
[[NEVPNManager sharedManager] loadFromPreferencesWithCompletionHandler: ^(NSError *error) {
if (error) {
NSLog(#"Load error: %#", error);
}
else {
// No errors! The rest of your codes goes here...
NEVPNProtocolIPSec *p = [[NEVPNProtocolIPSec alloc] init];
p.serverAddress = #"VPN SERVER ADDRESS";
p.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
p.localIdentifier = #"Local identifier";
p.remoteIdentifier = #"Remote identifier";
p.useExtendedAuthentication = YES;
p.identityData = [NSData dataWithBase64EncodedString:certBase64String];;
p.identityDataPassword = #"identity password";
p.disconnectOnSleep = NO;
// Set protocol
[[NEVPNManager sharedManager] setProtocol:p];
// Set on demand
NSMutableArray *rules = [[NSMutableArray alloc] init];
NEOnDemandRuleConnect *connectRule = [NEOnDemandRuleConnect new];
[rules addObject:connectRule];
[[NEVPNManager sharedManager] setOnDemandRules:rules];
// Set localized description
[[NEVPNManager sharedManager] setLocalizedDescription:#"Description"];
// Enable VPN
[[NEVPNManager sharedManager] setEnabled:YES];
// Save to preference
[[NEVPNManager sharedManager] saveToPreferencesWithCompletionHandler: ^(NSError *error) {
NSLog(#"Save VPN to preference complete");
if (error) {
NSLog(#"Save to preference error: %#", error);
}
}];
}
}];

Related

Importing SimpleAuth ForSquareWeb - 9 build time errors

I'm relatively new to objective-c and hardly know much of swift.
I've been trying to make an app which will implement simpleAuth in order to create a link to the ForsquareWeb API.
I'm using cocoapods and have imported the SimpleAuth related files into my product.
Every file seems to be fine except the SimpleAuth target, specifically the SimpleAuthForSquareWebProvider.m file. This is what the file itself looks like;
//
// SimpleAuthFoursquareWebProvider.m
// SimpleAuth
//
// Created by Julien Seren-Rosso on 23/01/2014.
// Copyright (c) 2014 Byliner, Inc. All rights reserved.
//
#import "SimpleAuthFoursquareWebProvider.h"
#import "SimpleAuthFoursquareWebLoginViewController.h"
#import <ReactiveCocoa/ReactiveCocoa.h>
#import "UIViewController+SimpleAuthAdditions.h"
#implementation SimpleAuthFoursquareWebProvider
#pragma mark - SimpleAuthProvider
+ (NSString *)type {
return #"foursquare-web";
}
+ (NSDictionary *)defaultOptions {
// Default present block
SimpleAuthInterfaceHandler presentBlock = ^(UIViewController *controller) {
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:controller];
navigation.modalPresentationStyle = UIModalPresentationFormSheet;
UIViewController *presented = [UIViewController SimpleAuth_presentedViewController];
[presented presentViewController:navigation animated:YES completion:nil];
};
// Default dismiss block
SimpleAuthInterfaceHandler dismissBlock = ^(id controller) {
[controller dismissViewControllerAnimated:YES completion:nil];
};
NSMutableDictionary *options = [NSMutableDictionary dictionaryWithDictionary:[super defaultOptions]];
options[SimpleAuthPresentInterfaceBlockKey] = presentBlock;
options[SimpleAuthDismissInterfaceBlockKey] = dismissBlock;
return options;
}
- (void)authorizeWithCompletion:(SimpleAuthRequestHandler)completion {
[[[self accessToken]
flattenMap:^RACStream *(NSString *response) {
NSArray *signals = #[
[self accountWithAccessToken:response],
[RACSignal return:response]
];
return [self rac_liftSelector:#selector(dictionaryWithAccount:accessToken:) withSignalsFromArray:signals];
}]
subscribeNext:^(NSDictionary *response) {
completion(response, nil);
}
error:^(NSError *error) {
completion(nil, error);
}];
}
#pragma mark - Private
- (RACSignal *)accessToken {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
dispatch_async(dispatch_get_main_queue(), ^{
SimpleAuthFoursquareWebLoginViewController *login = [[SimpleAuthFoursquareWebLoginViewController alloc] initWithOptions:self.options];
login.completion = ^(UIViewController *login, NSURL *URL, NSError *error) {
SimpleAuthInterfaceHandler dismissBlock = self.options[SimpleAuthDismissInterfaceBlockKey];
dismissBlock(login);
// Parse URL
NSString *fragment = [URL fragment];
NSDictionary *dictionary = [CMDQueryStringSerialization dictionaryWithQueryString:fragment];
NSString *token = dictionary[#"access_token"];
// Check for error
if (![token length]) {
[subscriber sendError:error];
return;
}
// Send completion
[subscriber sendNext:token];
[subscriber sendCompleted];
};
SimpleAuthInterfaceHandler block = self.options[SimpleAuthPresentInterfaceBlockKey];
block(login);
});
return nil;
}];
}
- (RACSignal *)accountWithAccessToken:(NSString *)accessToken {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSDictionary *parameters = #{ #"oauth_token" : accessToken };
NSString *query = [CMDQueryStringSerialization queryStringWithDictionary:parameters];
NSString *URLString = [NSString stringWithFormat:#"https://api.foursquare.com/v2/users/self?v=20140210&%#", query];
NSURL *URL = [NSURL URLWithString:URLString];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request queue:self.operationQueue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 99)];
NSInteger statusCode = [(NSHTTPURLResponse *)response statusCode];
if ([indexSet containsIndex:statusCode] && data) {
NSError *parseError = nil;
NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&parseError];
if (dictionary) {
[subscriber sendNext:dictionary];
[subscriber sendCompleted];
}
else {
[subscriber sendError:parseError];
}
}
else {
[subscriber sendError:connectionError];
}
}];
return nil;
}];
}
- (NSDictionary *)dictionaryWithAccount:(NSDictionary *)account accessToken:(NSString *)accessToken {
NSMutableDictionary *dictionary = [NSMutableDictionary new];
NSDictionary *userData = account[#"response"][#"user"];
// Provider
dictionary[#"provider"] = [[self class] type];
// Credentials
dictionary[#"credentials"] = #{
#"token" : accessToken
};
// User ID
dictionary[#"uid"] = userData[#"id"];
// Raw response
dictionary[#"extra"] = #{
#"raw_info" : userData
};
// User info
NSMutableDictionary *user = [NSMutableDictionary new];
if (userData[#"contact"][#"email"]) {
user[#"email"] = userData[#"contact"][#"email"];
}
if (userData[#"firstName"]) {
user[#"first_name"] = userData[#"firstName"];
}
if (userData[#"lastName"]) {
user[#"last_name"] = userData[#"lastName"];
}
user[#"name"] = [NSString stringWithFormat:#"%# %#", user[#"first_name"], user[#"last_name"]];
user[#"gender"] = userData[#"gender"];
if ([userData[#"photo"] isKindOfClass:NSDictionary.class]) {
user[#"image"] = [NSString stringWithFormat:#"%#500x500%#", userData[#"photo"][#"prefix"], userData[#"photo"][#"suffix"]];
} else if ([userData[#"photo"] isKindOfClass:NSString.class]) {
user[#"image"] = userData[#"photo"];
}
if (userData[#"photo"]) {
user[#"photo"] = userData[#"photo"];
}
if (userData[#"homeCity"]) {
NSString *homecity = [[userData[#"homeCity"] componentsSeparatedByString:#","] firstObject];
user[#"location"] = homecity;
}
user[#"urls"] = #{
#"Foursquare" : [NSString stringWithFormat:#"https://foursquare.com/user/%#", userData[#"id"]],
};
dictionary[#"info"] = user;
return dictionary;
}
#end
I think it would be easier to show you just a screen shot of the errors and where they're arising in the code itself;
I would really appreciate some insight into where the problem lies. I'm not sure why many of the errors say use of undeclared identifiers, are the imported files not correct?
After trying to re-install the pod file as it was suggested a class hadn't been installed I still have the same problem. Here are screen shots of my podfile and the terminals output when installing the pods;
I just used the Cocoapods application rather than terminal. I got additional information when clicking install.
"[!] The dPicDid [Debug] target overrides the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES build setting defined in `Pods/Target Support Files/Pods-dPicDid/Pods-dPicDid.debug.xcconfig'. This can lead to problems with the CocoaPods installation
[!] The dPicDid [Release] target overrides the ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES build setting defined in Pods/Target Support Files/Pods-dPicDid/Pods-dPicDid.release.xcconfig'. This can lead to problems with the CocoaPods installation
- Use the$(inherited)flag, or
- Remove the build settings from the target.
- Use the$(inherited)` flag, or
- Remove the build settings from the target.

chatDidReceiveMessage method not called QuickBlox

I am using QuickBlox-iOS SDK for chatting. Login/Signup is working perfectly. Also I am able to send message but the delegate method
- (void)chatDidReceiveMessage:(QBChatMessage *)message;
is not getting called. Here's the code I am using to setup chat. Adding the following code in appDelegate :
// connect to Chat
[[QBChat instance] addDelegate:self];
QBUUser *currentUser = [QBUUser user];
currentUser.ID = [Global sharedInstance].currentUser.ID;
currentUser.password = #"password";
[[QBChat instance] connectWithUser:currentUser completion:^(NSError * _Nullable error) {
NSLog(#"connect to chat error %#",error);
}];
And the below code I am using to send message :
QBChatMessage *message = [QBChatMessage message];
message.recipientID=[Global sharedInstance].QBUserID;
message.senderID=[Global sharedInstance].currentUser.ID;
[message setText:messageTextView.text];
message.dateSent = [NSDate date];
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[#"save_to_history"] = #YES;
[message setCustomParameters:params];
[QBRequest createMessage:message successBlock:^(QBResponse *response, QBChatMessage *createdMessage) {
NSLog(#"success: %#", createdMessage);
} errorBlock:^(QBResponse *response) {
NSLog(#"ERROR: %#", response.error);
}]
I checked on QuickBlox dashboard. It shows all the sent/received messages. But the delegate is not getting called when I send message to another user. I am not using any additional services classes (QMServices) like they are using in their Example Project. Any help would be appreciated. Thanks
I don't understand why you're using the [QBRequest createMessage:successBlock:errorBlock:] method to send messages to another user.
For me what always worked was to create a chatDialog with the user you're trying to message, like so:
QBChatDialog *dialog = [[QBChatDialog alloc] initWithDialogID:nil
type: QBChatDialogTypePrivate];
dialog.occupantIDs = #[#([Global instance].QBUserID),
#([Global instance].currentUser.user.ID)];
Afterwards, you can call Quickblox method to create the dialog on the servers:
if (dialog.ID == nil) {
[QBRequest createDialog:dialog successBlock:^(QBResponse *response, QBChatDialog *createdDialog) {
[self sendMessageToDialog: dialog withText:#"Hello friend!"];
} errorBlock:^(QBResponse *response) {
NSLog(#"dialog creation err: %#", response);
}];
}
Create the message:
- (QBChatMessage *) createMessageWithText: (NSString *)text andDialog: (QBChatDialog*)dialog {
QBChatMessage *message = [QBChatMessage message];
message.text = text;
message.senderID = [Global instance].currentUser.ID;
message.markable = YES;
message.deliveredIDs = #[#([Global instance].currentUser.ID)];
message.readIDs = #[#([Global instance].currentUser.ID)];
message.dialogID = dialog.ID;
message.dateSent = [NSDate date];
message.recipientID = dialog.recipientID;
message.customParameters = [NSMutableDictionary dictionary];
message.customParameters[kQMCustomParameterDialogID] = dialog.ID;
message.customParameters[kQMCustomParameterDialogType] = [NSString stringWithFormat:#"%lu",(unsigned long)dialog.type];
message.customParameters[#"application_id"] = #"<your-application-id>";
message.customParameters[#"save_to_history"] = #"1";
if (dialog.lastMessageDate != nil){
NSNumber *lastMessageDate = #((NSUInteger)[dialog.lastMessageDate timeIntervalSince1970]);
message.customParameters[kQMCustomParameterDialogRoomLastMessageDate] = [lastMessageDate stringValue];
}
if (dialog.updatedAt != nil) {
NSNumber *updatedAt = #((NSUInteger)[dialog.updatedAt timeIntervalSince1970]);
message.customParameters[kQMCustomParameterDialogRoomUpdatedDate] = [updatedAt stringValue];
}
return message;
}
And then send the message to the dialog:
- (void) sendMessageToDialog: (QBChatDialog *)dialog withText: (NSString *)text {
QBChatMessage *message = [[ChatService shared] createMessageWithText:text andDialog:self.dialog];
[dialog sendMessage:message completionBlock:^(NSError * _Nullable error) {
if (error != nil) {
NSLog(#"error creating message %#", error);
} else {
NSLog(#"message sent!");
}
}];
}
I think following this flux you'll be able to receive the callback through the delegate.
EDIT - I forgot to mention the consts I used in the code above are:
NSString const *kQMCustomParameterDialogID = #"dialog_id";
NSString const *kQMCustomParameterDialogRoomName = #"room_name";
NSString const *kQMCustomParameterDialogRoomPhoto = #"room_photo";
NSString const *kQMCustomParameterDialogRoomLastMessageDate = #"room_last_message_date";
NSString const *kQMCustomParameterDialogUpdatedDate = #"dialog_updated_date";
NSString const *kQMCustomParameterDialogType = #"type";
NSString const *kQMCustomParameterDialogRoomUpdatedDate = #"room_updated_date";
Have you added <QBChatDelegate> into your .h file.

Data duplicate while migrate PersistentStoreCoordinator from local to iCloud

I am doing iCloud sync with core data works fine. Now i want to add on/off switch(migrate store). If i migrate store to iCloud to local works fine but if migrate back to iCloud datas getting duplicate. Even i tried to remove ubiquity container before migrate not helping. I could not deduplicate because "StoreDidImportUbiquitousContent" method not called after duplicate data.
Code below :
// Migrate to local
-(NSURL *)localStoreURL
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"LocalExpns.sqlite"];
return storeURL;
}
- (void)moveStoreToLocal
{
NSError *error = nil;
NSPersistentStoreCoordinator * persistentStoreCoordinator = self.persistentStoreCoordinator;
NSPersistentStore * persistentStore = [[persistentStoreCoordinator persistentStores] firstObject];
if([[NSFileManager defaultManager]fileExistsAtPath:[self localStoreURL].path])
{
NSLog(#"File exists");
[[NSFileManager defaultManager] removeItemAtPath:[self localStoreURL].path error:& error];
NSLog(#"Removing error = %#", error);
}
NSMutableDictionary *localStoreOptions = [[self localStoreOptions] mutableCopy];
[localStoreOptions setObject:[NSNumber numberWithBool:YES] forKey:NSPersistentStoreRemoveUbiquitousMetadataOption];
id result = [persistentStoreCoordinator migratePersistentStore:persistentStore toURL:[self localStoreURL] options:localStoreOptions withType:NSSQLiteStoreType error:&error];
}
Above code working fine.
// Migrate to iCloud back
// fileURL is local file url
-(NSURL *)storeURL
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"Expns.sqlite"];
return storeURL;
}
- (void)moveStoreFileToICloud:(NSURL*)fileURL
{
NSPersistentStoreCoordinator * persistentStoreCoordinator = self.persistentStoreCoordinator;
id sourceStore = [[persistentStoreCoordinator persistentStores] firstObject];
if (!sourceStore) {
NSLog(#" failed to add old store");
} else {
NSLog(#" Successfully added store to migrate");
bool moveSuccess = NO;
NSError *error;
NSLog(#" About to migrate the store...");
// Now migrate the store using the iCloud options
id migrationSuccess = [self.persistentStoreCoordinator migratePersistentStore:sourceStore toURL:[self storeURL] options:[self icloudStoreOptions] withType:NSSQLiteStoreType error:&error];
if (migrationSuccess) {
moveSuccess = YES;
NSLog(#"store successfully migrated");
// Now delete the local file
if (shouldDelete) {
NSLog(#" deleting local store");
[self deleteLocalStore];
} else {
NSLog(#" not deleting local store");
}
}
else {
NSLog(#"Failed to migrate store: %#, %#", error, error.userInfo);
}
}
}
Now data get duplicate what ever in before migrate. I tried to remove ubiquity container also not helping.
I have solved this problem by de-duplicate my db manually like this

Connect to VPN ios8, persistent reference of password from keychain is null

I am trying to connect vpn using NETWORK EXTENSION framework, but have some issue while get persistent reference of password stored in keychain. It [NSLog(#"password: %#", [manager protocol].passwordReference);] returns null after a call to [manager saveToPreferencesWithCompletionHandler:^(NSError *error) {...} but when I try to log before calling saveToPreferencesWithCompletionHandler it give me a valid reference in keychain. When I use this code block it gets all the details I have stored in keychain for persistent reference.
NSDictionary *query = #{ (__bridge id)kSecValuePersistentRef: (__bridge NSData *)(KeychainUserPass.passwordPersistentReference),
(__bridge id)kSecReturnAttributes: (id)kCFBooleanTrue };
CFDictionaryRef passwordDict = nil;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef*)&passwordDict);
NSLog(#"status: %ld, passwordDict: %#", status, (__bridge NSDictionary*)passwordDict);
NSLog(#"keychain password: %#", p.passwordReference);
But why it does not get any detail when I use NEVPNManager class object to store password reference? and it prompts me to enter password whenever I tried to connect vpn instead of getting it from keychain?
Here is the code:
NEVPNProtocolIKEv2 *p = [[NEVPNProtocolIKEv2 alloc] init];
p.username = #"username";
p.passwordReference = (__bridge NSData *)(KeychainUserPass.passwordPersistentReference);
p.serverAddress = #"hostname";
p.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate;
p.serverCertificateIssuerCommonName = #"Certificate Issuer name";
p.serverCertificateCommonName = #"Certificate name";
p.identityData = //p12;
p.identityDataPassword = #"password for p12";
p.localIdentifier = #"local identifier";
p.remoteIdentifier = #"remote identifier";
p.useExtendedAuthentication = YES;
p.disconnectOnSleep = NO;
[manager setProtocol:p];
[manager setOnDemandEnabled:YES];
[manager setLocalizedDescription:#"VPN Profile"];
[manager saveToPreferencesWithCompletionHandler:^(NSError *error) {
if(error) {
NSLog(#"Save error: %#", error);
}
else {
NSLog(#"Saved!");
NSLog(#"OnDemandEnable: %d", [manager isOnDemandEnabled]);
NSLog(#"onDemandRules: %#", [manager onDemandRules]);
NSLog(#"username: %#", [manager protocol].username);
NSLog(#"password: %#", [manager protocol].passwordReference);
}
}];
}
P.S: Everything works fine except this issue.
You have to load the preferences before you can alter them. Make sure to run loadFromPreferencesWithCompletionHandler and then create your protocol and make your updates.

Uploading video with YouTube api v3.0 fails with undocumented error code (Objective C)

I am using the following code in an attempt to upload a video to YouTube from an iOS (installed) application via the v3.0 api:
...
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:#"test" ofType:#"MOV"];
NSFileHandle *handle = [NSFileHandle fileHandleForReadingAtPath:path];
if (!handle)
{
NSLog(#"Failed to open file for reading");
return;
}
GTLServiceYouTube *service = [[GTLServiceYouTube alloc] init];
service.authorizer = auth;
GTLUploadParameters *params = [GTLUploadParameters uploadParametersWithFileHandle:handle MIMEType:#"application/octet-stream"];
GTLYouTubeVideoSnippet *snippet = [GTLYouTubeVideoSnippet object];
snippet.title = #"Test title";
snippet.descriptionProperty = #"Test description";
snippet.tags = #[ #"TestOne", #"TestTwo" ];
snippet.categoryId = #"17";
GTLYouTubeVideoStatus *status = [GTLYouTubeVideoStatus object];
status.privacyStatus = #"private";
GTLYouTubeVideo *video = [GTLYouTubeVideo object];
video.snippet = snippet;
video.status = status;
GTLQueryYouTube *query = [GTLQueryYouTube queryForVideosInsertWithObject:video part:#"snippet,status" uploadParameters:params];
// Perform the upload
GTLServiceTicket *ticket = [service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error)
{
if (error)
{
NSLog(#"ERROR: %#", error);
return;
}
NSLog(#"SUCCESS! %#; %#;", ticket, object);
}];
ticket.uploadProgressBlock = ^(GTLServiceTicket *ticket, unsigned long long numberOfBytesRead, unsigned long long dataLength)
{
NSLog(#"%lld / %lld", numberOfBytesRead, dataLength);
};
...
All appears to be well until the upload finishes and I receive the following error:
Error Domain=com.google.GTLJSONRPCErrorDomain Code=-32602 "The operation couldn’t be completed. (Forbidden)" UserInfo=0x936ed70 {error=Forbidden, GTLStructuredError=GTLErrorObject 0x936e7f0: {message:"Forbidden" code:-32602 data:[1]}, NSLocalizedFailureReason=(Forbidden)}
I thought this may be an issue with authorization or my app settings; however, I am able to successfully fetch a video category list with the same credentials. I also verified that the app has been granted access to my Google account from my Google account's account page.
The scope used during auth is:
#"https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.upload"
I've been tweaking/searching for a few hours and can't seem to find anything about the error code I'm getting...