Ben Copsey

Added canned access policies

Remove GET/ACL constructors
Added lots of comments
General cleanup
1 // 1 //
2 // ASIS3Request.h 2 // ASIS3Request.h
3 -// Mac  
4 // 3 //
5 // Created by Ben Copsey on 30/06/2009. 4 // Created by Ben Copsey on 30/06/2009.
6 // Copyright 2009 All-Seeing Interactive. All rights reserved. 5 // Copyright 2009 All-Seeing Interactive. All rights reserved.
7 // 6 //
  7 +// A (basic) class for accessing data stored on Amazon's Simple Storage Service (http://aws.amazon.com/s3/)
  8 +// It uses the REST API, with canned access policies rather than full support for ACLs (though if you build/parse them yourself you can still use ACLs)
8 9
9 #import <Foundation/Foundation.h> 10 #import <Foundation/Foundation.h>
10 #import "ASIHTTPRequest.h" 11 #import "ASIHTTPRequest.h"
11 12
  13 +// See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/index.html?RESTAccessPolicy.html for what these mean
  14 +extern NSString *const ASIS3AccessPolicyPrivate; // This is the default in S3 when no access policy header is provided
  15 +extern NSString *const ASIS3AccessPolicyPublicRead;
  16 +extern NSString *const ASIS3AccessPolicyPublicReadWrote;
  17 +extern NSString *const ASIS3AccessPolicyAuthenticatedRead;
  18 +
12 @interface ASIS3Request : ASIHTTPRequest { 19 @interface ASIS3Request : ASIHTTPRequest {
  20 +
  21 + // Your S3 access key. Set it on the request, or set it globally using [ASIS3Request setSharedAccessKey:]
  22 + NSString *accessKey;
  23 +
  24 + // Your S3 secret access key. Set it on the request, or set it globally using [ASIS3Request setSharedSecretAccessKey:]
  25 + NSString *secretAccessKey;
  26 +
  27 + // Name of the bucket to talk to
13 NSString *bucket; 28 NSString *bucket;
  29 +
  30 + // path to the resource you want to access on S3. Leave empty for the bucket root
14 NSString *path; 31 NSString *path;
  32 +
  33 + // The string that will be used in the HTTP date header. Generally you'll want to ignore this and let the class add the current date for you, but the accessor is used by the tests
15 NSString *dateString; 34 NSString *dateString;
  35 +
  36 + // The mime type of the content for PUT requests
  37 + // Set this if having the correct mime type returned to you when you GET the data is important (eg it will be served by a web-server)
  38 + // Will be set to 'application/octet-stream' otherwise in iPhone apps, or autodetected on Mac OS X
16 NSString *mimeType; 39 NSString *mimeType;
17 - NSString *accessKey; 40 +
18 - NSString *secretAccessKey; 41 + NSString *accessPolicy;
19 } 42 }
20 43
21 #pragma mark Constructors 44 #pragma mark Constructors
22 45
  46 +// Create a request, building an appropriate url
23 + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path; 47 + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path;
  48 +
  49 +// Create a PUT request using the file at filePath as the body
