[PM-20191] Migrate OrganizationService to network module (#5049)

This commit is contained in:
Patrick Honkonen
2025-04-15 15:42:21 -04:00
committed by GitHub
parent e35be360d7
commit 83de8b888d
7 changed files with 8 additions and 8 deletions

View File

@@ -12,8 +12,8 @@ import com.bitwarden.network.service.IdentityService
import com.bitwarden.network.service.IdentityServiceImpl
import com.bitwarden.network.service.NewAuthRequestService
import com.bitwarden.network.service.NewAuthRequestServiceImpl
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationService
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationServiceImpl
import com.bitwarden.network.service.OrganizationService
import com.bitwarden.network.service.OrganizationServiceImpl
import com.x8bit.bitwarden.data.platform.datasource.network.retrofit.Retrofits
import dagger.Module
import dagger.Provides

View File

@@ -1,50 +0,0 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.bitwarden.network.model.OrganizationAutoEnrollStatusResponseJson
import com.bitwarden.network.model.OrganizationDomainSsoDetailsResponseJson
import com.bitwarden.network.model.OrganizationKeysResponseJson
import com.bitwarden.network.model.VerifiedOrganizationDomainSsoDetailsResponse
/**
* Provides an API for querying organization endpoints.
*/
interface OrganizationService {
/**
* Enrolls a user with the given [userId] in this organizations reset password functionality.
*/
suspend fun organizationResetPasswordEnroll(
organizationId: String,
userId: String,
passwordHash: String?,
resetPasswordKey: String,
): Result<Unit>
/**
* Request claimed organization domain information for an [email] needed for SSO requests.
*/
suspend fun getOrganizationDomainSsoDetails(
email: String,
): Result<OrganizationDomainSsoDetailsResponseJson>
/**
* Gets info regarding whether this organization enforces reset password auto enrollment.
*/
suspend fun getOrganizationAutoEnrollStatus(
organizationIdentifier: String,
): Result<OrganizationAutoEnrollStatusResponseJson>
/**
* Gets the public and private keys for this organization.
*/
suspend fun getOrganizationKeys(
organizationId: String,
): Result<OrganizationKeysResponseJson>
/**
* Request organization verified domain details for an [email] needed for SSO
* requests.
*/
suspend fun getVerifiedOrganizationDomainSsoDetails(
email: String,
): Result<VerifiedOrganizationDomainSsoDetailsResponse>
}

View File

@@ -1,72 +0,0 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.bitwarden.network.api.AuthenticatedOrganizationApi
import com.bitwarden.network.api.UnauthenticatedOrganizationApi
import com.bitwarden.network.model.OrganizationAutoEnrollStatusResponseJson
import com.bitwarden.network.model.OrganizationDomainSsoDetailsRequestJson
import com.bitwarden.network.model.OrganizationDomainSsoDetailsResponseJson
import com.bitwarden.network.model.OrganizationKeysResponseJson
import com.bitwarden.network.model.OrganizationResetPasswordEnrollRequestJson
import com.bitwarden.network.model.VerifiedOrganizationDomainSsoDetailsRequest
import com.bitwarden.network.model.VerifiedOrganizationDomainSsoDetailsResponse
import com.bitwarden.network.util.toResult
/**
* Default implementation of [OrganizationService].
*/
class OrganizationServiceImpl(
private val authenticatedOrganizationApi: AuthenticatedOrganizationApi,
private val unauthenticatedOrganizationApi: UnauthenticatedOrganizationApi,
) : OrganizationService {
override suspend fun organizationResetPasswordEnroll(
organizationId: String,
userId: String,
passwordHash: String?,
resetPasswordKey: String,
): Result<Unit> = authenticatedOrganizationApi
.organizationResetPasswordEnroll(
organizationId = organizationId,
userId = userId,
body = OrganizationResetPasswordEnrollRequestJson(
passwordHash = passwordHash,
resetPasswordKey = resetPasswordKey,
),
)
.toResult()
override suspend fun getOrganizationDomainSsoDetails(
email: String,
): Result<OrganizationDomainSsoDetailsResponseJson> = unauthenticatedOrganizationApi
.getClaimedDomainOrganizationDetails(
body = OrganizationDomainSsoDetailsRequestJson(
email = email,
),
)
.toResult()
override suspend fun getOrganizationAutoEnrollStatus(
organizationIdentifier: String,
): Result<OrganizationAutoEnrollStatusResponseJson> = authenticatedOrganizationApi
.getOrganizationAutoEnrollResponse(
organizationIdentifier = organizationIdentifier,
)
.toResult()
override suspend fun getOrganizationKeys(
organizationId: String,
): Result<OrganizationKeysResponseJson> = authenticatedOrganizationApi
.getOrganizationKeys(
organizationId = organizationId,
)
.toResult()
override suspend fun getVerifiedOrganizationDomainSsoDetails(
email: String,
): Result<VerifiedOrganizationDomainSsoDetailsResponse> = unauthenticatedOrganizationApi
.getVerifiedOrganizationDomainsByEmail(
body = VerifiedOrganizationDomainSsoDetailsRequest(
email = email,
),
)
.toResult()
}

View File

@@ -37,6 +37,7 @@ import com.bitwarden.network.service.AccountsService
import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.IdentityService
import com.bitwarden.network.service.OrganizationService
import com.bitwarden.network.util.isSslHandShakeError
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
@@ -45,7 +46,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.ForcePasswordResetRea
import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.network.model.DeviceDataModel
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationService
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toInt
import com.x8bit.bitwarden.data.auth.datasource.sdk.util.toKdfTypeJson

View File

@@ -6,8 +6,8 @@ import com.bitwarden.network.service.AccountsService
import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.IdentityService
import com.bitwarden.network.service.OrganizationService
import com.x8bit.bitwarden.data.auth.datasource.disk.AuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationService
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.manager.AuthRequestManager
import com.x8bit.bitwarden.data.auth.manager.KeyConnectorManager

View File

@@ -1,201 +0,0 @@
package com.x8bit.bitwarden.data.auth.datasource.network.service
import com.bitwarden.core.data.util.asSuccess
import com.bitwarden.network.api.AuthenticatedOrganizationApi
import com.bitwarden.network.api.UnauthenticatedOrganizationApi
import com.bitwarden.network.base.BaseServiceTest
import com.bitwarden.network.model.OrganizationAutoEnrollStatusResponseJson
import com.bitwarden.network.model.OrganizationDomainSsoDetailsResponseJson
import com.bitwarden.network.model.OrganizationKeysResponseJson
import com.bitwarden.network.model.VerifiedOrganizationDomainSsoDetailsResponse
import kotlinx.coroutines.test.runTest
import okhttp3.mockwebserver.MockResponse
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import retrofit2.create
import java.time.ZonedDateTime
class OrganizationServiceTest : BaseServiceTest() {
private val authenticatedOrganizationApi: AuthenticatedOrganizationApi = retrofit.create()
private val unauthenticatedOrganizationApi: UnauthenticatedOrganizationApi = retrofit.create()
private val organizationService = OrganizationServiceImpl(
authenticatedOrganizationApi = authenticatedOrganizationApi,
unauthenticatedOrganizationApi = unauthenticatedOrganizationApi,
)
@Test
fun `organizationResetPasswordEnroll when response is success should return Unit as success`() =
runTest {
server.enqueue(MockResponse().setResponseCode(200))
val result = organizationService.organizationResetPasswordEnroll(
organizationId = "orgId",
userId = "userId",
passwordHash = "passwordHash",
resetPasswordKey = "resetPasswordKey",
)
assertEquals(Unit.asSuccess(), result)
}
@Test
fun `organizationResetPasswordEnroll when response is an error should return an error`() =
runTest {
server.enqueue(MockResponse().setResponseCode(400))
val result = organizationService.organizationResetPasswordEnroll(
organizationId = "orgId",
userId = "userId",
passwordHash = "passwordHash",
resetPasswordKey = "resetPasswordKey",
)
assertTrue(result.isFailure)
}
@Suppress("MaxLineLength")
@Test
fun `getOrganizationDomainSsoDetails when response is success should return PrevalidateSsoResponseJson`() =
runTest {
val email = "test@gmail.com"
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody(ORGANIZATION_DOMAIN_SSO_DETAILS_JSON),
)
val result = organizationService.getOrganizationDomainSsoDetails(email)
assertEquals(ORGANIZATION_DOMAIN_SSO_BODY.asSuccess(), result)
}
@Test
fun `getOrganizationDomainSsoDetails when response is an error should return an error`() =
runTest {
val email = "test@gmail.com"
server.enqueue(MockResponse().setResponseCode(400))
val result = organizationService.getOrganizationDomainSsoDetails(email)
assertTrue(result.isFailure)
}
@Test
fun `getOrganizationAutoEnrollStatus when response is success should return valid response`() =
runTest {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody(ORGANIZATION_AUTO_ENROLL_STATUS_JSON),
)
val result = organizationService.getOrganizationAutoEnrollStatus("orgId")
assertEquals(ORGANIZATION_AUTO_ENROLL_STATUS_RESPONSE.asSuccess(), result)
}
@Test
fun `getOrganizationAutoEnrollStatus when response is an error should return an error`() =
runTest {
server.enqueue(MockResponse().setResponseCode(400))
val result = organizationService.getOrganizationAutoEnrollStatus("orgId")
assertTrue(result.isFailure)
}
@Test
fun `getOrganizationKeys when response is success should return valid response`() = runTest {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody(ORGANIZATION_KEYS_JSON),
)
val result = organizationService.getOrganizationKeys("orgId")
assertEquals(ORGANIZATION_KEYS_RESPONSE.asSuccess(), result)
}
@Test
fun `getOrganizationKeys when response is an error should return an error`() = runTest {
server.enqueue(MockResponse().setResponseCode(400))
val result = organizationService.getOrganizationKeys("orgId")
assertTrue(result.isFailure)
}
@Suppress("MaxLineLength")
@Test
fun `getVerifiedOrganizationDomainSsoDetails when response is success should return valid response`() =
runTest {
server.enqueue(
MockResponse()
.setResponseCode(200)
.setBody(ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_JSON),
)
val result =
organizationService.getVerifiedOrganizationDomainSsoDetails("example@bitwarden.com")
assertEquals(ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_RESPONSE.asSuccess(), result)
}
@Suppress("MaxLineLength")
@Test
fun `getVerifiedOrganizationDomainSsoDetails when response is an error should return an error`() =
runTest {
server.enqueue(MockResponse().setResponseCode(400))
val result =
organizationService.getVerifiedOrganizationDomainSsoDetails("example@bitwarden.com")
assertTrue(result.isFailure)
}
}
private const val ORGANIZATION_AUTO_ENROLL_STATUS_JSON = """
{
"id": "orgId",
"resetPasswordEnabled": true
}
"""
private val ORGANIZATION_AUTO_ENROLL_STATUS_RESPONSE = OrganizationAutoEnrollStatusResponseJson(
organizationId = "orgId",
isResetPasswordEnabled = true,
)
private const val ORGANIZATION_DOMAIN_SSO_DETAILS_JSON = """
{
"ssoAvailable": true,
"domainName": "bitwarden.com",
"organizationIdentifier": "Test Org",
"ssoRequired": false,
"verifiedDate": "2024-09-13T00:00:00.000Z"
}
"""
private val ORGANIZATION_DOMAIN_SSO_BODY = OrganizationDomainSsoDetailsResponseJson(
isSsoAvailable = true,
organizationIdentifier = "Test Org",
verifiedDate = ZonedDateTime.parse("2024-09-13T00:00:00.000Z"),
)
private const val ORGANIZATION_KEYS_JSON = """
{
"privateKey": "privateKey",
"publicKey": "publicKey"
}
"""
private val ORGANIZATION_KEYS_RESPONSE = OrganizationKeysResponseJson(
privateKey = "privateKey",
publicKey = "publicKey",
)
private const val ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_JSON = """
{
"data": [
{
"organizationIdentifier": "Test Identifier",
"organizationName": "Bitwarden",
"domainName": "bitwarden.com"
}
]
}
"""
private val ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_RESPONSE =
VerifiedOrganizationDomainSsoDetailsResponse(
verifiedOrganizationDomainSsoDetails = listOf(
VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail(
organizationIdentifier = "Test Identifier",
organizationName = "Bitwarden",
domainName = "bitwarden.com",
),
),
)

View File

@@ -56,6 +56,7 @@ import com.bitwarden.network.service.AccountsService
import com.bitwarden.network.service.DevicesService
import com.bitwarden.network.service.HaveIBeenPwnedService
import com.bitwarden.network.service.IdentityService
import com.bitwarden.network.service.OrganizationService
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.AccountTokensJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.EnvironmentUrlDataJson
@@ -64,7 +65,6 @@ import com.x8bit.bitwarden.data.auth.datasource.disk.model.OnboardingStatus
import com.x8bit.bitwarden.data.auth.datasource.disk.model.PendingAuthRequestJson
import com.x8bit.bitwarden.data.auth.datasource.disk.model.UserStateJson
import com.x8bit.bitwarden.data.auth.datasource.disk.util.FakeAuthDiskSource
import com.x8bit.bitwarden.data.auth.datasource.network.service.OrganizationService
import com.x8bit.bitwarden.data.auth.datasource.sdk.AuthSdkSource
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_0
import com.x8bit.bitwarden.data.auth.datasource.sdk.model.PasswordStrength.LEVEL_1