forked from mozilla-mobile/firefox-ios
-
Notifications
You must be signed in to change notification settings - Fork 0
/
TokenServerClient.swift
179 lines (156 loc) · 7.12 KB
/
TokenServerClient.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import Alamofire
import Shared
import Foundation
let TokenServerClientErrorDomain = "org.mozilla.token.error"
let TokenServerClientUnknownError = TokenServerError.Local(
NSError(domain: TokenServerClientErrorDomain, code: 999,
userInfo: [NSLocalizedDescriptionKey: "Invalid server response"]))
public struct TokenServerToken {
public let id: String
public let key: String
public let api_endpoint: String
public let uid: UInt64
public let durationInSeconds: UInt64
// A healthy token server reports its timestamp.
public let remoteTimestamp: Timestamp
/**
* Return true if this token points to the same place as the other token.
*/
public func sameDestination(other: TokenServerToken) -> Bool {
return self.uid == other.uid &&
self.api_endpoint == other.api_endpoint
}
public static func fromJSON(json: JSON) -> TokenServerToken? {
if let
id = json["id"].asString,
key = json["key"].asString,
api_endpoint = json["api_endpoint"].asString,
uid = json["uid"].asInt64,
durationInSeconds = json["duration"].asInt64,
remoteTimestamp = json["remoteTimestamp"].asInt64 {
return TokenServerToken(id: id, key: key, api_endpoint: api_endpoint, uid: UInt64(uid),
durationInSeconds: UInt64(durationInSeconds), remoteTimestamp: Timestamp(remoteTimestamp))
}
return nil
}
public func asJSON() -> JSON {
let D: [String: AnyObject] = [
"id": id,
"key": key,
"api_endpoint": api_endpoint,
"uid": NSNumber(unsignedLongLong: uid),
"duration": NSNumber(unsignedLongLong: durationInSeconds),
"remoteTimestamp": NSNumber(unsignedLongLong: remoteTimestamp),
]
return JSON(D)
}
}
enum TokenServerError {
// A Remote error definitely has a status code, but we may not have a well-formed JSON response
// with a status; and we could have an unhealthy server that is not reporting its timestamp.
case Remote(code: Int32, status: String?, remoteTimestamp: Timestamp?)
case Local(NSError)
}
extension TokenServerError: MaybeErrorType {
var description: String {
switch self {
case let Remote(code: code, status: status, remoteTimestamp: _):
if let status = status {
return "<TokenServerError.Remote \(code): \(status)>"
} else {
return "<TokenServerError.Remote \(code)>"
}
case let .Local(error):
return "<TokenServerError.Local Error Domain=\(error.domain) Code=\(error.code) \"\(error.localizedDescription)\">"
}
}
}
public class TokenServerClient {
let URL: NSURL
public init(URL: NSURL? = nil) {
self.URL = URL ?? ProductionSync15Configuration().tokenServerEndpointURL
}
public class func getAudienceForURL(URL: NSURL) -> String {
if let port = URL.port {
return "\(URL.scheme)://\(URL.host!):\(port)"
} else {
return "\(URL.scheme)://\(URL.host!)"
}
}
private class func parseTimestampHeader(header: String?) -> Timestamp? {
if let timestampString = header {
return decimalSecondsStringToTimestamp(timestampString)
} else {
return nil
}
}
private class func remoteErrorFromJSON(json: JSON, statusCode: Int, remoteTimestampHeader: String?) -> TokenServerError? {
if json.isError {
return nil
}
if 200 <= statusCode && statusCode <= 299 {
return nil
}
return TokenServerError.Remote(code: Int32(statusCode), status: json["status"].asString,
remoteTimestamp: parseTimestampHeader(remoteTimestampHeader))
}
private class func tokenFromJSON(json: JSON, remoteTimestampHeader: String?) -> TokenServerToken? {
if json.isError {
return nil
}
if let
remoteTimestamp = parseTimestampHeader(remoteTimestampHeader), // A token server that is not providing its timestamp is not healthy.
id = json["id"].asString,
key = json["key"].asString,
api_endpoint = json["api_endpoint"].asString,
uid = json["uid"].asInt,
durationInSeconds = json["duration"].asInt64
where durationInSeconds > 0 {
return TokenServerToken(id: id, key: key, api_endpoint: api_endpoint, uid: UInt64(uid),
durationInSeconds: UInt64(durationInSeconds), remoteTimestamp: remoteTimestamp)
}
return nil
}
lazy private var alamofire: Alamofire.Manager = {
let ua = UserAgent.tokenServerClientUserAgent
let configuration = NSURLSessionConfiguration.ephemeralSessionConfiguration()
return Alamofire.Manager.managerWithUserAgent(ua, configuration: configuration)
}()
public func token(assertion: String, clientState: String? = nil) -> Deferred<Maybe<TokenServerToken>> {
let deferred = Deferred<Maybe<TokenServerToken>>()
let mutableURLRequest = NSMutableURLRequest(URL: URL)
mutableURLRequest.setValue("BrowserID " + assertion, forHTTPHeaderField: "Authorization")
if let clientState = clientState {
mutableURLRequest.setValue(clientState, forHTTPHeaderField: "X-Client-State")
}
alamofire.request(mutableURLRequest)
.validate(contentType: ["application/json"])
.responseJSON { (_, response, result) in
// Don't cancel requests just because our Manager is deallocated.
withExtendedLifetime(self.alamofire) {
if let error = result.error as? NSError {
deferred.fill(Maybe(failure: TokenServerError.Local(error)))
return
}
if let data: AnyObject = result.value { // Declaring the type quiets a Swift warning about inferring AnyObject.
let json = JSON(data)
let remoteTimestampHeader = response?.allHeaderFields["x-timestamp"] as? String
if let remoteError = TokenServerClient.remoteErrorFromJSON(json, statusCode: response!.statusCode,
remoteTimestampHeader: remoteTimestampHeader) {
deferred.fill(Maybe(failure: remoteError))
return
}
if let token = TokenServerClient.tokenFromJSON(json, remoteTimestampHeader: remoteTimestampHeader) {
deferred.fill(Maybe(success: token))
return
}
}
deferred.fill(Maybe(failure: TokenServerClientUnknownError))
}
}
return deferred
}
}