@@ -5,35 +5,66 @@ import HTTP
55import Essentials
66import Foundation
77
8- class Authentication {
9- enum Method {
10- case get
11- case post
8+ struct Authentication {
9+ enum Method : String {
10+ case get = " GET "
11+ case put = " PUT "
12+ case post = " POST "
1213 }
1314
1415 let method : Method
1516 let service : String
1617 let host : String
1718 let region : String
1819 let baseURL : String
19- var amzDate : String
2020 let key : String
2121 let secret : String
22- let requestParam : String !
22+ let requestParam : String ?
23+
24+ //used for unit tests
25+ var unitTestDate : Date ?
26+
27+ static let awsQueryAllowed = CharacterSet (
28+ charactersIn: " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~= "
29+ )
30+
31+ static let awsPathAllowed = CharacterSet (
32+ charactersIn: " 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-._~/ "
33+ )
34+
35+ var amzDate : String {
36+ let dateFormatter = DateFormatter ( )
37+ dateFormatter. timeZone = TimeZone ( secondsFromGMT: 0 )
38+ dateFormatter. dateFormat = " YYYYMMdd'T'HHmmss'Z' "
39+ return dateFormatter. string ( from: unitTestDate ?? Date ( ) )
40+ }
2341
24- public init ( method: Method , service: String , host: String , region: String , baseURL: String , key: String , secret: String , requestParam: String ! ) {
42+ init (
43+ method: Method ,
44+ service: String ,
45+ host: String ,
46+ region: String ,
47+ baseURL: String ,
48+ key: String ,
49+ secret: String ,
50+ requestParam: String ? = nil
51+ ) {
2552 self . method = method
2653 self . service = service
2754 self . host = host
2855 self . region = region
29- self . baseURL = baseURL
30- self . amzDate = " "
56+ //TODO(Brett): Proper encoding and error handling.
57+ self . baseURL = baseURL. addingPercentEncoding (
58+ withAllowedCharacters: Authentication . awsPathAllowed
59+ ) !
3160 self . key = key
3261 self . secret = secret
33- self . requestParam = requestParam. addingPercentEncoding ( withAllowedCharacters: . urlHostAllowed)
62+ self . requestParam = requestParam? . addingPercentEncoding (
63+ withAllowedCharacters: Authentication . awsQueryAllowed
64+ )
3465 }
3566
36- public func getSignature( stringToSign: String ) throws -> String {
67+ func getSignature( stringToSign: String ) throws -> String {
3768 let dateHMAC = try HMAC . make ( . sha256, dateStamp ( ) . bytes, key: " AWS4 \( self . secret) " . bytes)
3869 let regionHMAC = try HMAC . make ( . sha256, self . region. bytes, key: dateHMAC)
3970 let serviceHMAC = try HMAC . make ( . sha256, self . service. bytes, key: regionHMAC)
@@ -44,11 +75,15 @@ class Authentication {
4475 return Data ( bytes: signature) . hexEncodedString ( )
4576 }
4677
47- public func canonicalRequest( ) -> String {
78+ func getCredentialScope( ) -> String {
79+ return " \( dateStamp ( ) ) / \( self . region) / \( self . service) /aws4_request "
80+ }
81+
82+ func getCanonicalRequest( ) -> String {
4883 var request : String = " "
4984
50- let uri = " / "
51- let queryString = self . requestParam
85+ let uri = baseURL
86+ let queryString = self . requestParam ?? " "
5287 let headers = " host: \( host) \n x-amz-date: \( amzDate) \n "
5388 let signedHeaders = " host;x-amz-date "
5489 var payload : [ UInt8 ]
@@ -57,24 +92,25 @@ class Authentication {
5792 do {
5893 payload = try Hash . make ( . sha256, " " )
5994 payloadHash = Data ( bytes: payload) . hexEncodedString ( )
60- request = " \( method) \n \( uri) \n \( queryString) \n \( headers) \n \( signedHeaders) \n \( payloadHash) "
95+ request = " \( method. rawValue ) \n \( uri) \n \( queryString) \n \( headers) \n \( signedHeaders) \n \( payloadHash) "
6196 } catch {
6297
6398 }
6499
65100 return request
66101 }
67-
68- public func authorizationHeader( ) -> String {
102+
103+ func authorizationHeader( ) -> String {
69104 let algorithm = " AWS4-HMAC-SHA256 "
70- let credentialScope = " \( dateStamp ( ) ) / \( self . region ) / \( self . service ) /aws4_request "
105+ let credentialScope = getCredentialScope ( )
71106 let canonicalHash : String
72107 let canonical : [ UInt8 ]
73108 var stringToSign : String = " "
74109
75110 do {
76- canonical = try Hash . make ( . sha256, canonicalRequest ( ) )
111+ canonical = try Hash . make ( . sha256, getCanonicalRequest ( ) )
77112 canonicalHash = Data ( bytes: canonical) . hexEncodedString ( )
113+ //TODO(Brett): pull out to make testable
78114 stringToSign = " \( algorithm) \n \( amzDate) \n \( credentialScope) \n \( canonicalHash) "
79115 } catch {
80116 }
@@ -89,26 +125,15 @@ class Authentication {
89125 return " \( algorithm) Credential= \( self . key) / \( credentialScope) , SignedHeaders=host;x-amz-date, Signature= \( signature) "
90126 }
91127
92- public func getAWSHeaders( ) -> [ HeaderKey : String ] {
93- amzDateHeader ( )
128+ func getCanonicalHeaders( ) -> [ HeaderKey : String ] {
94129 return [
95- " Authorization " : authorizationHeader ( ) ,
96- " x-amz-date " : amzDate
130+ " X-Amz-Date " : amzDate ,
131+ " Authorization " : authorizationHeader ( )
97132 ]
98133 }
99134
100- public func amzDateHeader( ) {
101- let date = Date ( )
102-
103- let dateFormatter = DateFormatter ( )
104- dateFormatter. timeZone = TimeZone ( secondsFromGMT: 0 )
105- dateFormatter. dateFormat = " YYYYMMdd'T'HHmmss'Z' "
106-
107- self . amzDate = dateFormatter. string ( from: date)
108- }
109-
110- public func dateStamp( ) -> String {
111- let date = Date ( )
135+ func dateStamp( ) -> String {
136+ let date = unitTestDate ?? Date ( )
112137
113138 let dateFormatter = DateFormatter ( )
114139
0 commit comments