PM-37911: Feat: Update Organization model (#6960)

This commit is contained in:
David Perez
2026-05-29 13:07:10 -05:00
committed by GitHub
parent aca9949874
commit a3bcff9463
15 changed files with 949 additions and 67 deletions

View File

@@ -1057,7 +1057,7 @@ class AuthRepositoryImpl(
?: return RemovePasswordResult.Error(error = MissingPropertyException("User Key"))
val keyConnectorUrl = organizations
.find {
it.shouldUseKeyConnector &&
it.isKeyConnectorEnabled &&
it.role != OrganizationType.OWNER &&
it.role != OrganizationType.ADMIN
}

View File

@@ -9,7 +9,7 @@ import com.bitwarden.network.model.OrganizationType
* @property name The name of the organization (if applicable).
* @property shouldManageResetPassword Indicates that this user has the permission to manage their
* own password.
* @property shouldUseKeyConnector Indicates that the organization uses a key connector.
* @property isKeyConnectorEnabled Indicates that the organization uses a key connector.
* @property role The user's role in the organization.
* @property keyConnectorUrl The key connector domain (if applicable).
* @property userIsClaimedByOrganization Indicates that the user is claimed by the organization.
@@ -20,7 +20,7 @@ data class Organization(
val id: String,
val name: String,
val shouldManageResetPassword: Boolean,
val shouldUseKeyConnector: Boolean,
val isKeyConnectorEnabled: Boolean,
val role: OrganizationType,
val keyConnectorUrl: String?,
val userIsClaimedByOrganization: Boolean,

View File

@@ -1,12 +1,24 @@
package com.x8bit.bitwarden.data.auth.repository.util
import com.bitwarden.core.data.util.decodeFromStringOrNull
import com.bitwarden.network.model.MemberDecryptionType
import com.bitwarden.network.model.OrganizationStatusType
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.OrganizationUserStatusType
import com.bitwarden.organizations.OrganizationUserType
import com.bitwarden.organizations.Permissions
import com.bitwarden.organizations.ProfileOrganization
import com.bitwarden.policies.PolicyType
import com.bitwarden.policies.PolicyView
import com.x8bit.bitwarden.data.auth.repository.model.Organization
import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation
import kotlinx.serialization.json.Json
import com.bitwarden.organizations.MemberDecryptionType as SdkMemberDecryptionType
import com.bitwarden.organizations.ProductTierType as SdkProductTierType
import com.bitwarden.organizations.ProviderType as SdkProviderType
private val JSON = Json {
ignoreUnknownKeys = true
@@ -22,7 +34,7 @@ fun SyncResponseJson.Profile.Organization.toOrganization(): Organization? =
Organization(
id = this.id,
name = it,
shouldUseKeyConnector = this.shouldUseKeyConnector,
isKeyConnectorEnabled = this.isKeyConnectorEnabled,
role = this.type,
shouldManageResetPassword = this.permissions.shouldManageResetPassword,
keyConnectorUrl = this.keyConnectorUrl,
@@ -39,6 +51,86 @@ fun SyncResponseJson.Profile.Organization.toOrganization(): Organization? =
fun List<SyncResponseJson.Profile.Organization>.toOrganizations(): List<Organization> =
this.mapNotNull { it.toOrganization() }
/**
* Maps the given list of [SyncResponseJson.Profile.Organization] to a list of
* [ProfileOrganization]s.
*/
@Suppress("MaxLineLength")
fun List<SyncResponseJson.Profile.Organization>.toSdkProfileOrganizations(): List<ProfileOrganization> =
this.mapNotNull { it.toSdkProfileOrganization() }
/**
* Maps the given [SyncResponseJson.Profile.Organization] to a [ProfileOrganization] or `null` if
* the [SyncResponseJson.Profile.Organization.name] is not present.
*/
@Suppress("LongMethod")
private fun SyncResponseJson.Profile.Organization.toSdkProfileOrganization(): ProfileOrganization? =
this.name?.let {
ProfileOrganization(
id = this.id,
name = it,
status = this.status.toSdkOrganizationUserStatusType(),
type = this.type.toSdkOrganizationUserType(),
enabled = this.isEnabled,
usePolicies = this.shouldUsePolicies,
useGroups = this.shouldUseGroups,
useDirectory = this.shouldUseDirectory,
useEvents = this.shouldUseEvents,
useTotp = this.shouldUseTotp,
use2fa = this.use2fa,
useApi = this.shouldUseApi,
useSso = this.useSso,
useOrganizationDomains = this.useOrganizationDomains,
useKeyConnector = this.shouldUseKeyConnector,
useScim = this.useScim,
useCustomPermissions = this.useCustomPermissions,
useResetPassword = this.useResetPassword,
useSecretsManager = this.useSecretsManager,
usePasswordManager = this.usePasswordManager,
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,
permissions = this.permissions.toSdkPermissions(),
resetPasswordEnrolled = this.resetPasswordEnrolled,
userId = this.userId,
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,
accessSecretsManager = this.accessSecretsManager,
limitCollectionCreation = this.limitCollectionCreation,
limitCollectionDeletion = this.limitCollectionDeletion,
limitItemDeletion = this.limitItemDeletion,
allowAdminAccessToAllCollectionItems = this.allowAdminAccessToAllCollectionItems,
userIsManagedByOrganization = this.userIsClaimedByOrganization,
useAccessIntelligence = this.useAccessIntelligence,
useAdminSponsoredFamilies = this.useAdminSponsoredFamilies,
useDisableSmAdsForUsers = this.useDisableSmAdsForUsers,
isAdminInitiated = this.isAdminInitiated,
ssoEnabled = this.ssoEnabled,
ssoMemberDecryptionType = this.ssoMemberDecryptionType?.toSdkMemberDecryptionType(),
usePhishingBlocker = this.usePhishingBlocker,
useMyItems = this.useMyItems,
)
}
/**
* Convert the JSON data of the [PolicyView] object into [PolicyInformation] data.
*/
@@ -64,3 +156,60 @@ val PolicyView.policyInformation: PolicyInformation?
else -> null
}
}
private fun SyncResponseJson.Profile.Permissions.toSdkPermissions(): Permissions =
Permissions(
accessEventLogs = this.accessEventLogs,
accessImportExport = this.accessImportExport,
accessReports = this.accessReports,
createNewCollections = this.createNewCollections,
editAnyCollection = this.editAnyCollection,
deleteAnyCollection = this.deleteAnyCollection,
manageGroups = this.manageGroups,
manageSso = this.manageSso,
managePolicies = this.shouldManagePolicies,
manageUsers = this.manageUsers,
manageResetPassword = this.shouldManageResetPassword,
manageScim = this.manageScim,
)
private fun OrganizationStatusType.toSdkOrganizationUserStatusType(): OrganizationUserStatusType =
when (this) {
OrganizationStatusType.REVOKED -> OrganizationUserStatusType.REVOKED
OrganizationStatusType.INVITED -> OrganizationUserStatusType.INVITED
OrganizationStatusType.ACCEPTED -> OrganizationUserStatusType.ACCEPTED
OrganizationStatusType.CONFIRMED -> OrganizationUserStatusType.CONFIRMED
}
private fun OrganizationType.toSdkOrganizationUserType(): OrganizationUserType =
when (this) {
OrganizationType.OWNER -> OrganizationUserType.OWNER
OrganizationType.ADMIN -> OrganizationUserType.ADMIN
OrganizationType.USER -> OrganizationUserType.USER
OrganizationType.CUSTOM -> OrganizationUserType.CUSTOM
}
private fun ProviderType.toSdkProviderType(): SdkProviderType =
when (this) {
ProviderType.MSP -> SdkProviderType.MSP
ProviderType.RESELLER -> SdkProviderType.RESELLER
ProviderType.BUSINESS_UNIT -> SdkProviderType.BUSINESS_UNIT
}
private fun ProductTierType.toSdkProductTierType(): SdkProductTierType =
when (this) {
ProductTierType.FREE -> SdkProductTierType.FREE
ProductTierType.FAMILIES -> SdkProductTierType.FAMILIES
ProductTierType.TEAMS -> SdkProductTierType.TEAMS
ProductTierType.ENTERPRISE -> SdkProductTierType.ENTERPRISE
ProductTierType.TEAMS_STARTER -> SdkProductTierType.TEAMS_STARTER
}
private fun MemberDecryptionType.toSdkMemberDecryptionType(): SdkMemberDecryptionType =
when (this) {
MemberDecryptionType.MASTER_PASSWORD -> SdkMemberDecryptionType.MASTER_PASSWORD
MemberDecryptionType.KEY_CONNECTOR -> SdkMemberDecryptionType.KEY_CONNECTOR
MemberDecryptionType.TRUSTED_DEVICE_ENCRYPTION -> {
SdkMemberDecryptionType.TRUSTED_DEVICE_ENCRYPTION
}
}

View File

@@ -32,7 +32,7 @@ class RemovePasswordViewModel @Inject constructor(
val org = authRepository.userStateFlow.value
?.activeAccount
?.organizations
?.firstOrNull { it.shouldUseKeyConnector }
?.firstOrNull { it.isKeyConnectorEnabled }
RemovePasswordState(
input = "",

View File

@@ -268,7 +268,7 @@ class RootNavViewModel @Inject constructor(
?.let(::parseJwtTokenDataOrNull)
?.isExternal == true
val usesKeyConnectorAndNotAdmin = this.activeAccount.organizations.any {
it.shouldUseKeyConnector &&
it.isKeyConnectorEnabled &&
it.role != OrganizationType.OWNER &&
it.role != OrganizationType.ADMIN
}

View File

@@ -5383,7 +5383,7 @@ class AuthRepositoryTest {
val organizations = listOf(
createMockOrganizationNetwork(
number = 1,
shouldUseKeyConnector = true,
isKeyConnectorEnabled = true,
type = OrganizationType.USER,
keyConnectorUrl = null,
),
@@ -5408,7 +5408,7 @@ class AuthRepositoryTest {
val organizations = listOf(
createMockOrganizationNetwork(
number = 1,
shouldUseKeyConnector = true,
isKeyConnectorEnabled = true,
type = OrganizationType.USER,
keyConnectorUrl = url,
),
@@ -5441,7 +5441,7 @@ class AuthRepositoryTest {
val organizations = listOf(
createMockOrganizationNetwork(
number = 1,
shouldUseKeyConnector = true,
isKeyConnectorEnabled = true,
type = OrganizationType.USER,
keyConnectorUrl = url,
),
@@ -5477,7 +5477,7 @@ class AuthRepositoryTest {
val organizations = listOf(
createMockOrganizationNetwork(
number = 1,
shouldUseKeyConnector = true,
isKeyConnectorEnabled = true,
type = OrganizationType.USER,
keyConnectorUrl = url,
),
@@ -5512,7 +5512,7 @@ class AuthRepositoryTest {
val organizations = listOf(
createMockOrganizationNetwork(
number = 1,
shouldUseKeyConnector = true,
isKeyConnectorEnabled = true,
type = OrganizationType.USER,
keyConnectorUrl = url,
),

View File

@@ -22,7 +22,7 @@ fun createMockOrganization(
id = id,
name = name,
shouldManageResetPassword = shouldManageResetPassword,
shouldUseKeyConnector = shouldUseKeyConnector,
isKeyConnectorEnabled = shouldUseKeyConnector,
role = role,
keyConnectorUrl = keyConnectorUrl,
userIsClaimedByOrganization = userIsClaimedByOrganization,

View File

@@ -0,0 +1,176 @@
@file:Suppress("LongParameterList")
package com.x8bit.bitwarden.data.auth.repository.model
import com.bitwarden.organizations.MemberDecryptionType
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 java.time.Instant
/**
* Creates a mock [ProfileOrganization] with the given [number].
*/
@Suppress("LongMethod")
fun createMockSdkProfileOrganization(
number: Int,
usePolicies: Boolean = false,
keyConnectorEnabled: Boolean = false,
keyConnectorUrl: String? = "mockKeyConnectorUrl-$number",
type: OrganizationUserType = OrganizationUserType.ADMIN,
seats: UInt? = 1u,
enabled: Boolean = false,
providerType: ProviderType? = ProviderType.RESELLER,
maxCollections: UInt? = 1u,
selfHost: Boolean = false,
permissions: Permissions = createMockSdkPermissions(),
providerId: String? = "mockProviderId-$number",
id: String = "mockId-$number",
useGroups: Boolean = false,
useDirectory: Boolean = false,
providerName: String? = "mockProviderName-$number",
usersGetPremium: Boolean = false,
maxStorageGb: UInt? = 1u,
identifier: String? = "mockIdentifier-$number",
use2fa: Boolean = false,
familySponsorshipToDelete: Boolean? = false,
userId: String? = "mockUserId-$number",
shouldUseEvents: Boolean = false,
familySponsorshipFriendlyName: String? = "mockFamilySponsorshipFriendlyName-$number",
useTotp: Boolean = false,
familySponsorshipLastSyncDate: Instant? = Instant.parse("2023-10-27T12:00:00Z"),
name: String = "mockName-$number",
useApi: Boolean = false,
familySponsorshipValidUntil: Instant? = Instant.parse("2023-10-27T12:00:00Z"),
status: OrganizationUserStatusType = OrganizationUserStatusType.CONFIRMED,
userIsClaimedByOrganization: Boolean = false,
limitItemDeletion: Boolean = false,
useSso: Boolean = false,
useOrganizationDomains: Boolean = false,
shouldUseKeyConnector: Boolean = false,
useScim: Boolean = false,
useCustomPermissions: Boolean = false,
useResetPassword: Boolean = false,
useSecretsManager: Boolean = false,
usePasswordManager: Boolean = false,
useActivateAutofillPolicy: Boolean = false,
useAutomaticUserConfirmation: Boolean = false,
ssoBound: Boolean = false,
resetPasswordEnrolled: Boolean = false,
organizationUserId: String? = "mockOrganizationUserId-$number",
hasPublicAndPrivateKeys: Boolean = false,
isProviderUser: Boolean = false,
isMember: Boolean = false,
familySponsorshipAvailable: Boolean = false,
productTierType: ProductTierType = ProductTierType.FREE,
accessSecretsManager: Boolean = false,
limitCollectionCreation: Boolean = false,
limitCollectionDeletion: Boolean = false,
allowAdminAccessToAllCollectionItems: Boolean = false,
useAccessIntelligence: Boolean = false,
useAdminSponsoredFamilies: Boolean = false,
useDisableSmAdsForUsers: Boolean = false,
isAdminInitiated: Boolean = false,
ssoEnabled: Boolean = false,
ssoMemberDecryptionType: MemberDecryptionType? = null,
usePhishingBlocker: Boolean = false,
useMyItems: 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,
)
/**
* Create a mock [Permissions].
*/
fun createMockSdkPermissions(
shouldManageResetPassword: Boolean = false,
shouldManagePolicies: Boolean = false,
accessEventLogs: Boolean = false,
accessImportExport: Boolean = false,
accessReports: Boolean = false,
createNewCollections: Boolean = false,
editAnyCollection: Boolean = false,
deleteAnyCollection: Boolean = false,
manageGroups: Boolean = false,
manageSso: Boolean = false,
manageUsers: Boolean = false,
manageScim: Boolean = false,
): Permissions =
Permissions(
manageResetPassword = shouldManageResetPassword,
managePolicies = shouldManagePolicies,
accessEventLogs = accessEventLogs,
accessImportExport = accessImportExport,
accessReports = accessReports,
createNewCollections = createNewCollections,
editAnyCollection = editAnyCollection,
deleteAnyCollection = deleteAnyCollection,
manageGroups = manageGroups,
manageSso = manageSso,
manageUsers = manageUsers,
manageScim = manageScim,
)

View File

@@ -1,11 +1,14 @@
package com.x8bit.bitwarden.data.auth.repository.util
import com.bitwarden.network.model.OrganizationType
import com.bitwarden.network.model.SyncResponseJson
import com.bitwarden.network.model.createMockOrganizationNetwork
import com.bitwarden.network.model.createMockPermissions
import com.bitwarden.organizations.ProfileOrganization
import com.bitwarden.policies.PolicyType
import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation
import com.x8bit.bitwarden.data.auth.repository.model.createMockOrganization
import com.x8bit.bitwarden.data.auth.repository.model.createMockSdkProfileOrganization
import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockPolicyView
import kotlinx.serialization.json.Json
import org.junit.jupiter.api.Assertions.assertEquals
@@ -36,7 +39,7 @@ class SyncResponseJsonExtensionsTest {
),
),
listOf(
createMockOrganizationNetwork(number = 1, shouldUseKeyConnector = true),
createMockOrganizationNetwork(number = 1, isKeyConnectorEnabled = true),
createMockOrganizationNetwork(
number = 2,
type = OrganizationType.USER,
@@ -47,6 +50,62 @@ class SyncResponseJsonExtensionsTest {
)
}
@Test
fun `toSdkProfileOrganizations should correctly map a single organization`() {
assertEquals(
listOf(createMockSdkProfileOrganization(number = 1)),
listOf(createMockOrganizationNetwork(number = 1)).toSdkProfileOrganizations(),
)
}
@Test
fun `toSdkProfileOrganizations should correctly map multiple organizations`() {
assertEquals(
listOf(
createMockSdkProfileOrganization(number = 1),
createMockSdkProfileOrganization(number = 2),
),
listOf(
createMockOrganizationNetwork(number = 1),
createMockOrganizationNetwork(number = 2),
)
.toSdkProfileOrganizations(),
)
}
@Test
fun `toSdkProfileOrganizations should filter out organizations with null names`() {
assertEquals(
listOf(createMockSdkProfileOrganization(number = 1)),
listOf(
createMockOrganizationNetwork(number = 1),
createMockOrganizationNetwork(number = 2, name = null),
)
.toSdkProfileOrganizations(),
)
}
@Suppress("MaxLineLength")
@Test
fun `toSdkProfileOrganizations should return empty list when all organizations have null names`() {
assertEquals(
emptyList<ProfileOrganization>(),
listOf(
createMockOrganizationNetwork(number = 1, name = null),
createMockOrganizationNetwork(number = 2, name = null),
)
.toSdkProfileOrganizations(),
)
}
@Test
fun `toSdkProfileOrganizations should return empty list for empty input`() {
assertEquals(
emptyList<ProfileOrganization>(),
emptyList<SyncResponseJson.Profile.Organization>().toSdkProfileOrganizations(),
)
}
@Test
fun `policyInformation converts the MasterPassword Json data to policy information`() {
val policyInformation = PolicyInformation.MasterPassword(

View File

@@ -0,0 +1,36 @@
package com.bitwarden.network.model
import androidx.annotation.Keep
import com.bitwarden.core.data.serializer.BaseEnumeratedIntSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* The type of encryption used for an SSO user organization.
*/
@Serializable(with = MemberDecryptionTypeSerializer::class)
enum class MemberDecryptionType {
/**
* Decryption using the user's master password.
*/
@SerialName("0")
MASTER_PASSWORD,
/**
* Decryption via Key Connector.
*/
@SerialName("1")
KEY_CONNECTOR,
/**
* Decryption via Trusted Device Encryption.
*/
@SerialName("2")
TRUSTED_DEVICE_ENCRYPTION,
}
@Keep
private class MemberDecryptionTypeSerializer : BaseEnumeratedIntSerializer<MemberDecryptionType>(
className = "MemberDecryptionType",
values = MemberDecryptionType.entries.toTypedArray(),
)

View File

@@ -0,0 +1,48 @@
package com.bitwarden.network.model
import androidx.annotation.Keep
import com.bitwarden.core.data.serializer.BaseEnumeratedIntSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* The subscription tier of an organization.
*/
@Serializable(with = ProductTierTypeSerializer::class)
enum class ProductTierType {
/**
* Free tier with limited features.
*/
@SerialName("0")
FREE,
/**
* Families plan for personal use.
*/
@SerialName("1")
FAMILIES,
/**
* Teams plan for small organizations.
*/
@SerialName("2")
TEAMS,
/**
* Enterprise plan with full features.
*/
@SerialName("3")
ENTERPRISE,
/**
* Starter tier for small teams.
*/
@SerialName("4")
TEAMS_STARTER,
}
@Keep
private class ProductTierTypeSerializer : BaseEnumeratedIntSerializer<ProductTierType>(
className = "ProductTierType",
values = ProductTierType.entries.toTypedArray(),
)

View File

@@ -0,0 +1,38 @@
package com.bitwarden.network.model
import androidx.annotation.Keep
import com.bitwarden.core.data.serializer.BaseEnumeratedIntSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
/**
* The type of provider.
*/
@Serializable(with = ProviderTypeSerializer::class)
enum class ProviderType {
/**
* Managed Service Provider - sells and manages its clients' Bitwarden organizations.
*/
@SerialName("0")
MSP,
/**
* Reseller partner - sells Bitwarden to its clients but does not have any administrative
* access.
*/
@SerialName("1")
RESELLER,
/**
* Business unit provider - used to manage multiple organizations which form part of a single
* large enterprise.
*/
@SerialName("2")
BUSINESS_UNIT,
}
@Keep
private class ProviderTypeSerializer : BaseEnumeratedIntSerializer<ProviderType>(
className = "ProviderType",
values = ProviderType.entries.toTypedArray(),
)

View File

@@ -252,41 +252,108 @@ data class SyncResponseJson(
val organizations: List<Organization>? get() = newOrganizations ?: legacyOrganizations
/**
* Represents an organization in the vault response.
* Represents an organization profile for the current user, containing the organization's
* feature flags, membership details, and configuration settings.
*
* @property shouldUsePolicies If the organization should use policies.
* @property keyConnectorUrl The key connector URL of the organization (nullable).
* @property type The type of organization.
* @property seats The number of seats in the organization (nullable).
* @property isEnabled If the organization is enabled.
* @property providerType They type of provider for the organization (nullable).
* @property maxCollections The max collections of the organization (nullable).
* @property isSelfHost If the organization is self hosted.
* @property permissions The permissions of the organization.
* @property providerId The provider ID of the organization (nullable).
* @property id The ID of the organization.
* @property shouldUseGroups If the organization should use groups.
* @property shouldUseDirectory If the organization should use a directory.
* @property shouldUsePolicies Whether the organization has access to policies features.
* @property isKeyConnectorEnabled Whether Key Connector is enabled for this organization.
* @property keyConnectorUrl The URL of the Key Connector service, if enabled.
* @property type The user's role in the organization.
* @property seats The number of licensed seats for the organization.
* @property isEnabled Whether the organization is currently enabled.
* @property providerType The type of provider managing this organization, if any.
* @property isProviderUser Whether the current user accesses this organization through a
* provider.
* @property maxCollections The maximum number of collections the organization can create.
* @property isSelfHost Whether the organization can create a license file for a self-hosted
* instance.
* @property permissions The current user's custom permissions, relevant when
* [OrganizationType.CUSTOM] is the user's type.
* @property providerId The ID of the provider managing this organization, if any.
* @property id Unique identifier for the organization.
* @property shouldUseGroups Whether the organization has access to groups features.
* @property shouldUseDirectory Whether the organization has access to directory sync
* features.
* @property key The key of the organization (nullable).
* @property providerName The provider name of the organization (nullable).
* @property shouldUsersGetPremium If users of the organization get Premium.
* @property maxStorageGb The max storage in Gb of the organization (nullable).
* @property identifier The identifier of the organization (nullable).
* @property use2fa If the organization uses 2FA.
* @property familySponsorshipToDelete If the organization has a
* family sponsorship to delete (nullable).
* @property userId The user id (nullable).
* @property shouldUseEvents If the organization should use events.
* @property familySponsorshipFriendlyName If the family sponsorship is a friendly name.
* @property shouldUseTotp If he organization should use TOTP.
* @property familySponsorshipLastSyncDate The last date the family sponsorship
* was synced (nullable).
* @property name The name of the organization (nullable).
* @property shouldUseApi If the organization should use API.
* @property familySponsorshipValidUntil The family sponsorship valid until
* of the organization (nullable).
* @property status The status of the organization.
* @property limitItemDeletion If the organization limits item deletion.
* @property providerName The name of the provider managing this organization, if any.
* @property shouldUsersGetPremium Whether organization members receive premium features.
* @property maxStorageGb The maximum encrypted storage in gigabytes, if limited.
* @property identifier The organization's SSO identifier.
* @property use2fa Whether the organization has access to two-factor authentication
* features.
* @property familySponsorshipToDelete Whether the families sponsorship is scheduled for
* deletion.
* @property userId The current user's personal user ID.
* @property shouldUseEvents Whether the organization has access to event logging features.
* @property familySponsorshipFriendlyName The friendly name of a pending families
* sponsorship, if any.
* @property shouldUseTotp Whether the organization can enforce TOTP for members.
* @property familySponsorshipLastSyncDate The date the families sponsorship was last
* synced, if applicable.
* @property name Display name of the organization.
* @property shouldUseApi Whether the organization has access to the Bitwarden Public API.
* @property familySponsorshipValidUntil The date the families sponsorship expires, if
* applicable.
* @property status The user's membership status in the organization.
* @property userIsClaimedByOrganization Whether the current user has been claimed by this
* organization.
* @property limitItemDeletion Whether item deletion is restricted to members with the
* Manage collection permission. When false, members with Edit permission can also delete
* items within their collections.
* @property useSso Whether the organization has access to SSO features.
* @property useOrganizationDomains Whether the organization can manage verified domains.
* @property useScim Whether the organization has access to SCIM provisioning.
* @property useCustomPermissions Whether the organization can use the
* [OrganizationType.CUSTOM] role.
* @property useResetPassword Whether the organization has access to the account recovery
* (admin password reset) feature.
* @property useSecretsManager Whether the organization has access to Secrets Manager.
* @property usePasswordManager Whether the organization has access to Password Manager.
* @property useActivateAutofillPolicy Whether the organization can use the activate
* autofill policy.
* @property useAutomaticUserConfirmation Whether the organization can automatically
* confirm new members without manual admin approval.
* @property ssoBound Whether the current user's account is bound to this organization
* via SSO.
* @property resetPasswordEnrolled Whether the current user is enrolled in account recovery
* for this organization.
* @property organizationUserId The current user's organization membership ID.
* @property hasPublicAndPrivateKeys Whether the organization has both a public and private
* key configured.
* @property isMember Whether the current user is a direct member of this organization (as
* opposed to provider-only access).
* @property familySponsorshipAvailable Whether the organization can sponsor a families
* plan for the current user.
* @property productTierType The subscription tier of the organization.
* @property shouldUseKeyConnector Whether the organization uses Key Connector for
* decryption.
* @property accessSecretsManager Whether the current user has access to Secrets Manager
* for this organization.
* @property limitCollectionCreation Whether collection creation is restricted to owners
* and admins only. When false, any member can create collections and automatically
* receives manage permissions over collections they create.
* @property limitCollectionDeletion Whether collection deletion is restricted to owners and
* admins only. When true, regular users cannot delete collections that they manage.
* @property allowAdminAccessToAllCollectionItems Whether owners and admins have implicit
* manage permissions over all collections. When true, owners and admins can alter items,
* groups, and permissions across all collections without requiring explicit collection
* assignments. When false, admins can only access collections where they have been
* explicitly assigned.
* @property useAccessIntelligence Whether the organization has access to Access
* Intelligence features.
* @property useAdminSponsoredFamilies Whether the organization can sponsor families plans
* for members (Families For Enterprises).
* @property useDisableSmAdsForUsers Whether Secrets Manager ads are disabled for users.
* @property isAdminInitiated Whether the organization's Families For Enterprises
* sponsorship was initiated by an admin.
* @property ssoEnabled Whether SSO login is currently enabled for this organization.
* @property ssoMemberDecryptionType The decryption type used for SSO members, if SSO is
* enabled.
* @property usePhishingBlocker Whether the organization has access to phishing blocker
* features.
* @property useMyItems Whether the organization has access to the My Items collection
* feature. This allows users to store personal items in the organization vault if the
* Centralize Organization Ownership policy is enabled.
*/
@Serializable
data class Organization(
@@ -294,7 +361,7 @@ data class SyncResponseJson(
val shouldUsePolicies: Boolean,
@SerialName("keyConnectorEnabled")
val shouldUseKeyConnector: Boolean,
val isKeyConnectorEnabled: Boolean,
@SerialName("keyConnectorUrl")
val keyConnectorUrl: String?,
@@ -303,19 +370,19 @@ data class SyncResponseJson(
val type: OrganizationType,
@SerialName("seats")
val seats: Int?,
val seats: UInt?,
@SerialName("enabled")
val isEnabled: Boolean,
@SerialName("providerType")
val providerType: Int?,
val providerType: ProviderType?,
@SerialName("isProviderUser")
val isProviderUser: Boolean = false,
@SerialName("maxCollections")
val maxCollections: Int?,
val maxCollections: UInt?,
@SerialName("selfHost")
val isSelfHost: Boolean,
@@ -345,7 +412,7 @@ data class SyncResponseJson(
val shouldUsersGetPremium: Boolean,
@SerialName("maxStorageGb")
val maxStorageGb: Int?,
val maxStorageGb: UInt?,
@SerialName("identifier")
val identifier: String?,
@@ -390,6 +457,93 @@ data class SyncResponseJson(
@SerialName("limitItemDeletion")
val limitItemDeletion: Boolean = false,
@SerialName("useSso")
val useSso: Boolean = false,
@SerialName("useOrganizationDomains")
val useOrganizationDomains: Boolean = false,
@SerialName("useScim")
val useScim: Boolean = false,
@SerialName("useCustomPermissions")
val useCustomPermissions: Boolean = false,
@SerialName("useResetPassword")
val useResetPassword: Boolean = false,
@SerialName("useSecretsManager")
val useSecretsManager: Boolean = false,
@SerialName("usePasswordManager")
val usePasswordManager: Boolean = false,
@SerialName("useActivateAutofillPolicy")
val useActivateAutofillPolicy: Boolean = false,
@SerialName("useAutomaticUserConfirmation")
val useAutomaticUserConfirmation: Boolean = false,
@SerialName("ssoBound")
val ssoBound: Boolean = false,
@SerialName("resetPasswordEnrolled")
val resetPasswordEnrolled: Boolean = false,
@SerialName("organizationUserId")
val organizationUserId: String?,
@SerialName("hasPublicAndPrivateKeys")
val hasPublicAndPrivateKeys: Boolean = false,
@SerialName("isMember")
val isMember: Boolean = false,
@SerialName("familySponsorshipAvailable")
val familySponsorshipAvailable: Boolean = false,
@SerialName("productTierType")
val productTierType: ProductTierType = ProductTierType.FREE,
@SerialName("useKeyConnector")
val shouldUseKeyConnector: Boolean = false,
@SerialName("accessSecretsManager")
val accessSecretsManager: Boolean = false,
@SerialName("limitCollectionCreation")
val limitCollectionCreation: Boolean = false,
@SerialName("limitCollectionDeletion")
val limitCollectionDeletion: Boolean = false,
@SerialName("allowAdminAccessToAllCollectionItems")
val allowAdminAccessToAllCollectionItems: Boolean = false,
@SerialName("useAccessIntelligence")
val useAccessIntelligence: Boolean = false,
@SerialName("useAdminSponsoredFamilies")
val useAdminSponsoredFamilies: Boolean = false,
@SerialName("useDisableSmAdsForUsers")
val useDisableSmAdsForUsers: Boolean = false,
@SerialName("isAdminInitiated")
val isAdminInitiated: Boolean = false,
@SerialName("ssoEnabled")
val ssoEnabled: Boolean = false,
@SerialName("ssoMemberDecryptionType")
val ssoMemberDecryptionType: MemberDecryptionType?,
@SerialName("usePhishingBlocker")
val usePhishingBlocker: Boolean = false,
@SerialName("useMyItems")
val useMyItems: Boolean = false,
)
/**
@@ -436,10 +590,24 @@ data class SyncResponseJson(
)
/**
* Represents permissions in the vault response.
* Custom permission set for a user with the [OrganizationType.CUSTOM] role.
*
* @property shouldManageResetPassword If reset password should be managed.
* @property shouldManagePolicies If policies should be managed.
* @property shouldManageResetPassword Can manage the account recovery (password reset)
* feature.
* @property shouldManagePolicies Can manage organization policies.
* @property accessEventLogs Can view the organization's event logs.
* @property accessImportExport Can import and export organization vault data.
* @property accessReports Can access organization reports.
* @property createNewCollections Can create new collections.
* @property editAnyCollection Can edit any collection, including those they are not
* assigned to.
* @property deleteAnyCollection Can delete any collection, including those they are not
* assigned to.
* @property manageGroups Can manage groups within the organization.
* @property manageSso Can manage SSO configuration.
* @property manageUsers Can manage organization members.
* @property manageScim Can manage SCIM (System for Cross-domain Identity Management)
* configuration.
*/
@Serializable
data class Permissions(
@@ -448,6 +616,36 @@ data class SyncResponseJson(
@SerialName("managePolicies")
val shouldManagePolicies: Boolean,
@SerialName("accessEventLogs")
val accessEventLogs: Boolean = false,
@SerialName("accessImportExport")
val accessImportExport: Boolean = false,
@SerialName("accessReports")
val accessReports: Boolean = false,
@SerialName("createNewCollections")
val createNewCollections: Boolean = false,
@SerialName("editAnyCollection")
val editAnyCollection: Boolean = false,
@SerialName("deleteAnyCollection")
val deleteAnyCollection: Boolean = false,
@SerialName("manageGroups")
val manageGroups: Boolean = false,
@SerialName("manageSso")
val manageSso: Boolean = false,
@SerialName("manageUsers")
val manageUsers: Boolean = false,
@SerialName("manageScim")
val manageScim: Boolean = false,
)
}

View File

@@ -97,7 +97,8 @@ private const val SYNC_SUCCESS_JSON = """
"editAnyCollection": false,
"accessEventLogs": false,
"createNewCollections": false,
"editAssignedCollections": false
"editAssignedCollections": false,
"manageScim": false
},
"providerId": "mockProviderId-1",
"id": "mockId-1",
@@ -119,7 +120,38 @@ private const val SYNC_SUCCESS_JSON = """
"useApi": false,
"familySponsorshipValidUntil": "2023-10-27T12:00:00.00Z",
"status": 2,
"userIsClaimedByOrganization": false
"userIsClaimedByOrganization": false,
"useSso": false,
"useOrganizationDomains": false,
"useKeyConnector": false,
"useScim": false,
"useCustomPermissions": false,
"useResetPassword": false,
"useSecretsManager": false,
"usePasswordManager": false,
"useActivateAutofillPolicy": false,
"useAutomaticUserConfirmation": false,
"ssoBound": false,
"resetPasswordEnrolled": false,
"organizationUserId": "mockOrganizationUserId-1",
"hasPublicAndPrivateKeys": false,
"isProviderUser": false,
"isMember": false,
"familySponsorshipAvailable": false,
"productTierType": 0,
"accessSecretsManager": false,
"limitCollectionCreation": false,
"limitCollectionDeletion": false,
"limitItemDeletion": false,
"allowAdminAccessToAllCollectionItems": false,
"useAccessIntelligence": false,
"useAdminSponsoredFamilies": false,
"useDisableSmAdsForUsers": false,
"isAdminInitiated": false,
"ssoEnabled": false,
"ssoMemberDecryptionType": null,
"usePhishingBlocker": false,
"useMyItems": false
}
],
"organizationsNew": [
@@ -146,7 +178,8 @@ private const val SYNC_SUCCESS_JSON = """
"editAnyCollection": false,
"accessEventLogs": false,
"createNewCollections": false,
"editAssignedCollections": false
"editAssignedCollections": false,
"manageScim": false
},
"providerId": "mockProviderId-1",
"id": "mockId-1",
@@ -168,7 +201,38 @@ private const val SYNC_SUCCESS_JSON = """
"useApi": false,
"familySponsorshipValidUntil": "2023-10-27T12:00:00.00Z",
"status": 2,
"userIsClaimedByOrganization": false
"userIsClaimedByOrganization": false,
"useSso": false,
"useOrganizationDomains": false,
"useKeyConnector": false,
"useScim": false,
"useCustomPermissions": false,
"useResetPassword": false,
"useSecretsManager": false,
"usePasswordManager": false,
"useActivateAutofillPolicy": false,
"useAutomaticUserConfirmation": false,
"ssoBound": false,
"resetPasswordEnrolled": false,
"organizationUserId": "mockOrganizationUserId-1",
"hasPublicAndPrivateKeys": false,
"isProviderUser": false,
"isMember": false,
"familySponsorshipAvailable": false,
"productTierType": 0,
"accessSecretsManager": false,
"limitCollectionCreation": false,
"limitCollectionDeletion": false,
"limitItemDeletion": false,
"allowAdminAccessToAllCollectionItems": false,
"useAccessIntelligence": false,
"useAdminSponsoredFamilies": false,
"useDisableSmAdsForUsers": false,
"isAdminInitiated": false,
"ssoEnabled": false,
"ssoMemberDecryptionType": null,
"usePhishingBlocker": false,
"useMyItems": false
}
],
"providers": [
@@ -187,7 +251,8 @@ private const val SYNC_SUCCESS_JSON = """
"editAnyCollection": false,
"accessEventLogs": false,
"createNewCollections": false,
"editAssignedCollections": false
"editAssignedCollections": false,
"manageScim": false
},
"name": "mockName-1",
"id": "mockId-1",
@@ -222,7 +287,8 @@ private const val SYNC_SUCCESS_JSON = """
"editAnyCollection": false,
"accessEventLogs": false,
"createNewCollections": false,
"editAssignedCollections": false
"editAssignedCollections": false,
"manageScim": false
},
"providerId": "mockProviderId-1",
"id": "mockId-1",
@@ -244,7 +310,38 @@ private const val SYNC_SUCCESS_JSON = """
"useApi": false,
"familySponsorshipValidUntil": "2023-10-27T12:00:00.00Z",
"status": 2,
"userIsClaimedByOrganization": false
"userIsClaimedByOrganization": false,
"useSso": false,
"useOrganizationDomains": false,
"useKeyConnector": false,
"useScim": false,
"useCustomPermissions": false,
"useResetPassword": false,
"useSecretsManager": false,
"usePasswordManager": false,
"useActivateAutofillPolicy": false,
"useAutomaticUserConfirmation": false,
"ssoBound": false,
"resetPasswordEnrolled": false,
"organizationUserId": "mockOrganizationUserId-1",
"hasPublicAndPrivateKeys": false,
"isProviderUser": false,
"isMember": false,
"familySponsorshipAvailable": false,
"productTierType": 0,
"accessSecretsManager": false,
"limitCollectionCreation": false,
"limitCollectionDeletion": false,
"limitItemDeletion": false,
"allowAdminAccessToAllCollectionItems": false,
"useAccessIntelligence": false,
"useAdminSponsoredFamilies": false,
"useDisableSmAdsForUsers": false,
"isAdminInitiated": false,
"ssoEnabled": false,
"ssoMemberDecryptionType": null,
"usePhishingBlocker": false,
"useMyItems": false
}
]
},

View File

@@ -63,16 +63,17 @@ fun createMockProfile(
/**
* Create a mock [SyncResponseJson.Profile.Organization] with a given [number].
*/
@Suppress("LongMethod")
fun createMockOrganizationNetwork(
number: Int,
shouldUsePolicies: Boolean = false,
shouldUseKeyConnector: Boolean = false,
isKeyConnectorEnabled: Boolean = false,
keyConnectorUrl: String? = "mockKeyConnectorUrl-$number",
type: OrganizationType = OrganizationType.ADMIN,
seats: Int? = 1,
seats: UInt? = 1u,
isEnabled: Boolean = false,
providerType: Int? = 1,
maxCollections: Int? = 1,
providerType: ProviderType? = ProviderType.RESELLER,
maxCollections: UInt? = 1u,
isSelfHost: Boolean = false,
permissions: SyncResponseJson.Profile.Permissions = createMockPermissions(),
providerId: String? = "mockProviderId-$number",
@@ -82,7 +83,7 @@ fun createMockOrganizationNetwork(
key: String? = "mockKey-$number",
providerName: String? = "mockProviderName-$number",
shouldUsersGetPremium: Boolean = false,
maxStorageGb: Int? = 1,
maxStorageGb: UInt? = 1u,
identifier: String? = "mockIdentifier-$number",
use2fa: Boolean = false,
familySponsorshipToDelete: Boolean? = false,
@@ -97,10 +98,40 @@ fun createMockOrganizationNetwork(
status: OrganizationStatusType = OrganizationStatusType.CONFIRMED,
userIsClaimedByOrganization: Boolean = false,
limitItemDeletion: Boolean = false,
useSso: Boolean = false,
useOrganizationDomains: Boolean = false,
shouldUseKeyConnector: Boolean = false,
useScim: Boolean = false,
useCustomPermissions: Boolean = false,
useResetPassword: Boolean = false,
useSecretsManager: Boolean = false,
usePasswordManager: Boolean = false,
useActivateAutofillPolicy: Boolean = false,
useAutomaticUserConfirmation: Boolean = false,
ssoBound: Boolean = false,
resetPasswordEnrolled: Boolean = false,
organizationUserId: String? = "mockOrganizationUserId-$number",
hasPublicAndPrivateKeys: Boolean = false,
isProviderUser: Boolean = false,
isMember: Boolean = false,
familySponsorshipAvailable: Boolean = false,
productTierType: ProductTierType = ProductTierType.FREE,
accessSecretsManager: Boolean = false,
limitCollectionCreation: Boolean = false,
limitCollectionDeletion: Boolean = false,
allowAdminAccessToAllCollectionItems: Boolean = false,
useAccessIntelligence: Boolean = false,
useAdminSponsoredFamilies: Boolean = false,
useDisableSmAdsForUsers: Boolean = false,
isAdminInitiated: Boolean = false,
ssoEnabled: Boolean = false,
ssoMemberDecryptionType: MemberDecryptionType? = null,
usePhishingBlocker: Boolean = false,
useMyItems: Boolean = false,
): SyncResponseJson.Profile.Organization =
SyncResponseJson.Profile.Organization(
shouldUsePolicies = shouldUsePolicies,
shouldUseKeyConnector = shouldUseKeyConnector,
isKeyConnectorEnabled = isKeyConnectorEnabled,
keyConnectorUrl = keyConnectorUrl,
type = type,
seats = seats,
@@ -131,6 +162,36 @@ fun createMockOrganizationNetwork(
status = status,
userIsClaimedByOrganization = userIsClaimedByOrganization,
limitItemDeletion = limitItemDeletion,
useSso = useSso,
useOrganizationDomains = useOrganizationDomains,
shouldUseKeyConnector = shouldUseKeyConnector,
useScim = useScim,
useCustomPermissions = useCustomPermissions,
useResetPassword = useResetPassword,
useSecretsManager = useSecretsManager,
usePasswordManager = usePasswordManager,
useActivateAutofillPolicy = useActivateAutofillPolicy,
useAutomaticUserConfirmation = useAutomaticUserConfirmation,
ssoBound = ssoBound,
resetPasswordEnrolled = resetPasswordEnrolled,
organizationUserId = organizationUserId,
hasPublicAndPrivateKeys = hasPublicAndPrivateKeys,
isProviderUser = isProviderUser,
isMember = isMember,
familySponsorshipAvailable = familySponsorshipAvailable,
productTierType = productTierType,
accessSecretsManager = accessSecretsManager,
limitCollectionCreation = limitCollectionCreation,
limitCollectionDeletion = limitCollectionDeletion,
allowAdminAccessToAllCollectionItems = allowAdminAccessToAllCollectionItems,
useAccessIntelligence = useAccessIntelligence,
useAdminSponsoredFamilies = useAdminSponsoredFamilies,
useDisableSmAdsForUsers = useDisableSmAdsForUsers,
isAdminInitiated = isAdminInitiated,
ssoEnabled = ssoEnabled,
ssoMemberDecryptionType = ssoMemberDecryptionType,
usePhishingBlocker = usePhishingBlocker,
useMyItems = useMyItems,
)
/**
@@ -150,10 +211,30 @@ fun createMockOrganizationKeys(
fun createMockPermissions(
shouldManageResetPassword: Boolean = false,
shouldManagePolicies: Boolean = false,
accessEventLogs: Boolean = false,
accessImportExport: Boolean = false,
accessReports: Boolean = false,
createNewCollections: Boolean = false,
editAnyCollection: Boolean = false,
deleteAnyCollection: Boolean = false,
manageGroups: Boolean = false,
manageSso: Boolean = false,
manageUsers: Boolean = false,
manageScim: Boolean = false,
): SyncResponseJson.Profile.Permissions =
SyncResponseJson.Profile.Permissions(
shouldManageResetPassword = shouldManageResetPassword,
shouldManagePolicies = shouldManagePolicies,
accessEventLogs = accessEventLogs,
accessImportExport = accessImportExport,
accessReports = accessReports,
createNewCollections = createNewCollections,
editAnyCollection = editAnyCollection,
deleteAnyCollection = deleteAnyCollection,
manageGroups = manageGroups,
manageSso = manageSso,
manageUsers = manageUsers,
manageScim = manageScim,
)
/**