Dialogflow V2 (beta1) SDK for Obj-C - objective-c

I would like to use dialogflow service in the app written using obj-c. Have been using api.ai library for a while but could not seem to find a library for obj-c for dialogflow v2(beta1) apis. My agent is upgraded to v2 already, but the api.ai internally is using /v1/ endpoints and I need to use v2beta1 specific features like access to knowledge bases. (https://cloud.google.com/dialogflow/docs/reference/rpc/google.cloud.dialogflow.v2beta1#queryparameters - knowledge_base_names).
The dialogflow api is a standard REST API, so all I need to have is OAuth2.0 & REST client, but coding this sounds like re-inventing the wheel.
Please advice. Thank you

I don't think there's a library written specifically for Dialogflow v2; however, the library google-api-objectivec-client-for-rest is a generic library provided by Google, that simplifies the code to consume their Rest APIs.
This library is updated to be used with Dialogflow V2. In order to use it, you'll need to match the Rest API, with the "Queries" (API methods) and "Objects" (API types) in the library, which is not that difficult because the names are basically the same.
For example, the detectIntent method full name is:
projects.agent.sessions.detectIntent
In the library, it is the equivalent to the Query:
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent
Here's an example of a detectIntent request:
// Create the service
GTLRDialogflowService *service = [[GTLRDialogflowService alloc] init];
// Create the request object (The JSON payload)
GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentRequest *request =
[GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentRequest object];
// Set the information in the request object
request.inputAudio = myInputAudio;
request.outputAudioConfig = myOutputAudioConfig;
request.queryInput = myQueryInput;
request.queryParams = myQueryParams;
// Create a query with session (Path parameter) and the request object
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent *query =
[GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent queryWithObject:request
session:#"session"];
// Create a ticket with a callback to fetch the result
GTLRServiceTicket *ticket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket,
GTLRDialogflow_GoogleCloudDialogflowV2DetectIntentResponse *detectIntentResponse,
NSError *callbackError) {
// This callback block is run when the fetch completes.
if (callbackError != nil) {
NSLog(#"Fetch failed: %#", callbackError);
} else {
// The response from the agent
NSLog(#"%#", detectIntentResponse.queryResult.fulfillmentText);
}
}];
You can find more information and samples, in the library wiki. Finally, the library also has a sample code using Google Cloud Storage which ilustrates its use with GCP services.
I think that without a specific library for Dialogflow V2, this might be the next thing to try before implementing it from scratch.
EDIT
Oops, I was missing the fact that the generated service for Dialogflow does not contain v2beta1.
In this case, it is needed an additional first step, which is to use the Dialogflow v2beta1 DiscoveryDocument and the ServiceGenerator, to create the service interface for v2beta1. Then you can continue working the same as I mentioned before.

Following #Tlaquetzal example, I ended up doing something like below
In pod file
pod 'GoogleAPIClientForREST'
pod 'JWT'
Using ServiceGenerator and Discovery Document as mentioned above, generated set of DialogFlow v2beta1 classes. Command line
./ServiceGenerator --outputDir . --verbose --gtlrFrameworkName GTLR --addServiceNameDir yes --guessFormattedNames https://dialogflow.googleapis.com/\$discovery/rest?version=v2beta1
And added them to the project.
#import "DialogflowV2Beta1/GTLRDialogflow.h"
Next step is to generate JWT Token. I have used this library JSON Web Token implementation in Objective-C. I want to connect using a service account.
NSInteger unixtime = [[NSNumber numberWithDouble: [[NSDate date] timeIntervalSince1970]] integerValue];
NSInteger expires = unixtime + 3600; //expire in one hour
NSString *iat = [NSString stringWithFormat:#"%ld", unixtime];
NSString *exp = [NSString stringWithFormat:#"%ld", expires];
NSDictionary *payload = #{
#"iss": #"<YOUR-SERVICE-ACCOUNT-EMAIL>",
#"sub": #"<YOUR-SERVICE-ACCOUNT-EMAIL>",
#"aud": #"https://dialogflow.googleapis.com/",
#"iat": iat,
#"exp": exp
};
NSDictionary *headers = #{
#"alg" : #"RS256",
#"typ" : #"JWT",
#"kid" : #"<KID FROM YOUR SERVICE ACCOUNT FILE>"
};
NSString *algorithmName = #"RS256";
NSData *privateKeySecretData = [[[NSDataAsset alloc] initWithName:#"<IOS-ASSET-NAME-JSON-SERVICE-ACCOUNT-FILE>"] data];
NSString *passphraseForPrivateKey = #"<PASSWORD-FOR-PRIVATE-KEY-IN-CERT-JSON>";
JWTBuilder *builder = [JWTBuilder encodePayload:payload].headers(headers).secretData(privateKeySecretData).privateKeyCertificatePassphrase(passphraseForPrivateKey).algorithmName(algorithmName);
NSString *token = builder.encode;
// check error
if (builder.jwtError == nil) {
JwtToken *jwtToken = [[JwtToken alloc] initWithToken:token expires:expires];
success(jwtToken);
}
else {
// error occurred.
MSLog(#"ERROR. jwtError = %#", builder.jwtError);
failure(builder.jwtError);
}
When token is generated, it can be used for an hour (or time you specify above).
To make a call to dialogflow you need to define your project path. To create a project path for the call, append to the code below your unique session identifier. Session is like a conversation for dialogflow, so different users should use different session ids
#define PROJECTPATH #"projects/<YOUR-PROJECT-NAME>/agent/sessions/"
Making dialogflow call
// Create the service
GTLRDialogflowService *service = [[GTLRDialogflowService alloc] init];
//authorise with token
service.additionalHTTPHeaders = #{
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", self.getToken.token]
};
// Create the request object (The JSON payload)
GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentRequest *request = [GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentRequest object];
//create query
GTLRDialogflow_GoogleCloudDialogflowV2beta1QueryInput *queryInput = [GTLRDialogflow_GoogleCloudDialogflowV2beta1QueryInput object];
//text query
GTLRDialogflow_GoogleCloudDialogflowV2beta1TextInput *userText = [GTLRDialogflow_GoogleCloudDialogflowV2beta1TextInput object];
userText.text = question;
userText.languageCode = LANGUAGE;
queryInput.text = #"YOUR QUESTION TO dialogflow agent"; //userText;
// Set the information in the request object
//request.inputAudio = myInputAudio;
//request.outputAudioConfig = myOutputAudioConfig;
request.queryInput = queryInput;
//request.queryParams = myQueryParams;
//Create API project path with session
NSString *pathAndSession = [NSString stringWithFormat:#"%#%#", PROJECTPATH, [self getSession]];
// Create a query with session (Path parameter) and the request object
GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent *query = [GTLRDialogflowQuery_ProjectsAgentSessionsDetectIntent queryWithObject:request session:pathAndSession];
// Create a ticket with a callback to fetch the result
// GTLRServiceTicket *ticket =
[service executeQuery:query
completionHandler:^(GTLRServiceTicket *callbackTicket, GTLRDialogflow_GoogleCloudDialogflowV2beta1DetectIntentResponse *detectIntentResponse, NSError *callbackError) {
// This callback block is run when the fetch completes.
if (callbackError != nil) {
NSLog(#"error");
NSLog(#"Fetch failed: %#", callbackError);
//TODO: Register failure with analytics
failure( callbackError );
}
else {
// NSLog(#"Success");
// The response from the agent
// NSLog(#"%#", detectIntentResponse.queryResult.fulfillmentText);
NSString *response = detectIntentResponse.queryResult.fulfillmentText;
success( response );
}
}];
This is a basic implementation, but works and good for demo.
Good luck

Related

how to get checkout details from paddle in mac app?

I have integrated my mac app with paddle this what i followed from documentation and integrated but what i want when user purchases from here i want all the order details.
// Your Paddle SDK Config from the Vendor Dashboard:
NSString* myPaddleProductID = #"12345";
NSString *myPaddleVendorID = #"56791";
NSString* myPaddleAPIKey = #"abc123def345hij678";
// Populate a local object in case we're unable to retrieve data
// from the Vendor Dashboard:
PADProductConfiguration *defaultProductConfig = [[PADProductConfiguration alloc] init];
defaultProductConfig.productName = #"My v4 Product";
defaultProductConfig.vendorName = #"My Company";
// Initialize the SDK Instance with Seller details:
Paddle *paddle = [Paddle sharedInstanceWithVendorID:myPaddleVendorID
apiKey:myPaddleAPIKey
productID:myPaddleProductID
configuration:defaultProductConfig];
// Initialize the Product you'd like to work with:
PADProduct *paddleProduct = [[PADProduct alloc] initWithProductID:myPaddleProductID productType:PADProductTypeSDKProduct configuration:nil];
// Ask the Product to get it's latest state and info from the Paddle Platform:
[paddleProduct refresh:^(NSDictionary * _Nullable productDelta, NSError * _Nullable error) {
// Launch the "Product Info" gatekeeper UI with buy, activate, etc:
[paddle showProductAccessDialogWithProduct:paddleProduct];
however in documentation for custom implementation by using below block we can get but i want by using showProductAccessDialogWithProduct
[paddle showCheckoutForProduct:paddleProduct options:nil checkoutStatusCompletion:^(PADCheckoutState state, NSDictionary * _Nullable checkoutData) {
// Examine checkout state to determine the checkout result
}];
Any Suggestions ?
Thanks In Advance !!
So i got the answer till V4.0.9 we cannot get the details it is included after v4.0.10.
All you need is PADProductDelegate
-(void)productPurchased:(PADCheckoutData *)checkoutData
using this delegate method you can get checkoutdata and if you need order details
get checkout_id from checkoutdata and pass it on below api.
https://checkout.paddle.com/api/1.0/order?checkout_id=xxxxxxxxx

Exception when trying to fetch channelId using the youtube api in objective c

I am trying to fetch youtube channel id using the google-api-objectivec-client. The problem I am having is basically that for some reason I am receiving exception when trying to access the channelId. The code I am using:
GTLServiceYouTube *service = [[GTLServiceYouTube alloc] init];
service.APIKey = _MY_API_KEY_;
GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"id"];
query.q = #"google";
query.type = #"channel";
query.maxResults = 1;
GTLServiceTicket *ticket = [service executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
if (error == nil) {
GTLYouTubeSearchListResponse *products = object;
for (id item in products.items) {
GTLYouTubeSearchResult *result = item;
NSLog(#"Identifier:%#",result.identifier);
GTLYouTubeResourceId* resourceId = result.identifier;
NSLog(#"kind:%#",resourceId.kind);
NSLog(#"channel:%#",resourceId.channelId);
}
}else{
NSLog(#"Error: %#", error.description);
}
}];
The output I get when i am running this code is:
2013-04-05 11:37:12.615 YouTest[21704:11303] Identifier:GTLYouTubeChannel 0x7233b00: {kind:"youtube#channel" channelId?:"UCK8sQmJBp8GCxrOtXWBpyEA"}
2013-04-05 11:37:12.617 YouTest[21704:11303] kind:youtube#channel
2013-04-05 11:37:12.617 YouTest[21704:11303] -[GTLYouTubeChannel channelId]: unrecognized selector sent to instance 0x7233b00
2013-04-05 11:37:12.618 YouTest[21704:11303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[GTLYouTubeChannel channelId]: unrecognized selector sent to instance 0x7233b00'
So my implementation crashes on the point where I am trying to access the channelId of the resourceId. From the documentation I understood that the channelId should be there as the type of the resourceId is youtube#channel. The channelId can be off course parsed from the result.identifier string that I am also printing, but since there is a property for the channelId I would prefer using that.
Any ideas about what is wrong with my code?
There is indeed a bug in the Google libraries. However I solved this problem by accessing the JSON string directly and parsing it with the help of the NSString+SBJSON.h class, as in this example.
#import "NSString+SBJSON.h"
...
GTLYouTubeResourceId *resource = channel.snippet.resourceId;
NSDictionary *jsonObject = [resource.JSONString JSONValue];
NSString *channelid = [jsonObject valueForKey:#"channelId"];
I'm not very familiar with Objective-C, but yeah, that looks like there's something wrong with the generated client library's YouTube Data API v3 bindings. Are you using the latest version from the project page? You might want to file a bug against the client library if you can reproduce it with the latest version. While troubleshooting this further, I'd check to see if you have the same problem when query.type = #"video"; and you try to access the videoId of the response item.
Here's an alternative you could try, though. The channel's id is also returned in the snippet.channelId property. If you request the snippet part via GTLQueryYouTube *query = [GTLQueryYouTube queryForSearchListWithPart:#"snippet"]; see if you can read that value instead.
I had the same issue. Solved it with the following...
NSDictionary *jsonObject = [NSJSONSerialization JSONObjectWithData:[resourceId.JSONString dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
NSString *channelId = [jsonObject valueForKey:#"channelId"];
NSLog(#"channelId is %#", channelId);
Workaround Code:
channel.snippet.resourceId.JSON[#"channelId"];
No need to parse the JSON yourself as the underlying JSON is exposed.
It looks like the automatic binding is not working for GTLYouTubeResourceId because the "kind" element of "youtube#channel" is throwing off the runtime object creation and creating a GTLYouTubeChannel instead.
Thorough Workaround Code:
ticket.surrogates = #{ (id)[GTLYouTubeChannel class] : [GTLYouTubeResourceId class] };
If you really want to force that binding to work you can workaround a little further upstream on the ticket when you execute the query.
Global Workaround Patch:
https://github.com/google/google-api-objectivec-client/pull/109
There's open tickets for the issue:
https://github.com/google/google-api-objectivec-client/issues/63
https://github.com/google/google-api-objectivec-client/issues/92
It seems they want to change the API to not call the resourceId.kind 'kind' to avoid this problem. But while we wait for the API to change, any of these three workarounds should serve your purposes.

Global events, the Mac App Store, and the sandbox

I'm working on an app where using global key-down events will be a requirement for its operation. Additionally, I plan on distributing this strictly via the App Store. (It's a Mac app, not iOS.) I've gotten an example of listening for the global events working via addGlobalMonitorForEventsMatchingMask, but with caveats.
Note: I am making the choice to use the modern API's and not rely on the earlier Carbon hotkey methods. In the event that they are deprecated eventually, I don't want to have to figure this problem out later.
The principle issue is that the app has to be trusted in order for global events to be detected. Otherwise, accessibility has to be enabled for all apps. When I enable accessibility, events are detected successfully. This requirement is documented here, https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/EventOverview/MonitoringEvents/MonitoringEvents.html.
I would prefer that for my users, they will not have to enable accessibility. From other research I've done, you can get an application to be trusted by calling AXMakeProcessTrusted, then restarting the application.
In the code that I'm using, I do not get an authentication prompt. The app will restart, but is still not trusted (likely because I don't get an authentication prompt). Here's my code for this part:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
if (!AXAPIEnabled() && !AXIsProcessTrusted()) {
NSString *appPath = [[NSBundle mainBundle] bundlePath];
AXError error = AXMakeProcessTrusted( (CFStringRef)CFBridgingRetain(appPath) );
[self restartApp];
}
}
- (void)restartApp{
NSTask *task = [[NSTask alloc] init];
NSMutableArray *args = [NSMutableArray array];
[args addObject:#"-c"];
[args addObject:[NSString stringWithFormat:#"sleep %d; open \"%#\"", 3, [[NSBundle mainBundle] bundlePath]]];
[task setLaunchPath:#"/bin/sh"];
[task setArguments:args];
[task launch];
[NSApp terminate:nil];
}
Further, I've looked at the documentation for Authorization Service Tasks here https://developer.apple.com/library/archive/documentation/Security/Conceptual/authorization_concepts/03authtasks/authtasks.html#//apple_ref/doc/uid/TP30000995-CH206-BCIGAIAG.
The first thing that worries me that pops out is this info box, "Important The authorization services API is not supported within an app sandbox because it allows privilege escalation."
If this API is required to get the authentication prompt before restarting the app, it seems that I may not be able to get global events without the accessibility feature enabled.
In summary, my specific questions are:
Is there an error in my sample code about how to get the
authentication prompt to appear?
In order to get the authentication prompt to appear, am I required
to use the Authorization Services API?
Is it possible, or not possible, to have a sandboxed app that has
access to global events?
First of all, there is no way you can automatically allow an app to use accessibility API which would work in a sandbox environment and thus in app store. The recommended way is to simply guide users so they can easily enable it themselves. The new API call AXIsProcessTrustedWithOptions is exactly for that:
NSDictionary *options = #{(id) kAXTrustedCheckOptionPrompt : #YES};
AXIsProcessTrustedWithOptions((CFDictionaryRef) options);
Now, to your first and second question (just for the sake of completeness - again it won't work in sandbox):
The idea behind AXMakeProcessTrusted was that you actually create a new auxiliary application that you run as root from the main application. This utility then calls AXMakeProcessTrusted passing in the executable of the main application. Finally you have to restart the main app. The API call has been deprecated in OSX 10.9.
To spawn a new process as a root you have to use launchd using SMJobSubmit. This will prompt a user with an authentication prompt saying that an application is trying to install a helper tool and whether it should be allowed. Concretely:
+ (BOOL)makeTrustedWithError:(NSError **)error {
NSString *label = FMTStr(#"%#.%#", kShiftItAppBundleId, #"mktrusted");
NSString *command = [[NSBundle mainBundle] pathForAuxiliaryExecutable:#"mktrusted"];
AuthorizationItem authItem = {kSMRightModifySystemDaemons, 0, NULL, 0};
AuthorizationRights authRights = {1, &authItem};
AuthorizationFlags flags = kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
AuthorizationRef auth;
if (AuthorizationCreate(&authRights, kAuthorizationEmptyEnvironment, flags, &auth) == errAuthorizationSuccess) {
// this is actually important - if from any reason the job was not removed, it won't relaunch
// to check for the running jobs use: sudo launchctl list
// the sudo is important since this job runs under root
SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
// this is actually the launchd plist for a new process
// https://developer.apple.com/library/mac/documentation/Darwin/Reference/Manpages/man5/launchd.plist.5.html#//apple_ref/doc/man/5/launchd.plist
NSDictionary *plist = #{
#"Label" : label,
#"RunAtLoad" : #YES,
#"ProgramArguments" : #[command],
#"Debug" : #YES
};
BOOL ret;
if (SMJobSubmit(kSMDomainSystemLaunchd, (CFDictionaryRef) plist, auth, (CFErrorRef *) error)) {
FMTLogDebug(#"Executed %#", command);
ret = YES;
} else {
FMTLogError(#"Failed to execute %# as priviledged process: %#", command, *error);
ret = NO;
}
// From whatever reason this did not work very well
// seems like it removed the job before it was executed
// SMJobRemove(kSMDomainSystemLaunchd, (CFStringRef) label, auth, false, NULL);
AuthorizationFree(auth, 0);
return ret;
} else {
FMTLogError(#"Unable to create authorization object");
return NO;
}
}
As for the restarting, this is usually done also using an external utility to which waits for a main application to finish and starts it again (by using PID). If you use sparkle framework you can reuse the existing one:
+ (void) relaunch {
NSString *relaunch = [[NSBundle bundleForClass:[SUUpdater class]] pathForResource:#"relaunch" ofType:#""];
NSString *path = [[NSBundle mainBundle] bundlePath];
NSString *pid = FMTStr(#"%d", [[NSProcessInfo processInfo] processIdentifier]);
[NSTask launchedTaskWithLaunchPath:relaunch arguments:#[path, pid]];
[NSApp terminate:self];
}
Another option is to hack the /Library/Application Support/com.apple.TCC/TCC.db sqlite database add the permissions manually using an auxiliary helper:
NSString *sqlite = #"/usr/bin/sqlite3";
NSString *sql = FMTStr(#"INSERT or REPLACE INTO access values ('kTCCServiceAccessibility', '%#', 1, 1, 1, NULL);", MY_BUNDLE_ID);
NSArray *args = #[#"/Library/Application Support/com.apple.TCC/TCC.db", sql];
NSTask *task = [NSTask launchedTaskWithLaunchPath:sqlite arguments:args];
[task waitUntilExit];
This however will disqualify the app from being app store. More over it is really just a hack and the db / schema can change any time. Some applications (e.g. Divvy.app used to do this) used this hack within the application installer post install script. This way prevents the dialog telling that an app is requesting to install an auxiliary tool.
Basically, MAS restrictions will require you to the route of having tge user turning on AX for all.
I found a potential solution on GitHub.
https://github.com/K8TIY/CW-Station
It has an auxiliary application which would be run at root to request access for the main application. It is a little outdated and is using some functions which have been deprecated so I am working on modernizing it. It looks like a good starting point.

Failed OAuth Using gtm-oauth2 for Instagram

I have set up the OAuth request following the documentation for a "Non-Google Service" and I get 90% of this working. The app brings up a web view where I can login to Instagram using the correct access token for my app and accept the scope request but once I accept the request I get the following error:
Error Error Domain=com.google.HTTPStatus Code=400 "The operation couldn’t be completed.
(com.google.HTTPStatus error 400.)" UserInfo=0x6b7ab00 {data=<7b22636f 6465223a 20343030 2c202265 72726f72 5f747970 65223a20 224f4175 74684578 63657074 696f6e22 2c202265 72726f72 5f6d6573 73616765 223a2022 596f7520 6d757374 20696e63 6c756465 20612076 616c6964 20636c69 656e745f 69642c20 20202020 20202020 20202020 72657370 6f6e7365 5f747970 652c2061 6e642072 65646972 6563745f 75726920 70617261 6d657465 7273227d>}
Error data:
{
code = 400;
"error_message" = "You must include a valid client_id, response_type, and redirect_uri parameters";
"error_type" = OAuthException;
}
It seems like the controller is attempting to complete the OAuth transaction against google.com instead of api.instagram.com.
Here is my objective-c code to set up and invoke the controller:
static NSString *const kKeychainItemName = #"Instagram OAuth2";
static NSString *const kClientID = #"xxxxxxxxx"; // Removed for security
static NSString *const kClientSecret = #"xxxxxxxxx"; // Removed for security
static NSString *const authURLString = #"https://api.instagram.com/oauth/authorize/";
static NSString *const tokenURLString = #"https://api.instagram.com/oauth/authorize/";
static NSString *const redirectURI = #"http://google.com/NeverGonnaFindMe/";
...
NSURL *tokenURL = [NSURL URLWithString:tokenURLString];
// Set up the OAuth request
GTMOAuth2Authentication *auth = [GTMOAuth2Authentication
authenticationWithServiceProvider:#"Instagram API"
tokenURL:tokenURL
redirectURI:redirectURI
clientID:kClientID
clientSecret:kClientSecret
];
// Specify the appropriate scope string, if any, according to the service's API documentation
auth.scope = #"basic likes comments relationships";
NSURL *authURL = [NSURL URLWithString:authURLString];
// Display the authentication view
GTMOAuth2ViewControllerTouch *viewController = [[GTMOAuth2ViewControllerTouch alloc]
initWithAuthentication:auth
authorizationURL:authURL
keychainItemName:kKeychainItemName
delegate:self
finishedSelector:#selector(viewController:finishedWithAuth:error:)
];
// Capture the Cookie to delete later
viewController.browserCookiesURL = [NSURL URLWithString:#"instagram.com/"];
// Now push our sign-in view
[[self navigationController] pushViewController:viewController animated:YES];
Solved
https://groups.google.com/forum/?fromgroups#!topic/instagram-api-developers/SUYPX7ZQHhs
It was the token URL that was incorrect. Correct token URL is: https://api.instagram.com/oauth/access_token/

NSURLCredential and NSURLConnection

I have been trying to search for a way to unset the credentials once you set a NSURLCredential with a NSURLConnection that is using NSURLCredentialPersistenceForSession, but I couldn't find a working solution. Removing the NSURLCredential from the NSURLCredentialStorage only removes it from the storage and not from the NSURLConnection cache. I tried turning cache off and it still keeps it. I need it to be NSURLCredentialPersistenceForSession as I don't want it to be uploading the large data then getting back the you need to authenticate message then authenticating with NSURLConnection and then resending the large data, I just want to authenticate once and send the large data once. I have a way to authenticate before sending the large data by checking some properties, but that doesn't allow me to logout or re-ask for authentication. I'm writing a WebDav client just so you understand where I stand, and the reason I need to unset the credentials is if someone has multiple accounts on the WebDav server and wants to login to another account. I tried looking into the cookies to see if it set something there, but it doesn't. I know it's only for the session, which means once your quit and relaunch the application, you can login as the other user. But that may confuse some people. I thought about writing my own Authentication system, but I wouldn't know how long that'll take.
Sorry if the above message is too long, I am just making sure I explain everything in detail so someone can try to help me with a valid answer and not a, go here leading me to something I tried.
Thanks for any help,
Mr. Gecko.
Update: Example code.
CFHTTPMessageRef message = [self httpMessageFromResponse:response];
authentication = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, message);
CFHTTPMessageRef message = [self httpMessageFromRequest:request];
CFStreamError error;
CFHTTPMessageApplyCredentials(message, authentication, (CFStringRef)[credentials user], (CFStringRef)[credentials password], &error);
NSLog(#"%d", error.error); // Returns -1000
CFStringRef value = CFHTTPMessageCopyHeaderFieldValue(message, CFSTR("Authorization"));
NSLog(#"%#", (NSString *)value); // Returns NULL
if (value!=NULL)
CFRelease(value);
Update: Code that I tried with removing credentials.
- (void)resetCredentials {
NSURLCredentialStorage *store = [NSURLCredentialStorage sharedCredentialStorage];
NSDictionary *allCredentials = [store allCredentials];
NSArray *keys = [allCredentials allKeys];
for (int i=0; i<[keys count]; i++) {
NSURLProtectionSpace *protectionSpace = [keys objectAtIndex:i];
NSDictionary *userToCredentialMap = [store credentialsForProtectionSpace:protectionSpace];
NSArray *mapKeys = [userToCredentialMap allKeys];
for (int u=0; u<[mapKeys count]; u++) {
NSString *user = [mapKeys objectAtIndex:u];
NSURLCredential *credential = [userToCredentialMap objectForKey:user];
NSLog(#"%#", credential);
[store removeCredential:credential forProtectionSpace:protectionSpace];
}
}
}
I'm working with a webdav server and had a similar issue. I found a solution within the sample code for apple's AdvancedURLConnection's project. It appears that the secret is to iterate over the protection spaces and then the credentials for each space. It's probably needlessly over complex but it works for me.
NSURLCredentialStorage *store = [NSURLCredentialStorage sharedCredentialStorage];
assert(store != nil);
for (NSURLProtectionSpace *protectionSpace in [store allCredentials]) {
NSDictionary *userToCredentialMap = [[NSURLCredentialStorage sharedCredentialStorage] credentialsForProtectionSpace:protectionSpace];
assert(userToCredentialMap != nil);
for (NSString *user in userToCredentialMap) {
NSURLCredential *credential = [userToCredentialMap objectForKey:user];
assert(credential != nil);
[store removeCredential:credential forProtectionSpace:protectionSpace];
}
}