Skip to content

Commit 06d76e5

Browse files
committed
[PM-20549] Refactor network layer to use BitwardenServiceClient
Introduce `BitwardenServiceClient` as the primary access point for Bitwarden services. Key changes include: - **`BitwardenServiceClient`:** Introduced `BitwardenServiceClient` and its implementation `BitwardenServiceClientImpl`. This class centralizes access to all Bitwarden API services and handles the underlying networking logic. - **Service Consolidation:** All network service interfaces (e.g., `AccountsService`, `ConfigService`, etc.) are now accessed through the `BitwardenServiceClient`. - **`BitwardenServiceClientConfig`:** Added `BitwardenServiceClientConfig` to provide a structured way to configure the `BitwardenServiceClient`, including client data, authentication, and other settings. - **`RefreshTokenProvider`:** Introduced `RefreshTokenProvider` and `AppIdProvider` interfaces for handling refresh tokens and app IDs, respectively. - **`RefreshAuthenticator`:** Created `RefreshAuthenticator` to manage token refresh logic using `RefreshTokenProvider`. - **Retrofit Refactoring:** Moved `Retrofits` and `RetrofitsImpl` to the `network` module. `RetrofitsImpl` now supports an optional `RefreshAuthenticator`. - **Module Updates:** - Moved `JwtTokenUtils`, `Retrofits`, `BitwardenX509ExtendedKeyManager` to the `network` module. - Updated `AuthDiskSource` to implement `AppIdProvider`. - Updated `AuthenticatorProvider` to implement `RefreshTokenProvider`. - Updated `Fido2NetworkModule` to use `BitwardenServiceClient`. - **Usage Updates:** Updated dependent modules (e.g., `authenticator`, `app`) to use the new `BitwardenServiceClient` and its services. - **Dependencies:** Updated dependencies between modules to reflect the changes. - **AppId provider:** Added `AppIdProvider` and the logic to generate unique appId in `AuthDiskSource`.
1 parent 5ac0f2b commit 06d76e5

File tree

38 files changed

+829
-666
lines changed

38 files changed

+829
-666
lines changed

app/src/main/java/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSource.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.x8bit.bitwarden.data.auth.datasource.disk
22

