@@ -13,6 +13,13 @@ import AppSyncRealTimeClient
13
13
14
14
class IAMAuthInterceptor : AuthInterceptor {
15
15
16
+ private static let defaultLowercasedHeaderKeys : Set = [ SubscriptionConstants . authorizationkey. lowercased ( ) ,
17
+ RealtimeProviderConstants . acceptKey. lowercased ( ) ,
18
+ RealtimeProviderConstants . contentEncodingKey. lowercased ( ) ,
19
+ RealtimeProviderConstants . contentTypeKey. lowercased ( ) ,
20
+ RealtimeProviderConstants . amzDate. lowercased ( ) ,
21
+ RealtimeProviderConstants . iamSecurityTokenKey. lowercased ( ) ]
22
+
16
23
let authProvider : AWSCredentialsProvider
17
24
let region : AWSRegionType
18
25
@@ -77,12 +84,34 @@ class IAMAuthInterceptor: AuthInterceptor {
77
84
}
78
85
let signer : AWSSignatureV4Signer = AWSSignatureV4Signer ( credentialsProvider: authProvider,
79
86
endpoint: awsEndpoint)
80
- let semaphore = DispatchSemaphore ( value: 0 )
81
87
let mutableRequest = NSMutableURLRequest ( url: endpoint)
88
+ return getAuthHeader ( host: host,
89
+ mutableRequest: mutableRequest,
90
+ signer: signer,
91
+ amzDate: date,
92
+ payload: payload)
93
+ }
94
+
95
+ /// The process of getting the auth header for an IAM based authencation request is as follows:
96
+ ///
97
+ /// 1. A request is created with the IAM based auth headers (date, accept, content encoding, content type, and
98
+ /// additional headers from the `mutableRequest`.
99
+ ///
100
+ /// 2. The request is SigV4 signed by using all the available headers on the request. By signing the request, the signature is added to
101
+ /// the request headers as authorization and security token.
102
+ ///
103
+ /// 3. The signed request headers are stored in an `IAMAuthenticationHeader` object, used for further encoding to
104
+ /// be added to the request for establishing the subscription connection.
105
+ func getAuthHeader( host: String ,
106
+ mutableRequest: NSMutableURLRequest ,
107
+ signer: AWSSignatureV4Signer ,
108
+ amzDate: String ,
109
+ payload: String ) -> IAMAuthenticationHeader {
110
+ let semaphore = DispatchSemaphore ( value: 0 )
82
111
mutableRequest. httpMethod = " POST "
83
112
mutableRequest. addValue ( RealtimeProviderConstants . iamAccept,
84
113
forHTTPHeaderField: RealtimeProviderConstants . acceptKey)
85
- mutableRequest. addValue ( date , forHTTPHeaderField: RealtimeProviderConstants . amzDate)
114
+ mutableRequest. addValue ( amzDate , forHTTPHeaderField: RealtimeProviderConstants . amzDate)
86
115
mutableRequest. addValue ( RealtimeProviderConstants . iamEncoding,
87
116
forHTTPHeaderField: RealtimeProviderConstants . contentEncodingKey)
88
117
mutableRequest. addValue ( RealtimeProviderConstants . iamConentType,
@@ -94,61 +123,97 @@ class IAMAuthInterceptor: AuthInterceptor {
94
123
return nil
95
124
}
96
125
semaphore. wait ( )
126
+
97
127
let authorization = mutableRequest. allHTTPHeaderFields ? [ SubscriptionConstants . authorizationkey] ?? " "
98
128
let securityToken = mutableRequest. allHTTPHeaderFields ? [ RealtimeProviderConstants . iamSecurityTokenKey] ?? " "
99
- let authHeader = IAMAuthenticationHeader ( authorization: authorization,
100
- host: host,
101
- token: securityToken,
102
- date: date,
103
- accept: RealtimeProviderConstants . iamAccept,
104
- contentEncoding: RealtimeProviderConstants . iamEncoding,
105
- contentType: RealtimeProviderConstants . iamConentType)
106
- return authHeader
129
+ let additionalHeaders = mutableRequest. allHTTPHeaderFields? . filter {
130
+ !Self. defaultLowercasedHeaderKeys. contains ( $0. key. lowercased ( ) )
131
+ }
132
+
133
+ return IAMAuthenticationHeader ( host: host,
134
+ authorization: authorization,
135
+ securityToken: securityToken,
136
+ amzDate: amzDate,
137
+ accept: RealtimeProviderConstants . iamAccept,
138
+ contentEncoding: RealtimeProviderConstants . iamEncoding,
139
+ contentType: RealtimeProviderConstants . iamConentType,
140
+ additionalHeaders: additionalHeaders)
107
141
}
108
142
}
109
143
110
- /// Authentication header for IAM based auth
111
- private class IAMAuthenticationHeader : AuthenticationHeader {
144
+ /// Stores the headers for an IAM based authentication. This object can be serialized to a JSON object and passed as the
145
+ /// headers value for establishing subscription connections. This is used as part of the overall interceptor logic
146
+ /// which expects a subclass of `AuthenticationHeader` to be returned.
147
+ /// See `IAMAuthInterceptor.getAuthHeader` for more details.
148
+ class IAMAuthenticationHeader : AuthenticationHeader {
112
149
let authorization : String
113
150
let securityToken : String
114
- let date : String
151
+ let amzDate : String
115
152
let accept : String
116
153
let contentEncoding : String
117
154
let contentType : String
118
155
119
- init ( authorization: String ,
120
- host: String ,
121
- token: String ,
122
- date: String ,
156
+ /// Additional headers that are not one of the expected headers in the request, but because additional headers are
157
+ /// also signed (and added the authorization header), they are required to be stored here to be further encoded.
158
+ let additionalHeaders : [ String : String ] ?
159
+
160
+ init ( host: String ,
161
+ authorization: String ,
162
+ securityToken: String ,
163
+ amzDate: String ,
123
164
accept: String ,
124
165
contentEncoding: String ,
125
- contentType: String ) {
126
- self . date = date
166
+ contentType: String ,
167
+ additionalHeaders : [ String : String ] ? ) {
127
168
self . authorization = authorization
128
- self . securityToken = token
169
+ self . securityToken = securityToken
170
+ self . amzDate = amzDate
129
171
self . accept = accept
130
172
self . contentEncoding = contentEncoding
131
173
self . contentType = contentType
174
+ self . additionalHeaders = additionalHeaders
132
175
super. init ( host: host)
133
176
}
134
177
135
- private enum CodingKeys : String , CodingKey {
136
- case authorization = " Authorization "
137
- case accept
138
- case contentEncoding = " content-encoding "
139
- case contentType = " content-type "
140
- case date = " x-amz-date "
141
- case securityToken = " x-amz-security-token "
178
+ private struct DynamicCodingKeys : CodingKey {
179
+ var stringValue : String
180
+ init ? ( stringValue: String ) {
181
+ self . stringValue = stringValue
182
+ }
183
+ var intValue : Int ?
184
+ init ? ( intValue: Int ) {
185
+ // We are not using this, thus just return nil. If we don't return nil, then it is expected all of the
186
+ // stored properties are initialized, forcing the implementation to have logic that maintains the two
187
+ // properties `stringValue` and `intValue`. Since we don't have a string representation of an int value
188
+ // and aren't using int values for determining the coding key, then simply return nil since the encoder
189
+ // will always pass in the header key string.
190
+ self . intValue = intValue
191
+ self . stringValue = " "
192
+
193
+ }
142
194
}
143
195
144
196
override func encode( to encoder: Encoder ) throws {
145
- var container = encoder. container ( keyedBy: CodingKeys . self)
146
- try container. encode ( authorization, forKey: . authorization)
147
- try container. encode ( accept, forKey: . accept)
148
- try container. encode ( contentEncoding, forKey: . contentEncoding)
149
- try container. encode ( contentType, forKey: . contentType)
150
- try container. encode ( date, forKey: . date)
151
- try container. encode ( securityToken, forKey: . securityToken)
197
+ var container = encoder. container ( keyedBy: DynamicCodingKeys . self)
198
+ // Force unwrapping when creating a `DynamicCodingKeys` will always be successful since the string constructor
199
+ // will never return nil even though the constructor is optional (conformance to CodingKey).
200
+ try container. encode ( authorization,
201
+ forKey: DynamicCodingKeys ( stringValue: SubscriptionConstants . authorizationkey) !)
202
+ try container. encode ( securityToken,
203
+ forKey: DynamicCodingKeys ( stringValue: RealtimeProviderConstants . iamSecurityTokenKey) !)
204
+ try container. encode ( amzDate,
205
+ forKey: DynamicCodingKeys ( stringValue: RealtimeProviderConstants . amzDate) !)
206
+ try container. encode ( accept,
207
+ forKey: DynamicCodingKeys ( stringValue: RealtimeProviderConstants . acceptKey) !)
208
+ try container. encode ( contentEncoding,
209
+ forKey: DynamicCodingKeys ( stringValue: RealtimeProviderConstants . contentEncodingKey) !)
210
+ try container. encode ( contentType,
211
+ forKey: DynamicCodingKeys ( stringValue: RealtimeProviderConstants . contentTypeKey) !)
212
+ if let headers = additionalHeaders {
213
+ for (key, value) in headers {
214
+ try container. encode ( value, forKey: DynamicCodingKeys ( stringValue: key) !)
215
+ }
216
+ }
152
217
try super. encode ( to: encoder)
153
218
}
154
219
}
0 commit comments