mirror of
https://github.com/bitwarden/android.git
synced 2026-02-03 18:17:54 -06:00
[PM-31069] Add OrganizationId support for Vault Migration operations (#6397)
This commit is contained in:
parent
2acf429f67
commit
0395d489c2
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,4 +13,5 @@ data class OrganizationEventJson(
|
||||
@SerialName("type") val type: OrganizationEventType,
|
||||
@SerialName("cipherId") val cipherId: String?,
|
||||
@SerialName("date") @Contextual val date: ZonedDateTime,
|
||||
@SerialName("organizationId") val organizationId: String?,
|
||||
)
|
||||
|
||||
@ -35,6 +35,7 @@ class EventServiceTest : BaseServiceTest() {
|
||||
type = OrganizationEventType.CIPHER_CREATED,
|
||||
cipherId = "cipher-id",
|
||||
date = ZonedDateTime.now(fixedClock),
|
||||
organizationId = null,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user