Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
75 changes: 47 additions & 28 deletions OpenHABCore/Sources/OpenHABCore/Util/Preferences.swift
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,32 @@ public struct UserDefaultObject<T: Codable & Sendable> {
}
}

@MainActor
public struct HomePreferences: Codable, Equatable {
public struct HomePreferences: Codable, Equatable, Sendable {
private enum CodingKeys: String, CodingKey {
case id
case defaultView
case demomode
case realTimeSliders
case showSearchField
case iconType
case defaultSitemap
case sortSitemapsBy
case defaultMainUIPath
case alwaysAllowWebRTC
case sitemapForWatch
case localConnectionConfig
case remoteConnectionConfig
case sitemapForWatchLabel
case homeName
case sseCommandItem
case alwaysSendSameAuthenticationToWebView
}

public let id: UUID
public var defaultView = "web"
public var demomode = true
public var realTimeSliders = false
public var showSearchField = true
public var iconType = 0
public var defaultSitemap = "demo"
public var sortSitemapsBy = 0
Expand All @@ -104,29 +124,36 @@ public struct HomePreferences: Codable, Equatable {
public var sitemapForWatchLabel = "watch"
public var homeName = "Home"
public var sseCommandItem = ""
public var alwaysSendSameAuthenticationToWebView = false

fileprivate init(id: UUID) {
self.id = id
}
}

@MainActor
public struct ApplicationPreferences: Codable, Equatable {
public var showSearchField = true
public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = (try? container.decode(UUID.self, forKey: .id)) ?? UUID()
defaultView = try container.decodeIfPresent(String.self, forKey: .defaultView) ?? "web"
demomode = try container.decodeIfPresent(Bool.self, forKey: .demomode) ?? true
realTimeSliders = try container.decodeIfPresent(Bool.self, forKey: .realTimeSliders) ?? false
showSearchField = try container.decodeIfPresent(Bool.self, forKey: .showSearchField) ?? true
iconType = try container.decodeIfPresent(Int.self, forKey: .iconType) ?? 0
defaultSitemap = try container.decodeIfPresent(String.self, forKey: .defaultSitemap) ?? "demo"
sortSitemapsBy = try container.decodeIfPresent(Int.self, forKey: .sortSitemapsBy) ?? 0
defaultMainUIPath = try container.decodeIfPresent(String.self, forKey: .defaultMainUIPath) ?? ""
alwaysAllowWebRTC = try container.decodeIfPresent(Bool.self, forKey: .alwaysAllowWebRTC) ?? false
sitemapForWatch = try container.decodeIfPresent(String.self, forKey: .sitemapForWatch) ?? "watch"
localConnectionConfig = try container.decodeIfPresent(ConnectionConfiguration.self, forKey: .localConnectionConfig) ?? .localDefault
remoteConnectionConfig = try container.decodeIfPresent(ConnectionConfiguration.self, forKey: .remoteConnectionConfig) ?? .remoteDefault
sitemapForWatchLabel = try container.decodeIfPresent(String.self, forKey: .sitemapForWatchLabel) ?? "watch"
homeName = try container.decodeIfPresent(String.self, forKey: .homeName) ?? "Home"
sseCommandItem = try container.decodeIfPresent(String.self, forKey: .sseCommandItem) ?? ""
alwaysSendSameAuthenticationToWebView = try container.decodeIfPresent(Bool.self, forKey: .alwaysSendSameAuthenticationToWebView) ?? false
}
}

// MARK: Retrieving preference from user defaults, reacting to preference change

// MARK: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

// MARK: !!

// MARK: When making changes to Preferences, always consider a migration for existing users. Otherwise, they risk to loose their existing preferences.

// MARK: !!

// MARK: !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

private enum PreferencesAccess {
@MainActor fileprivate static func getPreference<T>(key: String, defaultValue: T, encoder: (T) -> (some Sendable)?, decoder: (Any?) -> T?) -> T {
let preferenceValue = sharedDefaults.object(forKey: key)
Expand Down Expand Up @@ -176,12 +203,8 @@ public actor Preferences {
@UserDefault("idleOff", defaultValue: false)
public var idleOff: Bool

@UserDefaultObject(
"applicationPreferences",
defaultValue:
ApplicationPreferences()
)
public private(set) var applicationPreferences: ApplicationPreferences
@UserDefault("showSearchField", defaultValue: true)
public var showSearchField: Bool

@UserDefault("screensaverEnabled", defaultValue: false)
public var screensaverEnabled: Bool
Expand Down Expand Up @@ -355,12 +378,6 @@ public extension Preferences {
currentHomePreferences = homePreferences
storeActiveHome()
}

func modifyApplicationPreferences(modificationFunction: @MainActor (inout ApplicationPreferences) -> Void) {
var applicationPreferences = applicationPreferences
modificationFunction(&applicationPreferences)
self.applicationPreferences = applicationPreferences
}
}

@MainActor
Expand Down Expand Up @@ -404,6 +421,7 @@ public extension Preferences {
currentHomePreferences.remoteConnectionConfig.ignoreSSL = UserDefaults.standard.object(forKey: "ignoreSSL") as? Bool ?? currentHomePreferences.remoteConnectionConfig.ignoreSSL
currentHomePreferences.demomode = UserDefaults.standard.object(forKey: "demomode") as? Bool ?? currentHomePreferences.demomode
currentHomePreferences.realTimeSliders = UserDefaults.standard.object(forKey: "realTimeSliders") as? Bool ?? currentHomePreferences.realTimeSliders
currentHomePreferences.showSearchField = UserDefaults.standard.object(forKey: "showSearchField") as? Bool ?? currentHomePreferences.showSearchField
currentHomePreferences.iconType = UserDefaults.standard.object(forKey: "iconType") as? Int ?? currentHomePreferences.iconType
currentHomePreferences.defaultSitemap = UserDefaults.standard.string(forKey: "defaultSitemap") ?? currentHomePreferences.defaultSitemap
}
Expand Down Expand Up @@ -446,6 +464,7 @@ public extension Preferences {
currentHomePreferences.defaultView = sharedDefaults.string(forKey: "defaultView") ?? currentHomePreferences.defaultView
currentHomePreferences.demomode = sharedDefaults.object(forKey: "demomode") as? Bool ?? currentHomePreferences.demomode
currentHomePreferences.realTimeSliders = sharedDefaults.object(forKey: "realTimeSliders") as? Bool ?? currentHomePreferences.realTimeSliders
currentHomePreferences.showSearchField = sharedDefaults.object(forKey: "showSearchField") as? Bool ?? currentHomePreferences.showSearchField
currentHomePreferences.iconType = sharedDefaults.object(forKey: "iconType") as? Int ?? currentHomePreferences.iconType
currentHomePreferences.defaultSitemap = sharedDefaults.string(forKey: "defaultSitemap") ?? currentHomePreferences.defaultSitemap
currentHomePreferences.sortSitemapsBy = sharedDefaults.object(forKey: "sortSitemapsBy") as? Int ?? currentHomePreferences.sortSitemapsBy
Expand Down
43 changes: 33 additions & 10 deletions OpenHABCore/Sources/OpenHABCore/Util/SessionChallengeHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ public func onReceiveSessionTaskChallenge(with challenge: URLAuthenticationChall
public func onReceiveSessionChallenge(with challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {
let host = challenge.protectionSpace.host
let authenticationMethod = challenge.protectionSpace.authenticationMethod
Logger.sessionChallenge.warning("onReceiveSessionChallenge is not implemented fully (see TODOs)")
Logger.sessionChallenge.info("onReceiveSessionChallenge host=\(host, privacy: .public), method=\(authenticationMethod, privacy: .public)")
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling

Expand All @@ -98,19 +97,43 @@ public func onReceiveSessionChallenge(with challenge: URLAuthenticationChallenge
let result = CertificateManagers.clientCertificateManager.evaluateTrust(with: challenge)
logDecision(result.0, reason: "client-certificate-manager")
return result
// attemptCredentialAuthentication
default:
case NSURLAuthenticationMethodHTTPBasic, NSURLAuthenticationMethodDefault:
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
logDecision(disposition, reason: "previous-failure")
} else {
// TODO: in the last version, the httpClient had never been set and always remained nil. Figure out if and how this worked and if it is still needed
// credential = await NetworkTracker.shared.httpClient?.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
// if credential != nil {
// disposition = .useCredential
// }
logDecision(disposition, reason: "default-handling-no-credential")
return (disposition, nil)
}

let networkTracker = NetworkTracker.shared
let activeConnection = await networkTracker.activeConnection

var candidateConfigurations: [ConnectionConfiguration] = []
if let activeConfiguration = activeConnection?.configuration {
candidateConfigurations.append(activeConfiguration)
}
for configuration in await networkTracker.configuredConnections() where !candidateConfigurations.contains(configuration) {
candidateConfigurations.append(configuration)
}

let alwaysSend = Preferences.shared.currentHomePreferences.alwaysSendSameAuthenticationToWebView
let matchedConfiguration = candidateConfigurations.first { configuration in
URL(string: configuration.url)?.host == host
|| host == "home.myopenhab.org"
|| alwaysSend
}

if let matchedConfiguration {
let credential = URLCredential(user: matchedConfiguration.username, password: matchedConfiguration.password, persistence: .forSession)
let decision: URLSession.AuthChallengeDisposition = .useCredential
logDecision(decision, reason: "matched-webview-host-or-always-send")
return (decision, credential)
}

logDecision(disposition, reason: "no-webview-host-match-default-handling")
return (disposition, nil)

default:
logDecision(disposition, reason: "unsupported-auth-method-default-handling")
return (disposition, nil)
}
}
4 changes: 2 additions & 2 deletions openHAB/Models/SitemapPageViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class SitemapPageViewModel: ObservableObject {
@Published var isLoading = true
@Published var isUpdating = false
@Published var openHABRootUrl: String?
@Published var showSearchField = false
@Published var showSearchField = true
@Published private(set) var commandStates: [String: WidgetCommandLifecycleState] = [:]
@Published private(set) var trackerStatus: NetworkStatus = .stopped
@Published private(set) var widgetUpdateVersions: [String: Int] = [:]
Expand Down Expand Up @@ -329,7 +329,7 @@ class SitemapPageViewModel: ObservableObject {
extension SitemapPageViewModel {
func loadSettings() {
defaultSitemap = Preferences.shared.currentHomePreferences.defaultSitemap
showSearchField = Preferences.shared.applicationPreferences.showSearchField
showSearchField = Preferences.shared.currentHomePreferences.showSearchField
}

func stopPageHandling() {
Expand Down
8 changes: 8 additions & 0 deletions openHAB/Supporting Files/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,10 @@
}
}
},
"Always send credentials to web views" : {
"comment" : "A toggle that allows the user to configure whether the same credentials should be sent to all web view authentication challenges.",
"isCommentAutoGenerated" : true
},
"always_allow_webrtc" : {
"extractionState" : "manual",
"localizations" : {
Expand Down Expand Up @@ -11088,6 +11092,10 @@
}
}
},
"Send the server credentials to all web view authentication challenges, not only when the host matches the configured server URL." : {
"comment" : "A description of the feature that allows sending credentials to web view authentication challenges.",
"isCommentAutoGenerated" : true
},
"Sending commands: %lld" : {
"localizations" : {
"de" : {
Expand Down
9 changes: 8 additions & 1 deletion openHAB/UI/SettingsView/ConnectionSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct ConnectionSettingsView: View {
@Binding var settingsDemomode: Bool
@Binding var localConnectionConfiguration: ConnectionConfiguration
@Binding var remoteConnectionConfiguration: ConnectionConfiguration
@Binding var alwaysSendSameAuthenticationToWebView: Bool

var body: some View {
Toggle("Demo Mode", isOn: $settingsDemomode)
Expand All @@ -26,6 +27,10 @@ struct ConnectionSettingsView: View {
Group {
SingleConnectionSettingsView(headerText: String(localized: "Local server"), isLocalConnection: true, connectionConfig: $localConnectionConfiguration, showNotificationToggle: false)
SingleConnectionSettingsView(headerText: String(localized: "Remote server"), connectionConfig: $remoteConnectionConfiguration, showNotificationToggle: true)
Toggle(isOn: $alwaysSendSameAuthenticationToWebView) {
Text("Always send credentials to web views")
Text("Send the server credentials to all web view authentication challenges, not only when the host matches the configured server URL.")
}
}
}
}
Expand All @@ -51,14 +56,16 @@ struct ConnectionSettingsView: View {
username: "user",
password: "password123"
)
@State var alwaysSendSameAuthenticationToWebView = false

var body: some View {
NavigationStack {
Form {
ConnectionSettingsView(
settingsDemomode: $demoMode,
localConnectionConfiguration: $connectionConfig1,
remoteConnectionConfiguration: $connectionConfig2
remoteConnectionConfiguration: $connectionConfig2,
alwaysSendSameAuthenticationToWebView: $alwaysSendSameAuthenticationToWebView
)
}
}
Expand Down
13 changes: 7 additions & 6 deletions openHAB/UI/SettingsView/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ struct SettingsView: View {
@State private var settingsSortSitemapsBy: SortSitemapsOrder = .label
@State private var settingsDefaultMainUIPath = ""
@State private var settingsAlwaysAllowWebRTC = true
@State private var settingsAlwaysSendSameAuthenticationToWebView = false
@State private var settingsSitemapForWatch = ""

@State private var sitemaps: [OpenHABSitemap] = []
Expand All @@ -40,7 +41,8 @@ struct SettingsView: View {
ConnectionSettingsView(
settingsDemomode: $settingsDemomode,
localConnectionConfiguration: $settingsLocalConnectionConfiguration,
remoteConnectionConfiguration: $settingsRemoteConnectionConfiguration
remoteConnectionConfiguration: $settingsRemoteConnectionConfiguration,
alwaysSendSameAuthenticationToWebView: $settingsAlwaysSendSameAuthenticationToWebView
)

ApplicationSettingsView(
Expand Down Expand Up @@ -122,12 +124,13 @@ struct SettingsView: View {
settingsDemomode = Preferences.shared.currentHomePreferences.demomode
settingsIdleOff = Preferences.shared.idleOff
settingsRealTimeSliders = Preferences.shared.currentHomePreferences.realTimeSliders
settingsShowSearchField = Preferences.shared.applicationPreferences.showSearchField
settingsShowSearchField = Preferences.shared.currentHomePreferences.showSearchField
settingsSendCrashReports = Preferences.shared.sendCrashReports
settingsIconType = IconType(rawValue: Preferences.shared.currentHomePreferences.iconType) ?? .svg
settingsSortSitemapsBy = SortSitemapsOrder(rawValue: Preferences.shared.currentHomePreferences.sortSitemapsBy) ?? .label
settingsDefaultMainUIPath = Preferences.shared.currentHomePreferences.defaultMainUIPath
settingsAlwaysAllowWebRTC = Preferences.shared.currentHomePreferences.alwaysAllowWebRTC
settingsAlwaysSendSameAuthenticationToWebView = Preferences.shared.currentHomePreferences.alwaysSendSameAuthenticationToWebView
settingsSitemapForWatch = Preferences.shared.currentHomePreferences.sitemapForWatch
settingsLocalConnectionConfiguration = Preferences.shared.currentHomePreferences.localConnectionConfig
settingsRemoteConnectionConfiguration = Preferences.shared.currentHomePreferences.remoteConnectionConfig
Expand All @@ -139,10 +142,12 @@ struct SettingsView: View {
Preferences.shared.modifyActiveHome { @MainActor homePreferences in
homePreferences.demomode = settingsDemomode
homePreferences.realTimeSliders = settingsRealTimeSliders
homePreferences.showSearchField = settingsShowSearchField
homePreferences.iconType = settingsIconType.rawValue
homePreferences.sortSitemapsBy = settingsSortSitemapsBy.rawValue
homePreferences.defaultMainUIPath = settingsDefaultMainUIPath
homePreferences.alwaysAllowWebRTC = settingsAlwaysAllowWebRTC
homePreferences.alwaysSendSameAuthenticationToWebView = settingsAlwaysSendSameAuthenticationToWebView
homePreferences.sitemapForWatch = settingsSitemapForWatch
homePreferences.sitemapForWatchLabel = sitemaps.first { $0.name == settingsSitemapForWatch }?.label ?? "unknown"
homePreferences.localConnectionConfig = settingsLocalConnectionConfiguration
Expand All @@ -152,10 +157,6 @@ struct SettingsView: View {
Preferences.shared.idleOff = settingsIdleOff
Preferences.shared.sendCrashReports = settingsSendCrashReports

Preferences.shared.modifyApplicationPreferences { @MainActor applicationPreferences in
applicationPreferences.showSearchField = settingsShowSearchField
}

// Apply global UI changes immediately (status bar visibility)
UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
Expand Down
Loading