Skip to content
Merged
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
92 changes: 38 additions & 54 deletions FlowCrypt.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ final class SettingsViewController: TableNodeViewController {
}
}

static func filtered(with rules: OrganisationalRules) -> [SettingsMenuItem] {
static func filtered(with rules: ClientConfiguration) -> [SettingsMenuItem] {
var cases = SettingsMenuItem.allCases

if !rules.canBackupKeys {
Expand All @@ -45,18 +45,18 @@ final class SettingsViewController: TableNodeViewController {

private let decorator: SettingsViewDecoratorType
private let currentUser: User?
private let organisationalRules: OrganisationalRules
private let clientConfiguration: ClientConfiguration
private let rows: [SettingsMenuItem]

init(
decorator: SettingsViewDecoratorType = SettingsViewDecorator(),
currentUser: User? = DataService.shared.currentUser,
organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService()
clientConfigurationService: ClientConfigurationServiceType = ClientConfigurationService()
) {
self.decorator = decorator
self.currentUser = currentUser
self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser()
self.rows = SettingsMenuItem.filtered(with: self.organisationalRules)
self.clientConfiguration = clientConfigurationService.getSavedClientConfigurationForCurrentUser()
self.rows = SettingsMenuItem.filtered(with: self.clientConfiguration)
super.init(node: TableNode())
}

Expand Down Expand Up @@ -121,7 +121,7 @@ extension SettingsViewController {
viewController = ContactsListViewController()
case .backups:
guard let currentUser = currentUser,
organisationalRules.canBackupKeys else {
clientConfiguration.canBackupKeys else {
viewController = nil
return
}
Expand Down
15 changes: 6 additions & 9 deletions FlowCrypt/Controllers/Setup/SetupInitialViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,8 @@ final class SetupInitialViewController: TableNodeViewController {
private let user: UserId
private let router: GlobalRouterType
private let decorator: SetupViewDecorator
private let organisationalRules: OrganisationalRules
private let clientConfiguration: ClientConfiguration
private let emailKeyManagerApi: EmailKeyManagerApiType
private let clientConfigurationService: ClientConfigurationServiceType

private lazy var logger = Logger.nested(in: Self.self, with: .setup)

Expand All @@ -65,17 +64,15 @@ final class SetupInitialViewController: TableNodeViewController {
backupService: BackupServiceType = BackupService(),
router: GlobalRouterType = GlobalRouter(),
decorator: SetupViewDecorator = SetupViewDecorator(),
organisationalRulesService: OrganisationalRulesServiceType = OrganisationalRulesService(),
emailKeyManagerApi: EmailKeyManagerApiType = EmailKeyManagerApi(),
clientConfigurationService: ClientConfigurationServiceType = ClientConfigurationService()
clientConfigurationService: ClientConfigurationServiceType = ClientConfigurationService(),
emailKeyManagerApi: EmailKeyManagerApiType = EmailKeyManagerApi()
) {
self.user = user
self.backupService = backupService
self.router = router
self.decorator = decorator
self.organisationalRules = organisationalRulesService.getSavedOrganisationalRulesForCurrentUser()
self.clientConfiguration = clientConfigurationService.getSavedClientConfigurationForCurrentUser()
self.emailKeyManagerApi = emailKeyManagerApi
self.clientConfigurationService = clientConfigurationService

super.init(node: TableNode())
}
Expand Down Expand Up @@ -117,7 +114,7 @@ extension SetupInitialViewController {
}

private func searchKeyBackupsInInbox() {
if !organisationalRules.canBackupKeys {
if !clientConfiguration.canBackupKeys {
logger.logInfo("Skipping backups searching because canBackupKeys == false")
proceedToSetupWith(keys: [])
return
Expand All @@ -144,7 +141,7 @@ extension SetupInitialViewController {
}

private func decideIfEKMshouldBeUsed() {
switch clientConfigurationService.checkShouldUseEKM() {
switch clientConfiguration.checkUsesEKM() {
case .usesEKM:
state = .fetchingKeysFromEKM
case .doesNotUseEKM:
Expand Down
2 changes: 2 additions & 0 deletions FlowCrypt/Functionality/Error Handling/AppErr.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum AppErr: Error {
case user(String)
/// useful in Promises when you want to cancel execution without showing any error (eg after user clicks cancel button)
case silentAbort
case noCurrentUser
case general(String)

var userMessage: String {
Expand All @@ -40,6 +41,7 @@ extension AppErr: Equatable {
case (.user, .user): return true
case (.silentAbort, .silentAbort): return true
case (.general, .general): return true
case (.noCurrentUser, .noCurrentUser): return true
default: return false
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ protocol EnterpriseServerApiType {
func getActiveFesUrl(for email: String) -> Promise<String?>
func getActiveFesUrlForCurrentUser() -> Promise<String?>

func getClientConfiguration(for email: String) -> Promise<ClientConfiguration>
func getClientConfigurationForCurrentUser() -> Promise<ClientConfiguration>
func getClientConfiguration(for email: String) -> Promise<RawClientConfiguration>
func getClientConfigurationForCurrentUser() -> Promise<RawClientConfiguration>
}

enum EnterpriseServerApiError: Error {
Expand Down Expand Up @@ -47,9 +47,8 @@ class EnterpriseServerApi: EnterpriseServerApiType {
static let serviceNeededValue = "enterprise-server"
}

private struct ClientConfigurationContainer: Codable {
let clientConfiguration: ClientConfiguration

private struct ClientConfigurationResponse: Codable {
let clientConfiguration: RawClientConfiguration
private enum CodingKeys: String, CodingKey {
case clientConfiguration
}
Expand Down Expand Up @@ -110,8 +109,8 @@ class EnterpriseServerApi: EnterpriseServerApiType {
.recoverFromTimeOut(result: nil)
}

func getClientConfiguration(for email: String) -> Promise<ClientConfiguration> {
Promise<ClientConfiguration> { resolve, reject in
func getClientConfiguration(for email: String) -> Promise<RawClientConfiguration> {
Promise<RawClientConfiguration> { resolve, reject in
guard let userDomain = email.recipientDomain else {
reject(EnterpriseServerApiError.emailFormat)
return
Expand All @@ -137,7 +136,7 @@ class EnterpriseServerApi: EnterpriseServerApiType {
decoder.keyDecodingStrategy = .convertFromSnakeCase

guard let clientConfiguration = (try? decoder.decode(
ClientConfigurationContainer.self,
ClientConfigurationResponse.self,
from: safeReponse.data
))?.clientConfiguration
else {
Expand All @@ -148,9 +147,9 @@ class EnterpriseServerApi: EnterpriseServerApiType {
}
}

func getClientConfigurationForCurrentUser() -> Promise<ClientConfiguration> {
func getClientConfigurationForCurrentUser() -> Promise<RawClientConfiguration> {
guard let email = DataService.shared.currentUser?.email else {
return Promise<ClientConfiguration> { _, _ in
return Promise<RawClientConfiguration> { _, _ in
fatalError("User has to be set while getting client configuration")
}
}
Expand Down
4 changes: 2 additions & 2 deletions FlowCrypt/Functionality/Services/AppStartup.swift
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ struct AppStartup {

private func getUserOrgRulesIfNeeded() throws {
if DataService.shared.isLoggedIn {
let service = OrganisationalRulesService()
_ = try awaitPromise(service.fetchOrganisationalRulesForCurrentUser())
let service = ClientConfigurationService()
_ = try awaitPromise(service.fetchClientConfigurationForCurrentUser())
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,42 +11,42 @@ import Foundation

/// Organisational rules, set domain-wide, and delivered from FlowCrypt Backend
/// These either enforce, alter or forbid various behavior to fit customer needs
class OrganisationalRules {
class ClientConfiguration {

let clientConfiguration: ClientConfiguration
let raw: RawClientConfiguration

init(clientConfiguration: ClientConfiguration) {
self.clientConfiguration = clientConfiguration
init(raw: RawClientConfiguration) {
self.raw = raw
}

/// Internal company SKS-like public key server to trust above Attester
var customSksPubkeyServer: String? {
clientConfiguration.customKeyserverUrl
raw.customKeyserverUrl
}

/// an internal org FlowCrypt Email Key Manager instance, can manage both public and private keys
/// use this method when using for PRV sync
var keyManagerUrlForPrivateKeys: String? {
clientConfiguration.keyManagerUrl
raw.keyManagerUrl
}

/// an internal org FlowCrypt Email Key Manager instance, can manage both public and private keys
/// use this method when using for PUB sync
var keyManagerUrlForPublicKeys: String? {
(clientConfiguration.flags ?? []).contains(.noKeyManagerPubLookup)
(raw.flags ?? []).contains(.noKeyManagerPubLookup)
? nil
: clientConfiguration.keyManagerUrl
: raw.keyManagerUrl
}

/// use when finding out if EKM is in use, to change functionality without actually neededing the EKM
var isUsingKeyManager: Bool {
clientConfiguration.keyManagerUrl != nil
raw.keyManagerUrl != nil
}

/// Check if key manager url set properly
var isKeyManagerUrlValid: Bool {
// check for empty string
guard let urlString = clientConfiguration.keyManagerUrl, urlString.isNotEmpty else {
guard let urlString = raw.keyManagerUrl, urlString.isNotEmpty else {
return false
}
// check is url can be configured
Expand All @@ -55,43 +55,43 @@ class OrganisationalRules {

/// Enforce a key algo for keygen, eg rsa2048,rsa4096,curve25519
var enforcedKeygenAlgo: String? {
clientConfiguration.enforceKeygenAlgo
raw.enforceKeygenAlgo
}

/// Some orgs want to have newly generated keys include self-signatures that expire some time in the future.
var getEnforcedKeygenExpirationMonths: Int? {
clientConfiguration.enforceKeygenExpireMonths
raw.enforceKeygenExpireMonths
}

/// Some orgs expect 100% of their private keys to be imported from elsewhere (and forbid keygen in the extension)
var canCreateKeys: Bool {
!(clientConfiguration.flags ?? []).contains(.noPrivateKeyCreate)
!(raw.flags ?? []).contains(.noPrivateKeyCreate)
}

/// Some orgs want to forbid backing up of public keys (such as inbox or other methods)
var canBackupKeys: Bool {
!(clientConfiguration.flags ?? []).contains(.noPrivateKeyBackup)
!(raw.flags ?? []).contains(.noPrivateKeyBackup)
}

/// (normally, during setup, if a public key is submitted to Attester and there is
/// a conflicting key already submitted, the issue will be skipped)
/// Some orgs want to make sure that their public key gets submitted to attester and conflict errors are NOT ignored:
var mustSubmitAttester: Bool {
(clientConfiguration.flags ?? []).contains(.enforceAttesterSubmit)
(raw.flags ?? []).contains(.enforceAttesterSubmit)
}

/// Normally, during setup, "remember pass phrase" is unchecked
/// This option will cause "remember pass phrase" option to be checked by default
/// This behavior is also enabled as a byproduct of PASS_PHRASE_QUIET_AUTOGEN
var shouldRememberPassphraseByDefault: Bool {
(clientConfiguration.flags ?? []).contains(.defaultRememberPassphrase) || mustAutogenPassPhraseQuietly
(raw.flags ?? []).contains(.defaultRememberPassphrase) || mustAutogenPassPhraseQuietly
}

/// This is to be used for customers who run their own FlowCrypt Email Key Manager
/// If a key can be found on FEKM, it will be auto imported
/// If not, it will be autogenerated and stored there
var mustAutoImportOrAutogenPrvWithKeyManager: Bool {
guard let flags = clientConfiguration.flags else {
guard let flags = raw.flags else {
return false
}

Expand All @@ -100,6 +100,7 @@ class OrganisationalRules {
}

if keyManagerUrlForPrivateKeys == nil {
// todo - should not be a fatal error, since the input comes from external sources
fatalError("Wrong org rules config: using PRV_AUTOIMPORT_OR_AUTOGEN without key_manager_url")
}
return true
Expand All @@ -110,18 +111,18 @@ class OrganisationalRules {
/// The pass phrase will NOT be displayed to user, and it will never be asked of the user
/// This creates the smoothest user experience, for organisations that use full-disk-encryption and don't need pass phrase protection
var mustAutogenPassPhraseQuietly: Bool {
(clientConfiguration.flags ?? []).contains(.passphraseQuietAutogen)
(raw.flags ?? []).contains(.passphraseQuietAutogen)
}

/// Some orgs prefer to forbid publishing public keys publicly
var canSubmitPubToAttester: Bool {
!(clientConfiguration.flags ?? []).contains(.noAttesterSubmit)
!(raw.flags ?? []).contains(.noAttesterSubmit)
}

/// Some orgs have a list of email domains where they do NOT want such emails to be looked up on public sources (such as Attester)
/// This is because they already have other means to obtain public keys for these domains, such as from their own internal keyserver
func canLookupThisRecipientOnAttester(recipient email: String) throws -> Bool {
let disallowedDomains = clientConfiguration.disallowAttesterSearchForDomains ?? []
let disallowedDomains = raw.disallowAttesterSearchForDomains ?? []

if disallowedDomains.contains("*") {
return false
Expand All @@ -137,19 +138,78 @@ class OrganisationalRules {
/// -> enforcing that submitted keys match customer key server
/// Until the newer endpoint is ready, this flag will point users in those orgs to the original endpoint
var useLegacyAttesterSubmit: Bool {
(clientConfiguration.flags ?? []).contains(.useLegacyAttesterSubmit)
(raw.flags ?? []).contains(.useLegacyAttesterSubmit)
}

/// With this option, sent messages won't have any comment/version in armor, imported keys get imported without armor
var shouldHideArmorMeta: Bool {
(clientConfiguration.flags ?? []).contains(.hideArmorMeta)
(raw.flags ?? []).contains(.hideArmorMeta)
}

var forbidStoringPassPhrase: Bool {
(clientConfiguration.flags ?? []).contains(.forbidStoringPassphrase)
(raw.flags ?? []).contains(.forbidStoringPassphrase)
}

var keyManagerUrlString: String? {
clientConfiguration.keyManagerUrl?.addTrailingSlashIfNeeded
raw.keyManagerUrl?.addTrailingSlashIfNeeded
}

/**
* This method checks if the user is set up for using EKM, and if other client configuration is consistent with it.
* There are three possible outcomes:
* 1) EKM in use because isUsingKeyManager == true and other client configs are consistent with it (result: no error, use EKM)
* 2) EKM is in use because isUsingKeyManager == true and other client configs are NOT consistent with it (result: error)
* 3) EKM is not in use because isUsingKeyManager == false (result: normal login flow)
*/
func checkUsesEKM() -> CheckUsesEKMResult {
guard isUsingKeyManager else {
return .doesNotUseEKM
}
guard isKeyManagerUrlValid else {
return .inconsistentClientConfiguration(checkError: .urlNotValid)
}
guard mustAutoImportOrAutogenPrvWithKeyManager else {
return .inconsistentClientConfiguration(checkError: .autoImportOrAutogenPrvWithKeyManager)
}
guard !mustAutogenPassPhraseQuietly else {
return .inconsistentClientConfiguration(checkError: .autogenPassPhraseQuietly)
}
guard forbidStoringPassPhrase else {
return .inconsistentClientConfiguration(checkError: .forbidStoringPassPhrase)
}
guard !mustSubmitAttester else {
return .inconsistentClientConfiguration(checkError: .mustSubmitAttester)
}
return .usesEKM
}

enum CheckUsesEKMResult: Equatable {
case usesEKM
case inconsistentClientConfiguration(checkError: InconsistentClientConfigurationError)
case doesNotUseEKM
}

enum InconsistentClientConfigurationError: Error, CustomStringConvertible, Equatable {
case urlNotValid
case autoImportOrAutogenPrvWithKeyManager
case autogenPassPhraseQuietly
case forbidStoringPassPhrase
case mustSubmitAttester

var description: String {
switch self {
case .urlNotValid:
return "organisational_rules_url_not_valid".localized
case .autoImportOrAutogenPrvWithKeyManager:
return "organisational_rules_autoimport_or_autogen_with_private_key_manager_error".localized
case .autogenPassPhraseQuietly:
return "organisational_rules_autogen_passphrase_quitely_error".localized
case .forbidStoringPassPhrase:
return "organisational_rules_forbid_storing_passphrase_error".localized
case .mustSubmitAttester:
return "organisational_rules_must_submit_attester_error".localized
}
}
}

}
Loading