Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ubf adds proxy and dev setting #90

Merged
merged 6 commits into from
May 14, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions Sources/UBDevTools/DevTools.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Foundation
import UIKit
import UBFoundation

protocol DevTool {
static func setup()
Expand Down Expand Up @@ -42,6 +43,22 @@ public enum UBDevTools {
CacheDevTools.additionalCaches = caches
}

/// Sets a static proxy for networking debugging, if all requests should be proxied through a predefined proxy.
///
/// This only works in combination with the `friendlySharedSession` as this proxy setup is used only there.
/// Additionally you might want to set the `NSAllowsArbitraryLoads` flag in your `Info.plist`
///
/// - Parameters:
/// - host: The host of your proxy, e.g. 'myproxy.ubique.ch'
/// - port: The port of your proxy, e.g. 8888
/// - username: Set the username if the proxy needs authorization
/// - password: Set the password if the proxy needs authorization
public static func setupProxySettings(host: String, port: Int, username: String?, password: String?) {
UBDevToolsProxyHelper.shared.setProxy(
host: host, port: port, username: username, password: password
)
}

// MARK: - Helper methods

private static func setupNavbarAppearance() {
Expand Down
18 changes: 18 additions & 0 deletions Sources/UBDevTools/DevToolsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,15 @@ public struct DevToolsView: View {
CacheDevTools.clearCache(cache)
}
}
Section(header: Text("Proxy settings")) {
Toggle("Start Proxy", isOn: Binding(get: { Self.enableNetworkingProxySettings }, set: { Self.enableNetworkingProxySettings = $0 }))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Den Restart brauchen wir hier nicht, weil man sowieso Save and exit klickt, oder?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Genau, hatte ich ursprünglich drin, bis ich den "Speichern" Button gesehen habe.

TextField("Host", text: Binding(get: { Self.proxySettingsHost ?? "" }, set: { Self.proxySettingsHost = $0 }))
TextField("Port", text: Binding(get: {
Self.proxySettingsPort != nil ? String(Self.proxySettingsPort!) : ""
}, set: {
Self.proxySettingsPort = Int($0)
}))
}

#if !targetEnvironment(simulator)
ShareDocumentsView()
Expand Down Expand Up @@ -188,4 +197,13 @@ public struct DevToolsView: View {

@UBUserDefault(key: "io.openmobilemaps.debug.rastertiles.enabled", defaultValue: false)
public static var mapRasterTilesDebugOverlay: Bool

@UBUserDefault(key: "ubkit.devtools.proxy.enabled.key", defaultValue: false)
public static var enableNetworkingProxySettings: Bool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Damit man das nicht aus Versehen für immer laufe lässt, zwei Vorschläge:

  • Lösung 1: Statt Bool ein Start-Date speichern und enableNetworkingProxySettings als startDate <24h definieren
  • Lösung 2: Einen Badge, Banner als Overlay über die App legen


@UBUserDefault(key: "ubkit.devtools.proxy.host.key", defaultValue: nil)
public static var proxySettingsHost: String?

@UBUserDefault(key: "ubkit.devtools.proxy.port.key", defaultValue: nil)
public static var proxySettingsPort: Int?
}
58 changes: 58 additions & 0 deletions Sources/UBDevTools/ProxyDevTool.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
//
// ProxyDevTools.swift
//
//
// Created by Sandro Kolly on 10.05.2024.
//

import UBFoundation
import UIKit

class UBDevToolsProxyHelper {
static let shared = UBDevToolsProxyHelper()

fileprivate private(set) var proxy: Proxy? = nil

func setProxy(host: String, port: Int, username: String?, password: String?) {
proxy = Proxy(host: host, port: port, username: username, password: password)
}

fileprivate struct Proxy: UBURLSessionConfigurationProxy {
var host: String
var port: Int
var username: String?
var password: String?
}
}

public class UBFriendlyEvaluator: UBServerTrustEvaluator {
public func evaluate(_ trust: SecTrust, forHost host: String) throws {
// on purpose not throwing, we allow it all
}
}

@available(iOS 14.0, *)
public extension Networking {
/// This is a copy of the sharedSession including the proxy and friendly trust settings
static let friendlySharedSession: UBURLSession = {
guard DevToolsView.enableNetworkingProxySettings else { return Networking.sharedSession }

let queue = OperationQueue()
queue.name = "Friendly UBURLSession Shared"
queue.qualityOfService = .userInitiated

let proxy: UBDevToolsProxyHelper.Proxy?
if let host = DevToolsView.proxySettingsHost, host.isEmpty == false,
let port = DevToolsView.proxySettingsPort {
proxy = UBDevToolsProxyHelper.Proxy(host: host, port: port)
} else if let devProy = UBDevToolsProxyHelper.shared.proxy {
proxy = devProy
} else {
proxy = nil
}

let configuration = UBURLSessionConfiguration(defaultServerTrust: UBFriendlyEvaluator(), proxy: proxy)
configuration.sessionConfiguration.networkServiceType = .responsiveData
return UBURLSession(configuration: configuration, delegateQueue: queue)
}()
}
2 changes: 2 additions & 0 deletions Sources/UBFoundation/Networking/HTTPHeaderField.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public extension UBHTTPHeaderField {
case backoff = "Backoff"
/// Next refresh header field key
case nextRefresh = "X-Next-Refresh"
/// For authorized proxys
case proxyAuthorization = "Proxy-Authorization"
}

/// Amazon Header Keys
Expand Down
56 changes: 35 additions & 21 deletions Sources/UBFoundation/Networking/UBURLSession+Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,28 +19,42 @@ public class UBURLSessionConfiguration {
public let allowRedirections: Bool
/// If requests have the correct headers, then the session will schedule a call in the future for refreshing the data.
public let cachingLogic: UBCachingLogic?
/// The proxy setting for the URLSessionConfiguration,
public let proxy: UBURLSessionConfigurationProxy?


/// Initializes a configuration object
///
/// The configuration will include the "App-Version", "OS-Version" and "UBFoundation-Version" headers in addition to all the headers you pass in the sessionConfiguration object. If a conflict occures then the header in the session configuration is preferred.
/// The configuration will include the "App-Version", "OS-Version" and Proxy-Authorization" headers in addition to all the headers you pass in the sessionConfiguration object. If a conflict occures then the header in the session configuration is preferred.
///
/// - Parameters:
/// - sessionConfiguration: A configuration object that specifies certain behaviors, such as caching policies, timeouts, proxies, pipelining, TLS versions to support, cookie policies, credential storage, and so on.
/// - hostsServerTrusts: A dictionary of Hosts (keys) and their corresponding evaluator. It is highly recommended that you configure for each possible host an evaluator then MITM attacks will be nearly impossible.
/// - defaultServerTrust: A evaluator to use in case no matche is found in the list of hosts. If no default is set and the host is not found then the default OS behaviour is executed.
/// - allowRedirections: Set this flag to `false` if you do not want any redirection. If response wants to redirect, and the flag is set to false, the redirection will not happpen and the data task will be called with the response that caused the redirection.
/// - cachingLogic: If set the caching will use the passed object. If not then the caching will be the default by the URLSession.
public init(sessionConfiguration: URLSessionConfiguration = .default, hostsServerTrusts: [String: UBServerTrustEvaluator] = [:], defaultServerTrust: UBServerTrustEvaluator? = nil, allowRedirections: Bool = true, cachingLogic: UBCachingLogic? = nil) {
self.sessionConfiguration = sessionConfiguration.copy() as! URLSessionConfiguration
public init(sessionConfiguration: URLSessionConfiguration = .default, hostsServerTrusts: [String: UBServerTrustEvaluator] = [:],
defaultServerTrust: UBServerTrustEvaluator? = nil, allowRedirections: Bool = true, cachingLogic: UBCachingLogic? = nil,
proxy: UBURLSessionConfigurationProxy? = nil) {
let sessionConfiguration = sessionConfiguration.copy() as! URLSessionConfiguration
if let proxy {
var proxyDict = [AnyHashable: Any]()
proxyDict["HTTPSEnable"] = 1
proxyDict["HTTPSProxy"] = proxy.host
proxyDict["HTTPSPort"] = proxy.port
sessionConfiguration.connectionProxyDictionary = proxyDict
}
self.sessionConfiguration = sessionConfiguration
self.hostsServerTrusts = hostsServerTrusts
self.defaultServerTrust = defaultServerTrust
self.allowRedirections = allowRedirections
self.cachingLogic = cachingLogic ?? UBAutoRefreshCacheLogic(qos: sessionConfiguration.networkServiceType.equivalentDispatchQOS)
self.proxy = proxy

applyDefaultHeaders(configuration: sessionConfiguration)
applyDefaultHeaders(configuration: sessionConfiguration, proxy: proxy)
}

private func applyDefaultHeaders(configuration: URLSessionConfiguration) {
private func applyDefaultHeaders(configuration: URLSessionConfiguration, proxy: UBURLSessionConfigurationProxy?) {
var headers: [AnyHashable: Any] = [:]

// Add encoding
Expand All @@ -54,25 +68,25 @@ public class UBURLSessionConfiguration {
headers["App-Version"] = "\(appName) v\(shortVersionNumber ?? "unknown") (\(buildNumber ?? "unknown"))"
}

// Add framework information
if let info = Bundle(for: UBURLSessionConfiguration.self).infoDictionary {
let shortVersionNumber = info["CFBundleShortVersionString"] as? String
let buildNumber = info[kCFBundleVersionKey as String] as? String
headers["UBFoundation-Version"] = "v\(shortVersionNumber ?? "unknown") (\(buildNumber ?? "unknown"))"
}

// Add OS information
let osVersion = ProcessInfo.processInfo.operatingSystemVersion
let osVersionString = "\(osVersion.majorVersion).\(osVersion.minorVersion).\(osVersion.patchVersion)"
#if os(iOS)
headers["OS-Version"] = "iOS \(osVersionString)"
#elseif os(watchOS)
headers["OS-Version"] = "watchOS \(osVersionString)"
#elseif os(tvOS)
headers["OS-Version"] = "tvOS \(osVersionString)"
#elseif os(macOS)
headers["OS-Version"] = "macOS \(osVersionString)"
#endif
#if os(iOS)
headers["OS-Version"] = "iOS \(osVersionString)"
#elseif os(watchOS)
headers["OS-Version"] = "watchOS \(osVersionString)"
#elseif os(tvOS)
headers["OS-Version"] = "tvOS \(osVersionString)"
#elseif os(macOS)
headers["OS-Version"] = "macOS \(osVersionString)"
#elseif os(visionOS)
headers["OS-Version"] = "visionOS \(osVersionString)"
#endif

if let proxy, let username = proxy.username, let password = proxy.password,
let data = "\(username):\(password)".data(using: .utf8) {
headers[UBHTTPHeaderField.StandardKeys.proxyAuthorization.rawValue] = "Basic " + data.base64EncodedString()
}

if let configHeaders = configuration.httpAdditionalHeaders {
for header in configHeaders {
Expand Down
19 changes: 19 additions & 0 deletions Sources/UBFoundation/Networking/UBURLSession+Proxy.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// UBURLSessionConfigurationProxy.swift
//
//
// Created by Sandro Kolly on 07.05.2024.
//

import Foundation

/// A protocol to set a proxy on the UBURLSessionConfiguration
///
/// Currently only basic auth is supported for proxy. The auth header is set in the configuration httpAdditionalHeaders.
/// If the proxy needs a different auth, this can be overwritten in the session, which takes priority over the httpAdditionalHeaders.
public protocol UBURLSessionConfigurationProxy {
var host: String { get }
var port: Int { get }
var username: String? { get }
var password: String? { get }
}
Loading