mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-24 13:08:46 +00:00
Update username validation to use libsignal.
This commit is contained in:
@@ -182,7 +182,7 @@ public class UsernameEditFragment extends LoggingFragment {
|
||||
case DISCRIMINATOR_TOO_LONG -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits, UsernameUtil.MAX_DISCRIMINATOR_LENGTH);
|
||||
case DISCRIMINATOR_TOO_SHORT -> getString(R.string.UsernameEditFragment__invalid_username_enter_a_minimum_of_d_digits, UsernameUtil.MIN_DISCRIMINATOR_LENGTH);
|
||||
case DISCRIMINATOR_CANNOT_BE_00 -> getString(R.string.UsernameEditFragment__this_number_cant_be_00);
|
||||
case DISCRIMINATOR_CANNOT_START_WITH_00 -> getString(R.string.UsernameEditFragment__this_number_cant_start_with_00);
|
||||
case DISCRIMINATOR_CANNOT_START_WITH_0 -> getString(R.string.UsernameEditFragment__this_number_cant_start_with_0);
|
||||
};
|
||||
|
||||
int colorRes = error != null ? R.color.signal_colorError : R.color.signal_colorPrimary;
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.thoughtcrime.securesms.profiles.manage.UsernameRepository.UsernameSet
|
||||
import org.thoughtcrime.securesms.util.NetworkUtil
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.InvalidReason
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.checkDiscriminator
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.checkUsername
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.checkNickname
|
||||
import org.thoughtcrime.securesms.util.rx.RxStore
|
||||
import org.whispersystems.signalservice.api.util.Usernames
|
||||
import java.util.concurrent.TimeUnit
|
||||
@@ -159,7 +159,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
return
|
||||
}
|
||||
|
||||
val invalidReason: InvalidReason? = checkUsername(usernameState.getNickname())
|
||||
val invalidReason: InvalidReason? = checkNickname(usernameState.getNickname())
|
||||
if (invalidReason != null) {
|
||||
Log.w(TAG, "Username was submitted, but did not pass validity checks. Reason: $invalidReason")
|
||||
uiState.update { it.copy(buttonState = ButtonState.SUBMIT_DISABLED, usernameStatus = mapNicknameError(invalidReason)) }
|
||||
@@ -257,7 +257,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
return
|
||||
}
|
||||
|
||||
val invalidReason: InvalidReason? = checkUsername(nickname)
|
||||
val invalidReason: InvalidReason? = checkNickname(nickname)
|
||||
if (invalidReason != null) {
|
||||
uiState.update { uiState ->
|
||||
uiState.copy(
|
||||
@@ -366,7 +366,7 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
DISCRIMINATOR_TOO_LONG,
|
||||
DISCRIMINATOR_HAS_INVALID_CHARACTERS,
|
||||
DISCRIMINATOR_CANNOT_BE_00,
|
||||
DISCRIMINATOR_CANNOT_START_WITH_00
|
||||
DISCRIMINATOR_CANNOT_START_WITH_0
|
||||
}
|
||||
|
||||
enum class ButtonState {
|
||||
@@ -394,7 +394,9 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
InvalidReason.TOO_LONG -> UsernameStatus.TOO_LONG
|
||||
InvalidReason.STARTS_WITH_NUMBER -> UsernameStatus.CANNOT_START_WITH_NUMBER
|
||||
InvalidReason.INVALID_CHARACTERS -> UsernameStatus.INVALID_CHARACTERS
|
||||
InvalidReason.INVALID_NUMBER, InvalidReason.INVALID_NUMBER_PREFIX -> error("Unexpected reason $invalidReason")
|
||||
InvalidReason.INVALID_NUMBER,
|
||||
InvalidReason.INVALID_NUMBER_00,
|
||||
InvalidReason.INVALID_NUMBER_PREFIX_0 -> error("Unexpected reason $invalidReason")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -403,8 +405,8 @@ internal class UsernameEditViewModel private constructor(private val mode: Usern
|
||||
InvalidReason.TOO_SHORT -> UsernameStatus.DISCRIMINATOR_TOO_SHORT
|
||||
InvalidReason.TOO_LONG -> UsernameStatus.DISCRIMINATOR_TOO_LONG
|
||||
InvalidReason.INVALID_CHARACTERS -> UsernameStatus.DISCRIMINATOR_HAS_INVALID_CHARACTERS
|
||||
InvalidReason.INVALID_NUMBER -> UsernameStatus.DISCRIMINATOR_CANNOT_BE_00
|
||||
InvalidReason.INVALID_NUMBER_PREFIX -> UsernameStatus.DISCRIMINATOR_CANNOT_START_WITH_00
|
||||
InvalidReason.INVALID_NUMBER_00 -> UsernameStatus.DISCRIMINATOR_CANNOT_BE_00
|
||||
InvalidReason.INVALID_NUMBER_PREFIX_0 -> UsernameStatus.DISCRIMINATOR_CANNOT_START_WITH_0
|
||||
else -> UsernameStatus.INVALID_GENERIC
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
package org.thoughtcrime.securesms.util
|
||||
|
||||
import org.signal.core.util.logging.Log
|
||||
import org.signal.libsignal.usernames.BadDiscriminatorCharacterException
|
||||
import org.signal.libsignal.usernames.BadNicknameCharacterException
|
||||
import org.signal.libsignal.usernames.BaseUsernameException
|
||||
import org.signal.libsignal.usernames.CannotBeEmptyException
|
||||
import org.signal.libsignal.usernames.CannotStartWithDigitException
|
||||
import org.signal.libsignal.usernames.DiscriminatorCannotBeEmptyException
|
||||
import org.signal.libsignal.usernames.DiscriminatorCannotBeSingleDigitException
|
||||
import org.signal.libsignal.usernames.DiscriminatorCannotBeZeroException
|
||||
import org.signal.libsignal.usernames.DiscriminatorCannotHaveLeadingZerosException
|
||||
import org.signal.libsignal.usernames.DiscriminatorTooLargeException
|
||||
import org.signal.libsignal.usernames.NicknameTooLongException
|
||||
import org.signal.libsignal.usernames.NicknameTooShortException
|
||||
import org.signal.libsignal.usernames.Username
|
||||
import java.util.Locale
|
||||
import java.util.regex.Pattern
|
||||
|
||||
@@ -36,52 +49,75 @@ object UsernameUtil {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun checkUsername(value: String?): InvalidReason? {
|
||||
return when {
|
||||
value == null -> {
|
||||
InvalidReason.TOO_SHORT
|
||||
}
|
||||
value.length < MIN_NICKNAME_LENGTH -> {
|
||||
InvalidReason.TOO_SHORT
|
||||
}
|
||||
value.length > MAX_NICKNAME_LENGTH -> {
|
||||
InvalidReason.TOO_LONG
|
||||
}
|
||||
DIGIT_START_PATTERN.matcher(value).matches() -> {
|
||||
InvalidReason.STARTS_WITH_NUMBER
|
||||
}
|
||||
!FULL_PATTERN.matcher(value).matches() -> {
|
||||
InvalidReason.INVALID_CHARACTERS
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
fun checkNickname(value: String?): InvalidReason? {
|
||||
if (value == null) {
|
||||
return InvalidReason.TOO_SHORT
|
||||
}
|
||||
|
||||
return try {
|
||||
// We only want to check the nickname, so we pass in a known-valid discriminator
|
||||
Username.fromParts(value, "01", MIN_NICKNAME_LENGTH, MAX_NICKNAME_LENGTH)
|
||||
null
|
||||
} catch (e: BadNicknameCharacterException) {
|
||||
InvalidReason.INVALID_CHARACTERS
|
||||
} catch (e: CannotBeEmptyException) {
|
||||
InvalidReason.TOO_SHORT
|
||||
} catch (e: CannotStartWithDigitException) {
|
||||
InvalidReason.STARTS_WITH_NUMBER
|
||||
} catch (e: NicknameTooLongException) {
|
||||
InvalidReason.TOO_LONG
|
||||
} catch (e: NicknameTooShortException) {
|
||||
InvalidReason.TOO_SHORT
|
||||
} catch (e: BaseUsernameException) {
|
||||
Log.w(TAG, "Unhandled verification exception!", e)
|
||||
InvalidReason.INVALID_CHARACTERS
|
||||
}
|
||||
}
|
||||
|
||||
fun checkDiscriminator(value: String?): InvalidReason? {
|
||||
return when {
|
||||
value == null -> {
|
||||
null
|
||||
}
|
||||
value == "00" -> {
|
||||
InvalidReason.INVALID_NUMBER
|
||||
}
|
||||
value.startsWith("00") -> {
|
||||
InvalidReason.INVALID_NUMBER_PREFIX
|
||||
}
|
||||
value.length < MIN_DISCRIMINATOR_LENGTH -> {
|
||||
if (value == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (value.length < MIN_DISCRIMINATOR_LENGTH) {
|
||||
return InvalidReason.TOO_SHORT
|
||||
}
|
||||
|
||||
if (value.length > MAX_DISCRIMINATOR_LENGTH) {
|
||||
return InvalidReason.TOO_LONG
|
||||
}
|
||||
|
||||
return try {
|
||||
// We only want to check the discriminator, so we pass in a known-valid nickname
|
||||
Username.fromParts("spiderman", value, MIN_NICKNAME_LENGTH, MAX_NICKNAME_LENGTH)
|
||||
null
|
||||
} catch (e: BadDiscriminatorCharacterException) {
|
||||
InvalidReason.INVALID_CHARACTERS
|
||||
} catch (e: DiscriminatorCannotBeEmptyException) {
|
||||
InvalidReason.TOO_SHORT
|
||||
} catch (e: DiscriminatorCannotBeSingleDigitException) {
|
||||
InvalidReason.TOO_SHORT
|
||||
} catch (e: DiscriminatorCannotBeZeroException) {
|
||||
if (value.length < 2) {
|
||||
InvalidReason.TOO_SHORT
|
||||
} else if (value == "00") {
|
||||
InvalidReason.INVALID_NUMBER_00
|
||||
} else {
|
||||
InvalidReason.INVALID_NUMBER_PREFIX_0
|
||||
}
|
||||
value.length > MAX_DISCRIMINATOR_LENGTH -> {
|
||||
InvalidReason.TOO_LONG
|
||||
}
|
||||
value.toIntOrNull() == null -> {
|
||||
InvalidReason.INVALID_CHARACTERS
|
||||
}
|
||||
else -> {
|
||||
null
|
||||
} catch (e: DiscriminatorCannotHaveLeadingZerosException) {
|
||||
if (value.length < 2) {
|
||||
InvalidReason.TOO_SHORT
|
||||
} else if (value == "00") {
|
||||
InvalidReason.INVALID_NUMBER_00
|
||||
} else {
|
||||
InvalidReason.INVALID_NUMBER_PREFIX_0
|
||||
}
|
||||
} catch (e: DiscriminatorTooLargeException) {
|
||||
InvalidReason.TOO_LONG
|
||||
} catch (e: BaseUsernameException) {
|
||||
Log.w(TAG, "Unhandled verification exception!", e)
|
||||
InvalidReason.INVALID_CHARACTERS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +127,7 @@ object UsernameUtil {
|
||||
INVALID_CHARACTERS,
|
||||
STARTS_WITH_NUMBER,
|
||||
INVALID_NUMBER,
|
||||
INVALID_NUMBER_PREFIX
|
||||
INVALID_NUMBER_00,
|
||||
INVALID_NUMBER_PREFIX_0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2320,8 +2320,8 @@
|
||||
<string name="UsernameEditFragment__invalid_username_enter_a_maximum_of_d_digits">Invalid username, enter a maximum of %1$d digits.</string>
|
||||
<!-- Displayed when the chosen discriminator is 00 -->
|
||||
<string name="UsernameEditFragment__this_number_cant_be_00">This number can\'t be 00. Enter a digit between 1–9</string>
|
||||
<!-- Displayed when the chosen discriminator starts with 00 -->
|
||||
<string name="UsernameEditFragment__this_number_cant_start_with_00">This number can\'t start with 00. Enter a digit between 1–9</string>
|
||||
<!-- Displayed when the chosen discriminator starts with 0 and has a length > 2 -->
|
||||
<string name="UsernameEditFragment__this_number_cant_start_with_0">Numbers with more than 2 digits can\'t start with 0</string>
|
||||
<!-- The body of an alert dialog asking the user to confirm that they want to recover their username -->
|
||||
<string name="UsernameEditFragment_recovery_dialog_confirmation">Recovering your username will reset your existing QR code and link. Are you sure?</string>
|
||||
<!-- The body of an alert dialog asking the user to confirm that they want to change their username, even if it resets their link -->
|
||||
|
||||
@@ -3,45 +3,81 @@ package org.thoughtcrime.securesms.util
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.assertIs
|
||||
import org.thoughtcrime.securesms.assertIsNull
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.checkUsername
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.checkDiscriminator
|
||||
import org.thoughtcrime.securesms.util.UsernameUtil.checkNickname
|
||||
|
||||
class UsernameUtilTest {
|
||||
@Test
|
||||
fun checkUsername_tooShort() {
|
||||
checkUsername(null) assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
checkUsername("") assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
checkUsername("ab") assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
checkNickname(null) assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
checkNickname("") assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
checkNickname("ab") assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkUsername_tooLong() {
|
||||
checkUsername("abcdefghijklmnopqrstuvwxyz1234567") assertIs UsernameUtil.InvalidReason.TOO_LONG
|
||||
checkNickname("abcdefghijklmnopqrstuvwxyz1234567") assertIs UsernameUtil.InvalidReason.TOO_LONG
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkUsername_startsWithNumber() {
|
||||
checkUsername("0abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
|
||||
checkUsername("9abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
|
||||
checkUsername("8675309") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
|
||||
checkNickname("0abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
|
||||
checkNickname("9abcdefg") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
|
||||
checkNickname("8675309") assertIs UsernameUtil.InvalidReason.STARTS_WITH_NUMBER
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkUsername_invalidCharacters() {
|
||||
checkUsername("\$abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkUsername(" abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkUsername("ab cde") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkUsername("%%%%%") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkUsername("-----") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkUsername("asĸ_me") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkUsername("+18675309") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname("\$abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname(" abcd") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname("ab cde") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname("%%%%%") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname("-----") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname("asĸ_me") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkNickname("+18675309") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkUsername_validUsernames() {
|
||||
checkUsername("abcd").assertIsNull()
|
||||
checkUsername("abcdefghijklmnopqrstuvwxyz").assertIsNull()
|
||||
checkUsername("ABCDEFGHIJKLMNOPQRSTUVWXYZ").assertIsNull()
|
||||
checkUsername("web_head").assertIsNull()
|
||||
checkUsername("Spider_Fan_1991").assertIsNull()
|
||||
checkNickname("abcd").assertIsNull()
|
||||
checkNickname("abcdefghijklmnopqrstuvwxyz").assertIsNull()
|
||||
checkNickname("ABCDEFGHIJKLMNOPQRSTUVWXYZ").assertIsNull()
|
||||
checkNickname("web_head").assertIsNull()
|
||||
checkNickname("Spider_Fan_1991").assertIsNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDiscriminator_valid() {
|
||||
checkDiscriminator(null).assertIsNull()
|
||||
checkDiscriminator("01").assertIsNull()
|
||||
checkDiscriminator("111111111").assertIsNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDiscriminator_tooShort() {
|
||||
checkDiscriminator("0") assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
checkDiscriminator("") assertIs UsernameUtil.InvalidReason.TOO_SHORT
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDiscriminator_tooLong() {
|
||||
checkDiscriminator("1111111111") assertIs UsernameUtil.InvalidReason.TOO_LONG
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDiscriminator_00() {
|
||||
checkDiscriminator("00") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_00
|
||||
}
|
||||
|
||||
@Test
|
||||
fun checkDiscriminator_prefixZero() {
|
||||
checkDiscriminator("001") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_PREFIX_0
|
||||
checkDiscriminator("0001") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_PREFIX_0
|
||||
checkDiscriminator("011") assertIs UsernameUtil.InvalidReason.INVALID_NUMBER_PREFIX_0
|
||||
}
|
||||
|
||||
fun checkDiscriminator_invalidChars() {
|
||||
checkDiscriminator("a1") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
checkDiscriminator("1x") assertIs UsernameUtil.InvalidReason.INVALID_CHARACTERS
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user