33
import com.bitwarden.network.model.SyncResponseJson
4+
import com.bitwarden.network.provider.AppIdProvider
45
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
56
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
67
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
@@ -12,21 +13,14 @@ import java.time.Instant
1213
* Primary access point for disk information.
1314
*/
1415
@Suppress("TooManyFunctions")
15-
interface AuthDiskSource {
16+
interface AuthDiskSource : AppIdProvider {
1617

1718
/**
1819
* The currently persisted authenticator sync symmetric key. This key is used for
1920
* encrypting IPC traffic.
2021
*/
2122
var authenticatorSyncSymmetricKey: ByteArray?
2223

23-
/**
24-
* Retrieves a unique ID for the application that is stored locally. This will generate a new
25-
* one if it does not yet exist and it will only be reset for new installs or when clearing
26-
* application data.
27-
*/
28-
val uniqueAppId: String
29-
3024
/**
3125
* The currently persisted saved email address (or `null` if not set).
3226
*/
Lines changed: 15 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,17 @@
11
package com.x8bit.bitwarden.data.auth.datasource.network.di
22

3+
import com.bitwarden.network.BitwardenServiceClient
34
import com.bitwarden.network.service.AccountsService
4-
import com.bitwarden.network.service.AccountsServiceImpl
55
import com.bitwarden.network.service.AuthRequestsService
6-
import com.bitwarden.network.service.AuthRequestsServiceImpl
76
import com.bitwarden.network.service.DevicesService
8-
import com.bitwarden.network.service.DevicesServiceImpl
97
import com.bitwarden.network.service.HaveIBeenPwnedService
10-
import com.bitwarden.network.service.HaveIBeenPwnedServiceImpl
118
import com.bitwarden.network.service.IdentityService
12-
import com.bitwarden.network.service.IdentityServiceImpl
139
import com.bitwarden.network.service.NewAuthRequestService
14-
import com.bitwarden.network.service.NewAuthRequestServiceImpl
1510
import com.bitwarden.network.service.OrganizationService
16-
import com.bitwarden.network.service.OrganizationServiceImpl
17-
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
1811
import dagger.Module
1912
import dagger.Provides
2013
import dagger.hilt.InstallIn
2114
import dagger.hilt.components.SingletonComponent
22-
import kotlinx.serialization.json.Json
23-
import retrofit2.create
2415
import javax.inject.Singleton
2516

2617
/**
@@ -33,70 +24,42 @@ object AuthNetworkModule {
3324
@Provides
3425
@Singleton
3526
fun providesAccountService(
36-
retrofits: Retrofits,
37-
json: Json,
38-
): AccountsService = AccountsServiceImpl(
39-
unauthenticatedAccountsApi = retrofits.unauthenticatedApiRetrofit.create(),
40-
authenticatedAccountsApi = retrofits.authenticatedApiRetrofit.create(),
41-
unauthenticatedKeyConnectorApi = retrofits.createStaticRetrofit().create(),
42-
authenticatedKeyConnectorApi = retrofits
43-
.createStaticRetrofit(isAuthenticated = true)
44-
.create(),
45-
json = json,
46-
)
27+
bitwardenServiceClient: BitwardenServiceClient,
28+
): AccountsService = bitwardenServiceClient.accountsService
4729

4830
@Provides
4931
@Singleton
5032
fun providesAuthRequestsService(
51-
retrofits: Retrofits,
52-
): AuthRequestsService = AuthRequestsServiceImpl(
53-
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
54-
)
33+
bitwardenServiceClient: BitwardenServiceClient,
34+
): AuthRequestsService = bitwardenServiceClient.authRequestsService
5535

5636
@Provides
5737
@Singleton
5838
fun providesDevicesService(
59-
retrofits: Retrofits,
60-
): DevicesService = DevicesServiceImpl(
61-
authenticatedDevicesApi = retrofits.authenticatedApiRetrofit.create(),
62-
unauthenticatedDevicesApi = retrofits.unauthenticatedApiRetrofit.create(),
63-
)
39+
bitwardenServiceClient: BitwardenServiceClient,
40+
): DevicesService = bitwardenServiceClient.devicesService
6441

6542
@Provides
6643
@Singleton
6744
fun providesIdentityService(
68-
retrofits: Retrofits,
69-
json: Json,
70-
): IdentityService = IdentityServiceImpl(
71-
unauthenticatedIdentityApi = retrofits.unauthenticatedIdentityRetrofit.create(),
72-
json = json,
73-
)
45+
bitwardenServiceClient: BitwardenServiceClient,
46+
): IdentityService = bitwardenServiceClient.identityService
7447

7548
@Provides
7649
@Singleton
7750
fun providesHaveIBeenPwnedService(
78-
retrofits: Retrofits,
79-
): HaveIBeenPwnedService = HaveIBeenPwnedServiceImpl(
80-
api = retrofits
81-
.createStaticRetrofit(baseUrl = "https://api.pwnedpasswords.com")
82-
.create(),
83-
)
51+
bitwardenServiceClient: BitwardenServiceClient,
52+
): HaveIBeenPwnedService = bitwardenServiceClient.haveIBeenPwnedService
8453

8554
@Provides
8655
@Singleton
8756
fun providesNewAuthRequestService(
88-
retrofits: Retrofits,
89-
): NewAuthRequestService = NewAuthRequestServiceImpl(
90-
authenticatedAuthRequestsApi = retrofits.authenticatedApiRetrofit.create(),
91-
unauthenticatedAuthRequestsApi = retrofits.unauthenticatedApiRetrofit.create(),
92-
)
57+
bitwardenServiceClient: BitwardenServiceClient,
58+
): NewAuthRequestService = bitwardenServiceClient.newAuthRequestService
9359

9460
@Provides
9561
@Singleton
9662
fun providesOrganizationService(
97-
retrofits: Retrofits,
98-
): OrganizationService = OrganizationServiceImpl(
99-
authenticatedOrganizationApi = retrofits.authenticatedApiRetrofit.create(),
100-
unauthenticatedOrganizationApi = retrofits.unauthenticatedApiRetrofit.create(),
101-
)
63+
bitwardenServiceClient: BitwardenServiceClient,
64+
): OrganizationService = bitwardenServiceClient.organizationService
10265
}

app/src/main/java/com/x8bit/bitwarden/data/auth/repository/AuthRepositoryImpl.kt

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,11 @@ class AuthRepositoryImpl(
402402
.syncOrgKeysFlow
403403
.onEach {
404404
val userId = activeUserId ?: return@onEach
405-
refreshAccessTokenSynchronously(userId)
405+
// TODO: [PM-20593] Investigate why tokens are explicitly refreshed.
406+
refreshAccessTokenSynchronouslyInternal(
407+
userId = userId,
408+
logOutOnFailure = false,
409+
)
406410
vaultRepository.sync()
407411
}
408412
// This requires the ioScope to ensure that refreshAccessTokenSynchronously
@@ -755,33 +759,11 @@ class AuthRepositoryImpl(
755759
orgIdentifier = organizationIdentifier,
756760
)
757761

758-
override fun refreshAccessTokenSynchronously(userId: String): Result<RefreshTokenResponseJson> {
759-
val refreshToken = authDiskSource
760-
.getAccountTokens(userId = userId)
761-
?.refreshToken
762-
?: return IllegalStateException("Must be logged in.").asFailure()
763-
return identityService
764-
.refreshTokenSynchronously(refreshToken)
765-
.flatMap { refreshTokenResponse ->
766-
// Check to make sure the user is still logged in after making the request
767-
authDiskSource
768-
.userState
769-
?.accounts
770-
?.get(userId)
771-
?.let { refreshTokenResponse.asSuccess() }
772-
?: IllegalStateException("Must be logged in.").asFailure()
773-
}
774-
.onSuccess { refreshTokenResponse ->
775-
// Update the existing UserState with updated token information
776-
authDiskSource.storeAccountTokens(
777-
userId = userId,
778-
accountTokens = AccountTokensJson(
779-
accessToken = refreshTokenResponse.accessToken,
780-
refreshToken = refreshTokenResponse.refreshToken,
781-
),
782-
)
783-
}
784-
}
762+
override fun refreshAccessTokenSynchronously(userId: String): Result<RefreshTokenResponseJson> =
763+
refreshAccessTokenSynchronouslyInternal(
764+
userId = userId,
765+
logOutOnFailure = true,
766+
)
785767

786768
override fun logout(reason: LogoutReason) {
787769
activeUserId?.let { userId -> logout(userId = userId, reason = reason) }
@@ -1433,6 +1415,42 @@ class AuthRepositoryImpl(
14331415
onFailure = { LeaveOrganizationResult.Error(error = it) },
14341416
)
14351417

1418+
private fun refreshAccessTokenSynchronouslyInternal(
1419+
userId: String,
1420+
logOutOnFailure: Boolean,
1421+
): Result<RefreshTokenResponseJson> {
1422+
val refreshToken = authDiskSource
1423+
.getAccountTokens(userId = userId)
1424+
?.refreshToken
1425+
?: return IllegalStateException("Must be logged in.").asFailure()
1426+
return identityService
1427+
.refreshTokenSynchronously(refreshToken)
1428+
.flatMap { refreshTokenResponse ->
1429+
// Check to make sure the user is still logged in after making the request
1430+
authDiskSource
1431+
.userState
1432+
?.accounts
1433+
?.get(userId)
1434+
?.let { refreshTokenResponse.asSuccess() }
1435+
?: IllegalStateException("Must be logged in.").asFailure()
1436+
}
1437+
.onFailure {
1438+
if (logOutOnFailure) {
1439+
logout(userId = userId, reason = LogoutReason.TokenRefreshFail)
1440+
}
1441+
}
1442+
.onSuccess { refreshTokenResponse ->
1443+
// Update the existing UserState with updated token information
1444+
authDiskSource.storeAccountTokens(
1445+
userId = userId,
1446+
accountTokens = AccountTokensJson(
1447+
accessToken = refreshTokenResponse.accessToken,
1448+
refreshToken = refreshTokenResponse.refreshToken,
1449+
),
1450+
)
1451+
}
1452+
}
1453+
14361454
@Suppress("CyclomaticComplexMethod")
14371455
private suspend fun validatePasswordAgainstPolicy(
14381456
password: String,

app/src/main/java/com/x8bit/bitwarden/data/auth/repository/util/GetTokenResponseExtensions.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.data.auth.repository.util
22

33
import com.bitwarden.data.datasource.disk.model.EnvironmentUrlDataJson
44
import com.bitwarden.network.model.GetTokenResponseJson
5+
import com.bitwarden.network.util.parseJwtTokenDataOrNull
56
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
67
import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetReason
78
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
package com.x8bit.bitwarden.data.autofill.fido2.datasource.network.di
22

3+
import com.bitwarden.network.BitwardenServiceClient
34
import com.bitwarden.network.service.DigitalAssetLinkService
4-
import com.bitwarden.network.service.DigitalAssetLinkServiceImpl
5-
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
65
import dagger.Module
76
import dagger.Provides
87
import dagger.hilt.InstallIn
98
import dagger.hilt.components.SingletonComponent
10-
import retrofit2.create
119
import javax.inject.Singleton
1210

1311
/**
@@ -20,11 +18,7 @@ object Fido2NetworkModule {
2018
@Provides
2119
@Singleton
2220
fun provideDigitalAssetLinkService(
23-
retrofits: Retrofits,
21+
bitwardenServiceClient: BitwardenServiceClient,
2422
): DigitalAssetLinkService =
25-
DigitalAssetLinkServiceImpl(
26-
digitalAssetLinkApi = retrofits
27-
.createStaticRetrofit()
28-
.create(),
29-
)
23+
bitwardenServiceClient.digitalAssetLinkService
3024
}
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package com.x8bit.bitwarden.data.platform.datasource.network.authenticator
22

3-
import com.bitwarden.network.model.RefreshTokenResponseJson
3+
import com.bitwarden.network.provider.RefreshTokenProvider
44
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
55

66
/**
77
* A provider for all the functionality needed to properly refresh the users access token.
88
*/
9-
interface AuthenticatorProvider {
9+
interface AuthenticatorProvider : RefreshTokenProvider {
1010

1111
/**
1212
* The currently active user's ID.
@@ -17,12 +17,4 @@ interface AuthenticatorProvider {
1717
* Attempts to logout the user based on the [userId].
1818
*/
1919
fun logout(userId: String, reason: LogoutReason)
20-
21-
/**
22-
* Attempt to refresh the user's access token based on the [userId].
23-
*
24-
* This call is both synchronous and performs a network request. Make sure that you are calling
25-
* from an appropriate thread.
26-
*/
27-
fun refreshAccessTokenSynchronously(userId: String): Result<RefreshTokenResponseJson>
2820
}

app/src/main/java/com/x8bit/bitwarden/data/platform/datasource/network/authenticator/RefreshAuthenticator.kt

Lines changed: 0 additions & 73 deletions
This file was deleted.

0 commit comments

Comments
 (0)