diff --git a/NextcloudTalk.xcodeproj/project.pbxproj b/NextcloudTalk.xcodeproj/project.pbxproj index 4c23410d1..c66f2b38a 100644 --- a/NextcloudTalk.xcodeproj/project.pbxproj +++ b/NextcloudTalk.xcodeproj/project.pbxproj @@ -692,6 +692,7 @@ membershipExceptions = ( CurrentUserAbsence.swift, NCAppBranding.m, + NCAppBrandingExtensions.swift, NCKeyChainController.m, NCUserDefaults.m, NCUtils.swift, @@ -704,6 +705,7 @@ membershipExceptions = ( CurrentUserAbsence.swift, NCAppBranding.m, + NCAppBrandingExtensions.swift, NCKeyChainController.m, NCUserDefaults.m, NCUserStatus.m, @@ -717,6 +719,7 @@ membershipExceptions = ( CurrentUserAbsence.swift, NCAppBranding.m, + NCAppBrandingExtensions.swift, NCKeyChainController.m, NCUserStatus.m, NCUtils.swift, @@ -729,6 +732,7 @@ membershipExceptions = ( CurrentUserAbsence.swift, NCAppBranding.m, + NCAppBrandingExtensions.swift, NCKeyChainController.m, NCUserDefaults.m, NCUserStatus.m, diff --git a/NextcloudTalk/Login/AuthenticationViewController.m b/NextcloudTalk/Login/AuthenticationViewController.m index 34f35d83b..8ff133e79 100644 --- a/NextcloudTalk/Login/AuthenticationViewController.m +++ b/NextcloudTalk/Login/AuthenticationViewController.m @@ -65,10 +65,7 @@ - (void)viewDidLoad [request setValue:@"true" forHTTPHeaderField:@"OCS-APIRequest"]; self->_webView = [[DebounceWebView alloc] initWithFrame:self.view.frame configuration:configuration]; - NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; - NSString *deviceName = [[UIDevice currentDevice] name]; - NSString *userAgent = [NSString stringWithFormat:@"%@ (%@)", deviceName, appDisplayName]; - self->_webView.customUserAgent = [[NSString alloc] initWithCString:[userAgent UTF8String] encoding:NSASCIIStringEncoding]; + self->_webView.customUserAgent = [NCAppBranding userAgentForLogin]; self->_webView.navigationDelegate = self; self->_webView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; diff --git a/NextcloudTalk/Login/LoginViewController.swift b/NextcloudTalk/Login/LoginViewController.swift index da74e9af0..f879786f9 100644 --- a/NextcloudTalk/Login/LoginViewController.swift +++ b/NextcloudTalk/Login/LoginViewController.swift @@ -353,4 +353,25 @@ class LoginViewController: UIViewController, UITextFieldDelegate, CCCertificateD NCSettingsController.sharedInstance().addNewAccount(forUser: user, withToken: password, inServer: serverURL) delegate?.loginViewControllerDidFinish() } + + func qrScanner(_ scanner: QRScannerViewController, didScanNextcloudOnetimeLogin serverURL: String, user: String, onetimeToken: String) { + NotificationPresenter.shared().present(text: NSLocalizedString("Trying to login…", comment: "Waiting message when trying to login with QR code")) + NotificationPresenter.shared().displayActivityIndicator(true) + + // We received a onetime login token and need to convert it to a permanent one. The token only allows to retrieve a permanent one, no other routes allowed + NCAPIController.sharedInstance().getAppPasswordOnetime(forServer: serverURL, withUsername: user, andOnetimeToken: onetimeToken) { [weak self] permanentAppToken in + NotificationPresenter.shared().dismiss() + + guard let permanentAppToken else { + self?.showAlert( + title: NSLocalizedString("Could not login with QR code", comment: ""), + message: NSLocalizedString("The token might be used already or is expired. Please generate a new QR code and retry.", comment: "")) + + return + } + + NCSettingsController.sharedInstance().addNewAccount(forUser: user, withToken: permanentAppToken, inServer: serverURL) + self?.delegate?.loginViewControllerDidFinish() + } + } } diff --git a/NextcloudTalk/Login/QRScannerViewController.swift b/NextcloudTalk/Login/QRScannerViewController.swift index 2eed2045c..b46d64a46 100644 --- a/NextcloudTalk/Login/QRScannerViewController.swift +++ b/NextcloudTalk/Login/QRScannerViewController.swift @@ -8,6 +8,7 @@ import VisionKit @objc protocol QRScannerViewControllerDelegate: AnyObject { func qrScanner(_ scanner: QRScannerViewController, didScanNextcloudLogin serverURL: String, user: String, password: String) + func qrScanner(_ scanner: QRScannerViewController, didScanNextcloudOnetimeLogin serverURL: String, user: String, onetimeToken: String) } @objcMembers @@ -137,7 +138,10 @@ class QRScannerViewController: UIViewController, DataScannerViewControllerDelega guard case let .barcode(barcode) = item, let value = barcode.payloadStringValue else { return } - if let urlComponents = NSURLComponents(string: value), var path = urlComponents.path, urlComponents.scheme == "nc", urlComponents.host == "login" { + if let urlComponents = NSURLComponents(string: value), var path = urlComponents.path, urlComponents.scheme == "nc" { + let isOnetimeLogin = (urlComponents.host == "onetime-login") + guard urlComponents.host == "login" || isOnetimeLogin else { return } + if path.starts(with: "/") { path.removeFirst() } @@ -152,7 +156,12 @@ class QRScannerViewController: UIViewController, DataScannerViewControllerDelega scannerViewController?.stopScanning() self.dismiss(animated: true) - self.delegate?.qrScanner(self, didScanNextcloudLogin: serverUrl, user: user, password: password) + + if isOnetimeLogin { + self.delegate?.qrScanner(self, didScanNextcloudOnetimeLogin: serverUrl, user: user, onetimeToken: password) + } else { + self.delegate?.qrScanner(self, didScanNextcloudLogin: serverUrl, user: user, password: password) + } return } diff --git a/NextcloudTalk/Network/NCAPIController.m b/NextcloudTalk/Network/NCAPIController.m index a524548bb..cfbb412b1 100644 --- a/NextcloudTalk/Network/NCAPIController.m +++ b/NextcloudTalk/Network/NCAPIController.m @@ -155,9 +155,7 @@ - (void)initImageDownloaders [SDImageCache sharedImageCache].config.shouldRemoveExpiredDataWhenTerminate = NO; [SDImageCache sharedImageCache].config.shouldRemoveExpiredDataWhenEnterBackground = NO; - NSString *userAgent = [NSString stringWithFormat:@"Mozilla/5.0 (iOS) Nextcloud-Talk v%@", - [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]; - [[SDWebImageDownloader sharedDownloader] setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + [[SDWebImageDownloader sharedDownloader] setValue:[NCAppBranding userAgent] forHTTPHeaderField:@"User-Agent"]; } - (NSString *)authHeaderForAccount:(TalkAccount *)account diff --git a/NextcloudTalk/Network/NCAPIControllerExtensions.swift b/NextcloudTalk/Network/NCAPIControllerExtensions.swift index 66a4e79ff..a250b9a62 100644 --- a/NextcloudTalk/Network/NCAPIControllerExtensions.swift +++ b/NextcloudTalk/Network/NCAPIControllerExtensions.swift @@ -1360,4 +1360,33 @@ import NextcloudKit return NCChatMessage(dictionary: ocsResponse.dataDict, andAccountId: account.accountId) } + + // MARK: - Core + + @nonobjc + func getAppPasswordOnetime(forServer server: String, withUsername username: String, andOnetimeToken onetimeToken: String, completionBlock: @escaping (_ permanentAppToken: String?) -> Void) { + let appPasswordRoute = "\(server)/ocs/v2.php/core/getapppassword-onetime" + + let credentialsString = "\(username):\(onetimeToken)" + let authHeader = "Basic \(credentialsString.data(using: .utf8)!.base64EncodedString())" + + let configuration = URLSessionConfiguration.default + let apiSessionManager = NCAPISessionManager(configuration: configuration) + apiSessionManager.requestSerializer.setValue(authHeader, forHTTPHeaderField: "Authorization") + apiSessionManager.requestSerializer.setValue(NCAppBranding.userAgentForLogin(), forHTTPHeaderField: "User-Agent") + + _ = apiSessionManager.get(appPasswordRoute, parameters: nil, progress: nil) { _, result in + if let resultDict = result as? [String: AnyObject], + let ocs = resultDict["ocs"] as? [String: AnyObject], + let data = ocs["data"] as? [String: AnyObject], + let apppassword = data["apppassword"] as? String { + + completionBlock(apppassword) + } + + completionBlock(nil) + } failure: { _, _ in + completionBlock(nil) + } + } } diff --git a/NextcloudTalk/Network/NCBaseSessionManager.swift b/NextcloudTalk/Network/NCBaseSessionManager.swift index bbaaa5e38..6d9e15002 100644 --- a/NextcloudTalk/Network/NCBaseSessionManager.swift +++ b/NextcloudTalk/Network/NCBaseSessionManager.swift @@ -7,8 +7,7 @@ import Foundation @objcMembers public class NCBaseSessionManager: AFHTTPSessionManager { - public static var baseUserAgent = "Mozilla/5.0 (iOS) Nextcloud-Talk v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "Unknown")" - public var userAgent: String = baseUserAgent + public var userAgent: String = NCAppBranding.userAgent() init(configuration: URLSessionConfiguration, responseSerializer: AFHTTPResponseSerializer, requestSerializer: AFHTTPRequestSerializer) { super.init(baseURL: nil, sessionConfiguration: configuration) diff --git a/NextcloudTalk/Settings/NCAppBrandingExtensions.swift b/NextcloudTalk/Settings/NCAppBrandingExtensions.swift index 55ee6d1d8..97791747a 100644 --- a/NextcloudTalk/Settings/NCAppBrandingExtensions.swift +++ b/NextcloudTalk/Settings/NCAppBrandingExtensions.swift @@ -24,4 +24,17 @@ extension NCAppBranding { return NCAppBranding.getDynamicColor(lightColor, withDarkMode: darkColor) } + @objc + static func userAgent() -> String { + return "Mozilla/5.0 (iOS) Nextcloud-Talk v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "Unknown")" + } + + @objc + static func userAgentForLogin() -> String { + let appDisplayName = Bundle.main.infoDictionary?["CFBundleDisplayName"] ?? "Unknown app" + let deviceName = UIDevice.current.name + + return "\(deviceName) (\(appDisplayName)" + } + } diff --git a/NextcloudTalk/WebRTC/NCExternalSignalingController.swift b/NextcloudTalk/WebRTC/NCExternalSignalingController.swift index 8dd63dd67..ffe9482e3 100644 --- a/NextcloudTalk/WebRTC/NCExternalSignalingController.swift +++ b/NextcloudTalk/WebRTC/NCExternalSignalingController.swift @@ -119,11 +119,9 @@ public enum NCExternalSignalingSendMessageStatus { NCUtils.log("Connecting to: \(self.serverUrl)") - let userAgent = "Mozilla/5.0 (iOS) Nextcloud-Talk v\(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "Unknown")" - let wsSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil) var wsRequest = URLRequest(url: url, cachePolicy: .useProtocolCachePolicy, timeoutInterval: webSocketTimeoutInterval) - wsRequest.setValue(userAgent, forHTTPHeaderField: "User-Agent") + wsRequest.setValue(NCAppBranding.userAgent(), forHTTPHeaderField: "User-Agent") if self.resumeId != nil { let currentTimestamp = Date().timeIntervalSince1970 diff --git a/NextcloudTalk/en.lproj/Localizable.strings b/NextcloudTalk/en.lproj/Localizable.strings index 66dbbcaec..a59518221 100644 --- a/NextcloudTalk/en.lproj/Localizable.strings +++ b/NextcloudTalk/en.lproj/Localizable.strings @@ -730,6 +730,9 @@ /* No comment provided by engineer. */ "Could not leave conversation" = "Could not leave conversation"; +/* No comment provided by engineer. */ +"Could not login with QR code" = "Could not login with QR code"; + /* No comment provided by engineer. */ "Could not remove participant" = "Could not remove participant"; @@ -2150,6 +2153,9 @@ /* No comment provided by engineer. */ "The recording might include your voice, video from camera, and screen share. Your consent is required before joining the call." = "The recording might include your voice, video from camera, and screen share. Your consent is required before joining the call."; +/* No comment provided by engineer. */ +"The token might be used already or is expired. Please generate a new QR code and retry." = "The token might be used already or is expired. Please generate a new QR code and retry."; + /* No comment provided by engineer. */ "There is no account for user %@ in server %@ configured in this app." = "There is no account for user %1$@ in server %2$@ configured in this app."; @@ -2234,6 +2240,9 @@ /* No comment provided by engineer. */ "Translation failed" = "Translation failed"; +/* Waiting message when trying to login with QR code */ +"Trying to login…" = "Trying to login…"; + /* No comment provided by engineer. */ "TURN servers" = "TURN servers"; diff --git a/NotificationServiceExtension/NotificationService.m b/NotificationServiceExtension/NotificationService.m index 84f68a531..711943989 100644 --- a/NotificationServiceExtension/NotificationService.m +++ b/NotificationServiceExtension/NotificationService.m @@ -200,10 +200,7 @@ - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withConte NSURL *url = [NSURL URLWithString:urlString]; - NSString *userAgent = [NSString stringWithFormat:@"Mozilla/5.0 (iOS) Nextcloud-Talk v%@", - [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"]]; - - [[SDWebImageDownloader sharedDownloader] setValue:userAgent forHTTPHeaderField:@"User-Agent"]; + [[SDWebImageDownloader sharedDownloader] setValue:[NCAppBranding userAgent] forHTTPHeaderField:@"User-Agent"]; [SDWebImageDownloader sharedDownloader].config.downloadTimeout = 25.0; SDWebImageOptions options = SDWebImageRetryFailed | SDWebImageRefreshCached;