mirror of
https://github.com/bitwarden/android.git
synced 2024-11-21 17:05:44 +03:00
[PM-12406] Introduce new endpoint and replace SSO details response flow (#4177)
This commit is contained in:
parent
c2537f329d
commit
e5e0464929
18 changed files with 490 additions and 12 deletions
|
@ -2,6 +2,8 @@ package com.x8bit.bitwarden.data.auth.datasource.network.api
|
||||||
|
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
import retrofit2.http.Body
|
import retrofit2.http.Body
|
||||||
import retrofit2.http.POST
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
@ -16,4 +18,12 @@ interface UnauthenticatedOrganizationApi {
|
||||||
suspend fun getClaimedDomainOrganizationDetails(
|
suspend fun getClaimedDomainOrganizationDetails(
|
||||||
@Body body: OrganizationDomainSsoDetailsRequestJson,
|
@Body body: OrganizationDomainSsoDetailsRequestJson,
|
||||||
): Result<OrganizationDomainSsoDetailsResponseJson>
|
): Result<OrganizationDomainSsoDetailsResponseJson>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks for the verfied organization domains of an email for SSO purposes.
|
||||||
|
*/
|
||||||
|
@POST("/organizations/domain/sso/verified")
|
||||||
|
suspend fun getVerifiedOrganizationDomainsByEmail(
|
||||||
|
@Body body: VerifiedOrganizationDomainSsoDetailsRequest,
|
||||||
|
): Result<VerifiedOrganizationDomainSsoDetailsResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,26 @@
|
||||||
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response object returned when requesting organization domain SSO details.
|
* Response object returned when requesting organization domain SSO details.
|
||||||
*
|
*
|
||||||
* @property isSsoAvailable Whether or not SSO is available for this domain.
|
* @property isSsoAvailable Whether or not SSO is available for this domain.
|
||||||
* @property organizationIdentifier The organization's identifier.
|
* @property organizationIdentifier The organization's identifier.
|
||||||
|
* @property verifiedDate The date the domain was verified.
|
||||||
*/
|
*/
|
||||||
@Serializable
|
@Serializable
|
||||||
data class OrganizationDomainSsoDetailsResponseJson(
|
data class OrganizationDomainSsoDetailsResponseJson(
|
||||||
@SerialName("ssoAvailable") val isSsoAvailable: Boolean,
|
@SerialName("ssoAvailable")
|
||||||
@SerialName("organizationIdentifier") val organizationIdentifier: String,
|
val isSsoAvailable: Boolean,
|
||||||
|
|
||||||
|
@SerialName("organizationIdentifier")
|
||||||
|
val organizationIdentifier: String,
|
||||||
|
|
||||||
|
@SerialName("verifiedDate")
|
||||||
|
@Contextual
|
||||||
|
val verifiedDate: ZonedDateTime?,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request body object when retrieving organization verified domain SSO info.
|
||||||
|
*
|
||||||
|
* @param email The email address to check against.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class VerifiedOrganizationDomainSsoDetailsRequest(
|
||||||
|
@SerialName("email") val email: String,
|
||||||
|
)
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.datasource.network.model
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response object returned when requesting organization verified domain SSO details.
|
||||||
|
*
|
||||||
|
* @property verifiedOrganizationDomainSsoDetails The list of verified organization domain SSO
|
||||||
|
* details.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class VerifiedOrganizationDomainSsoDetailsResponse(
|
||||||
|
@SerialName("data")
|
||||||
|
val verifiedOrganizationDomainSsoDetails: List<VerifiedOrganizationDomainSsoDetail>,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Response body for an organization verified domain SSO details.
|
||||||
|
*
|
||||||
|
* @property organizationName The name of the organization.
|
||||||
|
* @property organizationIdentifier The identifier of the organization.
|
||||||
|
* @property domainName The name of the domain.
|
||||||
|
*/
|
||||||
|
@Serializable
|
||||||
|
data class VerifiedOrganizationDomainSsoDetail(
|
||||||
|
@SerialName("organizationName")
|
||||||
|
val organizationName: String,
|
||||||
|
|
||||||
|
@SerialName("organizationIdentifier")
|
||||||
|
val organizationIdentifier: String,
|
||||||
|
|
||||||
|
@SerialName("domainName")
|
||||||
|
val domainName: String,
|
||||||
|
)
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ package com.x8bit.bitwarden.data.auth.datasource.network.service
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides an API for querying organization endpoints.
|
* Provides an API for querying organization endpoints.
|
||||||
|
@ -38,4 +39,12 @@ interface OrganizationService {
|
||||||
suspend fun getOrganizationKeys(
|
suspend fun getOrganizationKeys(
|
||||||
organizationId: String,
|
organizationId: String,
|
||||||
): Result<OrganizationKeysResponseJson>
|
): Result<OrganizationKeysResponseJson>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request organization verified domain details for an [email] needed for SSO
|
||||||
|
* requests.
|
||||||
|
*/
|
||||||
|
suspend fun getVerifiedOrganizationDomainSsoDetails(
|
||||||
|
email: String,
|
||||||
|
): Result<VerifiedOrganizationDomainSsoDetailsResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,8 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomain
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationResetPasswordEnrollRequestJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsRequest
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default implementation of [OrganizationService].
|
* Default implementation of [OrganizationService].
|
||||||
|
@ -52,4 +54,13 @@ class OrganizationServiceImpl(
|
||||||
.getOrganizationKeys(
|
.getOrganizationKeys(
|
||||||
organizationId = organizationId,
|
organizationId = organizationId,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun getVerifiedOrganizationDomainSsoDetails(
|
||||||
|
email: String,
|
||||||
|
): Result<VerifiedOrganizationDomainSsoDetailsResponse> = unauthenticatedOrganizationApi
|
||||||
|
.getVerifiedOrganizationDomainsByEmail(
|
||||||
|
body = VerifiedOrganizationDomainSsoDetailsRequest(
|
||||||
|
email = email,
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
|
||||||
|
@ -329,6 +330,13 @@ interface AuthRepository : AuthenticatorProvider, AuthRequestManager {
|
||||||
email: String,
|
email: String,
|
||||||
): OrganizationDomainSsoDetailsResult
|
): OrganizationDomainSsoDetailsResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the verified organization domain SSO details for the given [email].
|
||||||
|
*/
|
||||||
|
suspend fun getVerifiedOrganizationDomainSsoDetails(
|
||||||
|
email: String,
|
||||||
|
): VerifiedOrganizationDomainSsoDetailsResult
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prevalidates the organization identifier used in an SSO request.
|
* Prevalidates the organization identifier used in an SSO request.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -68,6 +68,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserState
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.toLoginErrorResult
|
import com.x8bit.bitwarden.data.auth.repository.model.toLoginErrorResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||||
|
@ -1123,11 +1124,27 @@ class AuthRepositoryImpl(
|
||||||
OrganizationDomainSsoDetailsResult.Success(
|
OrganizationDomainSsoDetailsResult.Success(
|
||||||
isSsoAvailable = it.isSsoAvailable,
|
isSsoAvailable = it.isSsoAvailable,
|
||||||
organizationIdentifier = it.organizationIdentifier,
|
organizationIdentifier = it.organizationIdentifier,
|
||||||
|
verifiedDate = it.verifiedDate,
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onFailure = { OrganizationDomainSsoDetailsResult.Failure },
|
onFailure = { OrganizationDomainSsoDetailsResult.Failure },
|
||||||
)
|
)
|
||||||
|
|
||||||
|
override suspend fun getVerifiedOrganizationDomainSsoDetails(
|
||||||
|
email: String,
|
||||||
|
): VerifiedOrganizationDomainSsoDetailsResult = organizationService
|
||||||
|
.getVerifiedOrganizationDomainSsoDetails(
|
||||||
|
email = email,
|
||||||
|
)
|
||||||
|
.fold(
|
||||||
|
onSuccess = {
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResult.Success(
|
||||||
|
verifiedOrganizationDomainSsoDetails = it.verifiedOrganizationDomainSsoDetails,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onFailure = { VerifiedOrganizationDomainSsoDetailsResult.Failure },
|
||||||
|
)
|
||||||
|
|
||||||
override suspend fun prevalidateSso(
|
override suspend fun prevalidateSso(
|
||||||
organizationIdentifier: String,
|
organizationIdentifier: String,
|
||||||
): PrevalidateSsoResult = identityService
|
): PrevalidateSsoResult = identityService
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package com.x8bit.bitwarden.data.auth.repository.model
|
package com.x8bit.bitwarden.data.auth.repository.model
|
||||||
|
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Response types when checking for an email's claimed domain organization.
|
* Response types when checking for an email's claimed domain organization.
|
||||||
*/
|
*/
|
||||||
|
@ -9,10 +11,12 @@ sealed class OrganizationDomainSsoDetailsResult {
|
||||||
*
|
*
|
||||||
* @property isSsoAvailable Indicates if SSO is available for the email address.
|
* @property isSsoAvailable Indicates if SSO is available for the email address.
|
||||||
* @property organizationIdentifier The claimed organization identifier for the email address.
|
* @property organizationIdentifier The claimed organization identifier for the email address.
|
||||||
|
* @property verifiedDate The date and time when the domain was verified.
|
||||||
*/
|
*/
|
||||||
data class Success(
|
data class Success(
|
||||||
val isSsoAvailable: Boolean,
|
val isSsoAvailable: Boolean,
|
||||||
val organizationIdentifier: String,
|
val organizationIdentifier: String,
|
||||||
|
val verifiedDate: ZonedDateTime?,
|
||||||
) : OrganizationDomainSsoDetailsResult()
|
) : OrganizationDomainSsoDetailsResult()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.x8bit.bitwarden.data.auth.repository.model
|
||||||
|
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response types when checking for an email's claimed domain organization.
|
||||||
|
*/
|
||||||
|
sealed class VerifiedOrganizationDomainSsoDetailsResult {
|
||||||
|
/**
|
||||||
|
* The request was successful.
|
||||||
|
*
|
||||||
|
* @property verifiedOrganizationDomainSsoDetails The verified organization domain SSO details.
|
||||||
|
*/
|
||||||
|
data class Success(
|
||||||
|
val verifiedOrganizationDomainSsoDetails: List<VerifiedOrganizationDomainSsoDetail>,
|
||||||
|
) : VerifiedOrganizationDomainSsoDetailsResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The request failed.
|
||||||
|
*/
|
||||||
|
data object Failure : VerifiedOrganizationDomainSsoDetailsResult()
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ sealed class FlagKey<out T : Any> {
|
||||||
OnboardingCarousel,
|
OnboardingCarousel,
|
||||||
ImportLoginsFlow,
|
ImportLoginsFlow,
|
||||||
SshKeyCipherItems,
|
SshKeyCipherItems,
|
||||||
|
VerifiedSsoDomainEndpoint,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,6 +90,14 @@ sealed class FlagKey<out T : Any> {
|
||||||
override val defaultValue: Boolean = false
|
override val defaultValue: Boolean = false
|
||||||
override val isRemotelyConfigured: Boolean = true
|
override val isRemotelyConfigured: Boolean = true
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* Data object holding the feature flag key for the new verified SSO domain endpoint feature.
|
||||||
|
*/
|
||||||
|
data object VerifiedSsoDomainEndpoint : FlagKey<Boolean>() {
|
||||||
|
override val keyName: String = "pm-12337-refactor-sso-details-endpoint"
|
||||||
|
override val defaultValue: Boolean = false
|
||||||
|
override val isRemotelyConfigured: Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data object holding the key for a [Boolean] flag to be used in tests.
|
* Data object holding the key for a [Boolean] flag to be used in tests.
|
||||||
|
|
|
@ -9,12 +9,15 @@ import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.OrganizationDomainSsoDetailsResult
|
import com.x8bit.bitwarden.data.auth.repository.model.OrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.SSO_URI
|
import com.x8bit.bitwarden.data.auth.repository.util.SSO_URI
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForSso
|
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForSso
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager
|
import com.x8bit.bitwarden.data.platform.manager.NetworkConnectionManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.baseIdentityUrl
|
import com.x8bit.bitwarden.data.platform.repository.util.baseIdentityUrl
|
||||||
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
import com.x8bit.bitwarden.data.tools.generator.repository.GeneratorRepository
|
||||||
|
@ -43,6 +46,7 @@ private const val RANDOM_STRING_LENGTH = 64
|
||||||
class EnterpriseSignOnViewModel @Inject constructor(
|
class EnterpriseSignOnViewModel @Inject constructor(
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository,
|
||||||
private val environmentRepository: EnvironmentRepository,
|
private val environmentRepository: EnvironmentRepository,
|
||||||
|
private val featureFlagManager: FeatureFlagManager,
|
||||||
private val generatorRepository: GeneratorRepository,
|
private val generatorRepository: GeneratorRepository,
|
||||||
private val networkConnectionManager: NetworkConnectionManager,
|
private val networkConnectionManager: NetworkConnectionManager,
|
||||||
private val savedStateHandle: SavedStateHandle,
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
@ -123,6 +127,10 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||||
is EnterpriseSignOnAction.Internal.OnReceiveCaptchaToken -> {
|
is EnterpriseSignOnAction.Internal.OnReceiveCaptchaToken -> {
|
||||||
handleOnReceiveCaptchaToken(action)
|
handleOnReceiveCaptchaToken(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is EnterpriseSignOnAction.Internal.OnVerifiedOrganizationDomainSsoDetailsReceive -> {
|
||||||
|
handleOnVerifiedOrganizationDomainSsoDetailsReceive(action)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -209,6 +217,54 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleOnVerifiedOrganizationDomainSsoDetailsReceive(
|
||||||
|
action: EnterpriseSignOnAction.Internal.OnVerifiedOrganizationDomainSsoDetailsReceive,
|
||||||
|
) {
|
||||||
|
when (val orgDetails = action.verifiedOrgDomainSsoDetailsResult) {
|
||||||
|
is VerifiedOrganizationDomainSsoDetailsResult.Failure -> {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState = null,
|
||||||
|
orgIdentifierInput = authRepository.rememberedOrgIdentifier ?: "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is VerifiedOrganizationDomainSsoDetailsResult.Success -> {
|
||||||
|
handleOnVerifiedOrganizationDomainSsoDetailsSuccess(orgDetails)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleOnVerifiedOrganizationDomainSsoDetailsSuccess(
|
||||||
|
orgDetails: VerifiedOrganizationDomainSsoDetailsResult.Success,
|
||||||
|
) {
|
||||||
|
if (orgDetails.verifiedOrganizationDomainSsoDetails.isEmpty()) {
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
dialogState = null,
|
||||||
|
orgIdentifierInput = authRepository.rememberedOrgIdentifier ?: "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val organizationIdentifier = orgDetails
|
||||||
|
.verifiedOrganizationDomainSsoDetails
|
||||||
|
.first()
|
||||||
|
.organizationIdentifier
|
||||||
|
|
||||||
|
mutableStateFlow.update {
|
||||||
|
it.copy(
|
||||||
|
orgIdentifierInput = organizationIdentifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the email address is associated with a claimed organization we can proceed to the
|
||||||
|
// prevalidation step.
|
||||||
|
prevalidateSso()
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleOnOrganizationDomainSsoDetailsReceive(
|
private fun handleOnOrganizationDomainSsoDetailsReceive(
|
||||||
action: EnterpriseSignOnAction.Internal.OnOrganizationDomainSsoDetailsReceive,
|
action: EnterpriseSignOnAction.Internal.OnOrganizationDomainSsoDetailsReceive,
|
||||||
) {
|
) {
|
||||||
|
@ -226,7 +282,7 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||||
private fun handleOnOrganizationDomainSsoDetailsSuccess(
|
private fun handleOnOrganizationDomainSsoDetailsSuccess(
|
||||||
orgDetails: OrganizationDomainSsoDetailsResult.Success,
|
orgDetails: OrganizationDomainSsoDetailsResult.Success,
|
||||||
) {
|
) {
|
||||||
if (!orgDetails.isSsoAvailable) {
|
if (!orgDetails.isSsoAvailable || orgDetails.verifiedDate == null) {
|
||||||
mutableStateFlow.update {
|
mutableStateFlow.update {
|
||||||
it.copy(
|
it.copy(
|
||||||
dialogState = null,
|
dialogState = null,
|
||||||
|
@ -375,12 +431,23 @@ class EnterpriseSignOnViewModel @Inject constructor(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val result = authRepository.getOrganizationDomainSsoDetails(
|
if (featureFlagManager.getFeatureFlag(key = FlagKey.VerifiedSsoDomainEndpoint)) {
|
||||||
email = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
val result = authRepository.getVerifiedOrganizationDomainSsoDetails(
|
||||||
)
|
email = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
||||||
sendAction(
|
)
|
||||||
EnterpriseSignOnAction.Internal.OnOrganizationDomainSsoDetailsReceive(result),
|
sendAction(
|
||||||
)
|
EnterpriseSignOnAction.Internal.OnVerifiedOrganizationDomainSsoDetailsReceive(
|
||||||
|
result,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
val result = authRepository.getOrganizationDomainSsoDetails(
|
||||||
|
email = EnterpriseSignOnArgs(savedStateHandle).emailAddress,
|
||||||
|
)
|
||||||
|
sendAction(
|
||||||
|
EnterpriseSignOnAction.Internal.OnOrganizationDomainSsoDetailsReceive(result),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -561,6 +628,13 @@ sealed class EnterpriseSignOnAction {
|
||||||
* A captcha callback result has been received
|
* A captcha callback result has been received
|
||||||
*/
|
*/
|
||||||
data class OnReceiveCaptchaToken(val tokenResult: CaptchaCallbackTokenResult) : Internal()
|
data class OnReceiveCaptchaToken(val tokenResult: CaptchaCallbackTokenResult) : Internal()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A result was received when requesting an [VerifiedOrganizationDomainSsoDetailsResult].
|
||||||
|
*/
|
||||||
|
data class OnVerifiedOrganizationDomainSsoDetailsReceive(
|
||||||
|
val verifiedOrgDomainSsoDetailsResult: VerifiedOrganizationDomainSsoDetailsResult,
|
||||||
|
) : Internal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,6 +28,7 @@ fun <T : Any> FlagKey<T>.ListItemContent(
|
||||||
FlagKey.OnboardingFlow,
|
FlagKey.OnboardingFlow,
|
||||||
FlagKey.ImportLoginsFlow,
|
FlagKey.ImportLoginsFlow,
|
||||||
FlagKey.SshKeyCipherItems,
|
FlagKey.SshKeyCipherItems,
|
||||||
|
FlagKey.VerifiedSsoDomainEndpoint,
|
||||||
-> BooleanFlagItem(
|
-> BooleanFlagItem(
|
||||||
label = flagKey.getDisplayLabel(),
|
label = flagKey.getDisplayLabel(),
|
||||||
key = flagKey as FlagKey<Boolean>,
|
key = flagKey as FlagKey<Boolean>,
|
||||||
|
@ -71,4 +72,5 @@ private fun <T : Any> FlagKey<T>.getDisplayLabel(): String = when (this) {
|
||||||
FlagKey.OnboardingFlow -> stringResource(R.string.onboarding_flow)
|
FlagKey.OnboardingFlow -> stringResource(R.string.onboarding_flow)
|
||||||
FlagKey.ImportLoginsFlow -> stringResource(R.string.import_logins_flow)
|
FlagKey.ImportLoginsFlow -> stringResource(R.string.import_logins_flow)
|
||||||
FlagKey.SshKeyCipherItems -> stringResource(R.string.ssh_key_cipher_item_types)
|
FlagKey.SshKeyCipherItems -> stringResource(R.string.ssh_key_cipher_item_types)
|
||||||
|
FlagKey.VerifiedSsoDomainEndpoint -> stringResource(R.string.verified_sso_domain_verified)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1073,6 +1073,7 @@ Do you want to switch to this account?</string>
|
||||||
<string name="bitwarden_tools">Bitwarden Tools</string>
|
<string name="bitwarden_tools">Bitwarden Tools</string>
|
||||||
<string name="got_it">Got it</string>
|
<string name="got_it">Got it</string>
|
||||||
<string name="no_logins_were_imported">No logins were imported</string>
|
<string name="no_logins_were_imported">No logins were imported</string>
|
||||||
|
<string name="verified_sso_domain_verified">Verified SSO Domain Endpoint</string>
|
||||||
<string name="logins_imported">Logins imported</string>
|
<string name="logins_imported">Logins imported</string>
|
||||||
<string name="remember_to_delete_your_imported_password_file_from_your_computer">Remember to delete your imported password file from your computer</string>
|
<string name="remember_to_delete_your_imported_password_file_from_your_computer">Remember to delete your imported password file from your computer</string>
|
||||||
<string name="type_ssh_key">SSH key</string>
|
<string name="type_ssh_key">SSH key</string>
|
||||||
|
|
|
@ -5,6 +5,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.api.UnauthenticatedOrgan
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationAutoEnrollStatusResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationDomainSsoDetailsResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.OrganizationKeysResponseJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
import com.x8bit.bitwarden.data.platform.base.BaseServiceTest
|
||||||
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
import com.x8bit.bitwarden.data.platform.util.asSuccess
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
@ -13,6 +14,7 @@ import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
import org.junit.jupiter.api.Assertions.assertTrue
|
import org.junit.jupiter.api.Assertions.assertTrue
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import retrofit2.create
|
import retrofit2.create
|
||||||
|
import java.time.ZonedDateTime
|
||||||
|
|
||||||
class OrganizationServiceTest : BaseServiceTest() {
|
class OrganizationServiceTest : BaseServiceTest() {
|
||||||
private val authenticatedOrganizationApi: AuthenticatedOrganizationApi = retrofit.create()
|
private val authenticatedOrganizationApi: AuthenticatedOrganizationApi = retrofit.create()
|
||||||
|
@ -55,7 +57,9 @@ class OrganizationServiceTest : BaseServiceTest() {
|
||||||
runTest {
|
runTest {
|
||||||
val email = "test@gmail.com"
|
val email = "test@gmail.com"
|
||||||
server.enqueue(
|
server.enqueue(
|
||||||
MockResponse().setResponseCode(200).setBody(ORGANIZATION_DOMAIN_SSO_DETAILS_JSON),
|
MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setBody(ORGANIZATION_DOMAIN_SSO_DETAILS_JSON),
|
||||||
)
|
)
|
||||||
val result = organizationService.getOrganizationDomainSsoDetails(email)
|
val result = organizationService.getOrganizationDomainSsoDetails(email)
|
||||||
assertEquals(ORGANIZATION_DOMAIN_SSO_BODY.asSuccess(), result)
|
assertEquals(ORGANIZATION_DOMAIN_SSO_BODY.asSuccess(), result)
|
||||||
|
@ -74,7 +78,9 @@ class OrganizationServiceTest : BaseServiceTest() {
|
||||||
fun `getOrganizationAutoEnrollStatus when response is success should return valid response`() =
|
fun `getOrganizationAutoEnrollStatus when response is success should return valid response`() =
|
||||||
runTest {
|
runTest {
|
||||||
server.enqueue(
|
server.enqueue(
|
||||||
MockResponse().setResponseCode(200).setBody(ORGANIZATION_AUTO_ENROLL_STATUS_JSON),
|
MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setBody(ORGANIZATION_AUTO_ENROLL_STATUS_JSON),
|
||||||
)
|
)
|
||||||
val result = organizationService.getOrganizationAutoEnrollStatus("orgId")
|
val result = organizationService.getOrganizationAutoEnrollStatus("orgId")
|
||||||
assertEquals(ORGANIZATION_AUTO_ENROLL_STATUS_RESPONSE.asSuccess(), result)
|
assertEquals(ORGANIZATION_AUTO_ENROLL_STATUS_RESPONSE.asSuccess(), result)
|
||||||
|
@ -91,7 +97,9 @@ class OrganizationServiceTest : BaseServiceTest() {
|
||||||
@Test
|
@Test
|
||||||
fun `getOrganizationKeys when response is success should return valid response`() = runTest {
|
fun `getOrganizationKeys when response is success should return valid response`() = runTest {
|
||||||
server.enqueue(
|
server.enqueue(
|
||||||
MockResponse().setResponseCode(200).setBody(ORGANIZATION_KEYS_JSON),
|
MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setBody(ORGANIZATION_KEYS_JSON),
|
||||||
)
|
)
|
||||||
val result = organizationService.getOrganizationKeys("orgId")
|
val result = organizationService.getOrganizationKeys("orgId")
|
||||||
assertEquals(ORGANIZATION_KEYS_RESPONSE.asSuccess(), result)
|
assertEquals(ORGANIZATION_KEYS_RESPONSE.asSuccess(), result)
|
||||||
|
@ -103,6 +111,30 @@ class OrganizationServiceTest : BaseServiceTest() {
|
||||||
val result = organizationService.getOrganizationKeys("orgId")
|
val result = organizationService.getOrganizationKeys("orgId")
|
||||||
assertTrue(result.isFailure)
|
assertTrue(result.isFailure)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `getVerifiedOrganizationDomainSsoDetails when response is success should return valid response`() =
|
||||||
|
runTest {
|
||||||
|
server.enqueue(
|
||||||
|
MockResponse()
|
||||||
|
.setResponseCode(200)
|
||||||
|
.setBody(ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_JSON),
|
||||||
|
)
|
||||||
|
val result =
|
||||||
|
organizationService.getVerifiedOrganizationDomainSsoDetails("example@bitwarden.com")
|
||||||
|
assertEquals(ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_RESPONSE.asSuccess(), result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `getVerifiedOrganizationDomainSsoDetails when response is an error should return an error`() =
|
||||||
|
runTest {
|
||||||
|
server.enqueue(MockResponse().setResponseCode(400))
|
||||||
|
val result =
|
||||||
|
organizationService.getVerifiedOrganizationDomainSsoDetails("example@bitwarden.com")
|
||||||
|
assertTrue(result.isFailure)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val ORGANIZATION_AUTO_ENROLL_STATUS_JSON = """
|
private const val ORGANIZATION_AUTO_ENROLL_STATUS_JSON = """
|
||||||
|
@ -130,6 +162,7 @@ private const val ORGANIZATION_DOMAIN_SSO_DETAILS_JSON = """
|
||||||
private val ORGANIZATION_DOMAIN_SSO_BODY = OrganizationDomainSsoDetailsResponseJson(
|
private val ORGANIZATION_DOMAIN_SSO_BODY = OrganizationDomainSsoDetailsResponseJson(
|
||||||
isSsoAvailable = true,
|
isSsoAvailable = true,
|
||||||
organizationIdentifier = "Test Org",
|
organizationIdentifier = "Test Org",
|
||||||
|
verifiedDate = ZonedDateTime.parse("2024-09-13T00:00:00.000Z"),
|
||||||
)
|
)
|
||||||
|
|
||||||
private const val ORGANIZATION_KEYS_JSON = """
|
private const val ORGANIZATION_KEYS_JSON = """
|
||||||
|
@ -143,3 +176,26 @@ private val ORGANIZATION_KEYS_RESPONSE = OrganizationKeysResponseJson(
|
||||||
privateKey = "privateKey",
|
privateKey = "privateKey",
|
||||||
publicKey = "publicKey",
|
publicKey = "publicKey",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private const val ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_JSON = """
|
||||||
|
{
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"organizationIdentifier": "Test Identifier",
|
||||||
|
"organizationName": "Bitwarden",
|
||||||
|
"domainName": "bitwarden.com"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
private val ORGANIZATION_VERIFIED_DOMAIN_SSO_DETAILS_RESPONSE =
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResponse(
|
||||||
|
verifiedOrganizationDomainSsoDetails = listOf(
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail(
|
||||||
|
organizationIdentifier = "Test Identifier",
|
||||||
|
organizationName = "Bitwarden",
|
||||||
|
domainName = "bitwarden.com",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
|
@ -43,6 +43,7 @@ import com.x8bit.bitwarden.data.auth.datasource.network.model.TrustedDeviceUserD
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorAuthMethod
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.TwoFactorDataModel
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.UserDecryptionOptionsJson
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenRequestJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifyEmailTokenResponseJson
|
||||||
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
import com.x8bit.bitwarden.data.auth.datasource.network.service.AccountsService
|
||||||
|
@ -85,6 +86,7 @@ import com.x8bit.bitwarden.data.auth.repository.model.UserOrganizations
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePasswordResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
import com.x8bit.bitwarden.data.auth.repository.model.ValidatePinResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
import com.x8bit.bitwarden.data.auth.repository.model.VaultUnlockType
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifyOtpResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.DuoCallbackTokenResult
|
||||||
|
@ -149,6 +151,7 @@ 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
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class AuthRepositoryTest {
|
class AuthRepositoryTest {
|
||||||
|
@ -5217,6 +5220,7 @@ class AuthRepositoryTest {
|
||||||
} returns OrganizationDomainSsoDetailsResponseJson(
|
} returns OrganizationDomainSsoDetailsResponseJson(
|
||||||
isSsoAvailable = true,
|
isSsoAvailable = true,
|
||||||
organizationIdentifier = "Test Org",
|
organizationIdentifier = "Test Org",
|
||||||
|
verifiedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||||
)
|
)
|
||||||
.asSuccess()
|
.asSuccess()
|
||||||
val result = repository.getOrganizationDomainSsoDetails(email)
|
val result = repository.getOrganizationDomainSsoDetails(email)
|
||||||
|
@ -5224,11 +5228,53 @@ class AuthRepositoryTest {
|
||||||
OrganizationDomainSsoDetailsResult.Success(
|
OrganizationDomainSsoDetailsResult.Success(
|
||||||
isSsoAvailable = true,
|
isSsoAvailable = true,
|
||||||
organizationIdentifier = "Test Org",
|
organizationIdentifier = "Test Org",
|
||||||
|
verifiedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||||
),
|
),
|
||||||
result,
|
result,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `getVerifiedOrganizationDomainSsoDetails Success should return Success`() = runTest {
|
||||||
|
val email = "test@gmail.com"
|
||||||
|
coEvery {
|
||||||
|
organizationService.getVerifiedOrganizationDomainSsoDetails(email)
|
||||||
|
} returns VerifiedOrganizationDomainSsoDetailsResponse(
|
||||||
|
verifiedOrganizationDomainSsoDetails = listOf(
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail(
|
||||||
|
organizationIdentifier = "Test Identifier",
|
||||||
|
organizationName = "Bitwarden",
|
||||||
|
domainName = "bitwarden.com",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).asSuccess()
|
||||||
|
val result = repository.getVerifiedOrganizationDomainSsoDetails(email)
|
||||||
|
assertEquals(
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResult.Success(
|
||||||
|
verifiedOrganizationDomainSsoDetails = listOf(
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail(
|
||||||
|
organizationIdentifier = "Test Identifier",
|
||||||
|
organizationName = "Bitwarden",
|
||||||
|
domainName = "bitwarden.com",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `getVerifiedOrganizationDomainSsoDetails Failure should return Failure `() = runTest {
|
||||||
|
val email = "test@gmail.com"
|
||||||
|
val throwable = Throwable()
|
||||||
|
coEvery {
|
||||||
|
organizationService.getVerifiedOrganizationDomainSsoDetails(email)
|
||||||
|
} returns throwable.asFailure()
|
||||||
|
val result = repository.getVerifiedOrganizationDomainSsoDetails(email)
|
||||||
|
assertEquals(VerifiedOrganizationDomainSsoDetailsResult.Failure, result)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `prevalidateSso Failure should return Failure `() = runTest {
|
fun `prevalidateSso Failure should return Failure `() = runTest {
|
||||||
val organizationId = "organizationid"
|
val organizationId = "organizationid"
|
||||||
|
|
|
@ -4,14 +4,18 @@ import android.net.Uri
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.x8bit.bitwarden.R
|
import com.x8bit.bitwarden.R
|
||||||
|
import com.x8bit.bitwarden.data.auth.datasource.network.model.VerifiedOrganizationDomainSsoDetailsResponse
|
||||||
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
import com.x8bit.bitwarden.data.auth.repository.model.LoginResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.OrganizationDomainSsoDetailsResult
|
import com.x8bit.bitwarden.data.auth.repository.model.OrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
import com.x8bit.bitwarden.data.auth.repository.model.PrevalidateSsoResult
|
||||||
|
import com.x8bit.bitwarden.data.auth.repository.model.VerifiedOrganizationDomainSsoDetailsResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
import com.x8bit.bitwarden.data.auth.repository.util.CaptchaCallbackTokenResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
import com.x8bit.bitwarden.data.auth.repository.util.SsoCallbackResult
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForCaptcha
|
||||||
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForSso
|
import com.x8bit.bitwarden.data.auth.repository.util.generateUriForSso
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.FeatureFlagManager
|
||||||
|
import com.x8bit.bitwarden.data.platform.manager.model.FlagKey
|
||||||
import com.x8bit.bitwarden.data.platform.manager.util.FakeNetworkConnectionManager
|
import com.x8bit.bitwarden.data.platform.manager.util.FakeNetworkConnectionManager
|
||||||
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.EnvironmentRepository
|
||||||
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
import com.x8bit.bitwarden.data.platform.repository.util.FakeEnvironmentRepository
|
||||||
|
@ -34,6 +38,7 @@ import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Assertions.assertEquals
|
import org.junit.jupiter.api.Assertions.assertEquals
|
||||||
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
|
||||||
|
|
||||||
@Suppress("LargeClass")
|
@Suppress("LargeClass")
|
||||||
class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
|
@ -54,6 +59,12 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
|
|
||||||
private val generatorRepository: GeneratorRepository = FakeGeneratorRepository()
|
private val generatorRepository: GeneratorRepository = FakeGeneratorRepository()
|
||||||
|
|
||||||
|
private val featureFlagManager = mockk<FeatureFlagManager>() {
|
||||||
|
every {
|
||||||
|
getFeatureFlag(FlagKey.VerifiedSsoDomainEndpoint)
|
||||||
|
} returns false
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
fun setUp() {
|
fun setUp() {
|
||||||
mockkStatic(::generateUriForSso)
|
mockkStatic(::generateUriForSso)
|
||||||
|
@ -694,6 +705,37 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
||||||
isSsoAvailable = false,
|
isSsoAvailable = false,
|
||||||
organizationIdentifier = "Bitwarden without SSO",
|
organizationIdentifier = "Bitwarden without SSO",
|
||||||
|
verifiedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
authRepository.getOrganizationDomainSsoDetails(any())
|
||||||
|
} returns orgDetails
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
authRepository.rememberedOrgIdentifier
|
||||||
|
} returns "Bitwarden"
|
||||||
|
|
||||||
|
val viewModel = createViewModel(dismissInitialDialog = false)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(orgIdentifierInput = "Bitwarden"),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
authRepository.getOrganizationDomainSsoDetails(DEFAULT_EMAIL)
|
||||||
|
authRepository.rememberedOrgIdentifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `OrganizationDomainSsoDetails success with no verified date available should make a request, hide the dialog, and update the org input based on the remembered org`() =
|
||||||
|
runTest {
|
||||||
|
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
||||||
|
isSsoAvailable = true,
|
||||||
|
organizationIdentifier = "Bitwarden without SSO",
|
||||||
|
verifiedDate = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
coEvery {
|
coEvery {
|
||||||
|
@ -723,6 +765,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
||||||
isSsoAvailable = true,
|
isSsoAvailable = true,
|
||||||
organizationIdentifier = "",
|
organizationIdentifier = "",
|
||||||
|
verifiedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||||
)
|
)
|
||||||
|
|
||||||
coEvery {
|
coEvery {
|
||||||
|
@ -757,6 +800,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
val orgDetails = OrganizationDomainSsoDetailsResult.Success(
|
||||||
isSsoAvailable = true,
|
isSsoAvailable = true,
|
||||||
organizationIdentifier = "Bitwarden with SSO",
|
organizationIdentifier = "Bitwarden with SSO",
|
||||||
|
verifiedDate = ZonedDateTime.parse("2023-10-27T12:00:00Z"),
|
||||||
)
|
)
|
||||||
|
|
||||||
coEvery {
|
coEvery {
|
||||||
|
@ -784,6 +828,109 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `VerifiedOrganizationDomainSsoDetails success with valid organization should make a request then attempt to login`() =
|
||||||
|
runTest {
|
||||||
|
val orgDetails = VerifiedOrganizationDomainSsoDetailsResult.Success(
|
||||||
|
verifiedOrganizationDomainSsoDetails = listOf(
|
||||||
|
VerifiedOrganizationDomainSsoDetailsResponse.VerifiedOrganizationDomainSsoDetail(
|
||||||
|
organizationIdentifier = "Bitwarden with SSO",
|
||||||
|
organizationName = "Bitwarden",
|
||||||
|
domainName = "bitwarden.com",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
authRepository.getVerifiedOrganizationDomainSsoDetails(any())
|
||||||
|
} returns orgDetails
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
featureFlagManager.getFeatureFlag(FlagKey.VerifiedSsoDomainEndpoint)
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
// Just hang on this request; login is tested elsewhere
|
||||||
|
coEvery {
|
||||||
|
authRepository.prevalidateSso(any())
|
||||||
|
} just awaits
|
||||||
|
|
||||||
|
val viewModel = createViewModel(dismissInitialDialog = false)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
orgIdentifierInput = "Bitwarden with SSO",
|
||||||
|
dialogState = EnterpriseSignOnState.DialogState.Loading(
|
||||||
|
message = R.string.logging_in.asText(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
authRepository.getVerifiedOrganizationDomainSsoDetails(DEFAULT_EMAIL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `VerifiedOrganizationDomainSsoDetails success with no verified domains should make a request, hide the dialog, and update the org input based on the remembered org`() =
|
||||||
|
runTest {
|
||||||
|
val orgDetails = VerifiedOrganizationDomainSsoDetailsResult.Success(
|
||||||
|
verifiedOrganizationDomainSsoDetails = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
authRepository.getVerifiedOrganizationDomainSsoDetails(any())
|
||||||
|
} returns orgDetails
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
featureFlagManager.getFeatureFlag(FlagKey.VerifiedSsoDomainEndpoint)
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
val viewModel = createViewModel(dismissInitialDialog = false)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
orgIdentifierInput = "",
|
||||||
|
dialogState = null,
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
authRepository.getVerifiedOrganizationDomainSsoDetails(DEFAULT_EMAIL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MaxLineLength")
|
||||||
|
@Test
|
||||||
|
fun `VerifiedOrganizationDomainSsoDetails failure should make a request, hide dialog, load from remembered org identifier`() =
|
||||||
|
runTest {
|
||||||
|
coEvery {
|
||||||
|
authRepository.getVerifiedOrganizationDomainSsoDetails(any())
|
||||||
|
} returns VerifiedOrganizationDomainSsoDetailsResult.Failure
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
featureFlagManager.getFeatureFlag(FlagKey.VerifiedSsoDomainEndpoint)
|
||||||
|
} returns true
|
||||||
|
|
||||||
|
coEvery {
|
||||||
|
authRepository.rememberedOrgIdentifier
|
||||||
|
} returns "Bitwarden"
|
||||||
|
|
||||||
|
val viewModel = createViewModel(dismissInitialDialog = false)
|
||||||
|
assertEquals(
|
||||||
|
DEFAULT_STATE.copy(
|
||||||
|
orgIdentifierInput = "Bitwarden",
|
||||||
|
dialogState = null,
|
||||||
|
),
|
||||||
|
viewModel.stateFlow.value,
|
||||||
|
)
|
||||||
|
|
||||||
|
coVerify(exactly = 1) {
|
||||||
|
authRepository.getVerifiedOrganizationDomainSsoDetails(DEFAULT_EMAIL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Suppress("LongParameterList")
|
@Suppress("LongParameterList")
|
||||||
private fun createViewModel(
|
private fun createViewModel(
|
||||||
initialState: EnterpriseSignOnState? = null,
|
initialState: EnterpriseSignOnState? = null,
|
||||||
|
@ -802,6 +949,7 @@ class EnterpriseSignOnViewModelTest : BaseViewModelTest() {
|
||||||
): EnterpriseSignOnViewModel = EnterpriseSignOnViewModel(
|
): EnterpriseSignOnViewModel = EnterpriseSignOnViewModel(
|
||||||
authRepository = authRepository,
|
authRepository = authRepository,
|
||||||
environmentRepository = environmentRepository,
|
environmentRepository = environmentRepository,
|
||||||
|
featureFlagManager = featureFlagManager,
|
||||||
generatorRepository = generatorRepository,
|
generatorRepository = generatorRepository,
|
||||||
networkConnectionManager = FakeNetworkConnectionManager(isNetworkConnected),
|
networkConnectionManager = FakeNetworkConnectionManager(isNetworkConnected),
|
||||||
savedStateHandle = savedStateHandle,
|
savedStateHandle = savedStateHandle,
|
||||||
|
|
|
@ -113,6 +113,7 @@ private val DEFAULT_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||||
FlagKey.OnboardingFlow to true,
|
FlagKey.OnboardingFlow to true,
|
||||||
FlagKey.ImportLoginsFlow to true,
|
FlagKey.ImportLoginsFlow to true,
|
||||||
FlagKey.SshKeyCipherItems to true,
|
FlagKey.SshKeyCipherItems to true,
|
||||||
|
FlagKey.VerifiedSsoDomainEndpoint to true,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||||
|
@ -122,6 +123,7 @@ private val UPDATED_MAP_VALUE: Map<FlagKey<Any>, Any> = mapOf(
|
||||||
FlagKey.OnboardingFlow to false,
|
FlagKey.OnboardingFlow to false,
|
||||||
FlagKey.ImportLoginsFlow to false,
|
FlagKey.ImportLoginsFlow to false,
|
||||||
FlagKey.SshKeyCipherItems to false,
|
FlagKey.SshKeyCipherItems to false,
|
||||||
|
FlagKey.VerifiedSsoDomainEndpoint to false,
|
||||||
)
|
)
|
||||||
|
|
||||||
private val DEFAULT_STATE = DebugMenuState(
|
private val DEFAULT_STATE = DebugMenuState(
|
||||||
|
|
Loading…
Reference in a new issue