parent
f968b21bf6
commit
426fc924fb
22 changed files with 90 additions and 55 deletions
|
@ -42,7 +42,8 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
instance = this
|
instance = this
|
||||||
if (BuildConfig.ENABLE_DEBUG_FEATURES ||
|
if (
|
||||||
|
BuildConfig.ENABLE_DEBUG_FEATURES ||
|
||||||
prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)
|
prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)
|
||||||
) {
|
) {
|
||||||
LogcatLogger.install(AndroidLogcatLogger(DEBUG))
|
LogcatLogger.install(AndroidLogcatLogger(DEBUG))
|
||||||
|
|
|
@ -195,7 +195,8 @@ class AutofillDecryptActivity : AppCompatActivity() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
.onSuccess { result ->
|
.onSuccess { result ->
|
||||||
return when (val resultCode =
|
return when (
|
||||||
|
val resultCode =
|
||||||
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
|
result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)
|
||||||
) {
|
) {
|
||||||
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
OpenPgpApi.RESULT_CODE_SUCCESS -> {
|
||||||
|
|
|
@ -204,7 +204,8 @@ class AutofillFilterView : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
// Switch RecyclerView out for a "no results" message if the new list is empty and
|
// Switch RecyclerView out for a "no results" message if the new list is empty and
|
||||||
// the message is not yet shown (and vice versa).
|
// the message is not yet shown (and vice versa).
|
||||||
if ((list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) ||
|
if (
|
||||||
|
(list.isEmpty() && rvPasswordSwitcher.nextView.id == rvPasswordEmpty.id) ||
|
||||||
(list.isNotEmpty() && rvPasswordSwitcher.nextView.id == rvPassword.id)
|
(list.isNotEmpty() && rvPasswordSwitcher.nextView.id == rvPassword.id)
|
||||||
) {
|
) {
|
||||||
rvPasswordSwitcher.showNext()
|
rvPasswordSwitcher.showNext()
|
||||||
|
|
|
@ -274,7 +274,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
||||||
// Allow the user to quickly switch between storing the username as the filename or
|
// Allow the user to quickly switch between storing the username as the filename or
|
||||||
// in the encrypted extras. This only makes sense if the directory structure is
|
// in the encrypted extras. This only makes sense if the directory structure is
|
||||||
// FileBased.
|
// FileBased.
|
||||||
if (suggestedName == null &&
|
if (
|
||||||
|
suggestedName == null &&
|
||||||
AutofillPreferences.directoryStructure(this@PasswordCreationActivity) ==
|
AutofillPreferences.directoryStructure(this@PasswordCreationActivity) ==
|
||||||
DirectoryStructure.FileBased
|
DirectoryStructure.FileBased
|
||||||
) {
|
) {
|
||||||
|
@ -418,9 +419,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
||||||
GpgIdentifier.fromString(line)
|
GpgIdentifier.fromString(line)
|
||||||
?: run {
|
?: run {
|
||||||
// The line being empty means this is most likely an empty `.gpg-id`
|
// The line being empty means this is most likely an empty `.gpg-id`
|
||||||
// file
|
// file we created. Skip the validation so we can make the user add a
|
||||||
// we created. Skip the validation so we can make the user add a real
|
// real ID.
|
||||||
// ID.
|
|
||||||
if (line.isEmpty()) return@run
|
if (line.isEmpty()) return@run
|
||||||
if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) {
|
if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) {
|
||||||
snackbar(message = resources.getString(R.string.short_key_ids_unsupported))
|
snackbar(message = resources.getString(R.string.short_key_ids_unsupported))
|
||||||
|
@ -488,7 +488,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
||||||
// Additionally, if we were editing and the incoming and outgoing
|
// Additionally, if we were editing and the incoming and outgoing
|
||||||
// filenames differ, it means we renamed. Ensure that the target
|
// filenames differ, it means we renamed. Ensure that the target
|
||||||
// doesn't already exist to prevent an accidental overwrite.
|
// doesn't already exist to prevent an accidental overwrite.
|
||||||
if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) &&
|
if (
|
||||||
|
(!editing || (editing && suggestedName != file.nameWithoutExtension)) &&
|
||||||
file.exists()
|
file.exists()
|
||||||
) {
|
) {
|
||||||
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
||||||
|
@ -535,7 +536,8 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB
|
||||||
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directoryInputLayout.isVisible &&
|
if (
|
||||||
|
directoryInputLayout.isVisible &&
|
||||||
directoryInputLayout.isEnabled &&
|
directoryInputLayout.isEnabled &&
|
||||||
oldFileName != null
|
oldFileName != null
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -204,7 +204,8 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
|
||||||
// Allow the user to quickly switch between storing the username as the filename or
|
// Allow the user to quickly switch between storing the username as the filename or
|
||||||
// in the encrypted extras. This only makes sense if the directory structure is
|
// in the encrypted extras. This only makes sense if the directory structure is
|
||||||
// FileBased.
|
// FileBased.
|
||||||
if (suggestedName == null &&
|
if (
|
||||||
|
suggestedName == null &&
|
||||||
AutofillPreferences.directoryStructure(this@PasswordCreationActivityV2) ==
|
AutofillPreferences.directoryStructure(this@PasswordCreationActivityV2) ==
|
||||||
DirectoryStructure.FileBased
|
DirectoryStructure.FileBased
|
||||||
) {
|
) {
|
||||||
|
@ -368,8 +369,8 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
|
||||||
// Additionally, if we were editing and the incoming and outgoing
|
// Additionally, if we were editing and the incoming and outgoing
|
||||||
// filenames differ, it means we renamed. Ensure that the target
|
// filenames differ, it means we renamed. Ensure that the target
|
||||||
// doesn't already exist to prevent an accidental overwrite.
|
// doesn't already exist to prevent an accidental overwrite.
|
||||||
if ((!editing || (editing && suggestedName != file.nameWithoutExtension)) &&
|
if (
|
||||||
file.exists()
|
(!editing || (editing && suggestedName != file.nameWithoutExtension)) && file.exists()
|
||||||
) {
|
) {
|
||||||
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
snackbar(message = getString(R.string.password_creation_duplicate_error))
|
||||||
return@runCatching
|
return@runCatching
|
||||||
|
@ -407,7 +408,8 @@ class PasswordCreationActivityV2 : BasePgpActivity() {
|
||||||
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
returnIntent.putExtra(RETURN_EXTRA_USERNAME, username)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (directoryInputLayout.isVisible &&
|
if (
|
||||||
|
directoryInputLayout.isVisible &&
|
||||||
directoryInputLayout.isEnabled &&
|
directoryInputLayout.isEnabled &&
|
||||||
oldFileName != null
|
oldFileName != null
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -65,7 +65,8 @@ class SelectFolderFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
if (item.type == PasswordItem.TYPE_CATEGORY) {
|
||||||
model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly)
|
model.navigateTo(item.file, listMode = ListMode.DirectoriesOnly)
|
||||||
(requireActivity() as AppCompatActivity)
|
(requireActivity() as AppCompatActivity)
|
||||||
.supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
.supportActionBar
|
||||||
|
?.setDisplayHomeAsUpEnabled(true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -152,7 +152,8 @@ abstract class BaseGitActivity : ContinuationContainerActivity() {
|
||||||
private fun isExplicitlyUserInitiatedError(throwable: Throwable): Boolean {
|
private fun isExplicitlyUserInitiatedError(throwable: Throwable): Boolean {
|
||||||
var cause: Throwable? = throwable
|
var cause: Throwable? = throwable
|
||||||
while (cause != null) {
|
while (cause != null) {
|
||||||
if (cause is SSHException && cause.disconnectReason == DisconnectReason.AUTH_CANCELLED_BY_USER
|
if (
|
||||||
|
cause is SSHException && cause.disconnectReason == DisconnectReason.AUTH_CANCELLED_BY_USER
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
cause = cause.cause
|
cause = cause.cause
|
||||||
|
@ -170,11 +171,13 @@ abstract class BaseGitActivity : ContinuationContainerActivity() {
|
||||||
// exceptions.
|
// exceptions.
|
||||||
// Also, SSHJ's UserAuthException about exhausting available authentication methods hides
|
// Also, SSHJ's UserAuthException about exhausting available authentication methods hides
|
||||||
// more useful exceptions.
|
// more useful exceptions.
|
||||||
while ((rootCause is org.eclipse.jgit.errors.TransportException ||
|
while (
|
||||||
|
(rootCause is org.eclipse.jgit.errors.TransportException ||
|
||||||
rootCause is org.eclipse.jgit.api.errors.TransportException ||
|
rootCause is org.eclipse.jgit.api.errors.TransportException ||
|
||||||
rootCause is org.eclipse.jgit.api.errors.InvalidRemoteException ||
|
rootCause is org.eclipse.jgit.api.errors.InvalidRemoteException ||
|
||||||
(rootCause is UserAuthException &&
|
(rootCause is UserAuthException &&
|
||||||
rootCause.message == "Exhausted available authentication methods"))) {
|
rootCause.message == "Exhausted available authentication methods"))
|
||||||
|
) {
|
||||||
rootCause = rootCause.cause ?: break
|
rootCause = rootCause.cause ?: break
|
||||||
}
|
}
|
||||||
return rootCause
|
return rootCause
|
||||||
|
|
|
@ -140,7 +140,8 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
.show(supportFragmentManager, "SSH_SCHEME_WARNING")
|
.show(supportFragmentManager, "SSH_SCHEME_WARNING")
|
||||||
return@setOnClickListener
|
return@setOnClickListener
|
||||||
}
|
}
|
||||||
when (val updateResult =
|
when (
|
||||||
|
val updateResult =
|
||||||
gitSettings.updateConnectionSettingsIfValid(
|
gitSettings.updateConnectionSettingsIfValid(
|
||||||
newAuthMode = newAuthMode,
|
newAuthMode = newAuthMode,
|
||||||
newUrl = binding.serverUrl.text.toString().trim(),
|
newUrl = binding.serverUrl.text.toString().trim(),
|
||||||
|
@ -230,7 +231,8 @@ class GitServerConfigActivity : BaseGitActivity() {
|
||||||
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory())
|
val localDir = requireNotNull(PasswordRepository.getRepositoryDirectory())
|
||||||
val localDirFiles = localDir.listFiles() ?: emptyArray()
|
val localDirFiles = localDir.listFiles() ?: emptyArray()
|
||||||
// Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
|
// Warn if non-empty folder unless it's a just-initialized store that has just a .git folder
|
||||||
if (localDir.exists() &&
|
if (
|
||||||
|
localDir.exists() &&
|
||||||
localDirFiles.isNotEmpty() &&
|
localDirFiles.isNotEmpty() &&
|
||||||
!(localDirFiles.size == 1 && localDirFiles[0].name == ".git")
|
!(localDirFiles.size == 1 && localDirFiles[0].name == ".git")
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -303,7 +303,8 @@ class PasswordFragment : Fragment(R.layout.password_recycler_view) {
|
||||||
listener =
|
listener =
|
||||||
object : OnFragmentInteractionListener {
|
object : OnFragmentInteractionListener {
|
||||||
override fun onFragmentInteraction(item: PasswordItem) {
|
override fun onFragmentInteraction(item: PasswordItem) {
|
||||||
if (settings.getString(PreferenceKeys.SORT_ORDER) ==
|
if (
|
||||||
|
settings.getString(PreferenceKeys.SORT_ORDER) ==
|
||||||
PasswordSortOrder.RECENTLY_USED.name
|
PasswordSortOrder.RECENTLY_USED.name
|
||||||
) {
|
) {
|
||||||
// save the time when password was used
|
// save the time when password was used
|
||||||
|
|
|
@ -182,7 +182,8 @@ class PasswordStore : BaseGitActivity() {
|
||||||
|
|
||||||
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
|
||||||
// open search view on search key, or Ctr+F
|
// open search view on search key, or Ctr+F
|
||||||
if ((keyCode == KeyEvent.KEYCODE_SEARCH ||
|
if (
|
||||||
|
(keyCode == KeyEvent.KEYCODE_SEARCH ||
|
||||||
keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && !searchItem.isActionViewExpanded
|
keyCode == KeyEvent.KEYCODE_F && event.isCtrlPressed) && !searchItem.isActionViewExpanded
|
||||||
) {
|
) {
|
||||||
searchItem.expandActionView()
|
searchItem.expandActionView()
|
||||||
|
@ -353,7 +354,8 @@ class PasswordStore : BaseGitActivity() {
|
||||||
if (localDir != null && settings.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)) {
|
if (localDir != null && settings.getBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, false)) {
|
||||||
logcat { "Check, dir: ${localDir.absolutePath}" }
|
logcat { "Check, dir: ${localDir.absolutePath}" }
|
||||||
// do not push the fragment if we already have it
|
// do not push the fragment if we already have it
|
||||||
if (getPasswordFragment() == null || settings.getBoolean(PreferenceKeys.REPO_CHANGED, false)
|
if (
|
||||||
|
getPasswordFragment() == null || settings.getBoolean(PreferenceKeys.REPO_CHANGED, false)
|
||||||
) {
|
) {
|
||||||
settings.edit { putBoolean(PreferenceKeys.REPO_CHANGED, false) }
|
settings.edit { putBoolean(PreferenceKeys.REPO_CHANGED, false) }
|
||||||
val args = Bundle()
|
val args = Bundle()
|
||||||
|
|
|
@ -127,7 +127,8 @@ class SshKeyGenActivity : AppCompatActivity() {
|
||||||
this@SshKeyGenActivity,
|
this@SshKeyGenActivity,
|
||||||
R.string.biometric_prompt_title_ssh_keygen
|
R.string.biometric_prompt_title_ssh_keygen
|
||||||
) { result ->
|
) { result ->
|
||||||
// Do not cancel on failed attempts as these are handled by the authenticator UI.
|
// Do not cancel on failed attempts as these are handled by the
|
||||||
|
// authenticator UI.
|
||||||
if (result !is Result.Retry) cont.resume(result)
|
if (result !is Result.Retry) cont.resume(result)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,7 +87,8 @@ object BiometricAuthenticator {
|
||||||
BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> {
|
BiometricPrompt.ERROR_UNABLE_TO_PROCESS -> {
|
||||||
Result.Retry
|
Result.Retry
|
||||||
}
|
}
|
||||||
// We cover all guaranteed values above, but [errorCode] is still an Int at the end of
|
// We cover all guaranteed values above, but [errorCode] is still an Int
|
||||||
|
// at the end of
|
||||||
// the day so a
|
// the day so a
|
||||||
// catch-all else will always be required.
|
// catch-all else will always be required.
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
@ -145,7 +145,8 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseResult(request: Request, result: Intent): ApiResponse {
|
private suspend fun parseResult(request: Request, result: Intent): ApiResponse {
|
||||||
return when (result.getIntExtra(
|
return when (
|
||||||
|
result.getIntExtra(
|
||||||
SshAuthenticationApi.EXTRA_RESULT_CODE,
|
SshAuthenticationApi.EXTRA_RESULT_CODE,
|
||||||
SshAuthenticationApi.RESULT_CODE_ERROR
|
SshAuthenticationApi.RESULT_CODE_ERROR
|
||||||
)
|
)
|
||||||
|
@ -196,8 +197,8 @@ class OpenKeychainKeyProvider private constructor(val activity: ContinuationCont
|
||||||
privateKey =
|
privateKey =
|
||||||
object : OpenKeychainPrivateKey {
|
object : OpenKeychainPrivateKey {
|
||||||
override suspend fun sign(challenge: ByteArray, hashAlgorithm: Int) =
|
override suspend fun sign(challenge: ByteArray, hashAlgorithm: Int) =
|
||||||
when (val signingResponse =
|
when (
|
||||||
executeApiRequest(SigningRequest(challenge, keyId, hashAlgorithm))
|
val signingResponse = executeApiRequest(SigningRequest(challenge, keyId, hashAlgorithm))
|
||||||
) {
|
) {
|
||||||
is ApiResponse.Success -> (signingResponse.response as SigningResponse).signature
|
is ApiResponse.Success -> (signingResponse.response as SigningResponse).signature
|
||||||
is ApiResponse.GeneralError -> throw signingResponse.exception
|
is ApiResponse.GeneralError -> throw signingResponse.exception
|
||||||
|
|
|
@ -209,7 +209,8 @@ object SshKey {
|
||||||
|
|
||||||
// The file must have more than 2 lines, and the first and last line must have private key
|
// The file must have more than 2 lines, and the first and last line must have private key
|
||||||
// markers.
|
// markers.
|
||||||
if (lines.size < 2 ||
|
if (
|
||||||
|
lines.size < 2 ||
|
||||||
!Regex("BEGIN .* PRIVATE KEY").containsMatchIn(lines.first()) ||
|
!Regex("BEGIN .* PRIVATE KEY").containsMatchIn(lines.first()) ||
|
||||||
!Regex("END .* PRIVATE KEY").containsMatchIn(lines.last())
|
!Regex("END .* PRIVATE KEY").containsMatchIn(lines.last())
|
||||||
)
|
)
|
||||||
|
@ -349,9 +350,12 @@ object SshKey {
|
||||||
|
|
||||||
override fun getPrivate(): PrivateKey =
|
override fun getPrivate(): PrivateKey =
|
||||||
runCatching {
|
runCatching {
|
||||||
// The current MasterKey API does not allow getting a reference to an existing one
|
// The current MasterKey API does not allow getting a reference to an existing
|
||||||
// without specifying the KeySpec for a new one. However, the value for passed here
|
// one
|
||||||
// for `requireAuthentication` is not used as the key already exists at this point.
|
// without specifying the KeySpec for a new one. However, the value for passed
|
||||||
|
// here
|
||||||
|
// for `requireAuthentication` is not used as the key already exists at this
|
||||||
|
// point.
|
||||||
val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) }
|
val encryptedPrivateKeyFile = runBlocking { getOrCreateWrappedPrivateKeyFile(false) }
|
||||||
val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() }
|
val rawPrivateKey = encryptedPrivateKeyFile.openFileInput().use { it.readBytes() }
|
||||||
EdDSAPrivateKey(
|
EdDSAPrivateKey(
|
||||||
|
|
|
@ -150,7 +150,8 @@ constructor(
|
||||||
in listOf("ssh", null) -> Protocol.Ssh
|
in listOf("ssh", null) -> Protocol.Ssh
|
||||||
else -> return UpdateConnectionSettingsResult.FailedToParseUrl
|
else -> return UpdateConnectionSettingsResult.FailedToParseUrl
|
||||||
}
|
}
|
||||||
if ((newAuthMode != AuthMode.None && newProtocol != Protocol.Https) &&
|
if (
|
||||||
|
(newAuthMode != AuthMode.None && newProtocol != Protocol.Https) &&
|
||||||
parsedUrl.user.isNullOrBlank()
|
parsedUrl.user.isNullOrBlank()
|
||||||
)
|
)
|
||||||
return UpdateConnectionSettingsResult.MissingUsername(newProtocol)
|
return UpdateConnectionSettingsResult.MissingUsername(newProtocol)
|
||||||
|
|
|
@ -77,7 +77,8 @@ private fun migrateToGitUrlBasedConfig(sharedPrefs: SharedPreferences, gitSettin
|
||||||
remove(PreferenceKeys.GIT_REMOTE_USERNAME)
|
remove(PreferenceKeys.GIT_REMOTE_USERNAME)
|
||||||
remove(PreferenceKeys.GIT_REMOTE_PROTOCOL)
|
remove(PreferenceKeys.GIT_REMOTE_PROTOCOL)
|
||||||
}
|
}
|
||||||
if (url == null ||
|
if (
|
||||||
|
url == null ||
|
||||||
gitSettings.updateConnectionSettingsIfValid(
|
gitSettings.updateConnectionSettingsIfValid(
|
||||||
newAuthMode = gitSettings.authMode,
|
newAuthMode = gitSettings.authMode,
|
||||||
newUrl = url,
|
newUrl = url,
|
||||||
|
@ -99,7 +100,8 @@ private fun migrateToHideAll(sharedPrefs: SharedPreferences) {
|
||||||
|
|
||||||
private fun migrateToSshKey(filesDirPath: String, sharedPrefs: SharedPreferences) {
|
private fun migrateToSshKey(filesDirPath: String, sharedPrefs: SharedPreferences) {
|
||||||
val privateKeyFile = File(filesDirPath, ".ssh_key")
|
val privateKeyFile = File(filesDirPath, ".ssh_key")
|
||||||
if (sharedPrefs.contains(PreferenceKeys.USE_GENERATED_KEY) &&
|
if (
|
||||||
|
sharedPrefs.contains(PreferenceKeys.USE_GENERATED_KEY) &&
|
||||||
!SshKey.exists &&
|
!SshKey.exists &&
|
||||||
privateKeyFile.exists()
|
privateKeyFile.exists()
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -42,7 +42,8 @@ class UriTotpFinder @Inject constructor() : TotpFinder {
|
||||||
private fun getQueryParameter(content: String, parameterName: String): String? {
|
private fun getQueryParameter(content: String, parameterName: String): String? {
|
||||||
content.split("\n".toRegex()).forEach { line ->
|
content.split("\n".toRegex()).forEach { line ->
|
||||||
val uri = Uri.parse(line)
|
val uri = Uri.parse(line)
|
||||||
if (line.startsWith(TotpFinder.TOTP_FIELDS[0]) && uri.getQueryParameter(parameterName) != null
|
if (
|
||||||
|
line.startsWith(TotpFinder.TOTP_FIELDS[0]) && uri.getQueryParameter(parameterName) != null
|
||||||
) {
|
) {
|
||||||
return uri.getQueryParameter(parameterName)
|
return uri.getQueryParameter(parameterName)
|
||||||
}
|
}
|
||||||
|
|
|
@ -230,8 +230,10 @@ private fun getBrowserAutofillSupportLevel(
|
||||||
browserInfo.saveFlags == null -> BrowserAutofillSupportLevel.GeneralFill
|
browserInfo.saveFlags == null -> BrowserAutofillSupportLevel.GeneralFill
|
||||||
else -> BrowserAutofillSupportLevel.GeneralFillAndSave
|
else -> BrowserAutofillSupportLevel.GeneralFillAndSave
|
||||||
}.takeUnless { supportLevel ->
|
}.takeUnless { supportLevel ->
|
||||||
// On Android Oreo, only browsers with native Autofill support can be used with Password Store
|
// On Android Oreo, only browsers with native Autofill support can be used with Password
|
||||||
// (compatibility mode is only available on Android Pie and higher). Since all known browsers
|
// Store
|
||||||
|
// (compatibility mode is only available on Android Pie and higher). Since all known
|
||||||
|
// browsers
|
||||||
// with native Autofill support offer full save support as well, we reuse the list of those
|
// with native Autofill support offer full save support as well, we reuse the list of those
|
||||||
// browsers here.
|
// browsers here.
|
||||||
supportLevel != BrowserAutofillSupportLevel.GeneralFillAndSave && Build.VERSION.SDK_INT < 28
|
supportLevel != BrowserAutofillSupportLevel.GeneralFillAndSave && Build.VERSION.SDK_INT < 28
|
||||||
|
|
|
@ -39,6 +39,6 @@ class SpotlessPlugin : Plugin<Project> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
private const val KTFMT_VERSION = "0.37"
|
private const val KTFMT_VERSION = "0.38"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,8 @@ internal object Otp {
|
||||||
code[0] = (0x7f and code[0].toInt()).toByte()
|
code[0] = (0x7f and code[0].toInt()).toByte()
|
||||||
val codeInt = ByteBuffer.wrap(code).int
|
val codeInt = ByteBuffer.wrap(code).int
|
||||||
check(codeInt > 0)
|
check(codeInt > 0)
|
||||||
// SteamGuard is a horrible OTP implementation that generates non-standard 5 digit OTPs as well
|
// SteamGuard is a horrible OTP implementation that generates non-standard 5 digit OTPs as
|
||||||
|
// well
|
||||||
// as uses a custom character set.
|
// as uses a custom character set.
|
||||||
if (digits == "s" || issuer == "Steam") {
|
if (digits == "s" || issuer == "Steam") {
|
||||||
var remainingCodeInt = codeInt
|
var remainingCodeInt = codeInt
|
||||||
|
|
|
@ -36,7 +36,8 @@ internal object RandomPasswordGenerator {
|
||||||
var password = ""
|
var password = ""
|
||||||
while (password.length < targetLength) {
|
while (password.length < targetLength) {
|
||||||
val candidate = bank.secureRandomCharacter()
|
val candidate = bank.secureRandomCharacter()
|
||||||
if (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
if (
|
||||||
|
pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
||||||
candidate in PasswordGenerator.AMBIGUOUS_STR
|
candidate in PasswordGenerator.AMBIGUOUS_STR
|
||||||
) {
|
) {
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -97,7 +97,8 @@ internal object RandomPhonemesGenerator {
|
||||||
val candidate = elements.secureRandomElement()
|
val candidate = elements.secureRandomElement()
|
||||||
|
|
||||||
// Reroll if the candidate does not fulfill the current requirements.
|
// Reroll if the candidate does not fulfill the current requirements.
|
||||||
if (!candidate.flags.hasFlag(nextBasicType) ||
|
if (
|
||||||
|
!candidate.flags.hasFlag(nextBasicType) ||
|
||||||
(isStartOfPart && candidate.flags hasFlag NOT_FIRST) ||
|
(isStartOfPart && candidate.flags hasFlag NOT_FIRST) ||
|
||||||
// Don't let a diphthong that starts with a vowel follow a vowel.
|
// Don't let a diphthong that starts with a vowel follow a vowel.
|
||||||
(previousFlags hasFlag VOWEL &&
|
(previousFlags hasFlag VOWEL &&
|
||||||
|
@ -116,7 +117,8 @@ internal object RandomPhonemesGenerator {
|
||||||
val useUpperIfBothCasesAllowed =
|
val useUpperIfBothCasesAllowed =
|
||||||
(isStartOfPart || candidate.flags hasFlag CONSONANT) && secureRandomBiasedBoolean(20)
|
(isStartOfPart || candidate.flags hasFlag CONSONANT) && secureRandomBiasedBoolean(20)
|
||||||
password +=
|
password +=
|
||||||
if (pwFlags hasFlag PasswordGenerator.UPPERS &&
|
if (
|
||||||
|
pwFlags hasFlag PasswordGenerator.UPPERS &&
|
||||||
(!(pwFlags hasFlag PasswordGenerator.LOWERS) || useUpperIfBothCasesAllowed)
|
(!(pwFlags hasFlag PasswordGenerator.LOWERS) || useUpperIfBothCasesAllowed)
|
||||||
) {
|
) {
|
||||||
candidate.upperCase
|
candidate.upperCase
|
||||||
|
@ -131,15 +133,16 @@ internal object RandomPhonemesGenerator {
|
||||||
// Second part: Add digits and symbols with a certain probability (if requested) if
|
// Second part: Add digits and symbols with a certain probability (if requested) if
|
||||||
// they would not directly follow the first character in a pronounceable part.
|
// they would not directly follow the first character in a pronounceable part.
|
||||||
|
|
||||||
if (!isStartOfPart &&
|
if (
|
||||||
pwFlags hasFlag PasswordGenerator.DIGITS &&
|
!isStartOfPart && pwFlags hasFlag PasswordGenerator.DIGITS && secureRandomBiasedBoolean(30)
|
||||||
secureRandomBiasedBoolean(30)
|
|
||||||
) {
|
) {
|
||||||
var randomDigit: Char
|
var randomDigit: Char
|
||||||
do {
|
do {
|
||||||
randomDigit = secureRandomNumber(10).toString(10).first()
|
randomDigit = secureRandomNumber(10).toString(10).first()
|
||||||
} while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
} while (
|
||||||
randomDigit in PasswordGenerator.AMBIGUOUS_STR)
|
pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
||||||
|
randomDigit in PasswordGenerator.AMBIGUOUS_STR
|
||||||
|
)
|
||||||
|
|
||||||
password += randomDigit
|
password += randomDigit
|
||||||
// Begin a new pronounceable part after every digit.
|
// Begin a new pronounceable part after every digit.
|
||||||
|
@ -149,15 +152,16 @@ internal object RandomPhonemesGenerator {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isStartOfPart &&
|
if (
|
||||||
pwFlags hasFlag PasswordGenerator.SYMBOLS &&
|
!isStartOfPart && pwFlags hasFlag PasswordGenerator.SYMBOLS && secureRandomBiasedBoolean(20)
|
||||||
secureRandomBiasedBoolean(20)
|
|
||||||
) {
|
) {
|
||||||
var randomSymbol: Char
|
var randomSymbol: Char
|
||||||
do {
|
do {
|
||||||
randomSymbol = PasswordGenerator.SYMBOLS_STR.secureRandomCharacter()
|
randomSymbol = PasswordGenerator.SYMBOLS_STR.secureRandomCharacter()
|
||||||
} while (pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
} while (
|
||||||
randomSymbol in PasswordGenerator.AMBIGUOUS_STR)
|
pwFlags hasFlag PasswordGenerator.NO_AMBIGUOUS &&
|
||||||
|
randomSymbol in PasswordGenerator.AMBIGUOUS_STR
|
||||||
|
)
|
||||||
password += randomSymbol
|
password += randomSymbol
|
||||||
// Continue the password generation as if nothing was added.
|
// Continue the password generation as if nothing was added.
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue