AWS S3 Upload works on Simulator but not on Device - ios

I'm encountering an interesting problem. Trying to upload a file to AWS S3, it works perfectly when run on a simulator, also when run on a connected device (DEBUG mode). But when I package the app and deploy it to a device, it doesn't work. I keep getting "Request Timed Out".
This is my code,
- (void)uploadFileAtPath:(NSString *)filePath completionHandler:(void (^)(NSString *, NSError *))handler {
NSString *fileName = [filePath lastPathComponent];
AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
getPreSignedURLRequest.bucket = self.bucketName;
getPreSignedURLRequest.key = fileName;
getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodPUT;
getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:3600];
NSString *fileContentTypeStr = #"application/zip";
getPreSignedURLRequest.contentType = fileContentTypeStr;
[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest]
continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#",task.error);
} else {
NSURL *presignedURL = task.result;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:presignedURL];
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
[request setHTTPMethod:#"PUT"];
[request setValue:fileContentTypeStr forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[NSData dataWithContentsOfFile:filePath]];
NSURLSessionConfiguration* config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:nil];
NSURLSessionDataTask* uploadTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(#"URL Session Task Failed: %#", [error localizedDescription]);
}
else {
NSLog(#"URL Session Task Succeeded: HTTP %ld", ((NSHTTPURLResponse*)response).statusCode);
}
handler([[((NSHTTPURLResponse*)response).URL.absoluteString componentsSeparatedByString:#"?"] firstObject], error);
}];
[uploadTask resume];
}
return nil;
}];
}
And this is the error I get (obfuscated sensitive parts) -
Error: The request timed out.,
UserInfo: {
NSErrorFailingURLKey = "https://s3.amazonaws.com/bucketname/file-2-10-2015-10-13PM.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIHY7KKHBLYOPMTPA%2F20151003%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20151003T051306Z&X-Amz-Expires=3599&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=504e1f5083116as09dc6a04c3b4152816a8d9e8bccff8f4ea0cbss9fcdc21eac";
NSErrorFailingURLStringKey = "https://s3.amazonaws.com/bucketname/file-2-10-2015-10-13PM.zip?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIHY7KKHBLYOPMTPA%2F20151003%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20151003T051306Z&X-Amz-Expires=3599&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=504e1f5083116as09dc6a04c3b4152816a8d9e8bccff8f4ea0cbss9fcdc21eac";
NSLocalizedDescription = "The request timed out.";
NSUnderlyingError = "Error Domain=kCFErrorDomainCFNetwork Code=-1001 \"(null)\" UserInfo={_kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-2102}";
"_kCFStreamErrorCodeKey" = "-2102";
"_kCFStreamErrorDomainKey" = 4;
}
I have no idea what is going wrong here! Help much appreciated.
While the device connected to Xcode, I also got the URL out and uploaded the file via cURL. FWIW, that works too!

After 2 days, I finally found the problem! This was the part of the code I did not post, but it is how I was initializing the AWS Client.
AWSStaticCredentialsProvider *cp = [[AWSStaticCredentialsProvider alloc] initWithAccessKey:accesskey secretKey:secretKey];
AWSServiceConfiguration* config = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:cp];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = config;
Now, there was another library that I was using which did exactly the same thing, but with different credentials to a different bucket. Since this other library was getting called after my code, I was having problems with uploading the file. Now why was the error Request Timed Out instead of Invalid Credentials, is something I haven't found out yet.
I changed the initialization code to this --
AWSStaticCredentialsProvider *cp = [[AWSStaticCredentialsProvider alloc] initWithAccessKey:accesskey secretKey:secretKey];
AWSServiceConfiguration* config = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1 credentialsProvider:cp];
[AWSS3TransferManager registerS3TransferManagerWithConfiguration:config forKey:AWSLogFilesUploaderKey];
And obtained an instance of AWSS3TransferManager like this --
AWSS3TransferManager* tm = [AWSS3TransferManager S3TransferManagerForKey:AWSLogFilesUploaderKey];
And then everything went smooth!

Related

Send email in Gmail API - Objective C

