Feature/5575 custom auth params for sign up (#5577)

Add a fun `RegistrationWizard.registrationCustom()` to be able to use any parameters during the registration.
Move terms converter into `api` package.
This commit is contained in:
TarasSmakula 2022-04-21 23:02:27 +03:00 committed by GitHub
parent 96350b0ed0
commit 2839d1467f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 268 additions and 138 deletions

2
changelog.d/5575.sdk Normal file
View file

@ -0,0 +1,2 @@
- Added registrationCustom into RegistrationWizard to send custom auth params for sign up
- Moved terms converter into api package to make it accessible in sdk

View file

@ -1,5 +1,5 @@
/*
* Copyright 2019 New Vector Ltd
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package im.vector.app.features.login.terms
package org.matrix.android.sdk.api.auth
data class UrlAndName(
val url: String,

View file

@ -1,127 +1,127 @@
/*
* Copyright 2019 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.app.features.login.terms
import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
import org.matrix.android.sdk.api.auth.registration.TermPolicies
/**
* This method extract the policies from the login terms parameter, regarding the user language.
* For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
*
* Example of Data:
* <pre>
* "m.login.terms": {
* "policies": {
* "privacy_policy": {
* "version": "1.0",
* "en": {
* "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
* "name": "Terms and Conditions"
* }
* }
* }
* }
*</pre>
*
* @param userLanguage the user language
* @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
*/
fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
defaultLanguage: String = "en"): List<LocalizedFlowDataLoginTerms> {
val result = ArrayList<LocalizedFlowDataLoginTerms>()
val policies = get("policies")
if (policies is Map<*, *>) {
policies.keys.forEach { policyName ->
val localizedFlowDataLoginTermsPolicyName = policyName as String
var localizedFlowDataLoginTermsVersion: String? = null
var localizedFlowDataLoginTermsLocalizedUrl: String? = null
var localizedFlowDataLoginTermsLocalizedName: String? = null
val policy = policies[policyName]
// Enter this policy
if (policy is Map<*, *>) {
// Version
localizedFlowDataLoginTermsVersion = policy["version"] as String?
var userLanguageUrlAndName: UrlAndName? = null
var defaultLanguageUrlAndName: UrlAndName? = null
var firstUrlAndName: UrlAndName? = null
// Search for language
policy.keys.forEach { policyKey ->
when (policyKey) {
"version" -> Unit // Ignore
userLanguage -> {
// We found the data for the user language
userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
}
defaultLanguage -> {
// We found default language
defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
}
else -> {
if (firstUrlAndName == null) {
// Get at least some data
firstUrlAndName = extractUrlAndName(policy[policyKey])
}
}
}
}
// Copy found language data by priority
when {
userLanguageUrlAndName != null -> {
localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url
localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name
}
defaultLanguageUrlAndName != null -> {
localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url
localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name
}
firstUrlAndName != null -> {
localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url
localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name
}
}
}
result.add(LocalizedFlowDataLoginTerms(
policyName = localizedFlowDataLoginTermsPolicyName,
version = localizedFlowDataLoginTermsVersion,
localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
localizedName = localizedFlowDataLoginTermsLocalizedName
))
}
}
return result
}
private fun extractUrlAndName(policyData: Any?): UrlAndName? {
if (policyData is Map<*, *>) {
val url = policyData["url"] as String?
val name = policyData["name"] as String?
if (url != null && name != null) {
return UrlAndName(url, name)
}
}
return null
}
/*
* Copyright 2020 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.api.auth
import org.matrix.android.sdk.api.auth.data.LocalizedFlowDataLoginTerms
import org.matrix.android.sdk.api.auth.registration.TermPolicies
/**
* This method extract the policies from the login terms parameter, regarding the user language.
* For each policy, if user language is not found, the default language is used and if not found, the first url and name are used (not predictable)
*
* Example of Data:
* <pre>
* "m.login.terms": {
* "policies": {
* "privacy_policy": {
* "version": "1.0",
* "en": {
* "url": "http:\/\/matrix.org\/_matrix\/consent?v=1.0",
* "name": "Terms and Conditions"
* }
* }
* }
* }
*</pre>
*
* @param userLanguage the user language
* @param defaultLanguage the default language to use if the user language is not found for a policy in registrationFlowResponse
*/
fun TermPolicies.toLocalizedLoginTerms(userLanguage: String,
defaultLanguage: String = "en"): List<LocalizedFlowDataLoginTerms> {
val result = ArrayList<LocalizedFlowDataLoginTerms>()
val policies = get("policies")
if (policies is Map<*, *>) {
policies.keys.forEach { policyName ->
val localizedFlowDataLoginTermsPolicyName = policyName as String
var localizedFlowDataLoginTermsVersion: String? = null
var localizedFlowDataLoginTermsLocalizedUrl: String? = null
var localizedFlowDataLoginTermsLocalizedName: String? = null
val policy = policies[policyName]
// Enter this policy
if (policy is Map<*, *>) {
// Version
localizedFlowDataLoginTermsVersion = policy["version"] as String?
var userLanguageUrlAndName: UrlAndName? = null
var defaultLanguageUrlAndName: UrlAndName? = null
var firstUrlAndName: UrlAndName? = null
// Search for language
policy.keys.forEach { policyKey ->
when (policyKey) {
"version" -> Unit // Ignore
userLanguage -> {
// We found the data for the user language
userLanguageUrlAndName = extractUrlAndName(policy[policyKey])
}
defaultLanguage -> {
// We found default language
defaultLanguageUrlAndName = extractUrlAndName(policy[policyKey])
}
else -> {
if (firstUrlAndName == null) {
// Get at least some data
firstUrlAndName = extractUrlAndName(policy[policyKey])
}
}
}
}
// Copy found language data by priority
when {
userLanguageUrlAndName != null -> {
localizedFlowDataLoginTermsLocalizedUrl = userLanguageUrlAndName!!.url
localizedFlowDataLoginTermsLocalizedName = userLanguageUrlAndName!!.name
}
defaultLanguageUrlAndName != null -> {
localizedFlowDataLoginTermsLocalizedUrl = defaultLanguageUrlAndName!!.url
localizedFlowDataLoginTermsLocalizedName = defaultLanguageUrlAndName!!.name
}
firstUrlAndName != null -> {
localizedFlowDataLoginTermsLocalizedUrl = firstUrlAndName!!.url
localizedFlowDataLoginTermsLocalizedName = firstUrlAndName!!.name
}
}
}
result.add(LocalizedFlowDataLoginTerms(
policyName = localizedFlowDataLoginTermsPolicyName,
version = localizedFlowDataLoginTermsVersion,
localizedUrl = localizedFlowDataLoginTermsLocalizedUrl,
localizedName = localizedFlowDataLoginTermsLocalizedName
))
}
}
return result
}
private fun extractUrlAndName(policyData: Any?): UrlAndName? {
if (policyData is Map<*, *>) {
val url = policyData["url"] as String?
val name = policyData["name"] as String?
if (url != null && name != null) {
return UrlAndName(url, name)
}
}
return null
}