24 + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path; 50 + (id)PUTRequestForFile:(NSString *)filePath withBucket:(NSString *)bucket path:(NSString *)path;
25 -+ (id)GETRequestWithBucket:(NSString *)bucket path:(NSString *)path;  
26 -+ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker;  
27 -+ (id)ACLRequestWithBucket:(NSString *)bucket path:(NSString *)path;  
28 51
  52 +// Create a list request
  53 ++ (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker;
29 54
  55 +// Generates the request headers S3 needs
  56 +// Automatically called before the request begins in startRequest
30 - (void)generateS3Headers; 57 - (void)generateS3Headers;
31 -- (void)setDate:(NSDate *)date;  
32 58
  59 +// Uses the supplied date to create a Date header string
  60 +- (void)setDate:(NSDate *)date;
33 61
  62 +// Only works on Mac OS, will always return 'application/octet-stream' on iPhone
34 + (NSString *)mimeTypeForFileAtPath:(NSString *)path; 63 + (NSString *)mimeTypeForFileAtPath:(NSString *)path;
35 64
36 #pragma mark Shared access keys 65 #pragma mark Shared access keys
  66 +
  67 +// Get and set the global access key, this will be used for all requests the access key hasn't been set for
37 + (NSString *)sharedAccessKey; 68 + (NSString *)sharedAccessKey;
38 + (void)setSharedAccessKey:(NSString *)newAccessKey; 69 + (void)setSharedAccessKey:(NSString *)newAccessKey;
39 + (NSString *)sharedSecretAccessKey; 70 + (NSString *)sharedSecretAccessKey;
40 + (void)setSharedSecretAccessKey:(NSString *)newAccessKey; 71 + (void)setSharedSecretAccessKey:(NSString *)newAccessKey;
41 72
42 -#pragma mark S3 Authentication helpers  
43 -+ (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;  
44 -+ (NSString *)base64forData:(NSData *)theData;  
45 73
46 @property (retain) NSString *bucket; 74 @property (retain) NSString *bucket;
47 @property (retain) NSString *path; 75 @property (retain) NSString *path;
@@ -49,4 +77,5 @@ @@ -49,4 +77,5 @@
49 @property (retain) NSString *mimeType; 77 @property (retain) NSString *mimeType;
50 @property (retain) NSString *accessKey; 78 @property (retain) NSString *accessKey;
51 @property (retain) NSString *secretAccessKey; 79 @property (retain) NSString *secretAccessKey;
  80 +@property (assign) NSString *accessPolicy;
52 @end 81 @end
1 // 1 //
2 // ASIS3Request.m 2 // ASIS3Request.m
3 -// Mac  
4 // 3 //
5 // Created by Ben Copsey on 30/06/2009. 4 // Created by Ben Copsey on 30/06/2009.
6 // Copyright 2009 All-Seeing Interactive. All rights reserved. 5 // Copyright 2009 All-Seeing Interactive. All rights reserved.
@@ -9,11 +8,32 @@ @@ -9,11 +8,32 @@
9 #import "ASIS3Request.h" 8 #import "ASIS3Request.h"
10 #import <CommonCrypto/CommonHMAC.h> 9 #import <CommonCrypto/CommonHMAC.h>
11 10
  11 +NSString* const ASIS3AccessPolicyPrivate = @"private";
  12 +NSString* const ASIS3AccessPolicyPublicRead = @"public-read";
  13 +NSString* const ASIS3AccessPolicyPublicReadWrote = @"public-read-write";
  14 +NSString* const ASIS3AccessPolicyAuthenticatedRead = @"authenticated-read";
  15 +
12 static NSString *sharedAccessKey = nil; 16 static NSString *sharedAccessKey = nil;
13 static NSString *sharedSecretAccessKey = nil; 17 static NSString *sharedSecretAccessKey = nil;
14 18
  19 +// Private stuff
  20 +@interface ASIHTTPRequest ()
  21 + + (NSData *)HMACSHA1withKey:(NSString *)key forString:(NSString *)string;
  22 + + (NSString *)base64forData:(NSData *)theData;
  23 +@end
  24 +
15 @implementation ASIS3Request 25 @implementation ASIS3Request
16 26
  27 +- (void)dealloc
  28 +{
  29 + [bucket release];
  30 + [path release];
  31 + [dateString release];
  32 + [mimeType release];
  33 + [accessKey release];
  34 + [secretAccessKey release];
  35 + [super dealloc];
  36 +}
17 37
18 + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path 38 + (id)requestWithBucket:(NSString *)bucket path:(NSString *)path
19 { 39 {
@@ -33,25 +53,11 @@ static NSString *sharedSecretAccessKey = nil; @@ -33,25 +53,11 @@ static NSString *sharedSecretAccessKey = nil;
33 return request; 53 return request;
34 } 54 }
35 55
36 -+ (id)GETRequestWithBucket:(NSString *)bucket path:(NSString *)path  
37 -{  
38 - ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:path];  
39 - [request setRequestMethod:@"GET"];  
40 - return request;  
41 -}  
42 -  
43 -+ (id)ACLRequestWithBucket:(NSString *)bucket path:(NSString *)path  
44 -{  
45 - ASIS3Request *request = [ASIS3Request requestWithBucket:bucket path:[NSString stringWithFormat:@"%@?acl",path]];  
46 - [request setRequestMethod:@"GET"];  
47 - return request;  
48 -}  
49 56
50 + (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker 57 + (id)listRequestWithBucket:(NSString *)bucket prefix:(NSString *)prefix maxResults:(int)maxResults marker:(NSString *)marker
51 { 58 {
52 ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com/?prefix=/%@&max-keys=%hi&marker=%@",bucket,prefix,maxResults,marker]]] autorelease]; 59 ASIS3Request *request = [[[ASIS3Request alloc] initWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://%@.s3.amazonaws.com/?prefix=/%@&max-keys=%hi&marker=%@",bucket,prefix,maxResults,marker]]] autorelease];
53 [request setBucket:bucket]; 60 [request setBucket:bucket];
54 - [request setRequestMethod:@"GET"];  
55 return request; 61 return request;
56 } 62 }
57 63
@@ -60,6 +66,9 @@ static NSString *sharedSecretAccessKey = nil; @@ -60,6 +66,9 @@ static NSString *sharedSecretAccessKey = nil;
60 // NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time. 66 // NSTask does seem to exist in the 2.2.1 SDK, though it's not in the 3.0 SDK. It's probably best if we just use a generic mime type on iPhone all the time.
61 #if TARGET_OS_IPHONE 67 #if TARGET_OS_IPHONE
62 return @"application/octet-stream"; 68 return @"application/octet-stream";
  69 +
  70 +// Grab the mime type using an NSTask to run the 'file' program, with the Mac OS-specific parameters to grab the mime type
  71 +// Perhaps there is a better way to do this?
