Skip to content

Commit

Permalink
2024.10-rc1 changes from main (#1086)
Browse files Browse the repository at this point in the history
Co-authored-by: Federico Maccaroni <[email protected]>
Co-authored-by: Pranish <[email protected]>
Co-authored-by: aj-rosado <[email protected]>
Co-authored-by: Phil Cappelli <[email protected]>
  • Loading branch information
5 people authored Oct 28, 2024
1 parent 23cb2a8 commit b06bd63
Show file tree
Hide file tree
Showing 83 changed files with 685 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import Networking
/// The response returned from the API upon creating an account.
///
struct CreateAccountResponseModel: JSONResponse {
static let decoder = JSONDecoder()

// MARK: Properties

/// The captcha bypass token returned in this response.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ struct IdentityTokenResponseModel: Equatable, JSONResponse, KdfConfigProtocol {
// MARK: Account Properties

/// Whether the app needs to force a password reset.
let forcePasswordReset: Bool
@DefaultFalse var forcePasswordReset: Bool

/// The type of KDF algorithm to use.
let kdf: KdfType
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ import Networking

/// An object containing a value defining if this device has previously logged into this account or not.
struct KnownDeviceResponseModel: JSONResponse {
static let decoder = JSONDecoder()

// MARK: Properties

/// A flag indicating if this device is known or not.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ import Networking
/// Contains information necessary for encrypting the user's password for login.
///
struct PreLoginResponseModel: Equatable, JSONResponse, KdfConfigProtocol {
// MARK: Static Properties

static let decoder = JSONDecoder()

// MARK: Properties

/// The type of kdf algorithm to use for encryption.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import Networking
/// The response returned from the API upon pre-validating the single-sign on.
///
struct PreValidateSingleSignOnResponse: JSONResponse, Equatable {
static let decoder = JSONDecoder()

// MARK: Properties

/// The token returned in this response.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import Networking
/// The response returned from the API upon creating an account.
///
struct RegisterFinishResponseModel: JSONResponse {
static let decoder = JSONDecoder()

// MARK: Properties

/// The captcha bypass token returned in this response.
Expand Down
2 changes: 1 addition & 1 deletion BitwardenShared/Core/Auth/Services/AuthService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ class DefaultAuthService: AuthService { // swiftlint:disable:this type_body_leng
let appId = await appIdService.getOrCreateAppId()

// Initiate the login request and cache the result.
let loginWithDeviceData = try await clientService.auth().newAuthRequest(email: email)
let loginWithDeviceData = try await clientService.auth(isPreAuth: true).newAuthRequest(email: email)
let loginRequest = try await authAPIService.initiateLoginWithDevice(LoginWithDeviceRequestModel(
email: email,
publicKey: loginWithDeviceData.publicKey,
Expand Down
1 change: 1 addition & 0 deletions BitwardenShared/Core/Auth/Services/AuthServiceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ class AuthServiceTests: BitwardenTestCase { // swiftlint:disable:this type_body_
// Verify the results.
XCTAssertEqual(client.requests.count, 1)
XCTAssertEqual(clientService.mockAuth.newAuthRequestEmail, "[email protected]")
XCTAssertTrue(clientService.mockAuthIsPreAuth)
XCTAssertEqual(result.authRequestResponse, authRequestResponse)
XCTAssertEqual(result.requestId, LoginRequest.fixture().id)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ extension EnvironmentUrls {
/// - Parameter environmentUrlData: The environment URLs used to initialize `EnvironmentUrls`.
///
init(environmentUrlData: EnvironmentUrlData) {
// Use the default URLs if the region matches US or EU.
let environmentUrlData: EnvironmentUrlData = switch environmentUrlData.region {
case .europe: .defaultEU
case .unitedStates: .defaultUS
case .selfHosted: environmentUrlData
}

if environmentUrlData.region == .selfHosted, let base = environmentUrlData.base {
apiURL = base.appendingPathComponent("api")
baseURL = base
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,50 @@ class EnvironmentUrlsTests: BitwardenTestCase {
)
}

/// `init(environmentUrlData:)` defaults to the pre-defined EU URLs if the base URL matches the EU environment.
func test_init_environmentUrlData_baseUrl_europe() {
let subject = EnvironmentUrls(
environmentUrlData: EnvironmentUrlData(base: URL(string: "https://vault.bitwarden.eu")!)
)
XCTAssertEqual(
subject,
EnvironmentUrls(
apiURL: URL(string: "https://api.bitwarden.eu")!,
baseURL: URL(string: "https://vault.bitwarden.eu")!,
eventsURL: URL(string: "https://events.bitwarden.eu")!,
iconsURL: URL(string: "https://icons.bitwarden.eu")!,
identityURL: URL(string: "https://identity.bitwarden.eu")!,
importItemsURL: URL(string: "https://vault.bitwarden.eu/#/tools/import")!,
recoveryCodeURL: URL(string: "https://vault.bitwarden.eu/#/recover-2fa")!,
sendShareURL: URL(string: "https://vault.bitwarden.eu/#/send")!,
settingsURL: URL(string: "https://vault.bitwarden.eu/#/settings")!,
webVaultURL: URL(string: "https://vault.bitwarden.eu")!
)
)
}

/// `init(environmentUrlData:)` defaults to the pre-defined US URLs if the base URL matches the US environment.
func test_init_environmentUrlData_baseUrl_unitedStates() {
let subject = EnvironmentUrls(
environmentUrlData: EnvironmentUrlData(base: URL(string: "https://vault.bitwarden.com")!)
)
XCTAssertEqual(
subject,
EnvironmentUrls(
apiURL: URL(string: "https://api.bitwarden.com")!,
baseURL: URL(string: "https://vault.bitwarden.com")!,
eventsURL: URL(string: "https://events.bitwarden.com")!,
iconsURL: URL(string: "https://icons.bitwarden.net")!,
identityURL: URL(string: "https://identity.bitwarden.com")!,
importItemsURL: URL(string: "https://vault.bitwarden.com/#/tools/import")!,
recoveryCodeURL: URL(string: "https://vault.bitwarden.com/#/recover-2fa")!,
sendShareURL: URL(string: "https://send.bitwarden.com/#")!,
settingsURL: URL(string: "https://vault.bitwarden.com/#/settings")!,
webVaultURL: URL(string: "https://vault.bitwarden.com")!
)
)
}

/// `init(environmentUrlData:)` sets the URLs from the base URL which includes a trailing slash.
func test_init_environmentUrlData_baseUrlWithTrailingSlash() {
let subject = EnvironmentUrls(
Expand Down
14 changes: 12 additions & 2 deletions BitwardenShared/Core/Platform/Models/Domain/ServerConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ struct ServerConfig: Equatable, Codable, Sendable {
environment = responseModel.environment.map(EnvironmentServerConfig.init)
self.date = date
let features: [(FeatureFlag, AnyCodable)]
features = responseModel.featureStates.compactMap { key, value in
features = responseModel.featureStates?.compactMap { key, value in
guard let flag = FeatureFlag(rawValue: key) else { return nil }
return (flag, value)
}
} ?? []
featureStates = Dictionary(uniqueKeysWithValues: features)

gitHash = responseModel.gitHash
Expand All @@ -43,7 +43,9 @@ struct ServerConfig: Equatable, Codable, Sendable {
// MARK: Methods

/// Whether the server supports cipher key encryption.
///
/// - Returns: `true` if it's supported, `false` otherwise.
///
func supportsCipherKeyEncryption() -> Bool {
guard let minVersion = ServerVersion(Constants.cipherKeyEncryptionMinServerVersion),
let serverVersion = ServerVersion(version),
Expand All @@ -52,6 +54,14 @@ struct ServerConfig: Equatable, Codable, Sendable {
}
return true
}

/// Checks if the server is an official Bitwarden server.
///
/// - Returns: `true` if the server is `nil`, indicating an official Bitwarden server, otherwise `false`.
///
func isOfficialBitwardenServer() -> Bool {
server == nil
}
}

// MARK: - ThirdPartyServerConfig
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import Networking

extension JSONResponse {
/// The decoder used by default to decode JSON responses from the API.
static var decoder: JSONDecoder { .defaultDecoder }
static var decoder: JSONDecoder { .pascalOrSnakeCaseDecoder }
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ struct ConfigResponseModel: Equatable, JSONResponse {
let environment: EnvironmentServerConfigResponseModel?

/// Feature flags to configure the client.
let featureStates: [String: AnyCodable]
let featureStates: [String: AnyCodable]?

/// The git hash of the server.
let gitHash: String
Expand Down
47 changes: 47 additions & 0 deletions BitwardenShared/Core/Platform/Utilities/DefaultFalse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/// A property wrapper that will default the wrapped value to `false` if decoding fails. This is
/// useful for decoding a boolean value which may not be present in the response.
///
@propertyWrapper
struct DefaultFalse: Codable, Hashable {
// MARK: Properties

/// The wrapped value.
let wrappedValue: Bool

// MARK: Initialization

/// Initialize a `DefaultFalse` with a wrapped value.
///
/// - Parameter wrappedValue: The value that is contained in the property wrapper.
///
init(wrappedValue: Bool) {
self.wrappedValue = wrappedValue
}

// MARK: Decodable

init(from decoder: any Decoder) throws {
let container = try decoder.singleValueContainer()
wrappedValue = try container.decode(Bool.self)
}

// MARK: Encodable

func encode(to encoder: Encoder) throws {
try wrappedValue.encode(to: encoder)
}
}

// MARK: - KeyedDecodingContainer

extension KeyedDecodingContainer {
/// When decoding a `DefaultFalse` wrapped value, if the property doesn't exist, default to `false`.
///
/// - Parameters:
/// - type: The type of value to attempt to decode.
/// - key: The key used to decode the value.
///
func decode(_ type: DefaultFalse.Type, forKey key: Key) throws -> DefaultFalse {
try decodeIfPresent(type, forKey: key) ?? DefaultFalse(wrappedValue: false)
}
}
61 changes: 61 additions & 0 deletions BitwardenShared/Core/Platform/Utilities/DefaultFalseTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import XCTest

@testable import BitwardenShared

class DefaultFalseTests: BitwardenTestCase {
// MARK: Types

struct Model: Codable, Equatable {
@DefaultFalse var value: Bool
}

// MARK: Tests

/// `DefaultFalse` encodes a `false` wrapped value.
func test_encode_false() throws {
let subject = Model(value: false)
let data = try JSONEncoder().encode(subject)
XCTAssertEqual(String(data: data, encoding: .utf8), #"{"value":false}"#)
}

/// `DefaultFalse` encodes a `true` wrapped value.
func test_encode_true() throws {
let subject = Model(value: true)
let data = try JSONEncoder().encode(subject)
XCTAssertEqual(String(data: data, encoding: .utf8), #"{"value":true}"#)
}

/// Decoding a `DefaultFalse` wrapped value will decode a `false` value from the JSON.
func test_decode_false() throws {
let json = #"{"value": true}"#
let data = try XCTUnwrap(json.data(using: .utf8))
let subject = try JSONDecoder().decode(Model.self, from: data)
XCTAssertEqual(subject, Model(value: true))
}

/// Decoding a `DefaultFalse` wrapped value will default the value to `false` if the key is
/// missing from the JSON.
func test_decode_missing() throws {
let json = #"{}"#
let data = try XCTUnwrap(json.data(using: .utf8))
let subject = try JSONDecoder().decode(Model.self, from: data)
XCTAssertEqual(subject, Model(value: false))
}

/// Decoding a `DefaultFalse` wrapped value will default the value to `false` if the value is
/// `null` in the JSON.
func test_decode_null() throws {
let json = #"{"value": null}"#
let data = try XCTUnwrap(json.data(using: .utf8))
let subject = try JSONDecoder().decode(Model.self, from: data)
XCTAssertEqual(subject, Model(value: false))
}

/// Decoding a `DefaultFalse` wrapped value will decode a `true` value from the JSON.
func test_decode_true() throws {
let json = #"{"value": true}"#
let data = try XCTUnwrap(json.data(using: .utf8))
let subject = try JSONDecoder().decode(Model.self, from: data)
XCTAssertEqual(subject, Model(value: true))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ struct ProfileResponseModel: Codable, Equatable {
let premium: Bool

/// Whether the user has a premium account from their organization.
let premiumFromOrganization: Bool
@DefaultFalse var premiumFromOrganization: Bool

/// The user's private key.
let privateKey: String?
Expand All @@ -51,5 +51,5 @@ struct ProfileResponseModel: Codable, Equatable {
let twoFactorEnabled: Bool

/// Whether the user uses key connector.
let usesKeyConnector: Bool
@DefaultFalse var usesKeyConnector: Bool
}
23 changes: 18 additions & 5 deletions BitwardenShared/UI/Auth/Landing/LandingProcessor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -102,14 +102,23 @@ class LandingProcessor: StateProcessor<LandingState, LandingAction, LandingEffec

// MARK: Private Methods

/// Refreshes the configuration by forcing a refresh from the config service
/// and loads the latest feature flags.
///
private func refreshConfig() async {
await services.configService.getConfig(
forceRefresh: true,
isPreAuth: true
)
await loadFeatureFlag()
}

/// Sets the feature flag value to be used.
///
private func loadFeatureFlag() async {
state.emailVerificationFeatureFlag = await services.configService.getFeatureFlag(
FeatureFlag.emailVerification,
defaultValue: false,
forceRefresh: true,
isPreAuth: true
defaultValue: false
)
}

Expand Down Expand Up @@ -224,9 +233,13 @@ extension LandingProcessor: RegionDelegate {
guard !urls.isEmpty else { return }
await services.environmentService.setPreAuthURLs(urls: urls)
state.region = region
// After setting a new region, feature flags need to be reloaded

/// - Using `Task` for `refreshConfig` ensures that this call doesn’t delay other operations,

Check warning on line 237 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build Public Apps (Beta) / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.

Check warning on line 237 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build Internal App / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.

Check warning on line 237 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.

Check warning on line 237 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.
/// such as closing the Self-host settings view or triggering `.appeared` events. These issues
/// arose because `refreshConfig` was awaited directly, leading to delays when internet speed

Check warning on line 239 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build Public Apps (Production) / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.
/// was low.

Check warning on line 240 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build Public Apps (Beta) / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.

Check warning on line 240 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build Internal App / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.

Check warning on line 240 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.

Check warning on line 240 in BitwardenShared/UI/Auth/Landing/LandingProcessor.swift

View workflow job for this annotation

GitHub Actions / Build

(docComments) Use doc comments for API declarations, otherwise use regular comments.
Task {
await loadFeatureFlag()
await refreshConfig()
}
}
}
Loading

0 comments on commit b06bd63

Please sign in to comment.