Prestashop Search API Method in IOS Application - ios

I'm building my first IOS Application and I'm currently integrating my application with my Prestashop website. I already have grabbed a ton of data from the databases, but I'm having less luck using the Search Method from their API.
I have successfully called the REST API and authenticated with the server. I'm getting back a response status code of 200 so everything looks ok with the authentication, however the data I'm receiving from the server is the homepage of my site in HTML form rather than an XML file with the search results.
Below I'll list some of the parts of code in question that I'm using to access the Prestashop server
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://www.******.com:443/api/search/?query=%#&language=1",searchTerm]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
This is the method I use to set up the connection to the Prestashop Server
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
NSLog(#"Test 1.5");
[self clearCookiesForURL];
if([challenge previousFailureCount]==0)
{
NSURLCredential *credential = [NSURLCredential credentialWithUser:#"(I'm omitting the credential key but it does work)" password:#"" persistence:NSURLCredentialPersistenceForSession];
NSLog(#"Credential : %#",credential);
//NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
And this is the method for my authentication. I have all of the other connection methods instantiated (canAuthenticateAgainstProtectionSpace, didReceiveResponse, didReceiveData, etc.) and they are all being called and working fine.
NSLog(#"Data After : %#",responseData);
NSString *tempString;
tempString = [[NSString alloc] initWithData:responseData encoding:NSASCIIStringEncoding];
NSLog(#"String : %#",tempString);
This is the section of code I use to translate the data I get from the server into a string (which i thought was going to be XML but is instead HTML)
Here is a screenshot of the debug log showing that I'm getting an HTML File
Can anybody see what I'm doing wrong here? Any help would be greatly appreciated!

Related

Authentication challenge called in NSURLConnection

I'm creating NSURLConnection with a link https://www.wella.com which is finally passed to a UIWebView
NSURL *url = [NSURL URLWithString:[[u stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding] stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
self.authRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30];
self.authConnection = [NSURLConnection connectionWithRequest:authRequest delegate:self];
I am unexpectedly receiving authentication challenge, which of course I am currently not handling.
I've noticed that when using desktop browser, when I paste the link above, it's address is automatically changed to https://www.wella.com/professional/countryselector
When pasting the extended link, NSURLConnection works without any problems.
How can I get rid of this challenge and could it be somehow connected with that link auto change?
EDIT: I've solved it. The question, however, remains: why does the auto link change causes calling the authentication challenge?
Thanks to: Muralikrishna's answer
I've managed it by implementing the NSURLConnectionDelegate method and setting the trust credential:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
SecTrustRef trust = challenge.protectionSpace.serverTrust;
NSURLCredential *cred;
cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}

iOS 9 ATS blocking HTTPS request to server with Self-Signed Certficate

This issue is all over Stack Overflow and I have spent the past 2 days trying countless combinations of ATP configurations and getting my app to work. I'm going to be thorough with my problem as it appears the tiniest thing can affect how to resolve this.
I have just recently set up an Ubuntu 14 server with SSL and TLS 1.2 enabled. On my server are my server-side scripts which my app depends on. In my app I use a NSMutableURLRequest to request my API from the server like so:
NSString * noteDataString = [NSString stringWithFormat:#"email=%#&password=%#&type=%#", companyEmail, companyPassword, #"login"];
NSData * postData = [noteDataString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString * postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"https://mydomain.me:443/path/to/file.php"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSHTTPURLResponse * urlResponse = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
NSString * result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"Response Code: %ld", (long)[urlResponse statusCode]);
When I copy the url into Chrome the correct PHP return is shown. When requesting from Xcode 7 on iOS 9 I get this error:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
I have used countless info.plist settings as seen across similar issues. I have tried disabling the need for forward secrecy, I have tried enabling arbitrary loads, I have tried using the exception domain mydomain.me and its subdomains. The only progress I achieve is an error code -9813 switching to -9802.
I am aware of adding delegate methods for NSURLRequest but these are never called and assumed redundant for solving this problem.
I built a lot of the app off a MAMP server using localhost and http and the requests worked when I enabled arbitrary loads then, so nothing wrong with my plist.
It's mind-boggling onto why I have a special case, and I knew Stack Overflow was the place for such situations!
Thanks, and I hope that this helps many more developers beyond me when solved.
Solution. Thank you everybody in the comments for pointing me in the right direction. The solution was to create an NSObject that handled NSURLRequests for this special case.
Credit to: https://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/
The following is almost a direct copy from the tutorial in the link, but I figured it'd be easier to stay here.
So,
I had to create a new NSObject class with the following code:
BWWebService.h
#import <Foundation/Foundation.h>
#interface BWWebService : NSObject{
NSMutableData *receivedData;
NSURLConnection *connection;
NSStringEncoding encoding;
}
- (id)initWithURL:(NSURL *)url;
#end
BWWebService.m
#import "BWWebService.h"
#implementation BWWebService
- (id)initWithURL:(NSURL *)url{
if (self = [super init]){
NSURLRequest * request = [NSURLRequest requestWithURL:url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
}
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
// Every response could mean a redirect
receivedData = nil;
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)
[response textEncodingName]);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
if (!receivedData){
// No store yet, make one
receivedData = [[NSMutableData alloc] initWithData:data];
}else{
// Append to previous chunks
[receivedData appendData:data];
}
}
// All worked
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSString * xml = [[NSString alloc] initWithData:receivedData encoding:encoding];
NSLog(#"%#", xml);
}
// And error occurred
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Error retrieving data, %#", [error localizedDescription]);
}
// To deal with self-signed certificates
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
return [protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
// we only trust our own domain
if ([challenge.protectionSpace.host isEqualToString:#"myuntrusteddomain.me"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
#end
And if that wasn't enough, to make the request I used the following in replacement of my original request:
NSURL * url = [NSURL URLWithString:#"https://myuntrusteddomain.me:443/path/to/script.php"];
BWWebService * webService;
webService = [[BWWebService alloc] initWithURL:url];
I know that this does not POST data like the original, but that comes later. I am sure it will be a matter of handling the POST in initWithURL.
Thanks everyone.
Edit: It appears this application of the solution only works with Allows Arbitrary Loads set to YES.
Open Xcode, Command-Shift-2, enter 9813, and you immediately find -9813 = errSSLNoRootCert, which was to be expected since your self signed certificate has no root certificate, while -9802 = errSSLFatalAlert (you really buggered it up).
The problem seems to be that for security reasons, some software doesn't like self signed certificates. This can often be fixed by creating and installing your own root certificate, and having a certificate signed by your own root certificate.

Retrieving JSON data behind HTTPS and login redirect pages

I'm building an app that will (among other things) retrieve JSON data from a website that I built (I built the website using the Django framework). The entire website requires login credentials to gain access to anything. I set up a URL on the web server for testing that simply spits out some JSON data. My goal (at this point) is simply to access that JSON data from my app.
The problem I'm having is that when I try to connect to the URL, it fails since the web server redirects the app to the login page. I'm not sure how to send the credentials to the web server so that it doesn't re-direct to the login page but, instead, simply sends back the JSON data. In the future, I hope to do this in reverse - send data back to the server via JSON.
Here's what I have going so far. Again, I'm new to iOS to pardon the potential ugliness of this code:
NSURL *URL = [NSURL URLWithString:#"http:www.mysite.com/test_json/"];
NSURLRequest *request = [NSURLRequest requestWithURL:URL
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:30.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] == 0) {
NSLog(#"received authentication challenge");
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:#"garfonzo"
password:#"MyPassword"
persistence:NSURLCredentialPersistenceForSession];
NSLog(#"credential created");
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
NSLog(#"responded to authentication challenge");
}
else {
NSLog(#"previous authentication failure");
}
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// This is where we do stuff with the data received
NSString *strData = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", strData);
NSError *e = nil;
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData: data options: NSJSONReadingMutableContainers error: &e];
if (!jsonArray) {
NSLog(#"Error parsing JSON: %#", e);
} else {
for(NSDictionary *item in jsonArray) {
NSLog(#"Item: %#", item);
}
}
}
I wanted to see what strData was that was received and, sure enough, it was the HTML of the login webpage. So I know I'm at least making contact, but failing due to the redirect.
Any thoughts? Ideas for doing this correctly? Am I way off on my approach?
The following link may be of help to you:
Handling Redirects and Other Request Changes
Also, you can explore the delegate method :
- (NSURLRequest *)connection: (NSURLConnection *)inConnection
willSendRequest: (NSURLRequest *)inRequest
redirectResponse: (NSURLResponse *)inRedirectResponse;
Take a look at this link
: Handling redirects correctly with NSURLConnection
- (void)wsLooking{
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://example?data=example"]];
[self performSelectorOnMainThread:#selector(fetchedData:) withObject:data waitUntilDone:YES];
});
}
-(void)fetchedData:(NSData*)responseData {
if (responseData) {
NSError *error = nil;
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
NSLog(#"%#",json);
}
}
You can download source code from this that is code for parse json from http website
I figured out a round about way of managing this. Instead of trying to handle credentials in the Objective-C code, I simply send the credentials on the end of the URL as part of a GET request. The URL now looks like this:
NSURL *URL = [NSURL URLWithString:#"http://www.mysite.com/test_json/?username='USERNAME'&password='PASSWORD'"];
Now, to prevent my web server from redirecting to the login page (and since I'm using Django and have full control of the web server) I created a middleware to handle the above url. My custom middleware has this in it:
if request.path != '/accounts/login/' and request.user.is_anonymous():
url = request.path.split('/')[1]
if url == 'test_json':
username = request.GET['username']
password = request.GET['password']
user = authenticate(username = username, password = password)
if user is not None:
if user.is_active:
login(request, user)
This way I can have my web server handle any request from my app (which, in the future, will always have a URL base of mysite.com/m/ios/...) differently. It will assume the credentials are in the URL. In production, this is all handled over HTTPS so there will be encryption present.
Hopefully this helps someone else in a similar situation.

NSURLConnection only authenticates on second attempt

I am trying to load a secure website in a UIWebView my basic approach is to create a NSURL, the n a NSURLRequest, then a NSURLConnection, then to load the NSURLRequest in the UIWebView. When the website is loaded I receive
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
I respond to the challenge sender with
- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
But after that I get nothing... it just hangs. I put in break points so I know that
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
is being called. If I wait till I am sure that the NSURLConnection is not going to complete then reload the view no authentication challenge is sent but the view will load. I do not have any control over the server. I am open to using AFNetworking, but only if necessary.
The full listing of source code is provided below:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([challenge previousFailureCount] == 0)
{
NSString *username = #"username";
NSString *password = #"passsword";
NSURLCredential * cred = [NSURLCredential credentialWithUser:username
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
}
else
{
}
}
-(void)updateCard
{
NSURL * url = [NSURL URLWithString:#"https://ssl.letu.edu/applications/chapelattendance/attendance.html"];
NSURLRequest * request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:50.0];
self.webView =[[UIWebView alloc] initWithFrame:self.bounds];
self.webView.delegate = self;
[self.webView loadRequest:request];
self.connection = [[ NSURLConnection alloc]initWithRequest:request delegate:self];
[self.connection start];
}
Where did I go wrong?
You need to first retrieve the "authentication method" the server is requesting for:
[[challenge protectionSpace] authenticationMethod]
These are the authentication methods (which are string constants) which the expression above returns:
NSURLAuthenticationMethodDefault
NSURLAuthenticationMethodHTTPBasic
NSURLAuthenticationMethodHTTPDigest
NSURLAuthenticationMethodHTMLForm
NSURLAuthenticationMethodNegotiate
NSURLAuthenticationMethodNTLM
NSURLAuthenticationMethodClientCertificate
NSURLAuthenticationMethodServerTrust
Then, you have these options:
If you want to provide the credentials for the given authentication method, you invoke
useCredential:forAuthenticationChallenge:
If you don't want to handle that authentication method yourself and want the system try
to authenticate, you may invoke performDefaultHandlingForAuthenticationChallenge:
which may then fail or not, depending whether the system is capable to handle that type
of authentication and whether it can find credentials in well known storages.
If you cant handle that authentication method -- say authentication method
NSURLAuthenticationMethodNTLM for example -- you can skip this protection
space and try another protection space if another one
exists in this authentication challenge. Then you may possibly get an
authentication method NSURLAuthenticationMethodHTTPBasic which you
are capable to handle.
In order to reject the current protection space you send method
rejectProtectionSpaceAndContinueWithChallenge: to the
authentication challenge sender. Then, NSURLConnection will send
once again willSendRequestForAuthenticationChallenge: to your
delegate with another protection space if any further exists.
You may try to continue without providing credentials at all.
Likely, the authentication will fail. You can try it through
sending message continueWithoutCredentialForAuthenticationChallenge:
to the authentication challenge sender.
And finally, you can cancel the request through canceling the
authentication challenge: send cancelAuthenticationChallenge: to
the authentication challenge sender.
Note: NSURLAuthenticationMethodHTTPBasic and NSURLAuthenticationMethodHTTPDigest authentication methods can be handled with the same NSURLCredential object created with +credentialWithUser:password:persistence:
If anyone comes along and has the same problem be sure I want to share the solution I found. Use AFNetworking.
Here is the revised code:
-(void)updateCard
{
if(!self.webView)
{
self.webView =[[UIWebView alloc] initWithFrame:self.bounds];
self.webView.delegate = self;
}
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
NSString *username = #"username";
NSString *password = #"password";
NSURL *url = [NSURL URLWithString:#"https://ssl.letu.edu/"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL: url];
[client setAuthorizationHeaderWithUsername:username password:password];
NSMutableURLRequest *request = [client requestWithMethod:#"GET" path:#"applications/chapelattendance/attendance.html"
parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
[self.webView loadRequest:request];
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Could not load chapel attendance");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
}
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
operation.securityPolicy.allowInvalidCertificates = YES;
You need to send the username and password combination with the http header to authenticate the request while sending the same.
NSData *authData = [#"username:password" dataUsingEncoding:NSASCIIStringEncoding];
NSString *authorization = [NSString stringWithFormat:#"Basic %#", [authData base64Encoding]];
[mutableRequest addValue:authorization forHTTPHeaderField:#"Authorization"];

current library for iOS to consume RESTful web service

I have searched google for the different method and libraries used to consume a RESTful web service. But most of them are either outdated or failed to work when I tried the old libraries. This could be due to the fact that some of there were discontinued and not compatible with the new SDK.
I have tried SBJSON, ASIHTTP, stig's and jsonframework, but non of them seem to be working.
What are some of the current libraries that are being used in iOS to consume RESTful webservice? It will be helpful if anyone can give link to sample tutorial using the same libraries.
Try RestKit: http://restkit.org/
It is well known, popular, consumes XML as well as JSON and works with Core Data (response to NSManagedObjects mapping) which make great backend as a local cache.
Why dont you use iOS API classes like NSURLConnection? (iOS5 or above required I think)
You could invoke REST GET opperation for example like this:
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url];
[req setHTTPMethod:GET];
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:[url host]];//for https
connection = [[NSURLConnection alloc] initWithRequest:req delegate:self startImmediately:YES];
Where url should be an NSURL object pointing to your rest service operation url. And declare the corresponding delegate methods:
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
code = [httpResponse statusCode];
NSLog(#"%# %i",#"Response Status Code: ",code);
[data setLength:0];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)d {
[self.data appendData:d];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[[[UIAlertView alloc] initWithTitle:#"Error"
message:nil
delegate:nil
cancelButtonTitle:#"ok"
otherButtonTitles:nil] show];
self.connection = nil;
self.data = nil;
}
connection, data and code could be local variables to your implementation class. In those variables you are going to store the connection made, the JSON data received (or whatever) and the response http code like 200, 404.
And finally if you are planning to invoke a secured REST service, dont forget to include the authenticationchallenge delegate.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//set the user and password loged in
NSString *username = #"username";
NSString *password = #"password";
NSURLCredential *credential = [NSURLCredential credentialWithUser:username
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
Hope this helps!
Use the built-in NSURLSession and NSJSONSerialization and then use Github's Mantle framework to map the JSON dictionary to your custom ObjC objects.

Resources