Skip to content

Commit

Permalink
Merge branch 'bitwarden:main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
artpoli authored Nov 1, 2024
2 parents d4dc46f + 74e3bbb commit 97d0e55
Show file tree
Hide file tree
Showing 353 changed files with 5,024 additions and 780 deletions.
16 changes: 2 additions & 14 deletions .github/ISSUE_TEMPLATE/bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,7 @@ body:
value: |
Thanks for taking the time to fill out this bug report!
> [!WARNING]
> This is the new native Bitwarden Beta app repository. For the pubicly available apps in App Store / Play Store, submit your report in [bitwarden/mobile](https://github.com/bitwarden/mobile)
Please do not submit feature requests. The [Community Forums](https://community.bitwarden.com) has a section for submitting, voting for, and discussing product feature requests.
- type: checkboxes
id: beta
attributes:
label: Bitwarden Beta
options:
- label: "I'm using the new native Bitwarden Beta app and I'm aware that legacy .NET app bugs should be reported in [bitwarden/mobile](https://github.com/bitwarden/mobile)"
validations:
required: true
- type: textarea
id: reproduce
attributes:
Expand Down Expand Up @@ -68,8 +56,8 @@ body:
attributes:
label: Environment Details
placeholder: |
- Device: [e.g. iPhone 15 Pro, iPad Air (5th Generation)]
- OS Version: [e.g. 17.4.1]
- Device: [e.g. iPhone 16 Pro, iPad Air (5th Generation)]
- OS Version: [e.g. 18.0.1]
- type: checkboxes
id: issue-tracking-info
attributes:
Expand Down
3 changes: 0 additions & 3 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
blank_issues_enabled: false
contact_links:
- name: Legacy Android Bug Reports
url: https://github.com/bitwarden/mobile/issues
about: Bugs found in the publicly available .NET MAUI app should be reported in [bitwarden/mobile](https://github.com/bitwarden/mobile)
- name: Feature Requests
url: https://community.bitwarden.com/c/feature-requests/
about: Request new features using the Community Forums. Please search existing feature requests before making a new one.
Expand Down
8 changes: 6 additions & 2 deletions .github/workflows/CI-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ on:
description: "Distribute to TestFlight"
type: boolean
env:
XCODE_VERSION: '15.4'
DISTRIBUTE_TO_TESTFLIGHT: ${{ github.event_name == 'push' || inputs.distribute }}
INTERNAL_BETA_PATCH_NUMBER: 999

Expand All @@ -30,7 +29,7 @@ jobs:
outputs:
version_name: ${{ steps.version_info.outputs.version_name }}
version_number: ${{ steps.version_info.outputs.version_number }}
xcode_version: ${{ env.XCODE_VERSION }}
xcode_version: ${{ steps.xcode_version.outputs.xcode_version }}
distribute_to_testflight: ${{ env.DISTRIBUTE_TO_TESTFLIGHT }}
internal_beta_version_name: ${{ steps.internal_versions.outputs.internal_beta_version_name}}
steps:
Expand All @@ -43,6 +42,11 @@ jobs:
echo '```' >> $GITHUB_STEP_SUMMARY
echo "</details>" >> $GITHUB_STEP_SUMMARY
- name: Read default Xcode version
id: xcode_version
run: |
echo "xcode_version=$(cat .xcode-version | tr -d '\n')" >> "$GITHUB_OUTPUT"
- name: Calculate version
if: ${{ inputs.build-number == '' || inputs.build-version == '' }}
uses: bitwarden/ios/.github/actions/dispatch-and-download@main
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ on:
base_version_number:
description: "Base Version Number - Will be added to the calculated version number"
type: number
default: 2000
default: 1500
patch_version:
description: "Patch Version Override - e.g. '999'"
type: string
Expand Down Expand Up @@ -55,7 +55,7 @@ on:
base_version_number:
description: "Base Version Number - Will be added to the calculated version number"
type: number
default: 2000
default: 1500
patch_version:
description: "Patch Version Override - e.g. '999'"
type: string
Expand All @@ -72,7 +72,7 @@ env:
jobs:
build:
name: Build
runs-on: macos-14
runs-on: macos-15
env:
MINT_PATH: .mint/lib
MINT_LINK_PATH: .mint/bin
Expand Down Expand Up @@ -347,7 +347,7 @@ jobs:
- name: Upload IPA & dSYM files
uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3
with:
name: Bitwarden iOS ${{ steps.version_info.outputs.version_name }} (${{ steps.version_info.outputs.version_number }}) ${{ env.BUILD_VARIANT }} ${{ env.XCODE_VERSION }}
name: Bitwarden iOS ${{ steps.version_info.outputs.version_name }} (${{ steps.version_info.outputs.version_number }}) ${{ env.BUILD_VARIANT }} ${{ env.XCODE_VERSION || env.DEFAULT_XCODE_VERSION }}
path: export
if-no-files-found: error

Expand Down
12 changes: 7 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,8 @@ env:
MINT_LINK_PATH: .mint/bin
MINT_PATH: .mint/lib
RESULT_BUNDLE_PATH: build/BitwardenTests.xcresult
SIMULATOR_NAME: ${{ inputs.simulator-name || 'iPhone 15 Pro' }}
SIMULATOR_VERSION: ${{ inputs.simulator-version || '18.0' }}
SIMULATOR_NAME: ${{ inputs.simulator-name }}
SIMULATOR_VERSION: ${{ inputs.simulator-version }}
XCODE_VERSION: ${{ inputs.xcode-version }}

jobs:
Expand All @@ -47,7 +47,7 @@ jobs:

test:
name: Test
runs-on: macos-14-xlarge
runs-on: macos-15-xlarge
needs: check-run
permissions:
contents: read
Expand All @@ -69,9 +69,11 @@ jobs:
with:
ref: ${{ github.event.pull_request.head.sha }}

- name: Read default Xcode version
- name: Read default Xcode version and simulator configuration
run: |
echo "DEFAULT_XCODE_VERSION=$(cat .xcode-version | tr -d '\n')" >> "$GITHUB_ENV"
echo "DEFAULT_SIMULATOR_NAME=$(cat .test-simulator-device-name | tr -d '\n')" >> "$GITHUB_ENV"
echo "DEFAULT_SIMULATOR_VERSION=$(cat .test-simulator-ios-version | tr -d '\n')" >> "$GITHUB_ENV"
- name: Set Xcode version
uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0
Expand Down Expand Up @@ -117,7 +119,7 @@ jobs:
-project Bitwarden.xcodeproj \
-scheme Bitwarden \
-configuration Debug \
-destination "platform=iOS Simulator,name=${{ env.SIMULATOR_NAME }},OS=${{ env.SIMULATOR_VERSION }}" \
-destination "platform=iOS Simulator,name=${{ env.SIMULATOR_NAME || env.DEFAULT_SIMULATOR_NAME }},OS=${{ env.SIMULATOR_VERSION || env.DEFAULT_SIMULATOR_VERSION }}" \
-resultBundlePath ${{ env.RESULT_BUNDLE_PATH }} \
-derivedDataPath build/DerivedData \
| xcbeautify --renderer github-actions
Expand Down
1 change: 1 addition & 0 deletions .test-simulator-device-name
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
iPhone 16 Pro
1 change: 1 addition & 0 deletions .test-simulator-ios-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
18.1
2 changes: 1 addition & 1 deletion .xcode-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
15.4
16.1
52 changes: 50 additions & 2 deletions AuthenticatorBridgeKit/AuthenticatorBridgeItemService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,13 @@ public protocol AuthenticatorBridgeItemService {
///
func fetchAllForUserId(_ userId: String) async throws -> [AuthenticatorBridgeItemDataView]

/// Fetches the temporary item stored by the Authenticator app and removes all temporary items from the store.
/// If there are no temporary items in the store, this method will return `nil`.
///
/// - Returns: The temporary item from the store, or `nil` if none was found.
///
func fetchTemporaryItem() async throws -> AuthenticatorBridgeItemDataView?

/// Inserts the list of items into the store for the given userId.
///
/// - Parameters:
Expand All @@ -28,6 +35,20 @@ public protocol AuthenticatorBridgeItemService {
func insertItems(_ items: [AuthenticatorBridgeItemDataView],
forUserId userId: String) async throws

/// Inserts a temporary item into the store. This method is for an item that originate in the Authenticator app that
/// need to move to the PM app (e.g. the user chooses Move to BW, or manually creates an item and selects
/// Save in BW). When the item originates from the Authenticator, we don't yet know what account it will be stored
/// in, so we save it to a temporary account and retrieve it for processing in the PM app.
///
/// The expectation is that only *one* temporary item will be stored at a time. Each time this method is called, it
/// will replace the one temporary item. When `fetchTemporaryItem()`is called, it will retrieve only
/// the last item stored.
///
/// - Parameters:
/// - item: The temporary `AuthenticatorBridgeItemDataModel` to be inserted into the store.
///
func insertTemporaryItem(_ item: AuthenticatorBridgeItemDataView) async throws

/// Returns `true` if sync has been enabled for one or more accounts in the Bitwarden PM app, `false`
/// if there are no accounts with sync currently turned on.
///
Expand Down Expand Up @@ -55,6 +76,11 @@ public protocol AuthenticatorBridgeItemService {
/// A concrete implementation of the `AuthenticatorBridgeItemService` protocol.
///
public class DefaultAuthenticatorBridgeItemService: AuthenticatorBridgeItemService {
// MARK: Private Properties

/// A constant to use as the userId for storing a temporary item.
private static let temporaryUserId = "000000000000"

// MARK: Properties

/// Cryptography service for encrypting/decrypting items.
Expand Down Expand Up @@ -106,6 +132,17 @@ public class DefaultAuthenticatorBridgeItemService: AuthenticatorBridgeItemServi
return try await cryptoService.decryptAuthenticatorItems(encryptedItems)
}

public func fetchTemporaryItem() async throws -> AuthenticatorBridgeItemDataView? {
let decryptedItems = try await fetchAllForUserId(
DefaultAuthenticatorBridgeItemService.temporaryUserId
)
try await deleteAllForUserId(
DefaultAuthenticatorBridgeItemService.temporaryUserId
)

return decryptedItems.first
}

public func isSyncOn() async -> Bool {
let key = try? await sharedKeychainRepository.getAuthenticatorKey()
return key != nil
Expand All @@ -125,6 +162,13 @@ public class DefaultAuthenticatorBridgeItemService: AuthenticatorBridgeItemServi
)
}

public func insertTemporaryItem(_ item: AuthenticatorBridgeItemDataView) async throws {
try await replaceAllItems(
with: [item],
forUserId: DefaultAuthenticatorBridgeItemService.temporaryUserId
)
}

/// Deletes all existing items for a given user and inserts new items for the list of items provided.
///
/// - Parameters:
Expand All @@ -147,13 +191,17 @@ public class DefaultAuthenticatorBridgeItemService: AuthenticatorBridgeItemServi

public func sharedItemsPublisher() async throws ->
AnyPublisher<[AuthenticatorBridgeItemDataView], any Error> {
let fetchRequest = AuthenticatorBridgeItemData.fetchRequest()
let fetchRequest = AuthenticatorBridgeItemData.fetchRequest(
predicate: NSPredicate(
format: "userId != %@", DefaultAuthenticatorBridgeItemService.temporaryUserId
)
)
fetchRequest.sortDescriptors = [NSSortDescriptor(keyPath: \AuthenticatorBridgeItemData.userId, ascending: true)]
return FetchedResultsPublisher(
context: dataStore.persistentContainer.viewContext,
request: fetchRequest
)
.tryMap { dataItems in
.map { dataItems in
dataItems.compactMap(\.model)
}
.asyncTryMap { itemModel in
Expand Down
2 changes: 2 additions & 0 deletions AuthenticatorBridgeKit/SharedCryptographyService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ public class DefaultAuthenticatorCryptographyService: SharedCryptographyService
public func decryptAuthenticatorItems(
_ items: [AuthenticatorBridgeItemDataModel]
) async throws -> [AuthenticatorBridgeItemDataView] {
guard !items.isEmpty else { return [] }

let key = try await sharedKeychainRepository.getAuthenticatorKey()
let symmetricKey = SymmetricKey(data: key)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,26 @@ final class AuthenticatorBridgeItemServiceTests: AuthenticatorBridgeKitTestCase
XCTAssertEqual(emptyResult.count, 0)
}

/// When no temporary item has been stored, `fetchTemporaryItem()` returns `nil`
///
func test_fetchTemporaryItem_emptyResult() async throws {
let result = try await subject.fetchTemporaryItem()
XCTAssertNil(result)
}

/// Verify that the `fetchTemporaryItem()` is able to fetch the temporary item that was inserted
/// and removes it after fetching.
///
func test_fetchTemporaryItem_success() async throws {
let expectedItem = AuthenticatorBridgeItemDataView.fixture()
try await subject.insertTemporaryItem(expectedItem)
let result = try await subject.fetchTemporaryItem()
XCTAssertEqual(result, expectedItem)

let retryResult = try await subject.fetchTemporaryItem()
XCTAssertNil(retryResult)
}

/// Verify that the `insertItems(_:forUserId:)` method successfully inserts the list of items
/// for the given user id.
///
Expand All @@ -111,6 +131,32 @@ final class AuthenticatorBridgeItemServiceTests: AuthenticatorBridgeKitTestCase
XCTAssertEqual(result, expectedItems)
}

/// When `insertTemporaryItem(_)` is called multiple times, it should replace the temporary
/// item - i.e. only the last item will end up stored.
///
func test_insertTemporaryItem_replacesItem() async throws {
let initialItem = AuthenticatorBridgeItemDataView.fixture(name: "Initial Insert")
let expectedItem = AuthenticatorBridgeItemDataView.fixture()
try await subject.insertTemporaryItem(initialItem)
try await subject.insertTemporaryItem(expectedItem)
let result = try await subject.fetchTemporaryItem()

XCTAssertEqual(result, expectedItem)
}

/// Verify that the `insertTemporaryItem(_)` method successfully inserts a temporary item that is
/// then able to be retrieved by `fetchTemporaryItem()`.
///
func test_insertTemporaryItem_success() async throws {
let expectedItem = AuthenticatorBridgeItemDataView.fixture()
try await subject.insertTemporaryItem(expectedItem)
let result = try await subject.fetchTemporaryItem()

XCTAssertTrue(cryptoService.encryptCalled,
"Items should have been encrypted before inserting!!")
XCTAssertEqual(result, expectedItem)
}

/// Verify that `isSyncOn` returns false when the key is not present in the keychain.
///
func test_isSyncOn_false() async throws {
Expand Down Expand Up @@ -201,6 +247,29 @@ final class AuthenticatorBridgeItemServiceTests: AuthenticatorBridgeKitTestCase
XCTAssertEqual(results[0], combined)
}

/// Verify that the shared items publisher does not publish any temporary items
///
func test_sharedItemsPublisher_noTemporaryItems() async throws {
let expectedItems = AuthenticatorBridgeItemDataView.fixtures().sorted { $0.id < $1.id }
try await subject.insertItems(expectedItems, forUserId: "userId")
try await subject.insertTemporaryItem(.fixture())

var results: [[AuthenticatorBridgeItemDataView]] = []
let publisher = try await subject.sharedItemsPublisher()
.sink(
receiveCompletion: { _ in },
receiveValue: { value in
results.append(value)
}
)
defer { publisher.cancel() }

try await subject.insertTemporaryItem(.fixture())

waitFor(results.count == 1)
XCTAssertEqual(results[0], expectedItems)
}

/// Verify that the shared items publisher publishes all the items inserted initially.
///
func test_sharedItemsPublisher_success() async throws {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ final class SharedCryptographyServiceTests: AuthenticatorBridgeKitTestCase {

// MARK: Tests

/// Verify that `SharedCryptographyService.decryptAuthenticatorItems()' returns and empty
/// array when the input is an empty array even when the authenticator key is missing.
///
func test_decryptAuthenticatorItems_returnsEmpty() async throws {
try sharedKeychainRepository.deleteAuthenticatorKey()
await assertAsyncDoesNotThrow {
let result = try await subject.decryptAuthenticatorItems([])
XCTAssertEqual(result, [])
}
}

/// Verify that `SharedCryptographyService.decryptAuthenticatorItems(:)` correctly
/// decrypts an encrypted array of `AuthenticatorBridgeItemDataModel`.
///
Expand All @@ -44,11 +55,12 @@ final class SharedCryptographyServiceTests: AuthenticatorBridgeKitTestCase {
/// when the `SharedKeyRepository` authenticator key is missing.
///
func test_decryptAuthenticatorItems_throwsKeyMissingError() async throws {
let encryptedItems = try await subject.encryptAuthenticatorItems(items)
let error = AuthenticatorKeychainServiceError.keyNotFound(SharedKeychainItem.authenticatorKey)

try sharedKeychainRepository.deleteAuthenticatorKey()
await assertAsyncThrows(error: error) {
_ = try await subject.decryptAuthenticatorItems([])
_ = try await subject.decryptAuthenticatorItems(encryptedItems)
}
}

Expand Down
Loading

0 comments on commit 97d0e55

Please sign in to comment.