mirror of
https://github.com/bitwarden/android.git
synced 2026-03-11 12:44:17 -05:00
[PM-31069] Add OrganizationId support for Vault Migration operations (#6397)
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "2835802f9de260f6f5109c81081e9b46",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "organization_events",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `user_id` TEXT NOT NULL, `organization_event_type` TEXT NOT NULL, `cipher_id` TEXT, `date` INTEGER NOT NULL, `organization_id` TEXT)",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "userId",
|
||||
"columnName": "user_id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationEventType",
|
||||
"columnName": "organization_event_type",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "cipherId",
|
||||
"columnName": "cipher_id",
|
||||
"affinity": "TEXT"
|
||||
},
|
||||
{
|
||||
"fieldPath": "date",
|
||||
"columnName": "date",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "organizationId",
|
||||
"columnName": "organization_id",
|
||||
"affinity": "TEXT"
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": true,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [
|
||||
{
|
||||
"name": "index_organization_events_user_id",
|
||||
"unique": false,
|
||||
"columnNames": [
|
||||
"user_id"
|
||||
],
|
||||
"orders": [],
|
||||
"createSql": "CREATE INDEX IF NOT EXISTS `index_organization_events_user_id` ON `${TABLE_NAME}` (`user_id`)"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2835802f9de260f6f5109c81081e9b46')"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ class EventDiskSourceImpl(
|
||||
},
|
||||
cipherId = event.cipherId,
|
||||
date = event.date,
|
||||
organizationId = event.organizationId,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -48,6 +49,7 @@ class EventDiskSourceImpl(
|
||||
},
|
||||
cipherId = it.cipherId,
|
||||
date = it.date,
|
||||
organizationId = it.organizationId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.x8bit.bitwarden.data.platform.datasource.disk.database
|
||||
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.RoomDatabase
|
||||
import androidx.room.TypeConverters
|
||||
@@ -14,8 +15,11 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.convertor.ZonedDateTimeTyp
|
||||
entities = [
|
||||
OrganizationEventEntity::class,
|
||||
],
|
||||
version = 1,
|
||||
version = 2,
|
||||
exportSchema = true,
|
||||
autoMigrations = [
|
||||
AutoMigration(from = 1, to = 2),
|
||||
],
|
||||
)
|
||||
@TypeConverters(ZonedDateTimeTypeConverter::class)
|
||||
abstract class PlatformDatabase : RoomDatabase() {
|
||||
|
||||
@@ -25,4 +25,7 @@ data class OrganizationEventEntity(
|
||||
|
||||
@ColumnInfo(name = "date")
|
||||
val date: ZonedDateTime,
|
||||
|
||||
@ColumnInfo(name = "organization_id")
|
||||
val organizationId: String?,
|
||||
)
|
||||
|
||||
@@ -79,6 +79,7 @@ class OrganizationEventManagerImpl(
|
||||
type = event.type,
|
||||
cipherId = event.cipherId,
|
||||
date = ZonedDateTime.now(clock),
|
||||
organizationId = event.organizationId,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,11 +16,17 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
abstract val cipherId: String?
|
||||
|
||||
/**
|
||||
* The optional organization ID.
|
||||
*/
|
||||
abstract val organizationId: String?
|
||||
|
||||
/**
|
||||
* Tracks when a value is successfully auto-filled
|
||||
*/
|
||||
data class CipherClientAutoFilled(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_AUTO_FILLED
|
||||
@@ -31,6 +37,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientCopiedCardCode(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_COPIED_CARD_CODE
|
||||
@@ -41,6 +48,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientCopiedHiddenField(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_COPIED_HIDDEN_FIELD
|
||||
@@ -51,6 +59,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientCopiedPassword(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_COPIED_PASSWORD
|
||||
@@ -61,6 +70,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientToggledCardCodeVisible(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_CARD_CODE_VISIBLE
|
||||
@@ -71,6 +81,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientToggledCardNumberVisible(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_CARD_NUMBER_VISIBLE
|
||||
@@ -81,6 +92,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientToggledHiddenFieldVisible(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_HIDDEN_FIELD_VISIBLE
|
||||
@@ -91,6 +103,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientToggledPasswordVisible(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_TOGGLED_PASSWORD_VISIBLE
|
||||
@@ -101,6 +114,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data class CipherClientViewed(
|
||||
override val cipherId: String,
|
||||
override val organizationId: String? = null,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.CIPHER_CLIENT_VIEWED
|
||||
@@ -111,6 +125,7 @@ sealed class OrganizationEvent {
|
||||
*/
|
||||
data object UserClientExportedVault : OrganizationEvent() {
|
||||
override val cipherId: String? = null
|
||||
override val organizationId: String? = null
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.USER_CLIENT_EXPORTED_VAULT
|
||||
}
|
||||
@@ -119,8 +134,10 @@ sealed class OrganizationEvent {
|
||||
* Tracks when a user's personal ciphers have been migrated to their organization's My Items
|
||||
* folder as required by the organization's personal vault ownership policy.
|
||||
*/
|
||||
data object ItemOrganizationAccepted : OrganizationEvent() {
|
||||
override val cipherId: String? = null
|
||||
data class ItemOrganizationAccepted(
|
||||
override val cipherId: String? = null,
|
||||
override val organizationId: String,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.ORGANIZATION_ITEM_ORGANIZATION_ACCEPTED
|
||||
}
|
||||
@@ -129,8 +146,10 @@ sealed class OrganizationEvent {
|
||||
* Tracks when a user chooses to leave an organization instead of migrating their personal
|
||||
* ciphers to their organization's My Items folder.
|
||||
*/
|
||||
data object ItemOrganizationDeclined : OrganizationEvent() {
|
||||
override val cipherId: String? = null
|
||||
data class ItemOrganizationDeclined(
|
||||
override val cipherId: String? = null,
|
||||
override val organizationId: String,
|
||||
) : OrganizationEvent() {
|
||||
override val type: OrganizationEventType
|
||||
get() = OrganizationEventType.ORGANIZATION_ITEM_ORGANIZATION_DECLINED
|
||||
}
|
||||
|
||||
@@ -106,7 +106,9 @@ class LeaveOrganizationViewModel @Inject constructor(
|
||||
),
|
||||
)
|
||||
organizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.ItemOrganizationDeclined,
|
||||
event = OrganizationEvent.ItemOrganizationDeclined(
|
||||
organizationId = state.organizationId,
|
||||
),
|
||||
)
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialogState = null)
|
||||
|
||||
@@ -139,7 +139,9 @@ class MigrateToMyItemsViewModel @Inject constructor(
|
||||
when (val result = action.result) {
|
||||
is MigratePersonalVaultResult.Success -> {
|
||||
organizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.ItemOrganizationAccepted,
|
||||
event = OrganizationEvent.ItemOrganizationAccepted(
|
||||
organizationId = state.organizationId,
|
||||
),
|
||||
)
|
||||
clearDialog()
|
||||
sendEvent(MigrateToMyItemsEvent.NavigateToVault)
|
||||
|
||||
@@ -39,6 +39,7 @@ class EventDiskSourceTest {
|
||||
type = OrganizationEventType.CIPHER_DELETED,
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
)
|
||||
|
||||
eventDiskSource.addOrganizationEvent(
|
||||
@@ -54,6 +55,7 @@ class EventDiskSourceTest {
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
),
|
||||
fakeOrganizationEventDao.storedEvents,
|
||||
@@ -73,6 +75,7 @@ class EventDiskSourceTest {
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
OrganizationEventEntity(
|
||||
id = 2,
|
||||
@@ -80,6 +83,7 @@ class EventDiskSourceTest {
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-2",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -94,6 +98,7 @@ class EventDiskSourceTest {
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-2",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
),
|
||||
fakeOrganizationEventDao.storedEvents,
|
||||
@@ -113,6 +118,7 @@ class EventDiskSourceTest {
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
OrganizationEventEntity(
|
||||
id = 2,
|
||||
@@ -120,6 +126,7 @@ class EventDiskSourceTest {
|
||||
organizationEventType = "1102",
|
||||
cipherId = "cipherId-2",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
@@ -132,6 +139,7 @@ class EventDiskSourceTest {
|
||||
type = OrganizationEventType.CIPHER_DELETED,
|
||||
cipherId = "cipherId-1",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
),
|
||||
result,
|
||||
|
||||
@@ -74,6 +74,7 @@ class OrganizationEventManagerTest {
|
||||
type = OrganizationEventType.CIPHER_UPDATED,
|
||||
cipherId = CIPHER_ID,
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
)
|
||||
val events = listOf(organizationEvent)
|
||||
coEvery { eventDiskSource.getOrganizationEvents(userId = USER_ID) } returns events
|
||||
@@ -105,6 +106,7 @@ class OrganizationEventManagerTest {
|
||||
type = OrganizationEventType.CIPHER_UPDATED,
|
||||
cipherId = CIPHER_ID,
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
)
|
||||
val events = listOf(organizationEvent)
|
||||
coEvery { eventDiskSource.getOrganizationEvents(userId = USER_ID) } returns events
|
||||
@@ -209,6 +211,7 @@ class OrganizationEventManagerTest {
|
||||
type = OrganizationEventType.CIPHER_CLIENT_AUTO_FILLED,
|
||||
cipherId = CIPHER_ID,
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -138,7 +138,9 @@ class LeaveOrganizationViewModelTest : BaseViewModelTest() {
|
||||
),
|
||||
)
|
||||
mockOrganizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.ItemOrganizationDeclined,
|
||||
event = OrganizationEvent.ItemOrganizationDeclined(
|
||||
organizationId = ORGANIZATION_ID,
|
||||
),
|
||||
)
|
||||
mockVaultMigrationManager.clearMigrationState()
|
||||
}
|
||||
|
||||
@@ -158,7 +158,9 @@ class MigrateToMyItemsViewModelTest : BaseViewModelTest() {
|
||||
|
||||
verify {
|
||||
mockOrganizationEventManager.trackEvent(
|
||||
event = OrganizationEvent.ItemOrganizationAccepted,
|
||||
event = OrganizationEvent.ItemOrganizationAccepted(
|
||||
organizationId = ORGANIZATION_ID,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user