diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift
index cb334a8a8..7c6dc5bae 100644
--- a/Linphone/Core/CoreContext.swift
+++ b/Linphone/Core/CoreContext.swift
@@ -251,15 +251,39 @@ class CoreContext: ObservableObject {
}
}
}
- }, onAuthenticationRequested: { (_: Core, authInfo: AuthInfo, method: AuthMethod) in
- guard let username = authInfo.username, let server = authInfo.authorizationServer, !server.isEmpty else {
+ }, onAuthenticationRequested: { (core: Core, authInfo: AuthInfo, method: AuthMethod) in
+ guard let username = authInfo.username, let domain = authInfo.domain, let realm = authInfo.realm else {
Log.error("Authentication requested but either username [\(String(describing: authInfo.username))], domain [\(String(describing: authInfo.domain))] or server [\(String(describing: authInfo.authorizationServer))] is nil or empty!")
return
}
+
if method == .Bearer {
- Log.info("Authentication requested method is Bearer, starting Single Sign On activity with server URL \(server) and username \(username)")
- self.bearerAuthInfoPendingPasswordUpdate = authInfo
- SingleSignOnManager.shared.setUp(ssoUrl: server, user: username)
+ if let server = authInfo.authorizationServer, !server.isEmpty {
+ Log.info("Authentication requested method is Bearer, starting Single Sign On activity with server URL \(server) and username \(username)")
+ self.bearerAuthInfoPendingPasswordUpdate = authInfo
+ SingleSignOnManager.shared.setUp(ssoUrl: server, user: username)
+ }
+ }
+
+ if method == .HttpDigest {
+ guard let accountFound = core.accountList.first(where: {
+ $0.params?.identityAddress?.username == authInfo.username &&
+ $0.params?.identityAddress?.domain == authInfo.domain
+ }) else {
+ Log.info("[CoreContext] Failed to find account matching auth info, aborting auth dialog")
+ return
+ }
+
+ let identity = "\(authInfo.username ?? "username")@\(authInfo.domain ?? "domain")"
+ Log.info("[CoreContext] Authentication requested method is HttpDigest, showing dialog asking user for password for identity [\(identity)]")
+
+ DispatchQueue.main.async {
+ NotificationCenter.default.post(
+ name: NSNotification.Name("PasswordUpdate"),
+ object: nil,
+ userInfo: ["address": "sip:" + identity]
+ )
+ }
}
}, onTransferStateChanged: { (_: Core, transferred: Call, callState: Call.State) in
Log.info("[CoreContext] Transferred call \(transferred.remoteAddress!.asStringUriOnly()) state changed \(callState)")
diff --git a/Linphone/Localizable/cs.lproj/Localizable.strings b/Linphone/Localizable/cs.lproj/Localizable.strings
index 32fe2cf8f..b273f077e 100644
--- a/Linphone/Localizable/cs.lproj/Localizable.strings
+++ b/Linphone/Localizable/cs.lproj/Localizable.strings
@@ -22,6 +22,9 @@
"account_settings_avpf_title" = "AVPF";
"account_settings_conference_factory_uri_title" = "URI konfiguračního serveru konference";
"account_settings_cpim_in_basic_conversations_title" = "Použít CPIM v \"základních\" konverzacích";
+"account_settings_dialog_invalid_password_title" = "Vyžadováno ověření";
+"account_settings_dialog_invalid_password_message" = "Připojení se nezdařilo, protože chybí nebo je neplatné ověření účtu\n%@.\n\nMůžete znovu zadat heslo nebo zkontrolovat nastavení účtu v konfiguraci.";
+"account_settings_dialog_invalid_password_hint" = "Nové heslo";
"account_settings_enable_ice_title" = "Povolit ICE";
"account_settings_enable_turn_title" = "Povolit TURN";
"account_settings_expire_title" = "Platnost (v sekundách)";
diff --git a/Linphone/Localizable/en.lproj/Localizable.strings b/Linphone/Localizable/en.lproj/Localizable.strings
index 8b3824f94..84af55f12 100644
--- a/Linphone/Localizable/en.lproj/Localizable.strings
+++ b/Linphone/Localizable/en.lproj/Localizable.strings
@@ -38,6 +38,9 @@
"account_settings_ccmp_server_url_title" = "CCMP server URL";
"account_settings_conference_factory_uri_title" = "Conference factory URI";
"account_settings_cpim_in_basic_conversations_title" = "Use CPIM in \"basic\" conversations";
+"account_settings_dialog_invalid_password_title" = "Authentication needed";
+"account_settings_dialog_invalid_password_message" = "Connection failed because authentication is missing or invalid for account \n%@.\n\nYou can provide password again, or check your account configuration in the settings.";
+"account_settings_dialog_invalid_password_hint" = "Password";
"account_settings_enable_ice_title" = "Enable ICE";
"account_settings_enable_turn_title" = "Enable TURN";
"account_settings_expire_title" = "Expire (in seconds)";
diff --git a/Linphone/Localizable/fr.lproj/Localizable.strings b/Linphone/Localizable/fr.lproj/Localizable.strings
index 907950da3..8e7891cea 100644
--- a/Linphone/Localizable/fr.lproj/Localizable.strings
+++ b/Linphone/Localizable/fr.lproj/Localizable.strings
@@ -38,6 +38,9 @@
"account_settings_ccmp_server_url_title" = "URL du serveur CCMP";
"account_settings_conference_factory_uri_title" = "URI du serveur de conversations";
"account_settings_cpim_in_basic_conversations_title" = "Utiliser CPIM dans les conversations \"basiques\"";
+"account_settings_dialog_invalid_password_title" = "Autentification requise";
+"account_settings_dialog_invalid_password_message" = "La connexion a échoué pour le compte \n%@.\n\nVous pouvez renseigner votre mot de passe à nouveau ou bien vérifier les options de configuration de votre compte.";
+"account_settings_dialog_invalid_password_hint" = "Mot de passe";
"account_settings_enable_ice_title" = "Activer ICE";
"account_settings_enable_turn_title" = "Activer TURN";
"account_settings_expire_title" = "Expiration (en secondes)";
diff --git a/Linphone/Localizable/uk.lproj/Localizable.strings b/Linphone/Localizable/uk.lproj/Localizable.strings
index b7bc5531f..d892e94fb 100644
--- a/Linphone/Localizable/uk.lproj/Localizable.strings
+++ b/Linphone/Localizable/uk.lproj/Localizable.strings
@@ -298,6 +298,9 @@
"account_settings_bundle_mode_title" = "Режим об'єднання";
"account_settings_ccmp_server_url_title" = "URL-адреса сервера CCMP";
"account_settings_conference_factory_uri_title" = "URI ресурсу конференцій";
+"account_settings_dialog_invalid_password_title" = "Потрібна автентифікація";
+"account_settings_dialog_invalid_password_message" = "З’єднання не вдалося, оскільки автентифікація відсутня або недійсна для облікового запису\n%@.\n\nВи можете ввести пароль ще раз або перевірити конфігурацію облікового запису в налаштуваннях.";
+"account_settings_dialog_invalid_password_hint" = "Новий пароль";
"account_settings_enable_ice_title" = "Увімкнути ICE";
"account_settings_enable_turn_title" = "Увімкнути TURN";
"account_settings_expire_title" = "Спливає (в секундах)";
diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift
index 37dde86b2..14898305d 100644
--- a/Linphone/UI/Main/ContentView.swift
+++ b/Linphone/UI/Main/ContentView.swift
@@ -82,6 +82,9 @@ struct ContentView: View {
@State var isShowConversationInfoPopup: Bool = false
@State var conversationInfoPopupText: String = ""
+ @State var isShowUpdatePasswordPopup: Bool = false
+ @State var passwordUpdateAddress: String = ""
+
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
@@ -1293,6 +1296,18 @@ struct ContentView: View {
}
}
+ if isShowUpdatePasswordPopup {
+ PopupUpdatePassword(
+ isShowUpdatePasswordPopup: $isShowUpdatePasswordPopup,
+ passwordUpdateAddress: $passwordUpdateAddress
+ )
+ .background(.black.opacity(0.65))
+ .zIndex(3)
+ .onTapGesture {
+ self.isShowUpdatePasswordPopup.toggle()
+ }
+ }
+
if telecomManager.meetingWaitingRoomDisplayed {
MeetingWaitingRoomFragment()
.zIndex(3)
@@ -1353,6 +1368,10 @@ struct ContentView: View {
.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("CoreStarted"))) { _ in
accountProfileViewModel.setAvatarModel()
}
+ .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("PasswordUpdate")).compactMap { $0.userInfo?["address"] as? String }) { address in
+ passwordUpdateAddress = address
+ isShowUpdatePasswordPopup = true
+ }
}
.overlay {
if isMenuOpen {
diff --git a/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift b/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift
new file mode 100644
index 000000000..5efbfadf8
--- /dev/null
+++ b/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift
@@ -0,0 +1,153 @@
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+import SwiftUI
+
+struct PopupUpdatePassword: View {
+
+ @ObservedObject var sharedMainViewModel = SharedMainViewModel.shared
+
+ @Binding var isShowUpdatePasswordPopup: Bool
+ @Binding var passwordUpdateAddress: String
+
+ @State private var passwordPopupText: String = ""
+ @State private var isSecured: Bool = true
+
+ @FocusState var isPasswordFocused: Bool
+
+ var body: some View {
+ GeometryReader { geometry in
+ VStack(alignment: .leading) {
+ Text("account_settings_dialog_invalid_password_title")
+ .default_text_style_800(styleSize: 16)
+ .frame(alignment: .leading)
+ .padding(.bottom, 2)
+
+ Text(String(format: String(localized: "account_settings_dialog_invalid_password_message"), passwordUpdateAddress))
+ .default_text_style(styleSize: 15)
+ .padding(.bottom, 20)
+
+ ZStack(alignment: .trailing) {
+ Group {
+ if isSecured {
+ SecureField("account_settings_dialog_invalid_password_hint", text: $passwordPopupText)
+ .default_text_style(styleSize: 15)
+ .frame(height: 25)
+ .focused($isPasswordFocused)
+ } else {
+ TextField("account_settings_dialog_invalid_password_hint", text: $passwordPopupText)
+ .default_text_style(styleSize: 15)
+ .disableAutocorrection(true)
+ .autocapitalization(.none)
+ .frame(height: 25)
+ .focused($isPasswordFocused)
+ }
+ }
+
+ Button(action: {
+ isSecured.toggle()
+ }, label: {
+ Image(self.isSecured ? "eye-slash" : "eye")
+ .renderingMode(.template)
+ .resizable()
+ .foregroundStyle(Color.grayMain2c500)
+ .frame(width: 20, height: 20)
+ })
+ }
+ .padding(.horizontal, 20)
+ .padding(.vertical, 15)
+ .cornerRadius(60)
+ .overlay(
+ RoundedRectangle(cornerRadius: 60)
+ .inset(by: 0.5)
+ .stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1)
+ )
+ .padding(.bottom)
+
+ Button(action: {
+ isShowUpdatePasswordPopup = false
+ }, label: {
+ Text("dialog_cancel")
+ .default_text_style_orange_600(styleSize: 20)
+ .frame(height: 35)
+ .frame(maxWidth: .infinity)
+ })
+ .padding(.horizontal, 20)
+ .padding(.vertical, 10)
+ .cornerRadius(60)
+ .overlay(
+ RoundedRectangle(cornerRadius: 60)
+ .inset(by: 0.5)
+ .stroke(Color.orangeMain500, lineWidth: 1)
+ )
+ .padding(.bottom, 10)
+
+ Button(action: {
+ setNewPassword()
+ isShowUpdatePasswordPopup = false
+ }, label: {
+ Text("dialog_ok")
+ .default_text_style_white_600(styleSize: 20)
+ .frame(height: 35)
+ .frame(maxWidth: .infinity)
+ })
+ .padding(.horizontal, 20)
+ .padding(.vertical, 10)
+ .background(passwordPopupText.isEmpty ? Color.orangeMain100 : Color.orangeMain500)
+ .cornerRadius(60)
+ .disabled(passwordPopupText.isEmpty)
+ }
+ .padding(.horizontal, 20)
+ .padding(.vertical, 20)
+ .background(.white)
+ .cornerRadius(20)
+ .padding(.horizontal)
+ .frame(maxHeight: .infinity)
+ .shadow(color: Color.orangeMain500, radius: 0, x: 0, y: 2)
+ .frame(maxWidth: SharedMainViewModel.shared.maxWidth)
+ .position(x: geometry.size.width / 2, y: geometry.size.height / 2)
+ }
+ }
+
+ func setNewPassword() {
+ CoreContext.shared.doOnCoreQueue { core in
+ Log.info("[SetNewPassword] ---- \(core.defaultAccount?.params?.identityAddress?.asStringUriOnly() ?? "No account found") \(passwordUpdateAddress)")
+
+ if let account = core.accountList.first { $0.params?.identityAddress?.asStringUriOnly() == passwordUpdateAddress } {
+ let authInfo = account.findAuthInfo()
+ if (authInfo != nil) {
+ Log.info(
+ "[SetNewPassword] Updating password for username \(authInfo!.username) using auth info \(authInfo!)"
+ )
+ authInfo!.password = passwordPopupText
+ core.addAuthInfo(info: authInfo!)
+ core.refreshRegisters()
+ } else {
+ Log.warn(
+ "[SetNewPassword] Failed to find auth info for account \(account.params?.identityAddress?.asStringUriOnly())"
+ )
+ }
+ }
+ }
+ }
+}
+
+#Preview {
+ PopupUpdatePassword(isShowUpdatePasswordPopup: .constant(true), passwordUpdateAddress: .constant("example@sip.linphone.org"))
+}
diff --git a/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift b/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift
index 6311c3b05..58d22c217 100644
--- a/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift
+++ b/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift
@@ -1,9 +1,21 @@
-//
-// PopupViewWithTextField.swift
-// Linphone
-//
-// Created by Benoît Martins on 12/11/2024.
-//
+/*
+ * Copyright (c) 2010-2023 Belledonne Communications SARL.
+ *
+ * This file is part of linphone-iphone
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
import SwiftUI
diff --git a/LinphoneApp.xcodeproj/project.pbxproj b/LinphoneApp.xcodeproj/project.pbxproj
index 55a268cfd..804acf4d3 100644
--- a/LinphoneApp.xcodeproj/project.pbxproj
+++ b/LinphoneApp.xcodeproj/project.pbxproj
@@ -192,6 +192,7 @@
D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */; };
D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */; };
D7EFD1E42CD11F70005E67CD /* EphemeralFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EFD1E32CD11F53005E67CD /* EphemeralFragment.swift */; };
+ D7F01ADE2E1D0DA3006942C0 /* PopupUpdatePassword.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F01ADD2E1D0D92006942C0 /* PopupUpdatePassword.swift */; };
D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */; };
D7F5F6412C359F3B007FCF2F /* SipAddressesPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */; };
D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; };
@@ -408,6 +409,7 @@
D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsInnerFragment.swift; sourceTree = ""; };
D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = ""; };
D7EFD1E32CD11F53005E67CD /* EphemeralFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EphemeralFragment.swift; sourceTree = ""; };
+ D7F01ADD2E1D0D92006942C0 /* PopupUpdatePassword.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PopupUpdatePassword.swift; sourceTree = ""; };
D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListFragment.swift; sourceTree = ""; };
D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SipAddressesPopup.swift; sourceTree = ""; };
D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = ""; };
@@ -782,6 +784,7 @@
D74C9CFD2ACAEC150021626A /* Fragments */ = {
isa = PBXGroup;
children = (
+ D7F01ADD2E1D0D92006942C0 /* PopupUpdatePassword.swift */,
D74C9CFE2ACAEC5E0021626A /* PopupView.swift */,
D72343352AD037AF009AA24E /* ToastView.swift */,
D750D3382AD3E6EE00EC99C5 /* PopupLoadingView.swift */,
@@ -1342,6 +1345,7 @@
D7C48DF62AFCDF4700D938CB /* ContactInnerActionsFragment.swift in Sources */,
D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */,
662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */,
+ D7F01ADE2E1D0DA3006942C0 /* PopupUpdatePassword.swift in Sources */,
D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */,
66246C6A2C622AE900973E97 /* TimeZoneExtension.swift in Sources */,
66C491FB2B24D32600CEA16D /* CoreExtension.swift in Sources */,