View file

@ -16,6 +16,8 @@
package org.matrix.android.sdk.api.auth.registration
import org.matrix.android.sdk.api.util.JsonDict
/**
* Set of methods to be able to create an account on a homeserver.
*
@ -73,6 +75,13 @@ interface RegistrationWizard {
*/
suspend fun dummy(): RegistrationResult
/**
* Perform custom registration stage by sending a custom JsonDict.
* Current registration "session" param will be included into authParams by default.
* The authParams should contain at least one entry "type" with a String value.
*/
suspend fun registrationCustom(authParams: JsonDict): RegistrationResult
/**
* Perform the "m.login.email.identity" or "m.login.msisdn" stage.
*

View file

@ -26,6 +26,7 @@ import org.matrix.android.sdk.internal.auth.data.WebClientConfig
import org.matrix.android.sdk.internal.auth.login.ResetPasswordMailConfirmed
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationParams
import org.matrix.android.sdk.internal.auth.registration.AddThreePidRegistrationResponse
import org.matrix.android.sdk.internal.auth.registration.RegistrationCustomParams
import org.matrix.android.sdk.internal.auth.registration.RegistrationParams
import org.matrix.android.sdk.internal.auth.registration.SuccessResult
import org.matrix.android.sdk.internal.auth.registration.ValidationCodeBody
@ -68,6 +69,14 @@ internal interface AuthAPI {
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
suspend fun register(@Body registrationParams: RegistrationParams): Credentials
/**
* Register to the homeserver, or get error 401 with a RegistrationFlowResponse object if registration is incomplete
* method to perform other custom stages
* Ref: https://matrix.org/docs/spec/client_server/latest#account-registration-and-management
*/
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "register")
suspend fun registerCustom(@Body registrationCustomParams: RegistrationCustomParams): Credentials
/**
* Checks to see if a username is available, and valid, for the server.
*/

