@@ -5,35 +5,66 @@ import HTTP
5
5
import Essentials
6
6
import Foundation
7
7
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 "
12
13
}
13
14
14
15
let method : Method
15
16
let service : String
16
17
let host : String
17
18
let region : String
18
19
let baseURL : String
19
- var amzDate : String
20
20
let key : String
21
21
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
+ }
23
41
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
+ ) {
25
52
self . method = method
26
53
self . service = service
27
54
self . host = host
28
55
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
+ ) !
31
60
self . key = key
32
61
self . secret = secret
33
- self . requestParam = requestParam. addingPercentEncoding ( withAllowedCharacters: . urlHostAllowed)
62
+ self . requestParam = requestParam? . addingPercentEncoding (
63
+ withAllowedCharacters: Authentication . awsQueryAllowed
64
+ )
34
65
}
35
66
36
- public func getSignature( stringToSign: String ) throws -> String {
67
+ func getSignature( stringToSign: String ) throws -> String {
37
68
let dateHMAC = try HMAC . make ( . sha256, dateStamp ( ) . bytes, key: " AWS4 \( self . secret) " . bytes)
38
69
let regionHMAC = try HMAC . make ( . sha256, self . region. bytes, key: dateHMAC)
39
70
let serviceHMAC = try HMAC . make ( . sha256, self . service. bytes, key: regionHMAC)
@@ -44,11 +75,15 @@ class Authentication {
44
75
return Data ( bytes: signature) . hexEncodedString ( )
45
76
}
46
77
47
- public func canonicalRequest( ) -> String {
78
+ func getCredentialScope( ) -> String {
79
+ return " \( dateStamp ( ) ) / \( self . region) / \( self . service) /aws4_request "
80
+ }
81
+
82
+ func getCanonicalRequest( ) -> String {
48
83
var request : String = " "
49
84
50
- let uri = " / "
51
- let queryString = self . requestParam
85
+ let uri = baseURL
86
+ let queryString = self . requestParam ?? " "
52
87
let headers = " host: \( host) \n x-amz-date: \( amzDate) \n "
53
88
let signedHeaders = " host;x-amz-date "
54
89
var payload : [ UInt8 ]
@@ -57,24 +92,25 @@ class Authentication {
57
92
do {
58
93
payload = try Hash . make ( . sha256, " " )
59
94
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) "
61
96
} catch {
62
97
63
98
}
64
99
65
100
return request
66
101
}
67
-
68
- public func authorizationHeader( ) -> String {
102
+
103
+ func authorizationHeader( ) -> String {
69
104
let algorithm = " AWS4-HMAC-SHA256 "
70
- let credentialScope = " \( dateStamp ( ) ) / \( self . region ) / \( self . service ) /aws4_request "
105
+ let credentialScope = getCredentialScope ( )
71
106
let canonicalHash : String
72
107
let canonical : [ UInt8 ]
73
108
var stringToSign : String = " "
74
109
75
110
do {
76
- canonical = try Hash . make ( . sha256, canonicalRequest ( ) )
111
+ canonical = try Hash . make ( . sha256, getCanonicalRequest ( ) )
77
112
canonicalHash = Data ( bytes: canonical) . hexEncodedString ( )
113
+ //TODO(Brett): pull out to make testable
78
114
stringToSign = " \( algorithm) \n \( amzDate) \n \( credentialScope) \n \( canonicalHash) "
79
115
} catch {
80
116
}
@@ -89,26 +125,15 @@ class Authentication {
89
125
return " \( algorithm) Credential= \( self . key) / \( credentialScope) , SignedHeaders=host;x-amz-date, Signature= \( signature) "
90
126
}
91
127
92
- public func getAWSHeaders( ) -> [ HeaderKey : String ] {
93
- amzDateHeader ( )
128
+ func getCanonicalHeaders( ) -> [ HeaderKey : String ] {
94
129
return [
95
- " Authorization " : authorizationHeader ( ) ,
96
- " x-amz-date " : amzDate
130
+ " X-Amz-Date " : amzDate ,
131
+ " Authorization " : authorizationHeader ( )
97
132
]
98
133
}
99
134
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 ( )
112
137
113
138
let dateFormatter = DateFormatter ( )
114
139
0 commit comments