use onion routing for file server requests

pull/227/head
Ryan ZHAO 5 years ago
parent cc09f59926
commit 428daac5b3

@ -44,15 +44,8 @@ public final class FileServerAPI : DotNetAPI {
public static func getDeviceLinks(associatedWith hexEncodedPublicKeys: Set<String>) -> Promise<Set<DeviceLink>> {
let hexEncodedPublicKeysDescription = "[ \(hexEncodedPublicKeys.joined(separator: ", ")) ]"
print("[Loki] Getting device links for: \(hexEncodedPublicKeysDescription).")
return getAuthToken(for: server).then2 { token -> Promise<Set<DeviceLink>> in
let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
let url = URL(string: "\(server)/users?\(queryParameters)")!
let request = TSRequest(url: url)
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global(qos: .default)).map2 { rawResponse -> Set<DeviceLink> in
guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else {
print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
throw DotNetAPIError.parsingFailed
}
func handleRawResponseForDeviceLinks(rawResponse: JSON, data: [JSON]) -> Set<DeviceLink> {
return Set(data.flatMap { data -> [DeviceLink] in
guard let annotations = data["annotations"] as? [JSON], !annotations.isEmpty else { return [] }
guard let annotation = annotations.first(where: { $0["type"] as? String == deviceLinkType }),
@ -90,6 +83,30 @@ public final class FileServerAPI : DotNetAPI {
return deviceLink
}
})
}
return getAuthToken(for: server).then2 { token -> Promise<Set<DeviceLink>> in
let queryParameters = "ids=\(hexEncodedPublicKeys.map { "@\($0)" }.joined(separator: ","))&include_user_annotations=1"
let url = URL(string: "\(server)/users?\(queryParameters)")!
let request = TSRequest(url: url)
if (useOnionRequests) {
return OnionRequestAPI.sendOnionRequestFileServerDest(request, server: server, using: fileServerPublicKey).map2 { rawResponse -> Set<DeviceLink> in
guard let data = rawResponse["data"] as? [JSON] else {
print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
throw DotNetAPIError.parsingFailed
}
return handleRawResponseForDeviceLinks(rawResponse: rawResponse, data: data)
}.map2 { deviceLinks in
storage.setDeviceLinks(deviceLinks)
return deviceLinks
}
}
return LokiFileServerProxy(for: server).perform(request, withCompletionQueue: DispatchQueue.global(qos: .default)).map2 { rawResponse -> Set<DeviceLink> in
guard let json = rawResponse as? JSON, let data = json["data"] as? [JSON] else {
print("[Loki] Couldn't parse device links for users: \(hexEncodedPublicKeys) from: \(rawResponse).")
throw DotNetAPIError.parsingFailed
}
return handleRawResponseForDeviceLinks(rawResponse: json, data: data)
}.map2 { deviceLinks in
storage.setDeviceLinks(deviceLinks)
return deviceLinks
@ -109,7 +126,10 @@ public final class FileServerAPI : DotNetAPI {
let request = TSRequest(url: url, method: "PATCH", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json", "Authorization" : "Bearer \(token)" ]
return attempt(maxRetryCount: 8, recoveringOn: SnodeAPI.workQueue) {
LokiFileServerProxy(for: server).perform(request).map2 { _ in }
if (useOnionRequests) {
return OnionRequestAPI.sendOnionRequestFileServerDest(request, server: server, using: fileServerPublicKey).map2 { _ in }
}
return LokiFileServerProxy(for: server).perform(request).map2 { _ in }
}.handlingInvalidAuthTokenIfNeeded(for: server).recover2 { error in
print("Couldn't update device links due to error: \(error).")
throw error
@ -161,6 +181,16 @@ public final class FileServerAPI : DotNetAPI {
print("[Loki] Couldn't upload profile picture due to error: \(error).")
return Promise(error: error)
}
if (useOnionRequests) {
return OnionRequestAPI.sendOnionRequestFileServerDest(request, server: server, using: fileServerPublicKey).map2 { json in
guard let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else {
print("[Loki] Couldn't parse profile picture from: \(json).")
throw DotNetAPIError.parsingFailed
}
UserDefaults.standard[.lastProfilePictureUpload] = Date()
return downloadURL
}
}
return LokiFileServerProxy(for: server).performLokiFileServerNSURLRequest(request as NSURLRequest).map2 { responseObject in
guard let json = responseObject as? JSON, let data = json["data"] as? JSON, let downloadURL = data["url"] as? String else {
print("[Loki] Couldn't parse profile picture from: \(responseObject).")

@ -35,7 +35,9 @@ extension OnionRequestAPI {
let (promise, seal) = Promise<EncryptionResult>.pending()
DispatchQueue.global(qos: .userInitiated).async {
do {
// The wrapper is not needed when it is a file server onion request
guard JSONSerialization.isValidJSONObject(payload) else { return seal.reject(HTTP.Error.invalidJSON) }
if let destination = destination["destination"] {
let payloadAsData = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
let payloadAsString = String(data: payloadAsData, encoding: .utf8)! // Snodes only accept this as a string
let wrapper: JSON = [ "body" : payloadAsString, "headers" : "" ]
@ -43,6 +45,11 @@ extension OnionRequestAPI {
let plaintext = try JSONSerialization.data(withJSONObject: wrapper, options: [ .fragmentsAllowed ])
let result = try encrypt(plaintext, using: x25519Key)
seal.fulfill(result)
} else {
let plaintext = try JSONSerialization.data(withJSONObject: payload, options: [ .fragmentsAllowed ])
let result = try encrypt(plaintext, using: x25519Key)
seal.fulfill(result)
}
} catch (let error) {
seal.reject(error)
}

@ -198,7 +198,7 @@ public enum OnionRequestAPI {
// Recursively encrypt the layers of the onion (again in reverse order)
encryptionResult = r
var path = path
var destination: JSON = [:]
var destination = dest
func addLayer() -> Promise<EncryptionResult> {
if path.isEmpty {
return Promise<EncryptionResult> { $0.fulfill(encryptionResult) }
@ -218,6 +218,17 @@ public enum OnionRequestAPI {
}
// MARK: Internal API
internal static func getCanonicalHeaders(for request: NSURLRequest) -> [String:Any] {
guard let headers = request.allHTTPHeaderFields else { return [:] }
return headers.mapValues { value in
switch value.lowercased() {
case "true": return true
case "false": return false
default: return value
}
}
}
/// Sends an onion request to `snode`. Builds new paths as needed.
internal static func sendOnionRequestSnodeDest(invoking method: Snode.Method, on snode: Snode, with parameters: JSON, associatedWith publicKey: String) -> Promise<JSON> {
let payload: JSON = [ "method" : method.rawValue, "params" : parameters ]
@ -230,11 +241,39 @@ public enum OnionRequestAPI {
}
/// Sends an onion request to `file server`. Builds new paths as needed.
internal static func sendOnionRequestLsrpcDest(to host: String, with payload: JSON, using x25519Key: String, associatedWith publicKey: String) -> Promise<JSON> {
let destination: JSON = [ "host" : host,
internal static func sendOnionRequestFileServerDest(_ request: NSURLRequest, server: String, using x25519Key: String) -> Promise<JSON> {
var headers = getCanonicalHeaders(for: request)
let urlAsString = request.url!.absoluteString
let serverURLEndIndex = urlAsString.range(of: server)!.upperBound
let endpointStartIndex = urlAsString.index(after: serverURLEndIndex)
let endpoint = String(urlAsString[endpointStartIndex..<urlAsString.endIndex])
let parametersAsString: String
if let tsRequest = request as? TSRequest {
headers["Content-Type"] = "application/json"
let parametersAsData = try! JSONSerialization.data(withJSONObject: tsRequest.parameters, options: [ .fragmentsAllowed ])
parametersAsString = !tsRequest.parameters.isEmpty ? String(bytes: parametersAsData, encoding: .utf8)! : "null"
} else {
headers["Content-Type"] = request.allHTTPHeaderFields!["Content-Type"]
if let parametersAsInputStream = request.httpBodyStream, let parametersAsData = try? Data(from: parametersAsInputStream) {
parametersAsString = "{ \"fileUpload\" : \"\(String(data: parametersAsData.base64EncodedData(), encoding: .utf8) ?? "null")\" }"
} else {
parametersAsString = "null"
}
}
let payload: JSON = [
"body" : parametersAsString,
"endpoint": endpoint,
"method" : request.httpMethod,
"headers" : headers
]
let destination: JSON = [ "host" : request.url?.host,
"target" : "/loki/v1/lsrpc",
"method" : "POST"]
let promise = sendOnionRequest(on: nil, with: payload, to: destination, using: x25519Key, associatedWith: publicKey)
let promise = sendOnionRequest(on: nil, with: payload, to: destination, using: x25519Key, associatedWith: getUserHexEncodedPublicKey())
promise.recover2{ error -> Promise<JSON> in
// TODO: File Server API handle Error
throw error
}
return promise
}

Loading…
Cancel
Save