Making AWS Product Advertising API Request iOS - ios

Today i have been unable to make a request for an "ItemSearch" request for the product advertising api for aws.
First off, this was working Monday Feburary 24th and now March 1st it does not work. I don't know if there was any updates that may have changed the way AWS works. I couldn't find anything when searching.
My first error is: "Request has expired. Timestamp date is 140301235753Z" this means that the time i enter for the request "140301235753Z" is 15 minutes out of range of what AWS has stored for the UTC time. I do a bit of research and end up changing the code below.
NSDateFormatter *UTCFormatter = [[NSDateFormatter alloc] init];
UTCFormatter.dateFormat = #"yyMMddHHmmss'Z'";
UTCFormatter.timeZone = [NSTimeZone timeZoneWithAbbreviation:#"UTC"];
NSString *timeStamp = [UTCFormatter stringFromDate:[NSDate date]];
i changed the #"yyMMddHHmmss'Z'" to #"yyyy-MM-dd'T'HH:mm:ss'Z'"
Re-running the request again i came across this error: "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method." this means that the signature portion of the API call that i calculate is wrong when comparing the one AWS calculates when it receives my request.
I have used this link Amazon Signature Examples as a reference to check my code below.
// create HMAC with SHA256
const char *cKey = [secretKey cStringUsingEncoding:NSUTF8StringEncoding];
const char *cData = [canonicalString cStringUsingEncoding:NSUTF8StringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *hashData = [NSData dataWithBytes:cHMAC length:CC_SHA256_DIGEST_LENGTH];
NSString *signature = [[DTBase64Coding stringByEncodingData:hashData] stringByURLEncoding];
I don't see an error with my code. I have looked, via google, for an application that may be able to create the string i desire in order to test if my signature is correct, but AWS didn't seem to have anything (that i could find, i could just be a bad googler).
My secret key for the AWS had a "/" character in it. I thought this might be messing with the algorithms for calculating the HMAC. So i created new secret keys until amazon generated me one with out odd characters and tested it. It did not work....
Ultimately what i am trying to make is an AWS Product Advertising API Request using this information
NSString *verb = #"GET";
NSString *hostName = #"webservices.amazon.com";
NSString *path = #"/onca/xml";
NSDictionary *params = #{
#"Service": #"AWSECommerceService",
#"AWSAccessKeyId": accessKey,
#"Operation": #"ItemSearch",
#"ResponseGroup": #"Large",
#"SearchIndex": #"Books",
#"Title": bookTitle,
#"AssociateTag" : trackingID
};
I have substituted sensitive/dynamic information with variables.
I have searched through the documentation PDFs at AWS Product Advertising Documentation and can't seem to figure out my problem.
The most baffling part is that my code was working 6 days ago and i have changed nothing.

I've had a similar experience with another third party API. Apparently my request was being redirected and I had to resign my request.
Be sure to sign your request in this nsurlrequest delegate method:
- (NSURLRequest *)connection: (NSURLConnection *)inConnection
willSendRequest: (NSURLRequest *)inRequest
redirectResponse: (NSURLResponse *)inRedirectResponse;

I made a noob mistake and commented out the code that encoded the body parameters. After uncommenting it and setting the values it all worked fine. So make sure you encode your parameters.

Related

Google Direction API returns error in iOS

Am using following google direction url
https://maps.googleapis.com/maps/api/directions/json?origin=12.976600,77.599300&destination=12.991491,77.715347&client=gme-company&signature=Fwelfejfcb4bbb3hj5bb=
and its not giving any result
in browser its showing
Unable to authenticate the request. Provided 'signature' is not valid for the provided client ID, or the provided 'client' is not valid.
The signature was checked against the URL: /maps/api/directions/json?origin=12.976600,77.599300&destination=12.991491,77.715347&client=gme-company
If this does not match the URL you requested, please ensure that your request is URL encoded correctly. Learn more: https://developers.google.com/maps/documentation/business/webservices/auth
my method is this
- (void) estimateETAWithWithOrigin:(CLLocationCoordinate2D)origin destination:(CLLocationCoordinate2D)destination onSuccess:(DirectionsCompletionBlock) completionBlock {
NSString *baseUrl = [NSString stringWithFormat:#"%#?origin=%f,%f&destination=%f,%f&client=%#&signature=%#",
GOOGLE_DIRECTIONS_API,
origin.latitude,
origin.longitude,
destination.latitude,
destination.longitude,
CLIENTID,CRYPTO_KEY];
baseUrl = [baseUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSLog(#"%#",baseUrl);
.....
}
Pls help
You can find information on how to use digital signatures in the developers documentation
You even have a place there to test your signature.
The code for signing a request in Objective C can be found on the google maps github repo

Evernote iOS SDK - How do I authenticate with a token?

I am using Evernote SDK for iOS and I am saving the authentication token when the user has authorized access.
Once the user installs my application on a different device, I want to use that token to reauthenticate automatically, but it looks like SDK doesn't support that. Is there a way to do that?
I had the same issue last week, and their SDK indeed doesn't support it out-of-the-box, but after some research I found a solution that works perfectly. This solution mimics a valid authentication flow.
A little background:
When the ENSession class initializes, it retrieves the credentials that are saved on the keychain (unless [[ENSession sharedSession] unauthenticate] was called earlier). The problem is that the keychain is empty when using a different device, so our goal is to add a valid ENCredentials instance to the ENCredentialStore.
Solution:
Add the following imports to your code: ENCredentials.h and ENCredentialStore.h. We will need them later.
Initialize the ENSession like you already do, using setSharedSessionConsumerKey:(NSString *)key consumerSecret:(NSString *)secret optionalHost:(NSString *)host.
In order to create a valid ENCredentials object, we need to provide the following objects:
NSString * host
NSString * edamUserId
NSString * noteStoreUrl
NSString * webApiUrlPrefix
NSString * authenticationToken
NSDate * expirationDate
The host is always www.evernote.com (as defined in ENSession under ENSessionBootstrapServerBaseURLStringUS).
edamUserId is the user id you received when you got the original token. Same for the expirationDate. If you are not sure how to get them then you should use [[ENSession sharedSession].userStore getUserWithSuccess:^(EDAMUser *user) once authenticated.
So the only objects that are actually missing are noteStoreUrl and webApiUrlPrefix. Their format is always:
noteStoreUrl: https://www.evernote.com/shard/edam_shard/notestore
webApiUrlPrefix: https://www.evernote.com/shard/edam_shard/
Luckily, your token already contains edam_shared (value of S=, see this):
#"S=s161:U=5ce3f20:E=1561182201b:C=24eb9d000f8:P=285:A=app:V=2:H=e8ebf56eac26aaacdef2f3caed0bc309"
If you extract s161 and put it in the URLs above it will work (I am sure you know how to extract that, but let me know if you're having problems).
Now we are ready to authenticate using the token. First, expose the necessary functions from ENSession using a category:
#interface ENSession(Authentication)
- (void)startup;
- (void)addCredentials:(ENCredentials *)credentials;
#end
And authenticate using the token:
ENCredentials *credentials = [[ENCredentials alloc] initWithHost:ENSessionBootstrapServerBaseURLStringUS edamUserId:userId noteStoreUrl:noteStoreUrl webApiUrlPrefix:webApiUrlPrefix authenticationToken:token expirationDate:expirationDate];
[[ENSession sharedSession] addCredentials:credentials];
[[ENSession sharedSession] startup];
The last line is important in order to refresh the ENSession and retrieve the new stored credentials.
Now you are authenticated and ready to query the SDK. Good luck.

Apigee user authentication, access_token, and cache login session in iOS

I'm new to Apigee and can't seem to wrap my head around it. I am familiar with implementing an iOS app that talks to a database via a webservice call. The call would involve passing back and forth JSON or variables though POST, GET, etc.
The user flow I envision is a lot like Facebook long term token storage. Here are the steps:
Type username and password to login.
The app will remember the access_token in the keychain.
The access_token will be used with any future requests such as updating profile. This way the user doesn't have re-login every time he/she is using the app.
Log out will clear all the token.
If the token is invalid or expired, the app will take the user back to login.
I've taken multiple routes and ended up getting stuck on all of them when it comes to Apigee.
ROUTE 1
I made a call to logInUser and receive access_token in return.
[self.apigeeDataClient logInUser:username password:password];
All this is good until I want to update user's email address using the code below.
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
[requestDict setObject:email forKey:kDataEmail];
NSString *url = [NSString stringWithFormat:#"%#/%#/%#/users/%#?access_token=%#", BASE_URL, UG_ORG_NAME, APP_NAME, [userData objectForKey:kDataUUID], self.accessToken];
NSString *op = #"PUT";
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:[NSDictionary dictionaryWithDictionary:requestDict]
options:0
error:&error];
[self.apigeeDataClient apiRequest:url operation:op data:[NSString stringWithUTF8String:[jsonData bytes]]];
It seems that every other time it's giving me "No content to map to Object due to end of input" error. I checked out this thread but have no luck. I made sure the JSON object is not null. I tried changing the operation from PUT to POST and GET. None of which update the email address in the database.
ROUTE 2
Instead of using apiRequest, I switched to updateEntity.
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
[requestDict setObject:email forKey:kDataEmail];
[requestDict setObject:kDataUsers forKey:kDataType];
[requestDict setObject:self.accessToken forKey:kDataAccessToken];
NSString *entityID = [userData objectForKey:kDataUUID];
[self.apigeeDataClient updateEntity:entityID entity:requestDict];
It looks promising except I started getting "Subject does not have permission" like the issue described in this thread. I tried calling assignPermissions like mentioned in Apigee document but that didn't solve the problem. I even provide access_token with the call, even though I shouldn't have to.
In the attempt to avoid calling login also tried calling storeOAuth2TokensInKeychain and retrieveStoredOAuth2TokensFromKeychain mentioned here. That didn't work either.
The only thing way to resolve this error is by calling logInUser before making a call to updateEntity. This means the user will have to login every time he/she wants to use the app. I know I can store username/password in the keychain. But before I do that I'm wondering if there's better solution out there.
I know it's a long post. So thank you for reading this far. Any pointers are greatly appreciated.

Instagram Signed API Call from iOS

To make the Signed API calls for Instagram post methods to Follow user, Like user's image etc. Users Have limit of 20 Follow per Hour. But if we make Signed API call then user can make 60 Follow per hour. But My question is how to make Signed API call. ?
I tried this method as desribed on Instagram http://instagram.com/developer/restrict-api-requests/ and Make Enforced header enable .and Sent X-Insta-Forwarded-For header field with valid Id. But still after 20 follow it is showing Limit error. Can anyone please help me to how to make Signed API call .
Thanks in advance.
After searching for the things I resolved my issue by this, By making my app signed app:
to make Signed API call for Instagram user need to check both the checkbox in their insta App. under manage clients. and Have to follow The Implicit OAuth Grant flow.
For All Follow/Like post type request user need to add one header:
of Type as
X-Insta-Forwarded-For -> [IP information]|[Signature]
IP should be it the client's remote IP as detected by the your app's load balancer;
Signature is , apply an HMAC with SHA256, and append the hex representation of the signature there . On the IP address as data using your clientSecret as key.
Then join IP info and Signature using pipe | and set that as the value of the header field.
I had used the following code to generate Signature:
-(NSString *)signWithKey:(NSString *)key usingData:(NSString *)data
{
const char *cKey = [key cStringUsingEncoding:NSASCIIStringEncoding];
const char *cData = [data cStringUsingEncoding:NSASCIIStringEncoding];
unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];
CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC);
NSData *HMAC = [[NSData alloc] initWithBytes:cHMAC length:sizeof(cHMAC)];
return [[HMAC.description stringByTrimmingCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:#"<>"]] stringByReplacingOccurrencesOfString:#" " withString:#""];
}
-(NSString*)getheaderData
{
NSString *ipString = [self fetchMyIP];
NSString *signature = [self signWithKey:kClientSecret usingData:ipString];
}
To set header in iOS: [request setValue:[self getheaderData] forHTTPHeaderField:#"X-Insta-Forwarded-For"];
So the API call will be sent as the Signed API call.

How do I upload files to Google Cloud Storage in Objective-C/iOS?

I've been looking for documentation on how to upload files to my 'bucket' in Google Cloud Storage from my iOS app, but I can't find anything at all on the subject. No documentations, no tutorials, no example projects. Am I completely blind? I am simply trying to find a way for my app-users to upload a file to a "public bucket", and get a URL in return. All I can find is chunks of HTTP-protocols or JSON etc, and I have no idea how to use that, but there's no reference to it either. It feels like the author of those documentations expects me to know everything already. I've found some OSX-example codes, but they are too without documentation, and I've been trying to read the code they have provided, but with no luck.
What I'm looking for is something like this:
(This code is made up. It's what I want. I noticed Google used the prefix GTL* for their classes)
NSData *dataToUpload = ... ; //Or UIImage or some movie-format or whatever
NSURL *destination;
GTLStorageUploader *uploader = [GTLStorageUploader alloc]initWithBucket:#"myBucket" withHashOrKeyOrSomething:#"a1b2c3hashkeyOrWhatever"];
destination = [uploader uploadData:dataToUpload];//inBackground etc..
It's actually easier than this when using Parse.com, but there's simply not enough storage space for my app there, so I need to be able to upload the data files to Google Cloud Storage. How?
I did eventually get this to work. It wasn't pretty, though.
It's quite a long time ago now, so I can't really remember all the logic etc, but I'll post what made it work, and change the ID-stuff. I hope it helps, and sorry I didn't remember to write this when I found out and it was fresh in mind. I don't have time to get into this at the moment.
An important note: I also had to change a lot of permissions on the bucket and on the users/authorized on GoogleCloudStorage to make this work. We tried so many different combinations, but I THINK this was the stuff we did:
On each bucket: "Allow everyone to upload/delete/edit etc".
On the auth for the entire CloudStorage: "Allow only entities with certain accessToken to access this CloudStorage.
Allow only www.yourAppEngineURL.com to request such an accessToken.
This felt wrong, and still does. If anyone gets a hold of this accessToken, they can do whatever they want as long as that accessToken is valid. Like.. delete all files. But that was the only way we could make it work. Of course, only authorized users could request this accessToken from OUR appEngine, but still.. meh. I'm no security-guru, and this was just a fun project, so we let it go. Now to the code.
When uploading:
GTLServiceStorage *serv = [[GTLServiceStorage alloc] init];
serv.additionalHTTPHeaders = [NSDictionary dictionaryWithObjectsAndKeys:
#"123123123", #"x-goog-project-id",
#"application/json-rpc", #"Content-Type",
#"application/json-rpc", #"Accept", nil];
GTLStorageObjectAccessControl *objAccessControl = [GTLStorageObjectAccessControl new];
objAccessControl.entity = #"project-owners-123456"; //Some value from the control panel on CloudStorage or something. Or Apps.. Or AppEngine, not sure.
//Probably connected to the accessToken my AppEngine requests and sends to users.
objAccessControl.role = #"OWNER";
GTLStorageObjectAccessControl *objAccessControl2 = [GTLStorageObjectAccessControl new];
objAccessControl2.entity = #"allUsers";
objAccessControl2.role = #"READER";
//Don't remember why I need both. Or what they do. Hey ho.
//It looks like.. Everybody can read. Only my authorized accessToken-people can write? probably.
GTLStorageBucket *bucket = [[GTLStorageBucket alloc] init];
bucket.name = #"my_bucket";
NSError *err;
NSFileHandle *fileHandle = [NSFileHandle myLocalFileURLiThink error:&err];
if(err)
{
NSLog(#"Some error here");
}
GTLUploadParameters *params = [GTLUploadParameters uploadParametersWithFileHandle:fileHandle MIMEType:#"video/mp4 (or something else)"];
GTLStorageObject *storageObject = [[GTLStorageObject alloc] init];
storageObject.acl = #[objAccessControl, objAccessControl2];
NSString *key = [NSString stringWithFormat:#"fileName.mp4"];
// should probably be unique.. The url will be cloud.com/my_bucket/fileName.mp4
//You can generate something unique by putting together the user's userID and the timeStamp for the dateTime right now.
//This user will never upload two things within this second.
storageObject.name = key;
After this point in the code I do some magic I'm not gonna post. I ask for and receive an accessToken to use on GoogleCloudStorage from our own API.
I don't remember where or how I got that token to begin with, but I believe that the backend (AppEngine) had to request it from the CloudStorage-thing, using a pretty standard call.
And, as I said in the beginning, we changed some settings on CloudStorage making our AppEngine the only entity allowed to request this token. Or something.. This token has a lifecycle of like.. 15 minutes or so.. I don't know, it's provided by some Google-default-thing. I might look into it later if any of you need it.
NSString *receivedAccessToken = #"abc123"; //Received from CloudStorage via AppEngine.
NSString *accessToken = #"Bearer %#", receivedAccessToken" //(pseudo) Because it needed to say "Bearer " first. Don't know why, or how I found out..
[serv setAdditionalHTTPHeaders:[NSDictionary dictionaryWithObject:accessToken forKey:#"Authorization"]];
//Upload-magic:
GTLQueryStorage *query = [GTLQueryStorage queryForObjectsInsertWithObject:storageObject bucket:bucket.name uploadParameters:params];
GTLServiceTicket *t = [serv executeQuery:query completionHandler:^(GTLServiceTicket *ticket, id object, NSError *error) {
//Handle error first.
NSLog(#"Success!");
dispatch_async(dispatch_get_main_queue(), ^{
actualURL = [NSString stringWithFormat:#"%#%#", yourFullBucketURL /*( e.g www.googleCloud.com/my_bucket)*/, key /*file.mp4*/];
});
}];
And you can track the progress of the upload after this block, with the ticket-object (like, t.uploadProgressBlock = ...).
This code has been edited quite a bit for Stack-purposes now, so I might have screwed up something, so I probably doesn't work exactly like this. But read it, and try to understand how it works. Good luck. If you have the option, stay with Amazon or something else, this was not fun to work with. Worst documentation ever. Also, Amazon's S3 uploads/downloads faster than GoogleCloudStorage. I regret changing from Amazon to Google. Amazon had so much better API too, almost like the one in my question.
Here's the code used by AppEngine to request the AccessToken:
private GoogleCredential getGoogleCredential(String scope) throws GeneralSecurityException, IOException
{
JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
HttpTransport httpTransport = GoogleNetHttpTransport.newTrustedTransport();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(Constants.SERVICE_ACCOUNT_EMAIL)
.setServiceAccountPrivateKeyFromP12File(new File(Constants.CLOUD_STORAGE_KEY))
.setServiceAccountScopes(Collections.singleton(scope))
.build();
return credential;
}
The parameter "scope" sent in to this method is either https://www.googleapis.com/auth/devstorage.read_only or https://www.googleapis.com/auth/devstorage.read_write
The method returns a GoogleCredential-object, on which you can call googleCredential.refreshToken(). I believe this is the call made to get the token. I'm not sure though.
The Constants (email and key) are stuff you need to set up and get from the auth-page on Google Cloud Storage, I think.
This documentation should cover some of it (it looks more documented now than it did then, I think): https://cloud.google.com/storage/docs/authentication

Resources