diff --git a/Cryptomator/VaultDetail/UnlockSectionFooterViewModel.swift b/Cryptomator/VaultDetail/UnlockSectionFooterViewModel.swift index db95fee78..891c222f7 100644 --- a/Cryptomator/VaultDetail/UnlockSectionFooterViewModel.swift +++ b/Cryptomator/VaultDetail/UnlockSectionFooterViewModel.swift @@ -6,6 +6,7 @@ // Copyright © 2021 Skymatic GmbH. All rights reserved. // +import CryptomatorCloudAccessCore import CryptomatorCommonCore import Foundation @@ -31,21 +32,23 @@ class UnlockSectionFooterViewModel: HeaderFooterViewModel { } var biometryTypeName: String? + var vaultInfo: VaultInfo - init(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration) { + init(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration, vaultInfo: VaultInfo) { self.vaultUnlocked = vaultUnlocked self.biometricalUnlockEnabled = biometricalUnlockEnabled self.biometryTypeName = biometryTypeName - let titleText = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration) + let titleText = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration, vaultInfo: vaultInfo) self.title = Bindable(titleText) self.keepUnlockedDuration = keepUnlockedDuration + self.vaultInfo = vaultInfo } private func updateTitle() { - title.value = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration) + title.value = UnlockSectionFooterViewModel.getTitleText(vaultUnlocked: vaultUnlocked, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: biometryTypeName, keepUnlockedDuration: keepUnlockedDuration, vaultInfo: vaultInfo) } - private static func getTitleText(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration) -> String { + private static func getTitleText(vaultUnlocked: Bool, biometricalUnlockEnabled: Bool, biometryTypeName: String?, keepUnlockedDuration: KeepUnlockedDuration, vaultInfo: VaultInfo) -> String { let unlockedText: String if vaultUnlocked { unlockedText = LocalizedString.getValue("vaultDetail.unlocked.footer") @@ -62,7 +65,7 @@ class UnlockSectionFooterViewModel: HeaderFooterViewModel { keepUnlockedText = String(format: LocalizedString.getValue("vaultDetail.keepUnlocked.footer.limitedDuration"), keepUnlockedDuration.description ?? "") } var footerText = "\(unlockedText)\n\n\(keepUnlockedText)" - if let biometryTypeName = biometryTypeName { + if vaultInfo.vaultConfigType != .hub, let biometryTypeName = biometryTypeName { let biometricalUnlockText: String if biometricalUnlockEnabled { biometricalUnlockText = String(format: LocalizedString.getValue("vaultDetail.enabledBiometricalUnlock.footer"), biometryTypeName) diff --git a/Cryptomator/VaultDetail/VaultDetailViewModel.swift b/Cryptomator/VaultDetail/VaultDetailViewModel.swift index 0bc2872ae..934bed009 100644 --- a/Cryptomator/VaultDetail/VaultDetailViewModel.swift +++ b/Cryptomator/VaultDetail/VaultDetailViewModel.swift @@ -121,7 +121,7 @@ class VaultDetailViewModel: VaultDetailViewModelProtocol { private var lockSectionCells: [BindableTableViewCellViewModel] { var cells: [BindableTableViewCellViewModel] = [lockButton, keepUnlockedCellViewModel] - if let biometryTypeName = context.enrolledBiometricsAuthenticationName() { + if vaultInfo.vaultConfigType != .hub, let biometryTypeName = context.enrolledBiometricsAuthenticationName() { let switchCellViewModel = getSwitchCellViewModel(biometryTypeName: biometryTypeName) cells.append(switchCellViewModel) } @@ -146,7 +146,7 @@ class VaultDetailViewModel: VaultDetailViewModelProtocol { .lockingSection: unlockSectionFooterViewModel, .removeVaultSection: BaseHeaderFooterViewModel(title: LocalizedString.getValue("vaultDetail.removeVault.footer"))] - private lazy var unlockSectionFooterViewModel = UnlockSectionFooterViewModel(vaultUnlocked: vaultInfo.vaultIsUnlocked.value, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: context.enrolledBiometricsAuthenticationName(), keepUnlockedDuration: currentKeepUnlockedDuration.value) + private lazy var unlockSectionFooterViewModel = UnlockSectionFooterViewModel(vaultUnlocked: vaultInfo.vaultIsUnlocked.value, biometricalUnlockEnabled: biometricalUnlockEnabled, biometryTypeName: context.enrolledBiometricsAuthenticationName(), keepUnlockedDuration: currentKeepUnlockedDuration.value, vaultInfo: vaultInfo) private lazy var vaultInfoCellViewModel = BindableTableViewCellViewModel(title: vaultInfo.vaultName, detailTitle: vaultInfo.vaultPath.path, detailTitleTextColor: .secondaryLabel, image: UIImage(vaultIconFor: vaultInfo.cloudProviderType, state: .normal), selectionStyle: .none) private lazy var renameVaultCellViewModel = ButtonCellViewModel.createDisclosureButton(action: VaultDetailButtonAction.showRenameVault, title: LocalizedString.getValue("vaultDetail.button.renameVault"), detailTitle: vaultName) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 7347d8843..766f03ae3 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -20,6 +20,7 @@ public enum HubAuthenticationFlow { case needsDeviceRegistration case licenseExceeded case requiresAccountInitialization(at: URL) + case vaultArchived } public struct HubAuthenticationFlowSuccess { @@ -48,6 +49,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving public init() {} + // swiftlint:disable:next cyclomatic_complexity public func receiveKey(authState: OIDAuthState, vaultConfig: UnverifiedVaultConfig) async throws -> HubAuthenticationFlow { guard let hubConfig = vaultConfig.allegedHubConfig, let vaultBaseURL = getVaultBaseURL(from: vaultConfig) else { throw CryptomatorHubAuthenticatorError.invalidVaultConfig @@ -79,6 +81,8 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .requiresAccountInitialization(at: profileURL) case .legacyHubVersion: throw CryptomatorHubAuthenticatorError.incompatibleHubVersion + case .vaultArchived: + return .vaultArchived } let retrieveUserPrivateKeyResponse = try await getUserKey(apiBaseURL: apiBaseURL, authState: authState) @@ -240,8 +244,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .success(encryptedVaultKey: body, header: httpResponse?.allHeaderFields ?? [:]) case 402: return .licenseExceeded - case 403, 410: + case 403: return .accessNotGranted + case 410: + return .vaultArchived case 404: return .legacyHubVersion case 449: @@ -297,7 +303,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving private enum RetrieveVaultMasterkeyEncryptedForUserResponse { // 200 case success(encryptedVaultKey: String, header: [AnyHashable: Any]) - // 403, 410 + // 403 case accessNotGranted // 402 case licenseExceeded @@ -305,6 +311,8 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving case requiresAccountInitialization(at: URL) // 404 case legacyHubVersion + // 410 + case vaultArchived } private struct DeviceDto: Codable { diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift index 77e3c2815..df5b18340 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift @@ -20,7 +20,9 @@ public struct HubAuthenticationView: View { onRegisterTap: { Task { await viewModel.register() }} ) case .accessNotGranted: - HubAccessNotGrantedView(onRefresh: { Task { await viewModel.refresh() }}) + CryptomatorErrorWithRefreshView(headerTitle: LocalizedString.getValue("hubAuthentication.accessNotGranted"), onRefresh: { Task { await viewModel.refresh() }}) + case .vaultArchived: + CryptomatorErrorWithRefreshView(headerTitle: LocalizedString.getValue("hubAuthentication.vaultArchived"), onRefresh: { Task { await viewModel.refresh() }}) case .licenseExceeded: CryptomatorErrorView(text: LocalizedString.getValue("hubAuthentication.licenseExceeded")) case let .error(description): diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift index ee81e7354..98ef71ae6 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift @@ -32,6 +32,7 @@ public final class HubAuthenticationViewModel: ObservableObject { case licenseExceeded case deviceRegistration(DeviceRegistration) case error(description: String) + case vaultArchived } public enum DeviceRegistration: Equatable { @@ -108,6 +109,8 @@ public final class HubAuthenticationViewModel: ObservableObject { await setState(to: .licenseExceeded) case let .requiresAccountInitialization(profileURL): await delegate?.hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert(profileURL: profileURL) + case .vaultArchived: + await setState(to: .vaultArchived) } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteredSuccessfullyView.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteredSuccessfullyView.swift deleted file mode 100644 index c0328584f..000000000 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteredSuccessfullyView.swift +++ /dev/null @@ -1,19 +0,0 @@ -import SwiftUI - -struct HubAccessNotGrantedView: View { - var onRefresh: () -> Void - - var body: some View { - CryptomatorSimpleButtonView( - buttonTitle: LocalizedString.getValue("common.button.refresh"), - onButtonTap: onRefresh, - headerTitle: LocalizedString.getValue("hubAuthentication.accessNotGranted") - ) - } -} - -struct HubDeviceRegisteredSuccessfullyView_Previews: PreviewProvider { - static var previews: some View { - HubAccessNotGrantedView(onRefresh: {}) - } -} diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubErrorWithRefreshView.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubErrorWithRefreshView.swift new file mode 100644 index 000000000..f8b3bd0a6 --- /dev/null +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubErrorWithRefreshView.swift @@ -0,0 +1,20 @@ +import SwiftUI + +struct CryptomatorErrorWithRefreshView: View { + var headerTitle: String + var onRefresh: () -> Void + + var body: some View { + CryptomatorSimpleButtonView( + buttonTitle: LocalizedString.getValue("common.button.refresh"), + onButtonTap: onRefresh, + headerTitle: headerTitle + ) + } +} + +struct CryptomatorErrorWithRefreshView_Previews: PreviewProvider { + static var previews: some View { + CryptomatorErrorWithRefreshView(headerTitle: "Example Header Title", onRefresh: {}) + } +} diff --git a/SharedResources/en.lproj/Localizable.strings b/SharedResources/en.lproj/Localizable.strings index 6445a8fd8..be744841c 100644 --- a/SharedResources/en.lproj/Localizable.strings +++ b/SharedResources/en.lproj/Localizable.strings @@ -116,16 +116,17 @@ "getFolderIntent.error.noVaultSelected" = "No vault has been selected."; "hubAuthentication.title" = "Hub Vault"; -"hubAuthentication.accessNotGranted" = "Your device has not yet been authorized to access this vault. Ask the vault owner to authorize it."; +"hubAuthentication.accessNotGranted" = "You do not have permission to access this vault. Ask the vault owner to authorize you."; "hubAuthentication.licenseExceeded" = "Your Cryptomator Hub instance has an invalid license. Please inform a Hub administrator to upgrade or renew the license."; "hubAuthentication.deviceRegistration.deviceName.cells.name" = "Device Name"; "hubAuthentication.deviceRegistration.deviceName.footer.title" = "This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device."; "hubAuthentication.deviceRegistration.accountKey.footer.title" = "Your Account Key is required to login from new apps or browsers. It can be found in your profile."; "hubAuthentication.deviceRegistration.needsAuthorization.alert.title" = "Register Device Successful"; -"hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "To access the vault, your device needs to be authorized by the vault owner."; +"hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "To access the vault, the vault owner needs to grant you permission."; "hubAuthentication.requireAccountInit.alert.title" = "Action Required"; "hubAuthentication.requireAccountInit.alert.message" = "To proceed, please complete the steps required in your Hub user profile."; "hubAuthentication.requireAccountInit.alert.actionButton" = "Go to Profile"; +"hubAuthentication.vaultArchived" = "This vault has been archived. Please ask the vault owner to unarchive it."; "intents.saveFile.missingFile" = "The provided file is not valid."; "intents.saveFile.invalidFolder" = "The provided folder is not valid.";