From e3725d6121f812aab101f9733efe08c45592922d Mon Sep 17 00:00:00 2001 From: Matt Czech Date: Thu, 27 Jun 2024 17:04:46 -0500 Subject: [PATCH] BIT-2410: Support managed app config environment URLs --- .../Services/EnvironmentService.swift | 33 ++++++++++- .../Services/EnvironmentServiceTests.swift | 58 ++++++++++++++++++- 2 files changed, 87 insertions(+), 4 deletions(-) diff --git a/BitwardenShared/Core/Platform/Services/EnvironmentService.swift b/BitwardenShared/Core/Platform/Services/EnvironmentService.swift index fd98799742..ed5447d475 100644 --- a/BitwardenShared/Core/Platform/Services/EnvironmentService.swift +++ b/BitwardenShared/Core/Platform/Services/EnvironmentService.swift @@ -68,14 +68,21 @@ class DefaultEnvironmentService: EnvironmentService { /// The app's current environment URLs. private var environmentUrls: EnvironmentUrls + /// The shared UserDefaults instance (NOTE: this should be the standard one just for the app, + /// not one in the app group). + private let standardUserDefaults: UserDefaults + // MARK: Initialization /// Initialize a `DefaultEnvironmentService`. /// - /// - Parameter stateService: The service used by the application to manage account state. + /// - Parameters: + /// - stateService: The service used by the application to manage account state. + /// - standardUserDefaults: The shared UserDefaults instance. /// - init(stateService: StateService) { + init(stateService: StateService, standardUserDefaults: UserDefaults = .standard) { self.stateService = stateService + self.standardUserDefaults = standardUserDefaults environmentUrls = EnvironmentUrls(environmentUrlData: .defaultUS) } @@ -84,14 +91,18 @@ class DefaultEnvironmentService: EnvironmentService { func loadURLsForActiveAccount() async { let urls: EnvironmentUrlData + let managedSettingsUrls = managedSettingsUrls() if let environmentUrls = try? await stateService.getEnvironmentUrls() { urls = environmentUrls + } else if let managedSettingsUrls { + urls = managedSettingsUrls } else if let preAuthUrls = await stateService.getPreAuthEnvironmentUrls() { urls = preAuthUrls } else { urls = .defaultUS } - await setPreAuthURLs(urls: urls) + + await setPreAuthURLs(urls: managedSettingsUrls ?? urls) environmentUrls = EnvironmentUrls(environmentUrlData: urls) // swiftformat:disable:next redundantSelf @@ -105,6 +116,22 @@ class DefaultEnvironmentService: EnvironmentService { // swiftformat:disable:next redundantSelf Logger.application.info("Setting pre-auth URLs: \(String(describing: self.environmentUrls))") } + + // MARK: Private + + /// Returns the URLs that are specified as part of a managed app configuration. + /// + /// - Returns: The environment URLs that are specified as part of a managed app configuration. + /// + private func managedSettingsUrls() -> EnvironmentUrlData? { + let managedSettings = standardUserDefaults.dictionary(forKey: "com.apple.configuration.managed") + guard let baseUrlString = managedSettings?["baseEnvironmentUrl"] as? String, + let baseUrl = URL(string: baseUrlString) + else { + return nil + } + return EnvironmentUrlData(base: baseUrl) + } } extension DefaultEnvironmentService { diff --git a/BitwardenShared/Core/Platform/Services/EnvironmentServiceTests.swift b/BitwardenShared/Core/Platform/Services/EnvironmentServiceTests.swift index f2cd28c216..cb2ae457b2 100644 --- a/BitwardenShared/Core/Platform/Services/EnvironmentServiceTests.swift +++ b/BitwardenShared/Core/Platform/Services/EnvironmentServiceTests.swift @@ -6,6 +6,7 @@ class EnvironmentServiceTests: XCTestCase { // MARK: Properties var stateService: MockStateService! + var standardUserDefaults: UserDefaults! var subject: EnvironmentService! // MARK: Setup & Teardown @@ -14,14 +15,20 @@ class EnvironmentServiceTests: XCTestCase { super.setUp() stateService = MockStateService() + standardUserDefaults = UserDefaults(suiteName: "test") + standardUserDefaults.removeObject(forKey: "com.apple.configuration.managed") - subject = DefaultEnvironmentService(stateService: stateService) + subject = DefaultEnvironmentService( + stateService: stateService, + standardUserDefaults: standardUserDefaults + ) } override func tearDown() { super.tearDown() stateService = nil + standardUserDefaults = nil subject = nil } @@ -82,6 +89,55 @@ class EnvironmentServiceTests: XCTestCase { XCTAssertEqual(stateService.preAuthEnvironmentUrls, urls) } + /// `loadURLsForActiveAccount()` loads the managed config URLs. + func test_loadURLsForActiveAccount_managedConfig() async throws { + standardUserDefaults.setValue( + ["baseEnvironmentUrl": "https://vault.example.com"], + forKey: "com.apple.configuration.managed" + ) + + await subject.loadURLsForActiveAccount() + + let urls = try EnvironmentUrlData(base: XCTUnwrap(URL(string: "https://vault.example.com"))) + XCTAssertEqual(subject.apiURL, URL(string: "https://vault.example.com/api")) + XCTAssertEqual(subject.eventsURL, URL(string: "https://vault.example.com/events")) + XCTAssertEqual(subject.iconsURL, URL(string: "https://vault.example.com/icons")) + XCTAssertEqual(subject.identityURL, URL(string: "https://vault.example.com/identity")) + XCTAssertEqual(subject.importItemsURL, URL(string: "https://vault.example.com/#/tools/import")) + XCTAssertEqual(subject.region, .selfHosted) + XCTAssertEqual(subject.sendShareURL, URL(string: "https://vault.example.com/#/send")) + XCTAssertEqual(subject.settingsURL, URL(string: "https://vault.example.com/#/settings")) + XCTAssertEqual(subject.webVaultURL, URL(string: "https://vault.example.com")) + XCTAssertEqual(stateService.preAuthEnvironmentUrls, urls) + } + + /// `loadURLsForActiveAccount()` doesn't load the managed config URLs if there's an active + /// account, but sets the pre-auth URLs to the managed config URLs. + func test_loadURLsForActiveAccount_managedConfigActiveAccount() async throws { + let account = Account.fixture() + stateService.activeAccount = account + stateService.environmentUrls[account.profile.userId] = .defaultUS + standardUserDefaults.setValue( + ["baseEnvironmentUrl": "https://vault.example.com"], + forKey: "com.apple.configuration.managed" + ) + + await subject.loadURLsForActiveAccount() + + XCTAssertEqual(subject.apiURL, URL(string: "https://vault.bitwarden.com/api")) + XCTAssertEqual(subject.eventsURL, URL(string: "https://vault.bitwarden.com/events")) + XCTAssertEqual(subject.iconsURL, URL(string: "https://vault.bitwarden.com/icons")) + XCTAssertEqual(subject.identityURL, URL(string: "https://vault.bitwarden.com/identity")) + XCTAssertEqual(subject.importItemsURL, URL(string: "https://vault.bitwarden.com/#/tools/import")) + XCTAssertEqual(subject.region, .unitedStates) + XCTAssertEqual(subject.sendShareURL, URL(string: "https://vault.bitwarden.com/#/send")) + XCTAssertEqual(subject.settingsURL, URL(string: "https://vault.bitwarden.com/#/settings")) + XCTAssertEqual(subject.webVaultURL, URL(string: "https://vault.bitwarden.com")) + + let urls = try EnvironmentUrlData(base: XCTUnwrap(URL(string: "https://vault.example.com"))) + XCTAssertEqual(stateService.preAuthEnvironmentUrls, urls) + } + /// `loadURLsForActiveAccount()` loads the default URLs if there's no active account /// and no preauth URLs. func test_loadURLsForActiveAccount_noAccount() async {