mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-08-15 08:53:07 +09:00
189 lines
7.8 KiB
Swift
189 lines
7.8 KiB
Swift
/* 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
|
|
import Deferred
|
|
import SwiftyJSON
|
|
|
|
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 hashedFxAUID: String
|
|
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"].string,
|
|
let key = json["key"].string,
|
|
let api_endpoint = json["api_endpoint"].string,
|
|
let uid = json["uid"].int64,
|
|
let hashedFxAUID = json["hashed_fxa_uid"].string,
|
|
let durationInSeconds = json["duration"].int64,
|
|
let remoteTimestamp = json["remoteTimestamp"].int64 {
|
|
return TokenServerToken(id: id, key: key, api_endpoint: api_endpoint, uid: UInt64(uid),
|
|
hashedFxAUID: hashedFxAUID, durationInSeconds: UInt64(durationInSeconds),
|
|
remoteTimestamp: Timestamp(remoteTimestamp))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
public func asJSON() -> JSON {
|
|
let D: [String: AnyObject] = [
|
|
"id": id as AnyObject,
|
|
"key": key as AnyObject,
|
|
"api_endpoint": api_endpoint as AnyObject,
|
|
"uid": NSNumber(value: uid as UInt64),
|
|
"hashed_fxa_uid": hashedFxAUID as AnyObject,
|
|
"duration": NSNumber(value: durationInSeconds as UInt64),
|
|
"remoteTimestamp": NSNumber(value: remoteTimestamp),
|
|
]
|
|
return JSON(D as NSDictionary)
|
|
}
|
|
}
|
|
|
|
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)\">"
|
|
}
|
|
}
|
|
}
|
|
|
|
open class TokenServerClient {
|
|
let URL: URL
|
|
|
|
public init(URL: URL? = nil) {
|
|
self.URL = URL ?? ProductionSync15Configuration().tokenServerEndpointURL
|
|
}
|
|
|
|
open class func getAudience(forURL URL: URL) -> String {
|
|
if let port = URL.port {
|
|
return "\(URL.scheme!)://\(URL.host!):\(port)"
|
|
} else {
|
|
return "\(URL.scheme!)://\(URL.host!)"
|
|
}
|
|
}
|
|
|
|
fileprivate class func parseTimestampHeader(_ header: String?) -> Timestamp? {
|
|
if let timestampString = header {
|
|
return decimalSecondsStringToTimestamp(timestampString)
|
|
} else {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
fileprivate class func remoteError(fromJSON json: JSON, statusCode: Int, remoteTimestampHeader: String?) -> TokenServerError? {
|
|
if json.error != nil {
|
|
return nil
|
|
}
|
|
if 200 <= statusCode && statusCode <= 299 {
|
|
return nil
|
|
}
|
|
return TokenServerError.remote(code: Int32(statusCode), status: json["status"].string,
|
|
remoteTimestamp: parseTimestampHeader(remoteTimestampHeader))
|
|
}
|
|
|
|
fileprivate class func token(fromJSON json: JSON, remoteTimestampHeader: String?) -> TokenServerToken? {
|
|
if json.error != nil {
|
|
return nil
|
|
}
|
|
if let
|
|
remoteTimestamp = parseTimestampHeader(remoteTimestampHeader), // A token server that is not providing its timestamp is not healthy.
|
|
let id = json["id"].string,
|
|
let key = json["key"].string,
|
|
let api_endpoint = json["api_endpoint"].string,
|
|
let uid = json["uid"].int,
|
|
let hashedFxAUID = json["hashed_fxa_uid"].string,
|
|
let durationInSeconds = json["duration"].int64, durationInSeconds > 0 {
|
|
return TokenServerToken(id: id, key: key, api_endpoint: api_endpoint, uid: UInt64(uid),
|
|
hashedFxAUID: hashedFxAUID, durationInSeconds: UInt64(durationInSeconds),
|
|
remoteTimestamp: remoteTimestamp)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
lazy fileprivate var alamofire: SessionManager = {
|
|
let ua = UserAgent.tokenServerClientUserAgent
|
|
let configuration = URLSessionConfiguration.ephemeral
|
|
var defaultHeaders = SessionManager.default.session.configuration.httpAdditionalHeaders ?? [:]
|
|
defaultHeaders["User-Agent"] = ua
|
|
configuration.httpAdditionalHeaders = defaultHeaders
|
|
return SessionManager(configuration: configuration)
|
|
}()
|
|
|
|
open func token(_ assertion: String, clientState: String? = nil) -> Deferred<Maybe<TokenServerToken>> {
|
|
let deferred = Deferred<Maybe<TokenServerToken>>()
|
|
|
|
var mutableURLRequest = URLRequest(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 in
|
|
|
|
// Don't cancel requests just because our Manager is deallocated.
|
|
withExtendedLifetime(self.alamofire) {
|
|
if let error = response.result.error {
|
|
deferred.fill(Maybe(failure: TokenServerError.local(error as NSError)))
|
|
return
|
|
}
|
|
|
|
if let data = response.result.value as AnyObject? { // Declaring the type quiets a Swift warning about inferring AnyObject.
|
|
let json = JSON(data)
|
|
let remoteTimestampHeader = response.response?.allHeaderFields["X-Timestamp"] as? String
|
|
|
|
if let remoteError = TokenServerClient.remoteError(fromJSON: json, statusCode: response.response!.statusCode,
|
|
remoteTimestampHeader: remoteTimestampHeader) {
|
|
deferred.fill(Maybe(failure: remoteError))
|
|
return
|
|
}
|
|
|
|
if let token = TokenServerClient.token(fromJSON: json, remoteTimestampHeader: remoteTimestampHeader) {
|
|
deferred.fill(Maybe(success: token))
|
|
return
|
|
}
|
|
}
|
|
|
|
deferred.fill(Maybe(failure: TokenServerClientUnknownError))
|
|
}
|
|
}
|
|
return deferred
|
|
}
|
|
}
|