63 #else 72 #else
64 NSTask *task = [[NSTask alloc] init]; 73 NSTask *task = [[NSTask alloc] init];
65 [task setLaunchPath: @"/usr/bin/file"]; 74 [task setLaunchPath: @"/usr/bin/file"];
@@ -86,37 +95,51 @@ static NSString *sharedSecretAccessKey = nil; @@ -86,37 +95,51 @@ static NSString *sharedSecretAccessKey = nil;
86 95
87 - (void)generateS3Headers 96 - (void)generateS3Headers
88 { 97 {
  98 + // If an access key / secret access keyu haven't been set for this request, let's use the shared keys
89 if (![self accessKey]) { 99 if (![self accessKey]) {
90 [self setAccessKey:[ASIS3Request sharedAccessKey]]; 100 [self setAccessKey:[ASIS3Request sharedAccessKey]];
91 } 101 }
92 if (![self secretAccessKey]) { 102 if (![self secretAccessKey]) {
93 [self setAccessKey:[ASIS3Request sharedSecretAccessKey]]; 103 [self setAccessKey:[ASIS3Request sharedSecretAccessKey]];
94 } 104 }
  105 + // If a date string hasn't been set, we'll create one from the current time
95 if (![self dateString]) { 106 if (![self dateString]) {
96 [self setDate:[NSDate date]]; 107 [self setDate:[NSDate date]];
97 } 108 }
  109 + [self addRequestHeader:@"Date" value:[self dateString]];
  110 +
  111 + // Ensure our formatted string doesn't use '(null)' for the empty path
98 if (![self path]) { 112 if (![self path]) {
99 [self setPath:@""]; 113 [self setPath:@""];
100 } 114 }
  115 +
101 116
102 - [self addRequestHeader:@"Date" value:[self dateString]]; 117 + NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@/%@",[self bucket],[self path]];
103 118
104 - [self addRequestHeader:@"x-amz-acl" value:@"private"]; 119 + // Add a header for the access policy if one was set, otherwise we won't add one (and S3 will default to private)
105 - 120 + NSString *canonicalizedAmzHeaders = @"";
  121 + if ([self accessPolicy]) {
  122 + [self addRequestHeader:@"x-amz-acl" value:[self accessPolicy]];
  123 + canonicalizedAmzHeaders = [NSString stringWithFormat:@"x-amz-acl:%@\n",[self accessPolicy]];
  124 + }
  125 +
  126 + // Jump through hoops while eating hot food
106 NSString *stringToSign; 127 NSString *stringToSign;
107 - NSString *canonicalizedResource = [NSString stringWithFormat:@"/%@/%@",[self bucket],[self path]];  
108 - NSString *canonicalizedAmzHeaders = @"x-amz-acl:private";  
109 if ([[self requestMethod] isEqualToString:@"PUT"]) { 128 if ([[self requestMethod] isEqualToString:@"PUT"]) {
110 [self addRequestHeader:@"Content-Type" value:[self mimeType]]; 129 [self addRequestHeader:@"Content-Type" value:[self mimeType]];
111 - stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@",[self mimeType],dateString,canonicalizedResource]; 130 + stringToSign = [NSString stringWithFormat:@"PUT\n\n%@\n%@\n%@%@",[self mimeType],dateString,canonicalizedAmzHeaders,canonicalizedResource];
112 } else { 131 } else {
113 - stringToSign = [NSString stringWithFormat:@"%@\n\n\n%@\n%@",[self requestMethod],dateString,canonicalizedResource]; 132 + stringToSign = [NSString stringWithFormat:@"%@\n\n\n%@\n%@%@",[self requestMethod],dateString,canonicalizedAmzHeaders,canonicalizedResource];
114 } 133 }
115 - NSLog(@"%@",stringToSign);  
116 NSString *signature = [ASIS3Request base64forData:[ASIS3Request HMACSHA1withKey:[self secretAccessKey] forString:stringToSign]]; 134 NSString *signature = [ASIS3Request base64forData:[ASIS3Request HMACSHA1withKey:[self secretAccessKey] forString:stringToSign]];
117 NSString *authorizationString = [NSString stringWithFormat:@"AWS %@:%@",[self accessKey],signature]; 135 NSString *authorizationString = [NSString stringWithFormat:@"AWS %@:%@",[self accessKey],signature];
118 [self addRequestHeader:@"Authorization" value:authorizationString]; 136 [self addRequestHeader:@"Authorization" value:authorizationString];
119 - 137 +}
  138 +
  139 +- (void)startRequest
  140 +{
  141 + [self generateS3Headers];
  142 + [super startRequest];
120 } 143 }
121 144
122 #pragma mark Shared access keys 145 #pragma mark Shared access keys
@@ -203,4 +226,5 @@ static NSString *sharedSecretAccessKey = nil; @@ -203,4 +226,5 @@ static NSString *sharedSecretAccessKey = nil;
203 @synthesize mimeType; 226 @synthesize mimeType;
204 @synthesize accessKey; 227 @synthesize accessKey;
205 @synthesize secretAccessKey; 228 @synthesize secretAccessKey;
  229 +@synthesize accessPolicy;
