mirror of
https://github.com/bitwarden/android.git
synced 2025-12-12 08:40:49 -06:00
[PM-28468] Added service methods to migration to MyItems validation (#6248)
This commit is contained in:
parent
cd27fe339d
commit
4a874668f2
@ -25,4 +25,10 @@ interface PolicyManager {
|
|||||||
userId: String,
|
userId: String,
|
||||||
type: PolicyTypeJson,
|
type: PolicyTypeJson,
|
||||||
): List<SyncResponseJson.Policy>
|
): List<SyncResponseJson.Policy>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the organization id of the personal ownership policy.
|
||||||
|
* If multiple organizations enforce the policy, return the first to set it.
|
||||||
|
*/
|
||||||
|
fun getPersonalOwnershipPolicyOrganizationId(): String?
|
||||||
}
|
}
|
||||||
|
|||||||
@ -66,6 +66,13 @@ class PolicyManagerImpl(
|
|||||||
)
|
)
|
||||||
.orEmpty()
|
.orEmpty()
|
||||||
|
|
||||||
|
override fun getPersonalOwnershipPolicyOrganizationId(): String? =
|
||||||
|
this
|
||||||
|
.getActivePolicies(PolicyTypeJson.PERSONAL_OWNERSHIP)
|
||||||
|
.sortedBy { it.revisionDate }
|
||||||
|
.firstOrNull()
|
||||||
|
?.organizationId
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A helper method to filter policies.
|
* A helper method to filter policies.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -166,4 +166,11 @@ interface VaultRepository :
|
|||||||
* `null` if the item cannot be found.
|
* `null` if the item cannot be found.
|
||||||
*/
|
*/
|
||||||
fun getVaultListItemStateFlow(itemId: String): StateFlow<DataState<CipherListView?>>
|
fun getVaultListItemStateFlow(itemId: String): StateFlow<DataState<CipherListView?>>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if there are any personal vault items (items without an organization ID) in the vault.
|
||||||
|
*
|
||||||
|
* @return `true` if there are personal vault items, `false` otherwise.
|
||||||
|
*/
|
||||||
|
fun hasPersonalVaultItems(): Boolean
|
||||||
}
|
}
|
||||||
|
|||||||
@ -550,4 +550,9 @@ class VaultRepositoryImpl(
|
|||||||
organizationKeys = organizationKeys,
|
organizationKeys = organizationKeys,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun hasPersonalVaultItems(): Boolean {
|
||||||
|
val vaultData = vaultSyncManager.vaultDataStateFlow.value.data ?: return false
|
||||||
|
return vaultData.decryptCipherListResult.successes.any { it.organizationId.isNullOrEmpty() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,11 @@ import io.mockk.mockk
|
|||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
|
import org.junit.jupiter.api.Assertions.assertNull
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.BeforeEach
|
import org.junit.jupiter.api.BeforeEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
class PolicyManagerTest {
|
class PolicyManagerTest {
|
||||||
private val mutableUserStateFlow = MutableStateFlow<UserStateJson?>(null)
|
private val mutableUserStateFlow = MutableStateFlow<UserStateJson?>(null)
|
||||||
@ -277,6 +279,207 @@ class PolicyManagerTest {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPersonalOwnershipPolicyOrganizationId returns null when no active user`() {
|
||||||
|
every { authDiskSource.userState } returns null
|
||||||
|
|
||||||
|
assertNull(policyManager.getPersonalOwnershipPolicyOrganizationId())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPersonalOwnershipPolicyOrganizationId returns null when no policies exist`() {
|
||||||
|
val userState: UserStateJson = mockk {
|
||||||
|
every { activeUserId } returns USER_ID
|
||||||
|
}
|
||||||
|
every { authDiskSource.userState } returns userState
|
||||||
|
every { authDiskSource.getOrganizations(USER_ID) } returns emptyList()
|
||||||
|
every { authDiskSource.getPolicies(USER_ID) } returns emptyList()
|
||||||
|
|
||||||
|
assertNull(policyManager.getPersonalOwnershipPolicyOrganizationId())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPersonalOwnershipPolicyOrganizationId returns null when policy is disabled`() {
|
||||||
|
val userState: UserStateJson = mockk {
|
||||||
|
every { activeUserId } returns USER_ID
|
||||||
|
}
|
||||||
|
every { authDiskSource.userState } returns userState
|
||||||
|
every {
|
||||||
|
authDiskSource.getOrganizations(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockOrganization(
|
||||||
|
number = 1,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = true,
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
authDiskSource.getPolicies(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockPolicy(
|
||||||
|
organizationId = "mockId-1",
|
||||||
|
isEnabled = false,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertNull(policyManager.getPersonalOwnershipPolicyOrganizationId())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getPersonalOwnershipPolicyOrganizationId returns organization id for single policy`() {
|
||||||
|
val userState: UserStateJson = mockk {
|
||||||
|
every { activeUserId } returns USER_ID
|
||||||
|
}
|
||||||
|
val expectedOrganizationId = "mockId-1"
|
||||||
|
every { authDiskSource.userState } returns userState
|
||||||
|
every {
|
||||||
|
authDiskSource.getOrganizations(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockOrganization(
|
||||||
|
number = 1,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = true,
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
authDiskSource.getPolicies(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockPolicy(
|
||||||
|
organizationId = expectedOrganizationId,
|
||||||
|
isEnabled = true,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
expectedOrganizationId,
|
||||||
|
policyManager.getPersonalOwnershipPolicyOrganizationId(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `getPersonalOwnershipPolicyOrganizationId returns earliest policy when multiple orgs have policy`() {
|
||||||
|
val userState: UserStateJson = mockk {
|
||||||
|
every { activeUserId } returns USER_ID
|
||||||
|
}
|
||||||
|
val earliestRevisionDate = ZonedDateTime.parse("2024-01-01T00:00:00Z")
|
||||||
|
val middleRevisionDate = ZonedDateTime.parse("2024-06-01T00:00:00Z")
|
||||||
|
val latestRevisionDate = ZonedDateTime.parse("2024-12-01T00:00:00Z")
|
||||||
|
|
||||||
|
val expectedOrganizationId = "mockId-1"
|
||||||
|
|
||||||
|
every { authDiskSource.userState } returns userState
|
||||||
|
every {
|
||||||
|
authDiskSource.getOrganizations(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockOrganization(
|
||||||
|
number = 1,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = true,
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
createMockOrganization(
|
||||||
|
number = 2,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = true,
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
createMockOrganization(
|
||||||
|
number = 3,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = true,
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
authDiskSource.getPolicies(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockPolicy(
|
||||||
|
number = 3,
|
||||||
|
organizationId = "mockId-3",
|
||||||
|
isEnabled = true,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
revisionDate = latestRevisionDate,
|
||||||
|
),
|
||||||
|
createMockPolicy(
|
||||||
|
number = 1,
|
||||||
|
organizationId = expectedOrganizationId,
|
||||||
|
isEnabled = true,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
revisionDate = earliestRevisionDate,
|
||||||
|
),
|
||||||
|
createMockPolicy(
|
||||||
|
number = 2,
|
||||||
|
organizationId = "mockId-2",
|
||||||
|
isEnabled = true,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
revisionDate = middleRevisionDate,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
expectedOrganizationId,
|
||||||
|
policyManager.getPersonalOwnershipPolicyOrganizationId(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `getPersonalOwnershipPolicyOrganizationId filters out policies from organizations not using policies`() {
|
||||||
|
val userState: UserStateJson = mockk {
|
||||||
|
every { activeUserId } returns USER_ID
|
||||||
|
}
|
||||||
|
val earlierRevisionDate = ZonedDateTime.parse("2024-01-01T00:00:00Z")
|
||||||
|
val laterRevisionDate = ZonedDateTime.parse("2024-06-01T00:00:00Z")
|
||||||
|
val expectedOrganizationId = "mockId-2"
|
||||||
|
|
||||||
|
every { authDiskSource.userState } returns userState
|
||||||
|
every {
|
||||||
|
authDiskSource.getOrganizations(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockOrganization(
|
||||||
|
number = 1,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = false, // This org does NOT use policies
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
createMockOrganization(
|
||||||
|
number = 2,
|
||||||
|
isEnabled = true,
|
||||||
|
shouldUsePolicies = true, // This org uses policies
|
||||||
|
type = OrganizationType.USER,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
every {
|
||||||
|
authDiskSource.getPolicies(USER_ID)
|
||||||
|
} returns listOf(
|
||||||
|
createMockPolicy(
|
||||||
|
number = 1,
|
||||||
|
organizationId = "mockId-1",
|
||||||
|
isEnabled = true,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
revisionDate = earlierRevisionDate, // Earlier but org doesn't enforce
|
||||||
|
),
|
||||||
|
createMockPolicy(
|
||||||
|
number = 2,
|
||||||
|
organizationId = expectedOrganizationId,
|
||||||
|
isEnabled = true,
|
||||||
|
type = PolicyTypeJson.PERSONAL_OWNERSHIP,
|
||||||
|
revisionDate = laterRevisionDate,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
// Should return mockId-2 because mockId-1's organization doesn't enforce policies
|
||||||
|
assertEquals(
|
||||||
|
expectedOrganizationId,
|
||||||
|
policyManager.getPersonalOwnershipPolicyOrganizationId(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val USER_ID = "userId"
|
private const val USER_ID = "userId"
|
||||||
|
|||||||
@ -1390,6 +1390,100 @@ class VaultRepositoryTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hasPersonalVaultItems returns false when vault data is loading`() {
|
||||||
|
mutableVaultDataStateFlow.value = DataState.Loading
|
||||||
|
|
||||||
|
val result = vaultRepository.hasPersonalVaultItems()
|
||||||
|
|
||||||
|
assertEquals(false, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hasPersonalVaultItems returns false when all items belong to organizations`() {
|
||||||
|
mutableVaultDataStateFlow.value = DataState.Loaded(
|
||||||
|
data = VaultData(
|
||||||
|
decryptCipherListResult = DecryptCipherListResult(
|
||||||
|
successes = listOf(
|
||||||
|
createMockCipherListView(number = 1, organizationId = "org-1"),
|
||||||
|
createMockCipherListView(number = 2, organizationId = "org-2"),
|
||||||
|
),
|
||||||
|
failures = emptyList(),
|
||||||
|
),
|
||||||
|
collectionViewList = emptyList(),
|
||||||
|
folderViewList = emptyList(),
|
||||||
|
sendViewList = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = vaultRepository.hasPersonalVaultItems()
|
||||||
|
|
||||||
|
assertEquals(false, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hasPersonalVaultItems returns true when there are items without organization ID`() {
|
||||||
|
mutableVaultDataStateFlow.value = DataState.Loaded(
|
||||||
|
data = VaultData(
|
||||||
|
decryptCipherListResult = DecryptCipherListResult(
|
||||||
|
successes = listOf(
|
||||||
|
createMockCipherListView(number = 1, organizationId = null),
|
||||||
|
createMockCipherListView(number = 2, organizationId = "org-2"),
|
||||||
|
),
|
||||||
|
failures = emptyList(),
|
||||||
|
),
|
||||||
|
collectionViewList = emptyList(),
|
||||||
|
folderViewList = emptyList(),
|
||||||
|
sendViewList = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = vaultRepository.hasPersonalVaultItems()
|
||||||
|
|
||||||
|
assertEquals(true, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hasPersonalVaultItems returns true when there are items with empty organization ID`() {
|
||||||
|
mutableVaultDataStateFlow.value = DataState.Loaded(
|
||||||
|
data = VaultData(
|
||||||
|
decryptCipherListResult = DecryptCipherListResult(
|
||||||
|
successes = listOf(
|
||||||
|
createMockCipherListView(number = 1, organizationId = ""),
|
||||||
|
createMockCipherListView(number = 2, organizationId = "org-2"),
|
||||||
|
),
|
||||||
|
failures = emptyList(),
|
||||||
|
),
|
||||||
|
collectionViewList = emptyList(),
|
||||||
|
folderViewList = emptyList(),
|
||||||
|
sendViewList = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = vaultRepository.hasPersonalVaultItems()
|
||||||
|
|
||||||
|
assertEquals(true, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `hasPersonalVaultItems returns false when successes list is empty`() {
|
||||||
|
mutableVaultDataStateFlow.value = DataState.Loaded(
|
||||||
|
data = VaultData(
|
||||||
|
decryptCipherListResult = DecryptCipherListResult(
|
||||||
|
successes = emptyList(),
|
||||||
|
failures = emptyList(),
|
||||||
|
),
|
||||||
|
collectionViewList = emptyList(),
|
||||||
|
folderViewList = emptyList(),
|
||||||
|
sendViewList = emptyList(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = vaultRepository.hasPersonalVaultItems()
|
||||||
|
|
||||||
|
assertEquals(false, result)
|
||||||
|
}
|
||||||
|
|
||||||
//region Helper functions
|
//region Helper functions
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user