View file

@ -17,6 +17,7 @@
package org.matrix.android.sdk.internal.auth.registration
import kotlinx.coroutines.delay
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.LoginFlowTypes
import org.matrix.android.sdk.api.auth.registration.RegisterThreePid
import org.matrix.android.sdk.api.auth.registration.RegistrationAvailability
@ -25,6 +26,7 @@ import org.matrix.android.sdk.api.auth.registration.RegistrationWizard
import org.matrix.android.sdk.api.auth.registration.toFlowResult
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.Failure.RegistrationFlowError
import org.matrix.android.sdk.api.util.JsonDict
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.auth.PendingSessionStore
import org.matrix.android.sdk.internal.auth.SessionCreator
@ -45,6 +47,7 @@ internal class DefaultRegistrationWizard(
private val registerAvailableTask: RegisterAvailableTask = DefaultRegisterAvailableTask(authAPI)
private val registerAddThreePidTask: RegisterAddThreePidTask = DefaultRegisterAddThreePidTask(authAPI)
private val validateCodeTask: ValidateCodeTask = DefaultValidateCodeTask(authAPI)
private val registerCustomTask: RegisterCustomTask = DefaultRegisterCustomTask(authAPI)
override val currentThreePid: String?
get() {
@ -187,22 +190,51 @@ internal class DefaultRegistrationWizard(
return performRegistrationRequest(params)
}
private suspend fun performRegistrationRequest(registrationParams: RegistrationParams,
delayMillis: Long = 0): RegistrationResult {
override suspend fun registrationCustom(
authParams: JsonDict
): RegistrationResult {
val safeSession = pendingSessionData.currentSession
?: throw IllegalStateException("developer error, call createAccount() method first")
val mutableParams = authParams.toMutableMap()
mutableParams["session"] = safeSession
val params = RegistrationCustomParams(auth = mutableParams)
return performRegistrationOtherRequest(params)
}
private suspend fun performRegistrationRequest(
registrationParams: RegistrationParams,
delayMillis: Long = 0
): RegistrationResult {
delay(delayMillis)
return register { registerTask.execute(RegisterTask.Params(registrationParams)) }
}
private suspend fun performRegistrationOtherRequest(
registrationCustomParams: RegistrationCustomParams
): RegistrationResult {
return register { registerCustomTask.execute(RegisterCustomTask.Params(registrationCustomParams)) }
}
private suspend fun register(
execute: suspend () -> Credentials
): RegistrationResult {
val credentials = try {
registerTask.execute(RegisterTask.Params(registrationParams))
execute.invoke()
} catch (exception: Throwable) {
if (exception is RegistrationFlowError) {
pendingSessionData = pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
.also { pendingSessionStore.savePendingSessionData(it) }
pendingSessionData =
pendingSessionData.copy(currentSession = exception.registrationFlowResponse.session)
.also { pendingSessionStore.savePendingSessionData(it) }
return RegistrationResult.FlowResponse(exception.registrationFlowResponse.toFlowResult())
} else {
throw exception
}
}
val session = sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
val session =
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
return RegistrationResult.Success(session)
}

View file

@ -0,0 +1,47 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.auth.registration
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.failure.toRegistrationFlowResponse
import org.matrix.android.sdk.internal.auth.AuthAPI
import org.matrix.android.sdk.internal.network.executeRequest
import org.matrix.android.sdk.internal.task.Task
internal interface RegisterCustomTask : Task<RegisterCustomTask.Params, Credentials> {
data class Params(
val registrationCustomParams: RegistrationCustomParams
)
}
internal class DefaultRegisterCustomTask(
private val authAPI: AuthAPI
) : RegisterCustomTask {
override suspend fun execute(params: RegisterCustomTask.Params): Credentials {
try {
return executeRequest(null) {
authAPI.registerCustom(params.registrationCustomParams)
}
} catch (throwable: Throwable) {
throw throwable.toRegistrationFlowResponse()
?.let { Failure.RegistrationFlowError(it) }
?: throwable
}
}
}

View file

@ -0,0 +1,31 @@
/*
* Copyright 2021 The Matrix.org Foundation C.I.C.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.auth.registration
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import org.matrix.android.sdk.api.util.JsonDict
/**
* Class to pass parameters to the custom registration types for /register.
*/
@JsonClass(generateAdapter = true)
internal data class RegistrationCustomParams(
// authentication parameters
@Json(name = "auth")
val auth: JsonDict? = null,
)

View file

@ -42,10 +42,10 @@ import im.vector.app.features.analytics.plan.MobileScreen
import im.vector.app.features.home.HomeActivity
import im.vector.app.features.login.terms.LoginTermsFragment
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.login.terms.toLocalizedLoginTerms
import im.vector.app.features.pin.UnlockedActivity
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
import org.matrix.android.sdk.api.extensions.tryOrNull
/**

View file

@ -41,7 +41,6 @@ import im.vector.app.features.login.LoginWaitForEmailFragmentArgument
import im.vector.app.features.login.TextInputFormFragmentMode
import im.vector.app.features.login.isSupported
import im.vector.app.features.login.terms.LoginTermsFragmentArgument
import im.vector.app.features.login.terms.toLocalizedLoginTerms
import im.vector.app.features.login2.LoginAction2
import im.vector.app.features.login2.LoginCaptchaFragment2
import im.vector.app.features.login2.LoginFragmentSigninPassword2
@ -66,6 +65,7 @@ import im.vector.app.features.login2.created.AccountCreatedFragment
import im.vector.app.features.login2.terms.LoginTermsFragment2
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
import org.matrix.android.sdk.api.extensions.tryOrNull
private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG"

View file

@ -45,7 +45,6 @@ import im.vector.app.features.login.ServerType
import im.vector.app.features.login.SignMode
import im.vector.app.features.login.TextInputFormFragmentMode
import im.vector.app.features.login.isSupported
import im.vector.app.features.login.terms.toLocalizedLoginTerms
import im.vector.app.features.onboarding.OnboardingAction
import im.vector.app.features.onboarding.OnboardingActivity
import im.vector.app.features.onboarding.OnboardingVariant
@ -57,6 +56,7 @@ import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsFragment
import im.vector.app.features.onboarding.ftueauth.terms.FtueAuthTermsLegacyStyleFragmentArgument
import org.matrix.android.sdk.api.auth.registration.FlowResult
import org.matrix.android.sdk.api.auth.registration.Stage
import org.matrix.android.sdk.api.auth.toLocalizedLoginTerms
import org.matrix.android.sdk.api.extensions.tryOrNull
private const val FRAGMENT_REGISTRATION_STAGE_TAG = "FRAGMENT_REGISTRATION_STAGE_TAG"