We are working on an iOS project that involves sending emails through the Gmail API and we are having trouble finding documentation on how to actually do this.
First, we haven't completely figured out authentication. We are using AppAuth to handle that, and it's worked pretty well so far, but we are not quite sure how to link that up to the Gmail API in our code.
Second, how do we send the message itself? We have the content and everything formatted, we just can't figure out how to actually send the message. All we are looking to do is send a simple message to a specified email address from the user's own email account; no attachments or anything like that. We have seen a couple swift examples, however we would prefer to use Objective C. Any ideas on how we could do this?
Update:
After playing around with things a bit more, we found another way to connect to Gmail. Instead of using the classes from the Google API Objective C Client for REST, we are simply trying to send the email using an HTTP POST method. This appears to be way easier than dealing with all of the errors we were getting before. The only problem we have now is that we still can't quite send messages. With nearly everything we've tried, the API just creates an empty message and puts it in our Sent mailbox; that's it. Here's what we have right now:
- (void)sendEmail{
NSURL *userinfoEndpoint = [NSURL URLWithString:#"https://www.googleapis.com/upload/gmail/v1/users/TEST_USERNAME/messages/send?uploadType=media"];
NSString *currentAccessToken = _authState.lastTokenResponse.accessToken;
[self logMessage:#"Trying to authenticate...."];
// Handle refreshing tokens
NSString *message = [NSString stringWithFormat:#"{\"raw\": \"%#\"}",[self generateMessage]];
NSLog(#"%#", message);
// creates request to the userinfo endpoint, with access token in the Authorization header
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
NSString *authorizationHeaderValue = [NSString stringWithFormat:#"Bearer %#", accessToken];
[request addValue:authorizationHeaderValue forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"POST"];
[request setValue:#"message/rfc822" forHTTPHeaderField:#"Content-Type"];
[request setValue:[NSString stringWithFormat:#"%lu", (unsigned long)[message length]] forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding];
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:nil
delegateQueue:nil];
// performs HTTP request
NSURLSessionDataTask *postDataTask =
[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data,
NSURLResponse *_Nullable response,
NSError *_Nullable error) {
// Handle response
}];
[postDataTask resume];
}];
}
- (NSString *)generateMessage{
NSString *message = [NSString stringWithFormat:#"From: <TEST_USER#domain.com>\nTo: <TEST_USER#domain.com>\nSubject: Test\n\nThis is a test"];
NSString *rawMessage = [message stringByReplacingOccurrencesOfString:#"\\n" withString:#"\n"];
NSData *encodedMessage = [rawMessage dataUsingEncoding:NSUTF8StringEncoding];
NSString *encoded = [encodedMessage base64EncodedStringWithOptions:0];
NSLog(#"%#", encoded);
return encoded;
}
We have tested the encoding part and it is making a proper base64 string, however after that point, something clearly is not formatted right or something. We get a confirmation that the message was successfully created, however all the API does is create an empty email with no recipient, subject, or body. Any ideas on what we could do to get this to work?
I'm not an expert in this but I remembered we have done something similar in the past. Follow the instructions at the following link and make sure that you select the proper option in Gmail API wizard
https://developers.google.com/gmail/api/quickstart/ios?ver=objc
I hope you find this helpful
After numerous experimentations, here is the code that seems to finally work for me, i worked it off your example above.
1st you need to create google project in dev console, get its Client ID and Api-Key(this may not be necessary) and implement Google SignIn in AppDelegete in - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method:
[GIDSignIn sharedInstance].clientID = #"your proj client id here";
[GIDSignIn sharedInstance].delegate = self;
[GIDSignIn sharedInstance].scopes=[NSArray arrayWithObjects:#"https://www.googleapis.com/auth/gmail.send",#"https://www.googleapis.com/auth/gmail.readonly",#"https://www.googleapis.com/auth/gmail.modify", nil];
Now sending emails:
// refresh token
appDelegate.delAuthAccessToken=#"";
[[GIDSignIn sharedInstance] signInSilently];
NSDate *timeStart = [NSDate date];
NSTimeInterval timeSinceStart=0;
while([appDelegate.delAuthAccessToken isEqualToString:#""] && timeSinceStart<10){//wait for new token but no longer than 10s should be enough
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0f]];//1sec increment actually ~0.02s
timeSinceStart = [[NSDate date] timeIntervalSinceDate:timeStart];
}
if (timeSinceStart>=10) {//timed out
return;
}
//compose rfc2822 message AND DO NOT base64 ENCODE IT and DO NOT ADD {raw etc} TOO, put 'To:' 1st, add \r\n between the lines and double that before the actual text message
NSString *message = [NSString stringWithFormat:#"To: %#\r\nFrom: %#\r\nSubject: EzPic2Txt\r\n\r\n%#", appDelegate.delToEmails, appDelegate.delAuthUserEmail, appDelegate.delMessage];
NSURL *userinfoEndpoint = [NSURL URLWithString:#"https://www.googleapis.com/upload/gmail/v1/users/me/messages/send?uploadType=media"];
NSLog(#"%#", message);
//create request
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
[request setHTTPMethod:#"POST"];
[request setHTTPBody:[message dataUsingEncoding:NSUTF8StringEncoding]];//message is plain UTF8 string
//add all headers into session config, maybe ok adding to request too
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.HTTPAdditionalHeaders = #{
#"api-key" : #"api-key here, may not need it though",
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", appDelegate.delAuthAccessToken],
#"Content-type" : #"message/rfc822",
#"Accept" : #"application/json",
#"Content-Length": [NSString stringWithFormat:#"%lu", (unsigned long)[message length]]
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// performs HTTP request
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
// Handle response
}];
[postDataTask resume];
Hope it helps somebody
In my app I used to be able to use MailCore2 but it got blocked by Google (I got access denied when I switched to permitted send, readonly and modify scopes) since MailCore2 works only with FULL permissions. Google allowed to use ONLY send, readonly and modify scopes. There is no guide lines how to use their "great restful api" with Gmail in iOS though, so it seems like HTTP POST is the last resort until they shut it down too.
I cannot have my app to be deemed by Google as insecure. If you are OK with that you can still use MailCore2, no problem.
Receiving email with HTTP GET:
1st get up to 20 unread messages ids:
//get IDs of no more than 20 unread messages
//in query you can add extra filters, say messages only from specific emails
NSString *query=#"from:aaa#gmail.com|from:bbb#yahoo.com";
NSString *tmpStr=[NSString stringWithFormat:#"https://www.googleapis.com/gmail/v1/users/me/messages?maxResults=20&q=\"is:unread\" \"%#\"",query];
NSString *tmpStrURL=[tmpStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *userinfoEndpoint = [NSURL URLWithString:tmpStrURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
[request setHTTPMethod:#"GET"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.HTTPAdditionalHeaders = #{#"api-key" : #"your api key here",
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", yourTokenHere],
#"Accept" : #"application/json"
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// performs HTTP request
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
// Handle response
if (!error){
NSMutableDictionary *jsondata = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
long jsonMsgsCnt = [[jsondata valueForKey:#"resultSizeEstimate"] longValue];
if(jsonMsgsCnt>0){
NSMutableArray *jsonMsgs = [jsondata objectForKey:#"messages"];
for (NSMutableDictionary *tmp in jsonMsgs){
[delMsgsReceived addObject:[tmp objectForKey:#"id"]];
}
}
NSLog(#"retrieve Email Id postDataTask n msg:%li",delMsgsReceived.count);
}else{
NSLog(#"retrieve Email Id postDataTask error:%#",error.description);
}
}];
[postDataTask resume];
Now delMsgsReceived contains messagesIds. Process them to get actual emails one by one:
NSString *tmpStr=[NSString stringWithFormat:#"https://www.googleapis.com/gmail/v1/users/me/messages/%#?format=full", msgId];//supply message id here
NSString *tmpStrURL=[tmpStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURL *userinfoEndpoint = [NSURL URLWithString:tmpStrURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:userinfoEndpoint];
[request setHTTPMethod:#"GET"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
configuration.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
configuration.HTTPAdditionalHeaders = #{
#"api-key" : #"your api key",
#"Authorization" : [NSString stringWithFormat:#"Bearer %#", your auth token],
#"Accept" : #"application/json"
};
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// performs HTTP request
NSURLSessionDataTask *postDataTask =
[session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
// Handle response
if (!error){
NSMutableDictionary *jsondata = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:&error];
NSString *body=[jsondata objectForKey:#"snippet"];//not full msg!
//for full message get the whole payload and extract what you need from there NSMutableArray *jsonPayload = [[jsondata objectForKey:#"payload"] objectForKey:#"headers"];
}else{
//deal with error
NSLog(#"retrieving message error:%#",error.description);
}
}];
[postDataTask resume];

NSURLSession sending token back to server for getting data

I have app iPhone app which will be using API's call.
I made successful call with Username/Password with NSURLSession and receiving token from server....
NSString *username = #"admin";
NSString *password = #"test";
NSString *authString = [NSString stringWithFormat:#"%#:%#",
username,
password];
// 2 - convert authString to an NSData instance
NSData *authData = [authString dataUsingEncoding:NSUTF8StringEncoding];
// 3 - build the header string with base64 encoded data
NSString *authHeader = [NSString stringWithFormat: #"Basic %#",
[authData base64EncodedStringWithOptions:0]];
// 4 - create an NSURLSessionConfiguration instance
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
// 5 - add custom headers, including the Authorization header
[sessionConfig setHTTPAdditionalHeaders:#{
#"Accept": #"application/json",
#"Authorization": authHeader
}
];
// 6 - create an NSURLSession instance
NSURLSession *session =
[NSURLSession sessionWithConfiguration:sessionConfig delegate:self
delegateQueue:nil];
// 7 - create an NSURLSessionDataTask instance
NSString *urlString = #"http://test.myserver.am/api/authentication/Login?username=admin&password=test";
NSURL *url = [NSURL URLWithString:urlString];
NSURLSessionDataTask *task = [session dataTaskWithURL:url
completionHandler:
^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) {
if (error)
{
// do something with the error
return;
}
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
arrTokenData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
[self getDomain];
} else {
// failure: do something else on failure
NSLog(#"httpResponse code: %#", [NSString stringWithFormat:#"%ld", (unsigned long)httpResponse.statusCode]);
NSLog(#"httpResponse head: %#", httpResponse.allHeaderFields);
return;
}
}];
// 8 - resume the task
[task resume];
Now I am using token received from server and making another call to get user Data......
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
NSString *authValue = [arrTokenData valueForKey:#"Token"];
//Configure session with common header fields
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
sessionConfiguration.HTTPAdditionalHeaders = #{#"bearer": authValue};
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration];
NSString *url = #"http://test.myserver.am/api/mobile/LookUps/getuserdata";
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:url]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
if (!error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
NSDictionary *jsonData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers|NSJSONReadingAllowFragments error:nil];
//Process the data
}
}
}];
[task resume];
but I am receiving below status code and ....request is not getting successful....
httpResponse code: 500
httpResponse head: { "Cache-Control" = "no-cache"; "Content-Length" = 36; "Content-Type" = "application/json; charset=utf-8"; Date = "Thu, 06 Oct 2016 12:16:58 GMT"; Expires = "-1"; Pragma = "no-cache"; Server = "Microsoft-IIS/8.5"; "X-AspNet-Version" = "4.0.30319"; "X-Powered-By" = "ASP.NET"; }
****Please note the same APIs is working fine from another(xamarin APP) platform....**
I am using Objective-C.... IOS10
is there my sending token request is not proper....?
please help me out ..... I am stuck here from yesterday...
As has already been mentioned, I'm pretty sure it should be:
NSString *authValue = [NSString stringWithFormat:#"Bearer %#",
[arrTokenData valueForKey:#"Token"]];
sessionConfiguration.HTTPAdditionalHeaders = #{#"Authorization": authValue};
With that said, a 500 error is an internal server error, not an authentication error. It seems likely that the real problem has nothing to do with authentication, and that your request itself is malformed in some way or that there is a bug in the server-side code that you're somehow tickling.
Also, your code doesn't seem to be checking to see if the token is actually present in the response, unless you're doing that elsewhere.
I would start by checking to make sure the token is actually there, and if it is, enable whatever debugging you can enable on the server and look through the logs to try to figure out what is causing the 500 error on the server side. Chances are, the fix will be obvious once you see what's actually happening on the server side.
Firstly, you should not pass username / password in the ULR in the first authentication call, since you already pass it as a header field. Parameters in the URL are not secure. Sensitive data should always be passed by using POST instead of GET method. But this is not the problem.
Try set the header field in the second call like this:
NSString *authorizationHeader = [NSString stringWithFormat: #"Bearer %#", authValue];
[sessionConfig setHTTPAdditionalHeaders:#{
#"Accept": #"application/json",
#"Authorization": authorizationHeader
}
];
Basically, the auth header field should look like this (from postman):
image
Attention: I did not test / build my code!

Download secure file from S3 server using accesskey and secretkey

I'm trying to download a secure file from S3 server using NSURLSessionDownloadTask,but it returns me 403 error(Access Denied).
My Code:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://xxx.amazonaws.com/bucket-name/file_name"]];
request.HTTPMethod = #"GET";
[request setValue:#"kAccessKey" forHTTPHeaderField:#"accessKey"];
[request setValue:#"kSecretKey" forHTTPHeaderField:#"secretKey"];
NSURLSessionDownloadTask *downloadPicTask = [[NSURLSession sharedSession] downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
UIImage *downloadedImage = [UIImage imageWithData:
[NSData dataWithContentsOfURL:location]];
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.imageView.image = downloadedImage;
[weakSelf.activityIndicator stopAnimating];
});
}];
[downloadPicTask resume];
EDIT
I found this code :
AWSCognitoCredentialsProvider *credentialsProvider = [[AWSCognitoCredentialsProvider alloc]initWithRegionType:AWSRegionUSWest2 identityId:#"xxxxxxx" identityPoolId:#"xxxxxxxx" logins:nil];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc]initWithRegion:AWSRegionUSWest2 credentialsProvider:credentialsProvider];
// Construct the NSURL for the download location.
NSString *downloadingFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"sample_img.png"];
NSURL *downloadingFileURL = [NSURL fileURLWithPath:downloadingFilePath];
// Construct the download request.
AWSS3TransferManagerDownloadRequest *downloadRequest = [[AWSS3TransferManagerDownloadRequest alloc]init];
AWSS3TransferManager * transferManager = [AWSS3TransferManager S3TransferManagerForKey:[[configuration credentialsProvider]sessionKey]];
downloadRequest.bucket = #"test-upload-bucket";
downloadRequest.key = #"sample_img.png";
downloadRequest.downloadingFileURL = downloadingFileURL;
[[transferManager download:downloadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor]
withBlock:^id(AWSTask *task){
return nil;
}];
What is the input value for IdentityId and IdentityPoolId ?
A working summer 2017 function, to which you can pass your image name and table cell (I'm downloading logos for certain table entries). Make sure you modify your credentials region, and your key/secret credentials, and your bucket name. Note: Your credentials should not be root. Create a separate IAM user/group/policy and authorize only specific resources (buckets/objects) and specific actions. Create your key and secret. I did this, because I do not want to use amazon's cogito to manage my users. but want my mobile app to access S3 resources directly and securely, and also not via a some redundant server-side script. But, Amazon recommends for mobile, you use cogito and have each user use own/temp creds. Caveat emptor.
-(void) awsImageLoad:(NSString*)imageFile :(UITableViewCell*)cell {
NSArray *filepathelements = [imageFile componentsSeparatedByString:#"/"];
if (filepathelements.count == 0) return;
//extract only the name from a possibe folder/folder/imagename
NSString *imageName = [filepathelements objectAtIndex:filepathelements.count-1];
AWSStaticCredentialsProvider *credentialsProvider =
[[AWSStaticCredentialsProvider alloc]
initWithAccessKey:#"_______________"
secretKey:#"__________________________________"];
//My credentials exist on the US East 1 region server farm
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc]initWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
// Construct the NSURL for the temporary download location.
NSString *downloadingFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:imageName];
NSURL *downloadingFileURL = [NSURL fileURLWithPath:downloadingFilePath];
// Construct the download request.
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
// S3 has only a Global Region -- establish our creds configuration
[AWSS3TransferManager registerS3TransferManagerWithConfiguration:configuration forKey:#"GlobalS3TransferManager"];
AWSS3TransferManager * transferManager = [AWSS3TransferManager S3TransferManagerForKey:#"GlobalS3TransferManager"];
downloadRequest.bucket = #"my_bucket_name";
downloadRequest.key = imageFile;
downloadRequest.downloadingFileURL = downloadingFileURL;
[[transferManager download:downloadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor] withBlock:^id(AWSTask *task){
if (task.error){
if ([task.error.domain isEqualToString:AWSS3TransferManagerErrorDomain]) {
switch (task.error.code) {
case AWSS3TransferManagerErrorCancelled:
case AWSS3TransferManagerErrorPaused:
break;
default:
NSLog(#"Error: %#", task.error);
break;
}
} else {
NSLog(#"Error: %#", task.error);
}
}
if (task.result) {
// ...this runs on main thread already
cell.imageView.image = [UIImage imageWithContentsOfFile:downloadingFilePath];
}
return nil;
}];
}
All HTTP Request need to be properly signed before send to AWS server, the signing Process is very complicated Signature Version 4 Signing Process so I suggest to try AWS Mobile SDK for iOS v2
The example shown by Arun_ is a code snippets that how to use transferManager to download a file via AWS Mobile SDK for iOS v2.
This worked for me:
AWSStaticCredentialsProvider *credentialsProvider = [[AWSStaticCredentialsProvider alloc]initWithAccessKey:#"AccessKey" secretKey:#"secretKey"];
AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc]initWithRegion:AWSRegionUSWest2 credentialsProvider:credentialsProvider];
// Construct the NSURL for the download location.
NSString *downloadingFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"sample_img.png"];
NSURL *downloadingFileURL = [NSURL fileURLWithPath:downloadingFilePath];
// Construct the download request.
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
[AWSS3TransferManager registerS3TransferManagerWithConfiguration:configuration forKey:#"USWest2S3TransferManager"];
AWSS3TransferManager * transferManager = [AWSS3TransferManager S3TransferManagerForKey:#"USWest2S3TransferManager"];
downloadRequest.bucket = #"test-upload-bucket";
downloadRequest.key = #"sample_img.png";
downloadRequest.downloadingFileURL = downloadingFileURL;
[[transferManager download:downloadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor]
withBlock:^id(AWSTask *task){
return nil;
}];

AWSS3GetPreSignedURLRequest upload with custom headers giving 403

I've been having some problems with my AWSS3GetPreSignedURLRequest and AFNetworking's NSMutableURLRequest. I can successfully upload a file with just the Content-Type header. However, if I add the x-amz-acl and the x-amz-server-side-encryption, the upload will fail with 403 - No Permission error. What can I do? Is it an Amazon side problem, using server-side encryption or ACLs isn't allowed with pre-signed URLs, or changing the request will work? I looked pretty deep on the AWS documentation and the iOS SDK reference, but nothing on this. By the way, I'm using AWS iOS SDK v2. Does anyone how how to do this?
NSString *keyName;
NSString *fileContentTypeStr;
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:#"MM.d.h.mm.ss"];
if([multipleFileType isEqualToString:#"PNG"])
{
fileContentTypeStr = #"image/png";
keyName = [NSString stringWithFormat:#"image_%#.png", [formatter stringFromDate:[NSDate date]]];
}
else if([multipleFileType isEqualToString:#"JPG"])
{
fileContentTypeStr = #"image/jpeg";
keyName = [NSString stringWithFormat:#"image_%#.jpg", [formatter stringFromDate:[NSDate date]]];
}
self.imageUploadURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:#"image"]];
[imageData writeToURL:self.imageUploadURL atomically:YES];
AWSS3GetPreSignedURLRequest *getPreSignedURLRequest = [AWSS3GetPreSignedURLRequest new];
getPreSignedURLRequest.bucket = [NSString stringWithFormat:#"BUCKET-NAME/%#", folderObject.objectId];
getPreSignedURLRequest.key = keyName;
getPreSignedURLRequest.HTTPMethod = AWSHTTPMethodPUT;
getPreSignedURLRequest.expires = [NSDate dateWithTimeIntervalSinceNow:3600];
getPreSignedURLRequest.contentType = fileContentTypeStr;
[[[AWSS3PreSignedURLBuilder defaultS3PreSignedURLBuilder] getPreSignedURL:getPreSignedURLRequest]
continueWithBlock:^id(BFTask *task) {
if (task.error) {
NSLog(#"Error: %#",task.error);
} else {
NSURL *presignedURL = task.result;
NSMutableURLRequest *URLRequest = [NSMutableURLRequest requestWithURL:presignedURL];
[URLRequest setValue:fileContentTypeStr forHTTPHeaderField:#"Content-Type"];
[URLRequest setValue:#"AES-256" forHTTPHeaderField:#"x-amz-server-side-encryption"];
[URLRequest setValue:#"private" forHTTPHeaderField:#"x-amz-acl"];
URLRequest.HTTPMethod = #"PUT";
URLRequest.HTTPBody = imageData;
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSProgress *progress;
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:URLRequest progress:&progress completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if(!error){
NSLog(#"File was successfully uploaded.");
}
}];
[uploadTask resume];
}
return nil;
}];
Note: The code sample there lacks a few things that are on the global headers, and a few other completion blocks + delegates, but I believe everyone will understand the point here.
Anticipated thanks,
As #YosukeMatsuda noticed in comments you can use putObjectAcl: method.
I put ACL for uploaded object on URLSession didCompleteWithError: delegate method like this:
//
self.awss3 = [[AWSS3 alloc] initWithConfiguration:__your_config__];
//
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error)
{
NSLog(#"S3 UploadTask: %# completed with error: %#", task, [error localizedDescription]);
}
else
{
// AWSS3GetPreSignedURLRequest does not contain ACL property, so it has to be set after file was uploaded to bucket
AWSS3PutObjectAclRequest *aclRequest = [AWSS3PutObjectAclRequest new];
aclRequest.bucket = #"your_bucket";
aclRequest.key = #"your_key";
aclRequest.ACL = AWSS3ObjectCannedACLPublicRead;
[[self.awss3 putObjectAcl:aclRequest] continueWithBlock:^id(BFTask *bftask) {
if (bftask.error)
{
NSLog(#"Error putObjectAcl: %#", [bftask.error localizedDescription]);
}
else
{
AWSEndpoint *endpoint = self.awss3.configuration.endpoint;
NSURL *publicReadURL = [[endpoint.URL URLByAppendingPathComponent:backgroundUploadTask.bucket] URLByAppendingPathComponent:backgroundUploadTask.key];
}
return nil;
}];
}
}
We added - setValue:forRequestParameter: to AWSS3GetPreSignedURLRequest. You can use this method to add ACL and encryption headers.
Also, we introduced a new AWSS3TransferUtility for simplifying the background transfer. Refer to our blog post for more details.

iOS8 extension background NSURLSession sandbox error

I'm trying to upload a file from a sharing extension in the Photos app, using a background NSURLSession. Because a background NSURLSession only supports an upload task using the uploadTaskWithRequest:WithFile: API, I first get the URL for the image URL that was retrieved from the extension, write the image content to the shared container, then upload the new file. It seems like NSURLSession is having permission issues, I am getting this error:
"Failed to issue sandbox extension for file file:///private/var/mobile/Containers/Shared/AppGroup/..."
I know there are a few similar posts to this but none of them are loading an url from an extension and does not show where to write the temporary file to.
Here's the code:
- (void)fetchImageURLInExtensionContext:(NSExtensionContext*) context onComplete:(void (^)()) completion
{
NSExtensionItem *item = self.extensionContext.inputItems[0];
NSItemProvider *provider = item.attachments[0];
if ([provider hasItemConformingToTypeIdentifier:#"public.jpeg"]) {
[provider loadItemForTypeIdentifier:#"public.jpeg" options:nil completionHandler:^(id<NSSecureCoding> item, NSError *error) {
NSObject *obj = item;
if ([obj isKindOfClass:[NSURL class]]) {
self.imageURL = obj;
completion();
}
}];
}
}
- (void)postImage
{
// copy file to shared container
NSURL *containerURL = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:#"group.com.mytestgroup"];
NSString *writeToPath = [[containerURL path] stringByAppendingPathComponent:#"temp.jpg"];
BOOL success = [[NSData dataWithContentsOfURL:self.imageURL] writeToFile:writeToPath atomically:YES];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"https://api.imgur.com/3/image"]];
NSString *boundary = #"multipartboundary";
[request addValue:[NSString stringWithFormat:#"multipart/form-data; boundary=%#", boundary] forHTTPHeaderField:#"Content-Type"];
request.HTTPMethod = #"POST";
[request setValue:#"Client-ID my_imgur_client_id" forHTTPHeaderField:#"Authorization"];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:#"blah"];
config.sharedContainerIdentifier = #"group.com.mytestgroup";
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
NSURLSessionTask *uploadTask = [session uploadTaskWithRequest:request fromFile:[NSURL fileURLWithPath:writeToPath]];
[uploadTask resume];
}
workaround solution: move the file from inbox to temp directory and upload from there.

Resources