I have a question related with Amazon iOS Mobile SDK v2. I have a task to create an empty folder inside provided bucket. To create an empty folder I am using method:
- (void)createDirectory:(AWSS3Object *)directory inBucket:(AWSS3Bucket *)bucket success:(void (^)())success failure:(void (^)(NSError *error))failure
{
NSString *configurationKey = [NSString string];
if (self.configurationType == AmazonServiceConfigurationTypeDefault)
{
configurationKey = S3ConfigurationKey;
}
else if (self.configurationType == AmazonServiceConfigurationTypeHUB)
{
configurationKey = S3HUBConfigurationKey;
}
else if (self.configurationType == AmazonServiceConfigurationTypeTemporary)
{
configurationKey = S3TemporaryConfigurationKey;
}
AWSS3 *s3 = [AWSS3 S3ForKey:configurationKey];
AWSS3PutObjectRequest *putObjectRequest = [AWSS3PutObjectRequest new];
putObjectRequest.key = directory.key;
putObjectRequest.bucket = bucket.name;
putObjectRequest.body = [NSString string];
[[s3 putObject:putObjectRequest] continueWithBlock:^id(AWSTask *task)
{
if (task.error)
{
NSError *error = task.error;
failure(error);
}
else
{
success();
}
return nil;
}];
}
With this method I am always getting the error: The request signature we calculated does not match the signature you provided.
My access and secure keys are correct.
From the previous experience, I was used to get this error because of incorrect parameters.
My key: AWS iOS SDK v2 Test/test/
My bucket: abc-test
Previously, when I was using AWS iOS Mobile SDK v1, practically the same code has been working like a charm.
Does anyone know, where is the problem?
I am able to reproduce this problem, The cause of problem is the trailing slash of the key value has been stripped by NSURL.path ,lead to an incorrect request signature error. We will fix the AWS Mobile SDK in future release. Thanks for bringing it to our attention.
Related
I am trying to add Google Drive support to one of my apps using a private app data folder. I have sign-in working with the GIDSignIn class and the scope set to kGTLRAuthScopeDriveAppdata. Once I am signed in, I can create folders and get a file listing that shows the folders are there, then I can delete the folders and the file listing shows that they are gone. But for some reason when I try to upload a file I get a 403 error ("The user does not have sufficient permissions for this file."). This happens whether I try to put the file in the root of the app data folder or into a folder I have created.
I have set up a project in the Google Developer Console. I have an API key configured to work with my app's bundle ID and given it unrestricted API access. The Google Drive API is enabled.
My code is adapted from Google's own samples so a lot of this may look quite familiar. I've trimmed down the sign-in handling since that appears to be working fine.
- (instancetype) init
{
self = [super init];
if (!self) return nil;
[GIDSignIn sharedInstance].clientID = (NSString *)kGoogleClientId;
//kGoogleClientId is the ID from the developer console.
[GIDSignIn sharedInstance].delegate = self;
[GIDSignIn sharedInstance].scopes = #[kGTLRAuthScopeDriveAppdata];
return self;
}
//GIDSignInDelegate method
- (void) signIn:(GIDSignIn *)signIn didSignInForUser:(GIDGoogleUser *)user withError:(NSError *)error
{
authenticatedUser = user; //authenticatedUser is an instance variable
NSLog(#"Signed in to Google Drive with user %#", user.profile.name);
[delegate GoogleDriveDidSignIn:self];
}
- (GTLRDriveService *) driveService
{
static GTLRDriveService *service;
static dispatch_once_t onceToken;
dispatch_once(&onceToken,
^{
service = [[GTLRDriveService alloc] init];
service.APIKey = (NSString *)kGoogleApiKey;
//kGoogleApiKey matches the developer console too. It has unrestricted API access and is tied to my bundle ID
service.APIKeyRestrictionBundleID = [[NSBundle mainBundle] bundleIdentifier];
service.shouldFetchNextPages = YES;
service.retryEnabled = YES;
});
service.authorizer = authenticatedUser.authentication.fetcherAuthorizer;
//authenticatedUser is an instance variable which stores the user information returned by
//GIDSignIn when the user signs in
return service;
}
- (void) createFolderNamed:(NSString *)folderName completionHandler:(void(^)(NSString *foldername, NSString *newFolderId))completionHandler
{
GoogleDriveHandler * __weak weakself = self;
GTLRDriveService *service = [self driveService];
GTLRDrive_File *folder = [GTLRDrive_File object];
folder.name = folderName;
folder.mimeType = (NSString *)kMimeType_GoogleDriveFolder;
folder.parents = #[#"appDataFolder"];
GTLRDriveQuery_FilesCreate *query = [GTLRDriveQuery_FilesCreate queryWithObject:folder uploadParameters:nil];
[service executeQuery:query completionHandler:^(GTLRServiceTicket * _Nonnull callbackTicket, id _Nullable object, NSError * _Nullable callbackError)
{
if (callbackError)
{
NSLog(#"-createFolderNamed: callbackError: %#", callbackError.localizedDescription);
}
else
{
GTLRDrive_File *createdFolder = (GTLRDrive_File *)object;
if ( [createdFolder.mimeType isEqualToString:(NSString *)kMimeType_GoogleDriveFolder] )
{
NSLog(#"Google Drive created folder named \"%#\" with identifier \"%#\" and mime-type \"%#\"", createdFolder.name, createdFolder.identifier, createdFolder.mimeType);
}
else
{
NSLog(#"Error : Attempted to create folder, but Google Drive created item named \"%#\" with identifier \"%#\" and mime-type \"%#\"", createdFolder.name, createdFolder.identifier, createdFolder.mimeType);
}
}
}];
}
- (void) writeFileAtUrl:(NSURL *)source toFolderWithId:(NSString *)folderId completionHandler:(void(^)(NSString *filename, NSString *newFileId))completionHandler
{
GoogleDriveHandler * __weak weakself = self;
GTLRDriveService *service = [self driveService];
GTLRDrive_File *file = [GTLRDrive_File object];
file.name = source.lastPathComponent;
file.mimeType = #"binary/octet-stream";
file.parents = #[folderId];
file.spaces = #[#"appDataFolder"];
GTLRUploadParameters *parameters = [GTLRUploadParameters uploadParametersWithFileURL:source MIMEType:#"binary/octet-stream"];
parameters.shouldUploadWithSingleRequest = YES;
GTLRDriveQuery_FilesCreate *query = [GTLRDriveQuery_FilesCreate queryWithObject:file uploadParameters:parameters];
query.fields = #"id";
[service executeQuery:query completionHandler:^(GTLRServiceTicket * _Nonnull callbackTicket, id _Nullable object, NSError * _Nullable callbackError)
{
if (callbackTicket.statusCode == 200)
{
GTLRDrive_File *createdFile = (GTLRDrive_File *)object;
NSLog(#"Wrote file %# in Google Drive folder %#", createdFile.name, folderId);
if (completionHandler) completionHandler(createdFile.name, createdFile.identifier);
}
else
{
NSLog(#"-writeFileAtUrl:toFolderWithId:completionHandler: status code = %li : callbackError: %#", callbackTicket.statusCode, callbackError.localizedDescription);
}
}];
}
As an example, I've tried doing this after GIDSignIn logs in:
NSURL *sampleFile = [[NSBundle mainBundle] URLForResource:#"AValidTestFile" withExtension:#"png"];
if (sampleFile)
{
[self writeFileAtUrl:sampleFile toFolderWithId:#"appDataFolder" completionHandler:^(NSString *filename, NSString *newFileId)
{
NSLog(#"Uploaded file %# with ID %#", filename, newFileId);
}];
}
I still just get a 403 error.
At this point, I've read a huge number of tutorials, blog posts and forum threads in several different programming languages. I've gone over my own code several times and added an insane number of logging statements to double check everything, but I can't work out how I can have permission to create folders, but not to put files in them.
Some time later...
If you go through the credential wizard in the Google Console (rather than just selecting an iOS credential because you're creating an iOS app), you get a message which says "Application data cannot be accessed securely from iOS. Please consider selecting another platform" and it refuses to create a credential for you. Is it possible that this just doesn't work, despite the SDK having the necessary constants?
For those who follow after me, I think I've concluded that using the appDataFolder in iOS just doesn't work.
Having switched to using a folder in the Drive space, I've also found that the -uploadParametersWithFileURL:MIMEType: method of GTLRUploadParameters doesn't work. When I use that I get a file called 'Untitled' (containing the file metadata I set in my GTLRDrive_File object) in the root of the drive. As soon as I switched to -uploadParametersWithData:MIMEType: I got the correct file in the correct place.
I suppose the lesson so far is that if something isn't working, assume it’s the SDK.
Example project: http://cl.ly/360k3M3a2y05
I'm playing with the Reddit API for a school project, and came across this library for using it in Objective-C/Swift.
The professor wants us to get our toes wet with Swift, which I'm happy to do, and the goal of the project is to add an extra function onto an existing website's API. (I chose Reddit obviously.)
The mentioned library doesn't have a way to get all the subscriptions for a particular user (only to get one page at a time with the option to paginate), so I want to add the option to get them all in one clean call.
I'm leveraging the method in the aforementioned library that allows you to paginate, the method looks like this:
- (NSURLSessionDataTask *)subscribedSubredditsInCategory:(RKSubscribedSubredditCategory)category pagination:(RKPagination *)pagination completion:(RKListingCompletionBlock)completion {
NSMutableDictionary *taskParameters = [NSMutableDictionary dictionary];
[taskParameters addEntriesFromDictionary:[pagination dictionaryValue]];
NSString *path = [NSString stringWithFormat:#"subreddits/mine/%#.json", RKStringFromSubscribedSubredditCategory(category)];
return [self getPath:path parameters:taskParameters completion:^(NSHTTPURLResponse *response, id responseObject, NSError *error) {
if (!completion) return;
if (responseObject)
{
// A crude check to see if we have been redirected to the login page:
NSString *path = [[response URL] path];
NSRange range = [path rangeOfString:#"login"];
if (range.location != NSNotFound)
{
completion(nil, nil, [RKClient authenticationRequiredError]);
return;
}
// Parse the response:
NSArray *subredditsJSON = responseObject[#"data"][#"children"];
NSMutableArray *subredditObjects = [[NSMutableArray alloc] initWithCapacity:[subredditsJSON count]];
for (NSDictionary *subredditJSON in subredditsJSON)
{
NSError *mantleError = nil;
RKSubreddit *subreddit = [MTLJSONAdapter modelOfClass:[RKSubreddit class] fromJSONDictionary:subredditJSON error:&mantleError];
if (!mantleError)
{
[subredditObjects addObject:subreddit];
}
}
RKPagination *pagination = [RKPagination paginationFromListingResponse:responseObject];
completion([subredditObjects copy], pagination, nil);
}
else
{
completion(nil, nil, error);
}
}];
}
My addition is rather simple, I just call this above method recursively and save the pagination after each successful request, until there's no pages left, and then return the result:
- (void)allSubscribedSubredditsInCategory:(RKSubscribedSubredditCategory)category completion:(void (^)(NSArray *subreddits, NSError *error))completion {
RKPagination *pagination = [RKPagination paginationWithLimit:100];
[self recursiveSubscribedSubredditsWithPagination:pagination subredditsSoFar:[NSArray array] completion:completion];
}
- (void)recursiveSubscribedSubredditsWithPagination:(RKPagination *)pagination subredditsSoFar:(NSArray *)subredditsSoFar completion:(void (^)(NSArray *subreddits, NSError *error))completion {
[self subscribedSubredditsInCategory:RKSubscribedSubredditCategorySubscriber pagination:pagination completion:^(NSArray *newSubreddits, RKPagination *newPagination, NSError *newError) {
// If pagination is nil, we cannot go any further and have reached the end
if (newPagination == nil) {
NSArray *newSubredditsSoFar = [subredditsSoFar arrayByAddingObjectsFromArray:newSubreddits];
NSArray *subredditsWithoutDuplicates = [[NSSet setWithArray:newSubredditsSoFar] allObjects];
completion(subredditsWithoutDuplicates, newError);
} else {
NSArray *newSubredditsSoFar = [subredditsSoFar arrayByAddingObjectsFromArray:newSubreddits];
[self recursiveSubscribedSubredditsWithPagination:newPagination subredditsSoFar:newSubredditsSoFar completion:completion];
}
}];
}
So it looks like this in my viewDidLoad of my view controller:
RKClient.sharedClient().signInWithUsername("includedinproject", password: "includedinproject") { (error) -> Void in
RKClient.sharedClient().allSubscribedSubredditsInCategory(.Subscriber, completion: { (subreddits, error) -> Void in
print(subreddits)
}) <-- error occurs here?
}
However, whenever I call it, I get an EXC_BAD_ACCESS runtime error that doesn't really provide anything other than a memory address, and it appears to be caused at the end of the method in viewDidLoad, as labeled above.
The weird thing that occurs, however, is that this only occurs seemingly on the iPhone 4s simulator. If I build it to run on say, the newest 6s, it works fine. I'm puzzled (it has to work on all simulators for full points).
I went to my professor about it and he has no idea. We emulated the project in Objective-C (rebuilt the project as an Objective-C one) and the call seems to work fine.
My professor even did something with Instruments (not much experience myself) looking at "Zombies" and enabled it in the project as well, and nothing seemed to give him information either, we're both pretty confused.
What is going on here that's causing it to work great in Objective-C, and in Swift if the device isn't a 4s? Example project is at the top.
I integrate gmail into my app by using GData Obcective-C Client for authentication and obtaining therefrom contacts. For authentication I use gtm-oauth2 and this part work pretty good.
My scope for GTMOAuth2ViewControllerTouch init:
NSString *scope = [NSString stringWithFormat:#"https://www.googleapis.com/auth/plus.me %#", [GDataServiceGoogleContact authorizationScope]];
Auth init:
__keychainItemName = [infoPlist objectForKey:#"GoogleKeyChainItem"];
__auth = [GTMOAuth2ViewControllerTouch
authForGoogleFromKeychainForName:__keychainItemName
clientID:[infoPlist objectForKey:#"GoogleClientID"]
clientSecret:[infoPlist objectForKey:#"GoogleClientSecret"]];
For GData building i use this blog (with pics and stuff)
http://hoishing.wordpress.com/2011/08/23/gdata-objective-c-client-setup-in-xcode-4/
GData I get from google repository, just by running this in console
# Non-members may check out a read-only working copy anonymously over HTTP.
svn checkout http://gdata-objectivec-client.googlecode.com/svn/trunk/ gdata-objectivec-client-read-only
Problems begin when I try to get contacts:
- (GDataServiceGoogleContact *)contactService {
static GDataServiceGoogleContact* service = nil;
if (!service) {
service = [[GDataServiceGoogleContact alloc] init];
[service setShouldCacheResponseData:YES];
[service setServiceShouldFollowNextLinks:YES];
[service setAuthorizer:__auth];
}
return service;
}
- (void) methodExecute {
GDataServiceGoogleContact *service = [self contactService];
GDataServiceTicket *ticket;
const int kBuncha = 2000;
NSURL *feedURL = [GDataServiceGoogleContact contactFeedURLForUserID:kGDataServiceDefaultUser];
GDataQueryContact *query = [GDataQueryContact contactQueryWithFeedURL:feedURL];
[query setShouldShowDeleted:NO];
[query setMaxResults:kBuncha];
[ticket setAuthorizer:__auth];
ticket = [service fetchFeedWithQuery:query
delegate:self
didFinishSelector:#selector(contactsFetchTicket:finishedWithFeed:error:)];
}
- (void)contactsFetchTicket:(GDataServiceTicket *)ticket
finishedWithFeed:(GDataFeedContact *)feed
error:(NSError *)error {
if(error != nil){
NSLog(#"%#\n\n\n%#", error, feed);
}
else{
NSLog(#"%#\n\n\n%#", error, feed.entries);
}
}
And here is the point - instead of GDataEntryContact which have to be in feed, I get array of GDataEntryBase objects. There is object description example:
GDataEntryBase 0xb3b2300: {v:3.1 title:John Jackson etag:"Rn4_fjVSLit***."
categories:1 links:photo,self,edit edited:2013-03-14T17:55:57Z
id:http://www.google.com/m8/feeds/contacts/myemail%40gmail.com/base/kindofid
unparsed:<gContact:groupMembershipInfo>,<gd:name>,<gd:phoneNumber>}
I try to replace svn GData to This GData version, but everything is useless. I'm on the edge.
BTW I also turned "on" the option Contacts API at google console and added -DGDATA_INCLUDE_CONTACTS_SERVICE=1 in Other C Flags for GData.
Am I missed something or just stupid?
Great thanks for your reply!
I entered other linker flags only for project and they are, for some reason, do not applied for the whole target.
first let me say that I am new ios/xcode as well as AWS.
I am creating an app that writes data to an AWS S3 bucket. The app works when creating a bucket and putting objects to the US Standard Region. However, when I change the region to Singapore, the app creates the bucket successfully - but, I cannot put objects into the bucket and AWS does not produce an error or exception of any kind.
Here is the code in question. The commented code in the createBucket method successfully creates a bucket in Singapore. The processGrandCentralDispatchUpload method is works for the US Standard region, but does not put objects to my Singapore bucket.
- (void)createBucket
{
// Create the bucket.
#try {
//S3Region *region = [[S3Region alloc] initWithStringValue:kS3RegionAPSoutheast1];
//S3CreateBucketRequest *createBucketRequest = [[S3CreateBucketRequest alloc] initWithName:[Constants S3Bucket] andRegion:region];
S3CreateBucketRequest *createBucketRequest = [[S3CreateBucketRequest alloc] initWithName:[Constants S3Bucket]];
S3CreateBucketResponse *createBucketResponse = [self.s3 createBucket:createBucketRequest];
NSLog(#"create bucket response: %#", createBucketResponse.error);
if(createBucketResponse.error != nil)
{
NSLog(#"Error: %#", createBucketResponse.error);
}
}
#catch (AmazonServiceException* asex) {
NSLog(#"putObject - AmazonServiceException - %#", asex);
}
#catch (AmazonClientException* acex) {
NSLog(#"putObject - AmazonClientException - %#", acex);
}
}
- (void)processGrandCentralDispatchUpload:(NSData *)jsonData withTimestamp:(int)timestamp
{
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
UserData * user = [[[DataStore defaultStore] user] objectAtIndex:0];
NSString * dateKeyComponent = [self putRequestDateComponent:timestamp];
objectName = [NSString stringWithFormat:#"%#/%#/%#", user.email, user.uniqueIdentifier, dateKeyComponent];
S3PutObjectRequest *putObjectRequest = [[S3PutObjectRequest alloc] initWithKey:objectName
inBucket:[Constants S3Bucket]];
putObjectRequest.contentType = #"data/json";
putObjectRequest.data = jsonData;
// Put the image data into the specified s3 bucket and object.
#try {
S3PutObjectResponse *putObjectResponse = [self.s3 putObject:putObjectRequest];
dispatch_async(dispatch_get_main_queue(), ^{
if(putObjectResponse.error != nil)
{
NSLog(#"Error: %#", putObjectResponse.error);
[self showAlertMessage:[putObjectResponse.error.userInfo objectForKey:#"message"] withTitle:#"Upload Error"];
}
else
{
//[self showAlertMessage:#"The data was successfully uploaded." withTitle:#"Upload Completed"];
}
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
});
}
#catch (AmazonServiceException* asex) {
NSLog(#"putObject - AmazonServiceException - %#", asex);
}
#catch (AmazonClientException* acex) {
NSLog(#"putObject - AmazonClientException - %#", acex);
}
});
}
I am one of the maintainers of the AWS SDK for iOS. I'm sorry you're encountering difficulties.
If your bucket name contains non alpha numeric characters you may need to set the endpoint of the Amazon S3 client to the region of your bucket.
AmazonS3Client *s3 = [[AmazonS3Client alloc] initWithCredentialsProvider:provider];
s3.endpoint = [AmazonEndpoints s3Endpoint:AP_SOUTHEAST_1];
You can also turn on verbose logging in your application to see the raw responses from the service while using the SDK. If you do see errors coming back that are not being captured, please let us know.
[AmazonLogger verboseLogging];
A couple things I will note about your code that you may also want to consider:
Once the bucket is created, you no longer need to call create bucket. You may want to consider removing the S3CreateBucketRequest/S3CreateBucketResponse from your application.
S3 bucket naming is unique across all regions. If the bucket was created in US Standard then you cannot create it in Singapore without first deleting the bucket in US Standard.
You seem to be mixing both exception and error handling in your code. Please see our blog post on how to control exception handling in the SDK.
So I am trying to allow two users to swap files that each has in their Google Drive. That involves knowing the ID of the other person's file and using the API calls to retrieve it. Both files sit in folders that have been shared to anyone/public.
Trouble is when I execute the code below I am finding that each user can only use the downloadUrl corresponding to the file they own - the others return a 404. In this case either "mine" or "theirs" depending on the account I'm logged into.
// _driveService and its authorizer setup elsewhere
// Retrieve the metadata then the actual data
NSString *mine = #"0B4Pba9IBDsR3T1NVTC1XSGJTenc";
NSString *theirs = #"0B4n9OyY8tqWpNlNaN1dUc3FsNG8";
NSString *get = [NSString stringWithFormat:#"https://www.googleapis.com/drive/v2/files/%#",theirs];
[_driveService fetchObjectWithURL:[NSURL URLWithString:get] completionHandler:^
(GTLServiceTicket *ticket, GTLDriveFile *file, NSError *error)
{
if (error != nil)
NSLog(#"Error retrieving metadata: %#", error);
else
{
// Download the actual data
GTMHTTPFetcher *fetcher = [_driveService.fetcherService fetcherWithURLString:file.downloadUrl];
[fetcher beginFetchWithCompletionHandler:^
(NSData *data, NSError *error)
{
if (error != nil)
NSLog(#"Error retrieving actual data: %#", error);
else
{
NSString *content = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Content: %#", content);
}
}];
}
}];
Error retrieving actual data: Error Domain=com.google.HTTPStatus Code=404 "The operation couldn’t be completed. (com.google.HTTPStatus error 404.)"
What am I doing wrong here? If it's a permissions thing, why am I allowed to get the metadata?
Note this is for an iOS app and both files were created and uploaded from the app using the official client API (rev 353).
Hah, so it seems the devil is in the detail I left out of the question. When creating the authorizer the scope I was providing is kGTLAuthScopeDriveFile, which was the default in an example and I forgot all about it when everything else thus far worked fine. Apparently I need to use kGTLAuthScopeDrive instead (the differences are explained here)
The logic seems a bit flawed here though, I mean I don't want access to other files that weren't created with the app, I just want access to a public file somebody else created with the app...