Resolve compile breaks from the organization SDK model changes

`Repositories` gained an `organization` field (bitwarden/sdk-internal#1588).
Android has no SDK-managed organization repository, so this is passed as
`null` like the other repositories the client does not manage.

`ProfileOrganization` was restructured from a flat record into nested
`details`/`plan`/`membership`/`collectionManagement`/`provider`/`sso`/
`familySponsorship` records (bitwarden/sdk-internal#1588 and
bitwarden/sdk-internal#1608). Every flat field in the sync response mapping
moves to its new home; `userIsManagedByOrganization` is now
`membership.userIsClaimedByOrganization`.

Two new plan fields, `usePam` and `useInviteLinks`, have no counterpart in
`SyncResponseJson.Profile.Organization` and are passed as `false`.
`OrganizationMembership.userId` is no longer optional, so organizations
without a user ID are now filtered out alongside those without a name.
This commit is contained in:
bw-ghapp[bot] committed 2026-10-16 19:02:14 +00:00
1 parent 9ba89747d3
commit c9dc93eb6d
4 files changed
+172 -101

No files matched your search

@@ -7,10 +7,17 @@ import com.bitwarden.network.model.OrganizationType
import com.bitwarden.network.model.ProductTierType
import com.bitwarden.network.model.ProviderType
import com.bitwarden.network.model.SyncResponseJson
import com.bitwarden.organizations.CollectionManagementSettings
import com.bitwarden.organizations.FamilySponsorship
import com.bitwarden.organizations.OrganizationDetails
import com.bitwarden.organizations.OrganizationMembership
import com.bitwarden.organizations.OrganizationPlan
import com.bitwarden.organizations.OrganizationProvider
import com.bitwarden.organizations.OrganizationUserStatusType
import com.bitwarden.organizations.OrganizationUserType
import com.bitwarden.organizations.Permissions
import com.bitwarden.organizations.ProfileOrganization
import com.bitwarden.organizations.SsoSettings
import com.bitwarden.policies.Policy
import com.bitwarden.policies.PolicyType
import com.x8bit.bitwarden.data.auth.repository.model.Organization
@@ -61,17 +68,28 @@ fun List<SyncResponseJson.Profile.Organization>.toSdkProfileOrganizations(): Lis
/**
* Maps the given [SyncResponseJson.Profile.Organization] to a [ProfileOrganization] or `null` if
* the [SyncResponseJson.Profile.Organization.name] is not present.
* the [SyncResponseJson.Profile.Organization.name] or
* [SyncResponseJson.Profile.Organization.userId] is not present.
*/
@Suppress("LongMethod")
private fun SyncResponseJson.Profile.Organization.toSdkProfileOrganization(): ProfileOrganization? =
this.name?.let {
ProfileOrganization(
private fun SyncResponseJson.Profile.Organization.toSdkProfileOrganization(): ProfileOrganization? {
val organizationName = this.name ?: return null
// The SDK requires a non-null user ID, so an organization without one cannot be mapped.
val memberUserId = this.userId ?: return null
return ProfileOrganization(
details = OrganizationDetails(
id = this.id,
name = it,
status = this.status.toSdkOrganizationUserStatusType(),
type = this.type.toSdkOrganizationUserType(),
name = organizationName,
enabled = this.isEnabled,
hasPublicAndPrivateKeys = this.hasPublicAndPrivateKeys,
),
plan = OrganizationPlan(
productTierType = this.productTierType.toSdkProductTierType(),
seats = this.seats,
maxCollections = this.maxCollections,
maxStorageGb = this.maxStorageGb,
selfHost = this.isSelfHost,
usersGetPremium = this.shouldUsersGetPremium,
usePolicies = this.shouldUsePolicies,
useGroups = this.shouldUseGroups,
useDirectory = this.shouldUseDirectory,
@@ -87,49 +105,63 @@ private fun SyncResponseJson.Profile.Organization.toSdkProfileOrganization(): Pr
useResetPassword = this.useResetPassword,
useSecretsManager = this.useSecretsManager,
usePasswordManager = this.usePasswordManager,
// Not provided by the sync response.
usePam = false,
useActivateAutofillPolicy = this.useActivateAutofillPolicy,
useAutomaticUserConfirmation = this.useAutomaticUserConfirmation,
selfHost = this.isSelfHost,
usersGetPremium = this.shouldUsersGetPremium,
seats = this.seats,
maxCollections = this.maxCollections,
maxStorageGb = this.maxStorageGb,
ssoBound = this.ssoBound,
identifier = this.identifier,
useAccessIntelligence = this.useAccessIntelligence,
useAdminSponsoredFamilies = this.useAdminSponsoredFamilies,
useDisableSmAdsForUsers = this.useDisableSmAdsForUsers,
usePhishingBlocker = this.usePhishingBlocker,
useMyItems = this.useMyItems,
// Not provided by the sync response.
useInviteLinks = false,
),
membership = OrganizationMembership(
status = this.status.toSdkOrganizationUserStatusType(),
type = this.type.toSdkOrganizationUserType(),
permissions = this.permissions.toSdkPermissions(),
resetPasswordEnrolled = this.resetPasswordEnrolled,
userId = this.userId,
userId = memberUserId,
organizationUserId = this.organizationUserId,
hasPublicAndPrivateKeys = this.hasPublicAndPrivateKeys,
providerId = this.providerId,
providerName = this.providerName,
providerType = this.providerType?.toSdkProviderType(),
isProviderUser = this.isProviderUser,
isMember = this.isMember,
familySponsorshipFriendlyName = this.familySponsorshipFriendlyName,
familySponsorshipAvailable = this.familySponsorshipAvailable,
productTierType = this.productTierType.toSdkProductTierType(),
keyConnectorEnabled = this.isKeyConnectorEnabled,
keyConnectorUrl = this.keyConnectorUrl,
familySponsorshipLastSyncDate = this.familySponsorshipLastSyncDate,
familySponsorshipValidUntil = this.familySponsorshipValidUntil,
familySponsorshipToDelete = this.familySponsorshipToDelete,
isProviderUser = this.isProviderUser,
resetPasswordEnrolled = this.resetPasswordEnrolled,
ssoBound = this.ssoBound,
userIsClaimedByOrganization = this.userIsClaimedByOrganization,
accessSecretsManager = this.accessSecretsManager,
),
collectionManagement = CollectionManagementSettings(
limitCollectionCreation = this.limitCollectionCreation,
limitCollectionDeletion = this.limitCollectionDeletion,
limitItemDeletion = this.limitItemDeletion,
allowAdminAccessToAllCollectionItems = this.allowAdminAccessToAllCollectionItems,
userIsManagedByOrganization = this.userIsClaimedByOrganization,
useAccessIntelligence = this.useAccessIntelligence,
useAdminSponsoredFamilies = this.useAdminSponsoredFamilies,
useDisableSmAdsForUsers = this.useDisableSmAdsForUsers,
),
provider = this.providerId?.let { providerId ->
this.providerType?.let { providerType ->
OrganizationProvider(
id = providerId,
type = providerType.toSdkProviderType(),
name = this.providerName,
)
}
},
sso = SsoSettings(
enabled = this.ssoEnabled,
identifier = this.identifier,
memberDecryptionType = this.ssoMemberDecryptionType?.toSdkMemberDecryptionType(),
keyConnectorEnabled = this.isKeyConnectorEnabled,
keyConnectorUrl = this.keyConnectorUrl,
),
familySponsorship = FamilySponsorship(
available = this.familySponsorshipAvailable,
friendlyName = this.familySponsorshipFriendlyName,
validUntil = this.familySponsorshipValidUntil,
lastSyncDate = this.familySponsorshipLastSyncDate,
toDelete = this.familySponsorshipToDelete,
isAdminInitiated = this.isAdminInitiated,
ssoEnabled = this.ssoEnabled,
ssoMemberDecryptionType = this.ssoMemberDecryptionType?.toSdkMemberDecryptionType(),
usePhishingBlocker = this.usePhishingBlocker,
useMyItems = this.useMyItems,
)
}
),
)
}
/**
* Convert the JSON data of the [Policy] object into [PolicyInformation] data.
@@ -42,6 +42,7 @@ class SdkRepositoryFactoryImpl(
localUserDataKeyState = SdkLocalUserDataKeyStateRepository(
authDiskSource = authDiskSource,
),
organization = null,
organizationSharedKey = null,
policy = null,
send = null,
@@ -2,13 +2,20 @@
package com.x8bit.bitwarden.data.auth.repository.model
import com.bitwarden.organizations.CollectionManagementSettings
import com.bitwarden.organizations.FamilySponsorship
import com.bitwarden.organizations.MemberDecryptionType
import com.bitwarden.organizations.OrganizationDetails
import com.bitwarden.organizations.OrganizationMembership
import com.bitwarden.organizations.OrganizationPlan
import com.bitwarden.organizations.OrganizationProvider
import com.bitwarden.organizations.OrganizationUserStatusType
import com.bitwarden.organizations.OrganizationUserType
import com.bitwarden.organizations.Permissions
import com.bitwarden.organizations.ProductTierType
import com.bitwarden.organizations.ProfileOrganization
import com.bitwarden.organizations.ProviderType
import com.bitwarden.organizations.SsoSettings
import java.time.Instant
/**
@@ -37,7 +44,7 @@ fun createMockSdkProfileOrganization(
identifier: String? = "mockIdentifier-$number",
use2fa: Boolean = false,
familySponsorshipToDelete: Boolean? = false,
userId: String? = "mockUserId-$number",
userId: String = "mockUserId-$number",
shouldUseEvents: Boolean = false,
familySponsorshipFriendlyName: String? = "mockFamilySponsorshipFriendlyName-$number",
useTotp: Boolean = false,
@@ -78,69 +85,88 @@ fun createMockSdkProfileOrganization(
ssoMemberDecryptionType: MemberDecryptionType? = null,
usePhishingBlocker: Boolean = false,
useMyItems: Boolean = false,
usePam: Boolean = false,
useInviteLinks: Boolean = false,
): ProfileOrganization =
ProfileOrganization(
id = id,
name = name,
status = status,
type = type,
enabled = enabled,
usePolicies = usePolicies,
useGroups = useGroups,
useDirectory = useDirectory,
useEvents = shouldUseEvents,
useTotp = useTotp,
use2fa = use2fa,
useApi = useApi,
useSso = useSso,
useOrganizationDomains = useOrganizationDomains,
useKeyConnector = shouldUseKeyConnector,
useScim = useScim,
useCustomPermissions = useCustomPermissions,
useResetPassword = useResetPassword,
useSecretsManager = useSecretsManager,
usePasswordManager = usePasswordManager,
useActivateAutofillPolicy = useActivateAutofillPolicy,
useAutomaticUserConfirmation = useAutomaticUserConfirmation,
selfHost = selfHost,
usersGetPremium = usersGetPremium,
seats = seats,
maxCollections = maxCollections,
maxStorageGb = maxStorageGb,
ssoBound = ssoBound,
identifier = identifier,
permissions = permissions,
resetPasswordEnrolled = resetPasswordEnrolled,
userId = userId,
organizationUserId = organizationUserId,
hasPublicAndPrivateKeys = hasPublicAndPrivateKeys,
providerId = providerId,
providerName = providerName,
providerType = providerType,
isProviderUser = isProviderUser,
isMember = isMember,
familySponsorshipFriendlyName = familySponsorshipFriendlyName,
familySponsorshipAvailable = familySponsorshipAvailable,
productTierType = productTierType,
keyConnectorEnabled = keyConnectorEnabled,
keyConnectorUrl = keyConnectorUrl,
familySponsorshipLastSyncDate = familySponsorshipLastSyncDate,
familySponsorshipValidUntil = familySponsorshipValidUntil,
familySponsorshipToDelete = familySponsorshipToDelete,
accessSecretsManager = accessSecretsManager,
limitCollectionCreation = limitCollectionCreation,
limitCollectionDeletion = limitCollectionDeletion,
limitItemDeletion = limitItemDeletion,
allowAdminAccessToAllCollectionItems = allowAdminAccessToAllCollectionItems,
userIsManagedByOrganization = userIsClaimedByOrganization,
useAccessIntelligence = useAccessIntelligence,
useAdminSponsoredFamilies = useAdminSponsoredFamilies,
useDisableSmAdsForUsers = useDisableSmAdsForUsers,
isAdminInitiated = isAdminInitiated,
ssoEnabled = ssoEnabled,
ssoMemberDecryptionType = ssoMemberDecryptionType,
usePhishingBlocker = usePhishingBlocker,
useMyItems = useMyItems,
details = OrganizationDetails(
id = id,
name = name,
enabled = enabled,
hasPublicAndPrivateKeys = hasPublicAndPrivateKeys,
),
plan = OrganizationPlan(
productTierType = productTierType,
seats = seats,
maxCollections = maxCollections,
maxStorageGb = maxStorageGb,
selfHost = selfHost,
usersGetPremium = usersGetPremium,
usePolicies = usePolicies,
useGroups = useGroups,
useDirectory = useDirectory,
useEvents = shouldUseEvents,
useTotp = useTotp,
use2fa = use2fa,
useApi = useApi,
useSso = useSso,
useOrganizationDomains = useOrganizationDomains,
useKeyConnector = shouldUseKeyConnector,
useScim = useScim,
useCustomPermissions = useCustomPermissions,
useResetPassword = useResetPassword,
useSecretsManager = useSecretsManager,
usePasswordManager = usePasswordManager,
usePam = usePam,
useActivateAutofillPolicy = useActivateAutofillPolicy,
useAutomaticUserConfirmation = useAutomaticUserConfirmation,
useAccessIntelligence = useAccessIntelligence,
useAdminSponsoredFamilies = useAdminSponsoredFamilies,
useDisableSmAdsForUsers = useDisableSmAdsForUsers,
usePhishingBlocker = usePhishingBlocker,
useMyItems = useMyItems,
useInviteLinks = useInviteLinks,
),
membership = OrganizationMembership(
status = status,
type = type,
permissions = permissions,
userId = userId,
organizationUserId = organizationUserId,
isMember = isMember,
isProviderUser = isProviderUser,
resetPasswordEnrolled = resetPasswordEnrolled,
ssoBound = ssoBound,
userIsClaimedByOrganization = userIsClaimedByOrganization,
accessSecretsManager = accessSecretsManager,
),
collectionManagement = CollectionManagementSettings(
limitCollectionCreation = limitCollectionCreation,
limitCollectionDeletion = limitCollectionDeletion,
limitItemDeletion = limitItemDeletion,
allowAdminAccessToAllCollectionItems = allowAdminAccessToAllCollectionItems,
),
provider = providerId
?.let { id ->
providerType?.let { type ->
OrganizationProvider(id = id, type = type, name = providerName)
}
},
sso = SsoSettings(
enabled = ssoEnabled,
identifier = identifier,
memberDecryptionType = ssoMemberDecryptionType,
keyConnectorEnabled = keyConnectorEnabled,
keyConnectorUrl = keyConnectorUrl,
),
familySponsorship = FamilySponsorship(
available = familySponsorshipAvailable,
friendlyName = familySponsorshipFriendlyName,
validUntil = familySponsorshipValidUntil,
lastSyncDate = familySponsorshipLastSyncDate,
toDelete = familySponsorshipToDelete,
isAdminInitiated = isAdminInitiated,
),
)
/**
@@ -87,6 +87,18 @@ class SyncResponseJsonExtensionsTest {
)
}
@Test
fun `toSdkProfileOrganizations should filter out organizations with null user IDs`() {
assertEquals(
listOf(createMockSdkProfileOrganization(number = 1)),
listOf(
createMockOrganizationNetwork(number = 1),
createMockOrganizationNetwork(number = 2, userId = null),
)
.toSdkProfileOrganizations(),
)
}
@Suppress("MaxLineLength")
@Test
fun `toSdkProfileOrganizations should return empty list when all organizations have null names`() {