206 @end 230 @end
1 // 1 //
2 // ASIS3RequestTests.h 2 // ASIS3RequestTests.h
3 -// Mac 3 +// asi-http-request
4 // 4 //
5 // Created by Ben Copsey on 12/07/2009. 5 // Created by Ben Copsey on 12/07/2009.
6 // Copyright 2009 All-Seeing Interactive. All rights reserved. 6 // Copyright 2009 All-Seeing Interactive. All rights reserved.
1 // 1 //
2 // ASIS3RequestTests.m 2 // ASIS3RequestTests.m
3 -// Mac 3 +// asi-http-request
4 // 4 //
5 // Created by Ben Copsey on 12/07/2009. 5 // Created by Ben Copsey on 12/07/2009.
6 // Copyright 2009 All-Seeing Interactive. All rights reserved. 6 // Copyright 2009 All-Seeing Interactive. All rights reserved.
@@ -57,9 +57,9 @@ @@ -57,9 +57,9 @@
57 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request"); 57 GHAssertTrue(success,@"Failed to generate the correct authorisation header for a list request");
58 58
59 // Test fetch ACL 59 // Test fetch ACL
60 - path = @""; 60 + path = @"?acl";
61 dateString = @"Tue, 27 Mar 2007 19:44:46 +0000"; 61 dateString = @"Tue, 27 Mar 2007 19:44:46 +0000";
62 - request = [ASIS3Request ACLRequestWithBucket:bucket path:path]; 62 + request = [ASIS3Request requestWithBucket:bucket path:path];
63 [request setDateString:dateString]; 63 [request setDateString:dateString];
64 [request setSecretAccessKey:secretAccessKey]; 64 [request setSecretAccessKey:secretAccessKey];
65 [request setAccessKey:accessKey]; 65 [request setAccessKey:accessKey];