diff options
57 files changed, 1112 insertions, 384 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 435cbdcb..a7ef0f2d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,12 +1,23 @@ --- name: Bug report -about: Create a report to help us improve +about: Create a report to help us address issues you are facing with the app title: "[BUG]" -labels: bug +labels: bug, 'triage: needed' assignees: '' --- +<!-- +Thanks for taking the time to file this issue! Here are a few things to check before clicking the submit button :) + +1. Make sure you're on the latest version of the app. The current release can be found here: https://github.com/android-password-store/android-password-store/releases/latest. + +2. Search through **both** open and closed issues for your bug: https://github.com/android-password-store/Android-Password-Store/issues?q=is%3Aissue+sort%3Aupdated-desc+. + +3. ALWAYS fill this template. If you fail to do so, your issue will be immediately closed with no response. Maintaining open source projects for free is hard work, and we expect users to respect that time and effort by putting in a little bit of their own. That helps us fix these problems faster and in return you get a better app — everybody wins. + +--> + **Describe the bug** A clear and concise description of what the bug is. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index f4558654..66ac7d34 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -1,12 +1,23 @@ --- name: Feature request -about: Suggest an idea for this project +about: Suggest a new feature to be included in the app title: "[FEATURE]" -labels: enhancement -assignees: '' +labels: feature, 'triage: needed' +assignees: 'msfjarvis' --- +<!-- +Thanks for taking the time to file this issue! Here are a few things to check before clicking the submit button :) + +1. Make sure you're on the latest version of the app. Features are being added all the time and it is entirely possible what you're requesting has already been added. The current release can be found here: https://github.com/android-password-store/android-password-store/releases/latest. + +2. Check the changelog file to confirm that the feature hasn't been added for an upcoming release: https://github.com/android-password-store/Android-Password-Store/blob/develop/CHANGELOG.md. + +3. ALWAYS fill this template. If you fail to do so, your issue will be immediately closed with no response. Maintaining open source projects for free is hard work, and we expect users to respect that time and effort by putting in a little bit of their own. A feature request that is clear and explicit in its needs lets us get to work faster. + +--> + **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] diff --git a/.github/checksum.sh b/.github/checksum.sh deleted file mode 100755 index a1c7791a..00000000 --- a/.github/checksum.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash -RESULT_FILE=$1 - -if [ -f $RESULT_FILE ]; then - rm $RESULT_FILE -fi -touch $RESULT_FILE - -checksum_file() { - echo $(sha256sum $1 | awk '{print $1}') -} - -FILES=() -while read -r -d ''; do - FILES+=("$REPLY") -done < <(find . -type f \( -name "build.gradle*" -o -name "dependencies.gradle" -o -name "gradle-wrapper.properties" \) -print0) - -# Loop through files and append MD5 to result file -for FILE in ${FILES[@]}; do - echo $(checksum_file $FILE) >> $RESULT_FILE -done -# Now sort the file so that it is -sort $RESULT_FILE -o $RESULT_FILE diff --git a/.github/workflows/deploy_snapshot.yml b/.github/workflows/deploy_snapshot.yml index 3bb5d9c8..c31bbb78 100644 --- a/.github/workflows/deploy_snapshot.yml +++ b/.github/workflows/deploy_snapshot.yml @@ -20,40 +20,12 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Generate cache key - run: ./.github/checksum.sh checksum.txt - - - name: Cache gradle modules - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/modules-2 - key: ${{ runner.os }}-gradlemodules-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlemodules- - - - name: Cache gradle jars - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/jars-3 - key: ${{ runner.os }}-gradlejars-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlejars- - - - name: Cache gradle build - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/build-cache-1 - key: ${{ runner.os }}-gradlebuildcache-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlebuildcache- - - - name: Download gradle dependencies - run: ./gradlew dependencies - - name: Build release app - run: ./gradlew :app:assembleFreeRelease :app:assembleNonFreeRelease + uses: burrunan/gradle-cache-action@v1 env: SNAPSHOT: "true" + with: + arguments: :app:assembleFreeRelease :app:assembleNonFreeRelease - name: Clean secrets run: release/signing-cleanup.sh @@ -66,18 +38,3 @@ jobs: SERVER_ADDRESS: ${{ secrets.SERVER_ADDRESS }} SERVER_DESTINATION: ${{ secrets.SERVER_DESTINATION }} SSH_PORT: ${{ secrets.SSH_PORT }} - - - name: Install NodeJS - uses: actions/setup-node@v2-beta - with: - node-version: '12' - - - name: Install cfcli - run: npm install -g cloudflare-cli - - - name: Purge Cloudflare cache - run: | - cfcli --token ${CF_TOKEN} purge "https://dl.msfjarvis.dev/APS/$(cd ./app/build/outputs/apk/nonFree/release/; ls *.apk)" - cfcli --token ${CF_TOKEN} purge "https://dl.msfjarvis.dev/APS/$(cd ./app/build/outputs/apk/free/release/; ls *.apk)" - env: - CF_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} diff --git a/.github/workflows/draft_new_release.yml b/.github/workflows/draft_new_release.yml index 91b047fb..914e6182 100644 --- a/.github/workflows/draft_new_release.yml +++ b/.github/workflows/draft_new_release.yml @@ -15,7 +15,7 @@ jobs: - name: Extract version from milestone run: | VERSION="${{ github.event.milestone.title }}" - echo "::set-env name=RELEASE_VERSION::$VERSION" + echo "RELEASE_VERSION=$VERSION" >> $GITHUB_ENV - name: Create release branch run: git checkout -b release-${{ env.RELEASE_VERSION }} @@ -25,15 +25,16 @@ jobs: with: version: v${{ env.RELEASE_VERSION }} - - name: Initialize git config + - name: Initialize git config and commit changes run: | git config user.name "GitHub Actions" git config user.email noreply@github.com + git commit -am "Prepare release $RELEASE_VERSION" - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 + uses: repo-sync/pull-request@v2 with: - commit-message: "Prepare release ${{ env.RELEASE_VERSION }}" - branch: release-${{ env.RELEASE_VERSION }} - base: release - title: Release ${{ env.RELEASE_VERSION }} + source_branch: release-${{ env.RELEASE_VERSION }} + destination_branch: release + pr_title: Release ${{ env.RELEASE_VERSION }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f374c689..ec128f18 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -21,9 +21,9 @@ jobs: pull_number: context.payload.number, per_page: 100 }) - const serviceChanged = result.data.filter(f => f.filename.startsWith("app/") || f.filename.startsWith("buildSrc/") || f.filename.endsWith("gradle") || f.filename.startsWith(".github/workflows/pull_request.yml") || f.filename.startsWith("gradle") || f.filename.endsWith("properties")).length > 0 - console.log(serviceChanged) - return serviceChanged + const shouldRun = result.data.filter(f => !f.filename.endsWith(".md") || !f.filename.endsWith(".txt") || !f.filename.startsWith("contrib/") || !f.filename.endsWith(".yml")).length > 0 + console.log(shouldRun) + return shouldRun - name: Checkout repository if: ${{ steps.service-changed.outputs.result == 'true' }} @@ -33,40 +33,11 @@ jobs: if: ${{ steps.service-changed.outputs.result == 'true' }} run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Generate cache key - if: ${{ steps.service-changed.outputs.result == 'true' }} - run: ./.github/checksum.sh checksum.txt - - - name: Cache gradle modules - if: ${{ steps.service-changed.outputs.result == 'true' }} - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/modules-2 - key: ${{ runner.os }}-gradlemodules-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlemodules- - - - name: Cache gradle jars - if: ${{ steps.service-changed.outputs.result == 'true' }} - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/jars-3 - key: ${{ runner.os }}-gradlejars-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlejars- - - - name: Cache gradle build - if: ${{ steps.service-changed.outputs.result == 'true' }} - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/build-cache-1 - key: ${{ runner.os }}-gradlebuildcache-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlebuildcache- - - name: Run unit tests if: ${{ steps.service-changed.outputs.result == 'true' }} - run: ./gradlew testFreeDebug lintFreeDebug + uses: burrunan/gradle-cache-action@v1 + with: + arguments: apiCheck testFreeDebug lintFreeDebug - name: Run instrumentation tests if: ${{ steps.service-changed.outputs.result == 'true' }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10484521..521f7af1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,38 +20,10 @@ jobs: - name: Copy CI gradle.properties run: mkdir -p ~/.gradle ; cp .github/ci-gradle.properties ~/.gradle/gradle.properties - - name: Generate cache key - run: ./.github/checksum.sh checksum.txt - - - name: Cache gradle modules - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/modules-2 - key: ${{ runner.os }}-gradlemodules-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlemodules- - - - name: Cache gradle jars - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/jars-3 - key: ${{ runner.os }}-gradlejars-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlejars- - - - name: Cache gradle build - uses: actions/cache@v2 - with: - path: ~/.gradle/caches/build-cache-1 - key: ${{ runner.os }}-gradlebuildcache-${{ hashFiles('checksum.txt') }} - restore-keys: | - ${{ runner.os }}-gradlebuildcache- - - - name: Download gradle dependencies - run: ./gradlew dependencies - - name: Build release binaries - run: ./gradlew :app:assembleFreeRelease :app:assembleNonFreeRelease :app:bundleNonFreeRelease + uses: burrunan/gradle-cache-action@v1 + with: + arguments: :app:assembleFreeRelease :app:assembleNonFreeRelease :app:bundleNonFreeRelease - name: Upload non-free release APK uses: actions/upload-artifact@v2 diff --git a/.github/workflows/update_publicsuffix_data.yml b/.github/workflows/update_publicsuffix_data.yml index 29f1777f..e0e09f86 100644 --- a/.github/workflows/update_publicsuffix_data.yml +++ b/.github/workflows/update_publicsuffix_data.yml @@ -14,19 +14,22 @@ jobs: run: | git config user.name "GitHub Actions" git config user.email noreply@github.com + git checkout -b bot/update-psl - name: Download new publicsuffix data run: curl -L https://github.com/mozilla-mobile/android-components/raw/master/components/lib/publicsuffixlist/src/main/assets/publicsuffixes -o autofill-parser/src/main/assets/publicsuffixes - name: Compare list changes - run: if [[ $(git diff --binary --stat) != '' ]]; then echo "::set-env name=UPDATED::true"; fi + run: if [[ $(git diff --binary --stat) != '' ]]; then echo "UPDATED=true" >> $GITHUB_ENV; fi - name: Create Pull Request - uses: peter-evans/create-pull-request@v3 + uses: repo-sync/pull-request@v2 if: env.UPDATED == 'true' with: - commit-message: Update Public Suffix List data - branch: bot/update-psl - base: develop - title: 'Update Public Suffix List data' + source_branch: bot/update-psl + destination_branch: develop + pr_title: "Update Public Suffix List data" + pr_body: "This is an automated pull request to update the publicsuffixes file to the latest copy from Mozilla" assignees: msfjarvis + pr_label: PSL + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 --- a/.gitmodules +++ /dev/null diff --git a/CHANGELOG.md b/CHANGELOG.md index b4789e89..508b409a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. ## [Unreleased] +### Fixed + +- Some classes of errors would be swallowed by an unhelpful 'Invalid remote: origin' message +- Repositories created within APS would contain invalid `.gpg-id` files with no ability to fix them from the app +- Button labels were invisible in Autofill phishing warning screen + +### Added + +- Add GPG key selection step to onboarding flow +- Allow configuring an app-wide HTTP(S) proxy + ## [1.12.1] - 2020-10-13 ### Fixed diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 013e2e4e..a5a3b4c9 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -31,8 +31,8 @@ android { defaultConfig { applicationId = "dev.msfjarvis.aps" - versionCode = 11210 - versionName = "1.12.1" + versionCode = 11211 + versionName = "1.13.0-SNAPSHOT" } lintOptions { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3b0781f4..6cad066e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -36,6 +36,9 @@ android:name=".ui.onboarding.activity.OnboardingActivity" android:configChanges="orientation|screenSize" /> + <activity android:name=".ui.proxy.ProxySelectorActivity" + android:windowSoftInputMode="adjustResize" /> + <activity android:name=".LaunchActivity" android:configChanges="orientation|screenSize" diff --git a/app/src/main/java/com/zeapo/pwdstore/Application.kt b/app/src/main/java/com/zeapo/pwdstore/Application.kt index 91e47793..3f3963d0 100644 --- a/app/src/main/java/com/zeapo/pwdstore/Application.kt +++ b/app/src/main/java/com/zeapo/pwdstore/Application.kt @@ -14,27 +14,31 @@ import com.github.ajalt.timberkt.Timber.DebugTree import com.github.ajalt.timberkt.Timber.plant import com.zeapo.pwdstore.git.sshj.setUpBouncyCastleForSshj import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.ProxyUtils import com.zeapo.pwdstore.utils.getString import com.zeapo.pwdstore.utils.sharedPrefs @Suppress("Unused") class Application : android.app.Application(), SharedPreferences.OnSharedPreferenceChangeListener { + private val prefs by lazy { sharedPrefs } + override fun onCreate() { super.onCreate() instance = this if (BuildConfig.ENABLE_DEBUG_FEATURES || - sharedPrefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) { + prefs.getBoolean(PreferenceKeys.ENABLE_DEBUG_LOGGING, false)) { plant(DebugTree()) } - sharedPrefs.registerOnSharedPreferenceChangeListener(this) + prefs.registerOnSharedPreferenceChangeListener(this) setNightMode() setUpBouncyCastleForSshj() runMigrations(applicationContext) + ProxyUtils.setDefaultProxy() } override fun onTerminate() { - sharedPrefs.unregisterOnSharedPreferenceChangeListener(this) + prefs.unregisterOnSharedPreferenceChangeListener(this) super.onTerminate() } @@ -45,7 +49,7 @@ class Application : android.app.Application(), SharedPreferences.OnSharedPrefere } private fun setNightMode() { - AppCompatDelegate.setDefaultNightMode(when (sharedPrefs.getString(PreferenceKeys.APP_THEME) + AppCompatDelegate.setDefaultNightMode(when (prefs.getString(PreferenceKeys.APP_THEME) ?: getString(R.string.app_theme_def)) { "light" -> MODE_NIGHT_NO "dark" -> MODE_NIGHT_YES diff --git a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt index 46c4ecd3..0f27e5ef 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ClipboardService.kt @@ -10,7 +10,6 @@ import android.app.PendingIntent import android.app.Service import android.content.ClipData import android.content.Intent -import android.content.SharedPreferences import android.os.Build import android.os.IBinder import androidx.core.app.NotificationCompat @@ -32,7 +31,6 @@ import kotlinx.coroutines.withContext class ClipboardService : Service() { private val scope = CoroutineScope(Job() + Dispatchers.Main) - private val settings: SharedPreferences by lazy { sharedPrefs } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { if (intent != null) { @@ -45,7 +43,7 @@ class ClipboardService : Service() { } ACTION_START -> { - val time = settings.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull() ?: 45 + val time = sharedPrefs.getString(PreferenceKeys.GENERAL_SHOW_TIME)?.toIntOrNull() ?: 45 if (time == 0) { stopSelf() @@ -80,7 +78,7 @@ class ClipboardService : Service() { } private fun clearClipboard() { - val deepClear = settings.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, false) + val deepClear = sharedPrefs.getBoolean(PreferenceKeys.CLEAR_CLIPBOARD_20X, false) val clipboard = clipboard if (clipboard != null) { diff --git a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt index 6da5de7a..a73dd40d 100644 --- a/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt +++ b/app/src/main/java/com/zeapo/pwdstore/SearchableRepositoryViewModel.kt @@ -139,7 +139,7 @@ class SearchableRepositoryViewModel(application: Application) : AndroidViewModel private val root get() = PasswordRepository.getRepositoryDirectory() - private val settings by lazy { application.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { application.sharedPrefs } private val showHiddenContents get() = settings.getBoolean(PreferenceKeys.SHOW_HIDDEN_CONTENTS, false) private val defaultSearchMode diff --git a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt index 45915213..7f6727f2 100644 --- a/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt +++ b/app/src/main/java/com/zeapo/pwdstore/UserPreference.kt @@ -50,6 +50,7 @@ import com.zeapo.pwdstore.git.sshj.SshKey import com.zeapo.pwdstore.pwgenxkpwd.XkpwdDictionary import com.zeapo.pwdstore.sshkeygen.ShowSshKeyFragment import com.zeapo.pwdstore.sshkeygen.SshKeyGenActivity +import com.zeapo.pwdstore.ui.proxy.ProxySelectorActivity import com.zeapo.pwdstore.utils.BiometricAuthenticator import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PreferenceKeys @@ -418,6 +419,11 @@ class UserPreference : AppCompatActivity() { } } + findPreference<Preference>(PreferenceKeys.PROXY_SETTINGS)?.onPreferenceClickListener = ClickListener { + startActivity(Intent(requireContext(), ProxySelectorActivity::class.java)) + true + } + val prefCustomXkpwdDictionary = findPreference<Preference>(PreferenceKeys.PREF_KEY_CUSTOM_DICT) prefCustomXkpwdDictionary?.onPreferenceClickListener = ClickListener { prefsActivity.storeCustomDictionaryPath() diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt index 0e73b908..ec3d2b77 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/AutofillResponseBuilder.kt @@ -15,12 +15,12 @@ import android.service.autofill.SaveInfo import android.widget.RemoteViews import androidx.annotation.RequiresApi import com.github.ajalt.timberkt.e -import com.github.michaelbull.result.fold import com.github.androidpasswordstore.autofillparser.AutofillAction import com.github.androidpasswordstore.autofillparser.AutofillScenario import com.github.androidpasswordstore.autofillparser.Credentials import com.github.androidpasswordstore.autofillparser.FillableForm import com.github.androidpasswordstore.autofillparser.fillWith +import com.github.michaelbull.result.fold import com.zeapo.pwdstore.autofill.oreo.ui.AutofillDecryptActivity import com.zeapo.pwdstore.autofill.oreo.ui.AutofillFilterView import com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity @@ -88,8 +88,13 @@ class AutofillResponseBuilder(form: FillableForm) { publisherChangedException: AutofillPublisherChangedException ): Dataset { val remoteView = makeWarningRemoteView(context) + // If the user decides to trust the new publisher, they can choose reset the list of + // matches. In this case we need to immediately show a new `FillResponse` as if the app were + // autofilled for the first time. This `FillResponse` needs to be returned as a result from + // `AutofillPublisherChangedActivity`, which is why we create and pass it on here. + val fillResponseAfterReset = makeFillResponse(context, emptyList()) val intentSender = AutofillPublisherChangedActivity.makePublisherChangedIntentSender( - context, publisherChangedException + context, publisherChangedException, fillResponseAfterReset ) return makePlaceholderDataset(remoteView, intentSender, AutofillAction.Match) } diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt index 44ed3446..34edf8f5 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillPublisherChangedActivity.kt @@ -12,8 +12,10 @@ import android.content.IntentSender import android.content.pm.PackageManager import android.os.Build import android.os.Bundle +import android.service.autofill.FillResponse import android.text.format.DateUtils import android.view.View +import android.view.autofill.AutofillManager import androidx.appcompat.app.AppCompatActivity import com.github.ajalt.timberkt.e import com.github.androidpasswordstore.autofillparser.FormOrigin @@ -33,14 +35,18 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { private const val EXTRA_APP_PACKAGE = "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_APP_PACKAGE" + private const val EXTRA_FILL_RESPONSE_AFTER_RESET = + "com.zeapo.pwdstore.autofill.oreo.ui.EXTRA_FILL_RESPONSE_AFTER_RESET" private var publisherChangedRequestCode = 1 fun makePublisherChangedIntentSender( context: Context, - publisherChangedException: AutofillPublisherChangedException + publisherChangedException: AutofillPublisherChangedException, + fillResponseAfterReset: FillResponse?, ): IntentSender { val intent = Intent(context, AutofillPublisherChangedActivity::class.java).apply { putExtra(EXTRA_APP_PACKAGE, publisherChangedException.formOrigin.identifier) + putExtra(EXTRA_FILL_RESPONSE_AFTER_RESET, fillResponseAfterReset) } return PendingIntent.getActivity( context, publisherChangedRequestCode++, intent, PendingIntent.FLAG_CANCEL_CURRENT @@ -66,12 +72,16 @@ class AutofillPublisherChangedActivity : AppCompatActivity() { with(binding) { okButton.setOnClickListener { finish() } advancedButton.setOnClickListener { - advancedButton.visibility = View.INVISIBLE + advancedButton.visibility = View.GONE warningAppAdvancedInfo.visibility = View.VISIBLE resetButton.visibility = View.VISIBLE } resetButton.setOnClickListener { AutofillMatcher.clearMatchesFor(this@AutofillPublisherChangedActivity, FormOrigin.App(appPackage)) + val fillResponse = intent.getParcelableExtra<FillResponse>(EXTRA_FILL_RESPONSE_AFTER_RESET) + setResult(RESULT_OK, Intent().apply { + putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillResponse) + }) finish() } } diff --git a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt index 7f83d483..f4c296ee 100644 --- a/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/autofill/oreo/ui/AutofillSaveActivity.kt @@ -82,7 +82,7 @@ class AutofillSaveActivity : AppCompatActivity() { } } - private val formOrigin: FormOrigin? by lazy { + private val formOrigin by lazy(LazyThreadSafetyMode.NONE) { val shouldMatchApp: String? = intent.getStringExtra(EXTRA_SHOULD_MATCH_APP) val shouldMatchWeb: String? = intent.getStringExtra(EXTRA_SHOULD_MATCH_WEB) if (shouldMatchApp != null && shouldMatchWeb == null) { diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt index 8380d7d0..6b6c2032 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/BasePgpActivity.kt @@ -45,24 +45,24 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou /** * Full path to the repository */ - val repoPath: String by lazy { intent.getStringExtra("REPO_PATH") } + val repoPath: String by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra("REPO_PATH") } /** * Full path to the password file being worked on */ - val fullPath: String by lazy { intent.getStringExtra("FILE_PATH") } + val fullPath: String by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra("FILE_PATH") } /** * Name of the password file * * Converts personal/auth.foo.org/john_doe@example.org.gpg to john_doe.example.org */ - val name: String by lazy { File(fullPath).nameWithoutExtension } + val name: String by lazy(LazyThreadSafetyMode.NONE) { File(fullPath).nameWithoutExtension } /** * Get the timestamp for when this file was last modified. */ - val lastChangedString: CharSequence by lazy { + val lastChangedString: CharSequence by lazy(LazyThreadSafetyMode.NONE) { getLastChangedString( intent.getLongExtra( "LAST_CHANGED_TIMESTAMP", @@ -74,7 +74,7 @@ open class BasePgpActivity : AppCompatActivity(), OpenPgpServiceConnection.OnBou /** * [SharedPreferences] instance used by subclasses to persist settings */ - val settings: SharedPreferences by lazy { sharedPrefs } + val settings: SharedPreferences by lazy(LazyThreadSafetyMode.NONE) { sharedPrefs } /** * Handle to the [OpenPgpApi] instance that is used by subclasses to interface with OpenKeychain. diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt index 3685cb17..cb43534d 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/DecryptActivity.kt @@ -39,7 +39,7 @@ class DecryptActivity : BasePgpActivity(), OpenPgpServiceConnection.OnBound { private val binding by viewBinding(DecryptLayoutBinding::inflate) - private val relativeParentPath by lazy { getParentPath(fullPath, repoPath) } + private val relativeParentPath by lazy(LazyThreadSafetyMode.NONE) { getParentPath(fullPath, repoPath) } private var passwordEntry: PasswordEntry? = null private val userInteractionRequiredResult = registerForActivityResult(StartIntentSenderForResult()) { result -> diff --git a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt index 07694089..01d85f2b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/crypto/PasswordCreationActivity.kt @@ -56,12 +56,12 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB private val binding by viewBinding(PasswordCreationActivityBinding::inflate) - private val suggestedName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) } - private val suggestedPass by lazy { intent.getStringExtra(EXTRA_PASSWORD) } - private val suggestedExtra by lazy { intent.getStringExtra(EXTRA_EXTRA_CONTENT) } - private val shouldGeneratePassword by lazy { intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) } - private val editing by lazy { intent.getBooleanExtra(EXTRA_EDITING, false) } - private val oldFileName by lazy { intent.getStringExtra(EXTRA_FILE_NAME) } + private val suggestedName by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_FILE_NAME) } + private val suggestedPass by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_PASSWORD) } + private val suggestedExtra by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_EXTRA_CONTENT) } + private val shouldGeneratePassword by lazy(LazyThreadSafetyMode.NONE) { intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false) } + private val editing by lazy(LazyThreadSafetyMode.NONE) { intent.getBooleanExtra(EXTRA_EDITING, false) } + private val oldFileName by lazy(LazyThreadSafetyMode.NONE) { intent.getStringExtra(EXTRA_FILE_NAME) } private var oldCategory: String? = null private var copy: Boolean = false private var encryptionIntent: Intent = Intent() @@ -277,6 +277,7 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB @OptIn(ExperimentalUnsignedTypes::class) private fun parseGpgIdentifier(identifier: String): GpgIdentifier? { + if (identifier.isEmpty()) return null // Match long key IDs: // FF22334455667788 or 0xFF22334455667788 val maybeLongKeyId = identifier.removePrefix("0x").takeIf { @@ -342,6 +343,9 @@ class PasswordCreationActivity : BasePgpActivity(), OpenPgpServiceConnection.OnB .filter { it.isNotBlank() } .map { line -> parseGpgIdentifier(line) ?: run { + // The line being empty means this is most likely an empty `.gpg-id` file + // we created. Skip the validation so we can make the user add a real ID. + if (line.isEmpty()) return@run if (line.removePrefix("0x").matches("[a-fA-F0-9]{8}".toRegex())) { snackbar(message = resources.getString(R.string.short_key_ids_unsupported)) } else { diff --git a/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt b/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt index b0aed087..36b8b6fb 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/BaseGitActivity.kt @@ -128,11 +128,12 @@ abstract class BaseGitActivity : AppCompatActivity() { */ private fun rootCauseException(throwable: Throwable): Throwable { var rootCause = throwable - // JGit's TransportException hides the more helpful SSHJ exceptions. + // JGit's InvalidRemoteException and TransportException hide the more helpful SSHJ exceptions. // Also, SSHJ's UserAuthException about exhausting available authentication methods hides // more useful exceptions. while ((rootCause is org.eclipse.jgit.errors.TransportException || rootCause is org.eclipse.jgit.api.errors.TransportException || + rootCause is org.eclipse.jgit.api.errors.InvalidRemoteException || (rootCause is UserAuthException && rootCause.message == "Exhausted available authentication methods"))) { rootCause = rootCause.cause ?: break diff --git a/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt b/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt index ed5411ac..ef584cf7 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/GitCommandExecutor.kt @@ -51,8 +51,10 @@ class GitCommandExecutor( // the previous status will eventually be used to avoid a commit if (nbChanges > 0) { withContext(Dispatchers.IO) { + val name = GitSettings.authorName.ifEmpty { "root" } + val email = GitSettings.authorEmail.ifEmpty { "localhost" } command - .setAuthor(PersonIdent(GitSettings.authorName, GitSettings.authorEmail)) + .setAuthor(PersonIdent(name, email)) .call() } } diff --git a/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt b/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt index e43deaf4..27ceb5cb 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/config/GitSettings.kt @@ -11,6 +11,7 @@ import com.zeapo.pwdstore.Application import com.zeapo.pwdstore.utils.PasswordRepository import com.zeapo.pwdstore.utils.PreferenceKeys import com.zeapo.pwdstore.utils.getEncryptedGitPrefs +import com.zeapo.pwdstore.utils.getEncryptedProxyPrefs import com.zeapo.pwdstore.utils.getString import com.zeapo.pwdstore.utils.sharedPrefs import java.io.File @@ -52,8 +53,9 @@ object GitSettings { private const val DEFAULT_BRANCH = "master" - private val settings by lazy { Application.instance.sharedPrefs } - private val encryptedSettings by lazy { Application.instance.getEncryptedGitPrefs() } + private val settings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.sharedPrefs } + private val encryptedSettings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.getEncryptedGitPrefs() } + private val proxySettings by lazy(LazyThreadSafetyMode.PUBLICATION) { Application.instance.getEncryptedProxyPrefs() } var authMode get() = AuthMode.fromString(settings.getString(PreferenceKeys.GIT_REMOTE_AUTH)) @@ -108,6 +110,38 @@ object GitSettings { } } + var proxyHost + get() = proxySettings.getString(PreferenceKeys.PROXY_HOST) + set(value) { + proxySettings.edit { + putString(PreferenceKeys.PROXY_HOST, value) + } + } + + var proxyPort + get() = proxySettings.getInt(PreferenceKeys.PROXY_PORT, -1) + set(value) { + proxySettings.edit { + putInt(PreferenceKeys.PROXY_PORT, value) + } + } + + var proxyUsername + get() = settings.getString(PreferenceKeys.PROXY_USERNAME) + set(value) { + proxySettings.edit { + putString(PreferenceKeys.PROXY_USERNAME, value) + } + } + + var proxyPassword + get() = proxySettings.getString(PreferenceKeys.PROXY_PASSWORD) + set(value) { + proxySettings.edit { + putString(PreferenceKeys.PROXY_PASSWORD, value) + } + } + sealed class UpdateConnectionSettingsResult { class MissingUsername(val newProtocol: Protocol) : UpdateConnectionSettingsResult() class AuthModeMismatch(val newProtocol: Protocol, val validModes: List<AuthMode>) : UpdateConnectionSettingsResult() diff --git a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt index ccd2f88a..15b9f64d 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/log/GitLogModel.kt @@ -41,7 +41,7 @@ class GitLogModel { // This is because the commit graph is walked from HEAD to the last commit to obtain. // Additionally, tests with 1000 commits in the log have not produced a significant delay in the // user experience. - private val cache: MutableList<GitCommit> by lazy { + private val cache: MutableList<GitCommit> by lazy(LazyThreadSafetyMode.NONE) { commits().map { GitCommit(it.hash, it.shortMessage, it.authorIdent.name, it.time) }.toMutableList() diff --git a/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt b/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt index 73072f7d..5e6cfb1b 100644 --- a/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt +++ b/app/src/main/java/com/zeapo/pwdstore/git/sshj/SshKey.kt @@ -50,7 +50,7 @@ private const val PROVIDER_ANDROID_KEY_STORE = "AndroidKeyStore" private const val KEYSTORE_ALIAS = "sshkey" private const val ANDROIDX_SECURITY_KEYSET_PREF_NAME = "androidx_sshkey_keyset_prefs" -private val androidKeystore: KeyStore by lazy { +private val androidKeystore: KeyStore by lazy(LazyThreadSafetyMode.NONE) { KeyStore.getInstance(PROVIDER_ANDROID_KEY_STORE).apply { load(null) } } @@ -119,7 +119,7 @@ object SshKey { putString(PreferenceKeys.GIT_REMOTE_KEY_TYPE, value?.value) } - private val isStrongBoxSupported by lazy { + private val isStrongBoxSupported by lazy(LazyThreadSafetyMode.NONE) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) context.packageManager.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE) else diff --git a/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt b/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt index ea6255c4..556b2c87 100644 --- a/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt +++ b/app/src/main/java/com/zeapo/pwdstore/model/PasswordEntry.kt @@ -59,7 +59,7 @@ class PasswordEntry(content: String, private val totpFinder: TotpFinder = UriTot return Otp.calculateCode(totpSecret, Date().time / (1000 * totpPeriod), totpAlgorithm, digits).get() } - val extraContentWithoutAuthData by lazy { + val extraContentWithoutAuthData by lazy(LazyThreadSafetyMode.NONE) { extraContent.splitToSequence("\n").filter { line -> return@filter when { USERNAME_FIELDS.any { prefix -> line.startsWith(prefix, ignoreCase = true) } -> { diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt index 19a14695..acd0151c 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/CloneFragment.kt @@ -25,7 +25,7 @@ class CloneFragment : Fragment(R.layout.fragment_clone) { private val binding by viewBinding(FragmentCloneBinding::bind) - private val settings by lazy { requireActivity().applicationContext.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } private val cloneAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == AppCompatActivity.RESULT_OK) { diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt new file mode 100644 index 00000000..3dc03954 --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/KeySelectionFragment.kt @@ -0,0 +1,66 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package com.zeapo.pwdstore.ui.onboarding.fragments + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.crypto.GetKeyIdsActivity +import com.zeapo.pwdstore.databinding.FragmentKeySelectionBinding +import com.zeapo.pwdstore.utils.PasswordRepository +import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.commitChange +import com.zeapo.pwdstore.utils.finish +import com.zeapo.pwdstore.utils.sharedPrefs +import com.zeapo.pwdstore.utils.viewBinding +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.msfjarvis.openpgpktx.util.OpenPgpApi + +class KeySelectionFragment : Fragment(R.layout.fragment_key_selection) { + + private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } + private val binding by viewBinding(FragmentKeySelectionBinding::bind) + + private val gpgKeySelectAction = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == AppCompatActivity.RESULT_OK) { + result.data?.getStringArrayExtra(OpenPgpApi.EXTRA_KEY_IDS)?.let { keyIds -> + lifecycleScope.launch { + withContext(Dispatchers.IO) { + val gpgIdentifierFile = File(PasswordRepository.getRepositoryDirectory(), ".gpg-id") + gpgIdentifierFile.writeText(keyIds.joinToString("\n")) + } + settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } + requireActivity().commitChange(getString( + R.string.git_commit_gpg_id, + getString(R.string.app_name) + )) + } + } + } else { + throw IllegalStateException("Failed to initialize repository state.") + } + finish() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.selectKey.setOnClickListener { gpgKeySelectAction.launch(Intent(requireContext(), GetKeyIdsActivity::class.java)) } + } + + companion object { + + fun newInstance() = KeySelectionFragment() + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt index 9e8a8f9d..d451fabe 100644 --- a/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt +++ b/app/src/main/java/com/zeapo/pwdstore/ui/onboarding/fragments/RepoLocationFragment.kt @@ -14,6 +14,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.edit import androidx.fragment.app.Fragment import com.github.ajalt.timberkt.d +import com.github.ajalt.timberkt.e import com.github.michaelbull.result.onFailure import com.github.michaelbull.result.runCatching import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -26,14 +27,14 @@ import com.zeapo.pwdstore.utils.finish import com.zeapo.pwdstore.utils.getString import com.zeapo.pwdstore.utils.isPermissionGranted import com.zeapo.pwdstore.utils.listFilesRecursively +import com.zeapo.pwdstore.utils.performTransactionWithBackStack import com.zeapo.pwdstore.utils.sharedPrefs import com.zeapo.pwdstore.utils.viewBinding import java.io.File class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { - private val firstRunActivity by lazy { requireActivity() } - private val settings by lazy { firstRunActivity.applicationContext.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { requireActivity().applicationContext.sharedPrefs } private val binding by viewBinding(FragmentRepoLocationBinding::bind) private val sortOrder: PasswordRepository.PasswordSortOrder get() = PasswordRepository.PasswordSortOrder.getSortOrder(settings) @@ -151,18 +152,14 @@ class RepoLocationFragment : Fragment(R.layout.fragment_repo_location) { if (!PasswordRepository.isInitialized) { PasswordRepository.initialize() } - if (File(localDir.absolutePath + "/.gpg-id").createNewFile()) { - settings.edit { putBoolean(PreferenceKeys.REPOSITORY_INITIALIZED, true) } - } else { - throw IllegalStateException("Failed to initialize repository state.") - } + parentFragmentManager.performTransactionWithBackStack(KeySelectionFragment.newInstance()) }.onFailure { e -> - e.printStackTrace() + e(e) if (!localDir.delete()) { d { "Failed to delete local repository: $localDir" } } + finish() } - finish() } private fun initializeRepositoryInfo() { diff --git a/app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt b/app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt new file mode 100644 index 00000000..29c4d53a --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/ui/proxy/ProxySelectorActivity.kt @@ -0,0 +1,75 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package com.zeapo.pwdstore.ui.proxy + +import android.os.Bundle +import android.os.Handler +import android.os.Looper +import android.util.Patterns +import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.edit +import androidx.core.os.postDelayed +import androidx.core.widget.doOnTextChanged +import com.zeapo.pwdstore.R +import com.zeapo.pwdstore.databinding.ActivityProxySelectorBinding +import com.zeapo.pwdstore.git.config.GitSettings +import com.zeapo.pwdstore.utils.PreferenceKeys +import com.zeapo.pwdstore.utils.ProxyUtils +import com.zeapo.pwdstore.utils.getEncryptedProxyPrefs +import com.zeapo.pwdstore.utils.getString +import com.zeapo.pwdstore.utils.viewBinding + +private val IP_ADDRESS_REGEX = Patterns.IP_ADDRESS.toRegex() +private val WEB_ADDRESS_REGEX = Patterns.WEB_URL.toRegex() + +class ProxySelectorActivity : AppCompatActivity() { + + private val binding by viewBinding(ActivityProxySelectorBinding::inflate) + private val proxyPrefs by lazy(LazyThreadSafetyMode.NONE) { applicationContext.getEncryptedProxyPrefs() } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + with(binding) { + proxyHost.setText(proxyPrefs.getString(PreferenceKeys.PROXY_HOST)) + proxyUser.setText(proxyPrefs.getString(PreferenceKeys.PROXY_USERNAME)) + proxyPrefs.getInt(PreferenceKeys.PROXY_PORT, -1).takeIf { it != -1 }?.let { + proxyPort.setText("$it") + } + proxyPassword.setText(proxyPrefs.getString(PreferenceKeys.PROXY_PASSWORD)) + save.setOnClickListener { saveSettings() } + proxyHost.doOnTextChanged { text, _, _, _ -> + if (text != null) { + proxyHost.error = if (text.matches(IP_ADDRESS_REGEX) || text.matches(WEB_ADDRESS_REGEX)) { + null + } else { + getString(R.string.invalid_proxy_url) + } + } + } + } + + } + + private fun saveSettings() { + proxyPrefs.edit { + binding.proxyHost.text?.toString()?.takeIf { it.isNotEmpty() }.let { + GitSettings.proxyHost = it + } + binding.proxyUser.text?.toString()?.takeIf { it.isNotEmpty() }.let { + GitSettings.proxyUsername = it + } + binding.proxyPort.text?.toString()?.takeIf { it.isNotEmpty() }?.let { + GitSettings.proxyPort = it.toInt() + } + binding.proxyPassword.text?.toString()?.takeIf { it.isNotEmpty() }.let { + GitSettings.proxyPassword = it + } + } + ProxyUtils.setDefaultProxy() + Handler(Looper.getMainLooper()).postDelayed(500) { finish() } + } +} diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt b/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt index 3ee1820d..408e9d5e 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/AndroidExtensions.kt @@ -72,6 +72,12 @@ val Context.clipboard fun Context.getEncryptedGitPrefs() = getEncryptedPrefs("git_operation") /** + * Wrapper for [getEncryptedPrefs] to get the encrypted preference set for the HTTP + * proxy. + */ +fun Context.getEncryptedProxyPrefs() = getEncryptedPrefs("http_proxy") + +/** * Get an instance of [EncryptedSharedPreferences] with the given [fileName] */ private fun Context.getEncryptedPrefs(fileName: String): SharedPreferences { diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt index a1b4a279..12ba84f1 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/BiometricAuthenticator.kt @@ -6,7 +6,6 @@ package com.zeapo.pwdstore.utils import android.app.KeyguardManager import androidx.annotation.StringRes -import androidx.biometric.BiometricConstants import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager.Authenticators import androidx.biometric.BiometricPrompt @@ -43,12 +42,12 @@ object BiometricAuthenticator { super.onAuthenticationError(errorCode, errString) tag(TAG).d { "BiometricAuthentication error: errorCode=$errorCode, msg=$errString" } callback(when (errorCode) { - BiometricConstants.ERROR_CANCELED, BiometricConstants.ERROR_USER_CANCELED, - BiometricConstants.ERROR_NEGATIVE_BUTTON -> { + BiometricPrompt.ERROR_CANCELED, BiometricPrompt.ERROR_USER_CANCELED, + BiometricPrompt.ERROR_NEGATIVE_BUTTON -> { Result.Cancelled } - BiometricConstants.ERROR_HW_NOT_PRESENT, BiometricConstants.ERROR_HW_UNAVAILABLE, - BiometricConstants.ERROR_NO_BIOMETRICS, BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL -> { + BiometricPrompt.ERROR_HW_NOT_PRESENT, BiometricPrompt.ERROR_HW_UNAVAILABLE, + BiometricPrompt.ERROR_NO_BIOMETRICS, BiometricPrompt.ERROR_NO_DEVICE_CREDENTIAL -> { Result.HardwareUnavailableOrDisabled } else -> Result.Failure(errorCode, activity.getString(R.string.biometric_auth_error_reason, errString)) diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt index f1655b3b..6e090ba3 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/PasswordRepository.kt @@ -93,7 +93,7 @@ open class PasswordRepository protected constructor() { companion object { private var repository: Repository? = null - private val settings by lazy { Application.instance.sharedPrefs } + private val settings by lazy(LazyThreadSafetyMode.NONE) { Application.instance.sharedPrefs } private val filesDir get() = Application.instance.filesDir diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt index 1b2c7abb..5ec40639 100644 --- a/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt +++ b/app/src/main/java/com/zeapo/pwdstore/utils/PreferenceKeys.kt @@ -80,4 +80,10 @@ object PreferenceKeys { @Deprecated("To be used only in Migrations.kt") const val USE_GENERATED_KEY = "use_generated_key" + + const val PROXY_SETTINGS = "proxy_settings" + const val PROXY_HOST = "proxy_host" + const val PROXY_PORT = "proxy_port" + const val PROXY_USERNAME = "proxy_username" + const val PROXY_PASSWORD = "proxy_password" } diff --git a/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt b/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt new file mode 100644 index 00000000..4bb3dfcb --- /dev/null +++ b/app/src/main/java/com/zeapo/pwdstore/utils/ProxyUtils.kt @@ -0,0 +1,66 @@ +/* + * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + * SPDX-License-Identifier: GPL-3.0-only + */ + +package com.zeapo.pwdstore.utils + +import com.zeapo.pwdstore.git.config.GitSettings +import java.io.IOException +import java.net.Authenticator +import java.net.InetSocketAddress +import java.net.PasswordAuthentication +import java.net.Proxy +import java.net.ProxySelector +import java.net.SocketAddress +import java.net.URI + +/** + * Utility class for [Proxy] handling. + */ +object ProxyUtils { + + private const val HTTP_PROXY_USER_PROPERTY = "http.proxyUser" + private const val HTTP_PROXY_PASSWORD_PROPERTY = "http.proxyPassword" + + /** + * Set the default [Proxy] and [Authenticator] for the app based on user provided settings. + */ + fun setDefaultProxy() { + ProxySelector.setDefault(object : ProxySelector() { + override fun select(uri: URI?): MutableList<Proxy> { + val host = GitSettings.proxyHost + val port = GitSettings.proxyPort + return if (host == null || port == -1) { + mutableListOf(Proxy.NO_PROXY) + } else { + mutableListOf(Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(host, port))) + } + } + + override fun connectFailed(uri: URI?, sa: SocketAddress?, ioe: IOException?) { + if (uri == null || sa == null || ioe == null) { + throw IllegalArgumentException("Arguments can't be null.") + } + } + }) + val user = GitSettings.proxyUsername ?: "" + val password = GitSettings.proxyPassword ?: "" + if (user.isEmpty() || password.isEmpty()) { + System.clearProperty(HTTP_PROXY_USER_PROPERTY) + System.clearProperty(HTTP_PROXY_PASSWORD_PROPERTY) + } else { + System.setProperty(HTTP_PROXY_USER_PROPERTY, user) + System.setProperty(HTTP_PROXY_PASSWORD_PROPERTY, password) + } + Authenticator.setDefault(object : Authenticator() { + override fun getPasswordAuthentication(): PasswordAuthentication? { + return if (requestorType == RequestorType.PROXY) { + PasswordAuthentication(user, password.toCharArray()) + } else { + null + } + } + }) + } +} diff --git a/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml b/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml index 92d4e094..8a735e5b 100644 --- a/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml +++ b/app/src/main/res/layout/activity_oreo_autofill_publisher_changed.xml @@ -6,57 +6,69 @@ <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" - android:layout_width="280dp" + android:layout_width="match_parent" android:layout_height="wrap_content" android:animateLayoutChanges="true" android:elevation="2dp" + android:paddingBottom="16dp" android:scrollbars="vertical" tools:context="com.zeapo.pwdstore.autofill.oreo.ui.AutofillPublisherChangedActivity"> <ImageView android:id="@+id/cover" - android:layout_width="0dp" - android:layout_height="50dp" + android:layout_width="60dp" + android:layout_height="60dp" android:background="@color/primary_color" android:contentDescription="@string/app_name" android:src="@mipmap/ic_launcher_foreground" app:layout_constraintBottom_toTopOf="@id/warningSign" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toStartOf="@id/app_name" + app:layout_constraintHorizontal_chainStyle="packed" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" - app:layout_constraintVertical_bias="0.0" - app:layout_constraintVertical_chainStyle="packed" /> + app:layout_constraintTop_toTopOf="parent" /> + + <TextView + android:id="@+id/app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/app_name" + android:textSize="18sp" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="@id/cover" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/cover" + app:layout_constraintTop_toTopOf="@id/cover" /> <ImageView android:id="@+id/warningSign" - android:layout_width="0dp" - android:layout_height="50dp" + android:layout_width="40dp" + android:layout_height="40dp" + android:layout_marginStart="@dimen/activity_horizontal_margin" android:contentDescription="@string/oreo_autofill_warning_publisher_warning_sign_description" android:src="@drawable/ic_warning_red_24dp" - app:layout_constraintBottom_toTopOf="@id/warningHeader" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/warningHeader" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/cover" /> + app:layout_constraintBottom_toBottomOf="@id/warningHeader" /> <TextView android:id="@+id/warningHeader" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginStart="8dp" android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:text="@string/oreo_autofill_warning_publisher_header" android:textSize="12sp" - app:layout_constraintBottom_toTopOf="@id/warningAppName" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/warningSign" /> + app:layout_constraintStart_toEndOf="@id/warningSign" + app:layout_constraintTop_toBottomOf="@id/cover" /> <TextView android:id="@+id/warningAppName" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" android:layout_marginEnd="@dimen/activity_horizontal_margin" android:gravity="center_horizontal" android:textSize="12sp" @@ -87,79 +99,77 @@ android:layout_marginStart="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:text="@string/oreo_autofill_warning_publisher_footer" android:textSize="12sp" - app:layout_constraintBottom_toTopOf="@id/okButton" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/warningAppInstallDate" /> - <Button + <com.google.android.material.button.MaterialButton android:id="@+id/okButton" - style="@style/Widget.MaterialComponents.Button.OutlinedButton" + style="@style/AppTheme.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:minWidth="240dp" android:text="@string/oreo_autofill_warning_publisher_changed_disable_autofill_button" android:textSize="12sp" - app:layout_constraintBottom_toTopOf="@id/advancedButton" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/warningAppFooter" /> - <Button + <com.google.android.material.button.MaterialButton android:id="@+id/advancedButton" - style="@style/Widget.MaterialComponents.Button.OutlinedButton" + style="@style/AppTheme.OutlinedButton" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:minWidth="240dp" android:text="@string/oreo_autofill_warning_publisher_advanced_info_button" android:textSize="12sp" - app:layout_constraintBottom_toTopOf="@id/warningAppAdvancedInfo" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/okButton" /> + app:layout_constraintTop_toBottomOf="@id/okButton" + tools:visibility="visible" /> - <TextView - android:id="@+id/warningAppAdvancedInfo" - android:layout_width="0dp" + <com.google.android.material.button.MaterialButton + android:id="@+id/resetButton" + style="@style/AppTheme.OutlinedButton" + android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:fontFamily="monospace" - android:gravity="center_horizontal" - android:textIsSelectable="true" - android:textSize="10sp" - android:visibility="invisible" - app:layout_constraintBottom_toTopOf="@id/resetButton" + android:minWidth="240dp" + android:text="@string/oreo_autofill_warning_publisher_reenable_button" + android:textColor="?attr/colorOnSurface" + android:textSize="12sp" + android:visibility="gone" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/advancedButton" - tools:ignore="SmallSp" - tools:text="Package: com.example.banking\n\nHash:\n8P1sW0EPJcslw7UzRsiXL64w+O50Ed+RBICtay1g24M=" tools:visibility="visible" /> - <Button - android:id="@+id/resetButton" - style="@style/Widget.MaterialComponents.Button.TextButton" - android:layout_width="wrap_content" + <TextView + android:id="@+id/warningAppAdvancedInfo" + android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="16dp" android:layout_marginEnd="@dimen/activity_horizontal_margin" - android:text="@string/oreo_autofill_warning_publisher_reenable_button" + android:fontFamily="monospace" + android:gravity="center_horizontal" + android:textIsSelectable="true" android:textSize="10sp" - android:visibility="invisible" + android:visibility="gone" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/warningAppAdvancedInfo" + app:layout_constraintTop_toBottomOf="@id/resetButton" tools:ignore="SmallSp" + tools:text="Package: com.example.banking\n\nHash:\n8P1sW0EPJcslw7UzRsiXL64w+O50Ed+RBICtay1g24M=" tools:visibility="visible" /> </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/activity_proxy_selector.xml b/app/src/main/res/layout/activity_proxy_selector.xml new file mode 100644 index 00000000..e732dfe0 --- /dev/null +++ b/app/src/main/res/layout/activity_proxy_selector.xml @@ -0,0 +1,106 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + ~ SPDX-License-Identifier: GPL-3.0-only + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/proxy_host_input_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:hint="@string/proxy_hostname" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:layout_editor_absoluteY="64dp"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/proxy_host" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textUri" + android:nextFocusForward="@id/proxy_user" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/proxy_user_input_layout" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/normal_margin" + android:layout_marginEnd="@dimen/normal_margin" + android:hint="@string/username" + app:layout_constraintEnd_toStartOf="@id/proxy_port_input_layout" + app:layout_constraintHorizontal_weight="0.65" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/proxy_host_input_layout"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/proxy_user" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textWebEmailAddress" + android:nextFocusForward="@id/proxy_port" /> + + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/proxy_port_input_layout" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/normal_margin" + android:layout_marginTop="@dimen/normal_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:hint="@string/port" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_weight="0.35" + app:layout_constraintStart_toEndOf="@id/proxy_user_input_layout" + app:layout_constraintTop_toBottomOf="@id/proxy_host_input_layout"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/proxy_port" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="number" + android:nextFocusForward="@id/proxy_password" /> + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/proxy_password_input_layout" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/normal_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:hint="@string/password" + app:endIconMode="password_toggle" + app:layout_constraintStart_toStartOf="@id/proxy_user_input_layout" + app:layout_constraintTop_toBottomOf="@id/proxy_user_input_layout"> + + <com.google.android.material.textfield.TextInputEditText + android:id="@+id/proxy_password" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textPassword" /> + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.button.MaterialButton + android:id="@+id/save" + style="@style/AppTheme.OutlinedButton" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/normal_margin" + android:layout_marginEnd="@dimen/activity_horizontal_margin" + android:text="@string/crypto_save" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/proxy_password_input_layout" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_key_selection.xml b/app/src/main/res/layout/fragment_key_selection.xml new file mode 100644 index 00000000..9a391276 --- /dev/null +++ b/app/src/main/res/layout/fragment_key_selection.xml @@ -0,0 +1,81 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. + ~ SPDX-License-Identifier: GPL-3.0-only + --> + +<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/colorPrimary" + android:orientation="vertical"> + + <androidx.appcompat.widget.AppCompatImageView + android:id="@+id/app_icon" + android:layout_width="64dp" + android:layout_height="64dp" + android:layout_marginStart="32dp" + android:layout_marginTop="100dp" + android:contentDescription="@string/app_icon_hint" + android:src="@mipmap/ic_launcher" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <androidx.appcompat.widget.AppCompatTextView + android:id="@+id/app_name" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_centerHorizontal="true" + android:layout_marginStart="16dp" + android:text="@string/app_name" + android:textAppearance="@style/TextAppearance.MaterialComponents.Headline5" + android:textColor="@color/color_control_normal" + android:textStyle="bold" + app:layout_constraintBottom_toBottomOf="@id/app_icon" + app:layout_constraintStart_toEndOf="@id/app_icon" + app:layout_constraintTop_toTopOf="@+id/app_icon" /> + + <TextView + android:id="@+id/gpg_key" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="48dp" + android:text="@string/select_gpg_key_title" + android:textAppearance="@style/TextAppearance.MaterialComponents.Headline4" + android:textColor="@color/color_control_normal" + android:textStyle="bold" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0" + app:layout_constraintStart_toStartOf="@id/app_icon" + app:layout_constraintTop_toBottomOf="@id/app_icon" /> + + <TextView + android:id="@+id/gpg_key_text" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="48dp" + android:layout_marginEnd="16dp" + android:text="@string/select_gpg_key_message" + android:textAppearance="@style/TextAppearance.MaterialComponents.Body1" + android:textColor="@color/color_control_normal" + android:textStyle="bold" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="@id/gpg_key" + app:layout_constraintTop_toBottomOf="@id/gpg_key" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/select_key" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="16dp" + android:layout_marginTop="48dp" + android:layout_marginEnd="16dp" + android:maxWidth="300dp" + android:minWidth="100dp" + android:text="@string/gpg_key_select" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/gpg_key_text" /> + +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/values-v23/colors.xml b/app/src/main/res/values-v23/colors.xml deleted file mode 100644 index 7c3be280..00000000 --- a/app/src/main/res/values-v23/colors.xml +++ /dev/null @@ -1,8 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?><!-- - ~ Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved. - ~ SPDX-License-Identifier: GPL-3.0-only - --> - -<resources> - <color name="navigation_bar_color">#000000</color> -</resources> diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index bea2e960..b090c761 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -15,7 +15,7 @@ <!-- Theme variables --> <color name="color_control_normal">@color/primary_text_color</color> <color name="list_multiselect_background">#668eacbb</color> - <color name="navigation_bar_color">@color/primary_color</color> + <color name="navigation_bar_color">#000000</color> <color name="status_bar_color">@color/primary_color</color> <color name="ripple_color">#aa003e5b</color> <color name="button_color">#44003e5b</color> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 57c32126..cc9f1f24 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,10 +100,10 @@ <!-- DECRYPT Layout --> <string name="action_search">Search</string> - <string name="password">Password:</string> - <string name="otp">OTP:</string> + <string name="password">Password</string> + <string name="otp">OTP</string> <string name="extra_content">Extra content:</string> - <string name="username">Username:</string> + <string name="username">Username</string> <string name="edit_password">Edit password</string> <string name="copy_password">Copy password</string> <string name="share_as_plaintext">Share as plaintext</string> @@ -400,9 +400,18 @@ <string name="select_repo_type_text">Select if you want to create a local repo or clone a remote repo.</string> <string name="clone_remote_repo">Clone Remote Repo</string> <string name="create_local_repo">Create Local Repo</string> + <string name="select_gpg_key_title">Select\nGPG\nKey</string> + <string name="select_gpg_key_message">Select a GPG key to initialize your store with</string> + <string name="gpg_key_select">Select key</string> <!-- SSH port validation --> <string name="ssh_scheme_needed_title">Potentially incorrect URL</string> <string name="ssh_scheme_needed_message">It appears that your URL contains a custom port, but does not specify the ssh:// scheme.\nThis can cause the port to be considered a part of your path. Press OK here to fix the URL.</string> + <!-- Proxy configuration activity --> + <string name="proxy_hostname">Proxy hostname</string> + <string name="port">Port</string> + <string name="pref_proxy_settings">HTTP(S) proxy settings</string> + <string name="invalid_proxy_url">Invalid URL</string> + </resources> diff --git a/app/src/main/res/xml/preference.xml b/app/src/main/res/xml/preference.xml index 1d82df18..58a173c5 100644 --- a/app/src/main/res/xml/preference.xml +++ b/app/src/main/res/xml/preference.xml @@ -49,6 +49,9 @@ app:key="git_server_info" app:title="@string/pref_edit_server_info" /> <Preference + app:key="proxy_settings" + app:title="@string/pref_proxy_settings" /> + <Preference app:key="git_config" app:title="@string/pref_edit_git_config" /> <Preference diff --git a/autofill-parser/api/autofill-parser.api b/autofill-parser/api/autofill-parser.api new file mode 100644 index 00000000..85f02081 --- /dev/null +++ b/autofill-parser/api/autofill-parser.api @@ -0,0 +1,381 @@ +public final class com/github/androidpasswordstore/autofillparser/AutofillAction : java/lang/Enum { + public static final field FillOtpFromSms Lcom/github/androidpasswordstore/autofillparser/AutofillAction; + public static final field Generate Lcom/github/androidpasswordstore/autofillparser/AutofillAction; + public static final field Match Lcom/github/androidpasswordstore/autofillparser/AutofillAction; + public static final field Search Lcom/github/androidpasswordstore/autofillparser/AutofillAction; + public static fun valueOf (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/AutofillAction; + public static fun values ()[Lcom/github/androidpasswordstore/autofillparser/AutofillAction; +} + +public abstract interface annotation class com/github/androidpasswordstore/autofillparser/AutofillDsl : java/lang/annotation/Annotation { +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillHelperKt { + public static final fun computeCertificatesHash (Landroid/content/Context;Ljava/lang/String;)Ljava/lang/String; + public static final fun findNodeByAutofillId (Landroid/app/assist/AssistStructure;Landroid/view/autofill/AutofillId;)Landroid/app/assist/AssistStructure$ViewNode; + public static final fun getWebOrigin (Landroid/app/assist/AssistStructure$ViewNode;)Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillRule { + public synthetic fun <init> (Ljava/util/List;ZZLjava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun match (Ljava/util/List;Ljava/util/List;Ljava/util/List;ZZ)Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillRule$AutofillRuleMatcher { + public fun <init> (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType;Lcom/github/androidpasswordstore/autofillparser/FieldMatcher;ZZ)V + public final fun component1 ()Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public final fun component2 ()Lcom/github/androidpasswordstore/autofillparser/FieldMatcher; + public final fun component3 ()Z + public final fun component4 ()Z + public final fun copy (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType;Lcom/github/androidpasswordstore/autofillparser/FieldMatcher;ZZ)Lcom/github/androidpasswordstore/autofillparser/AutofillRule$AutofillRuleMatcher; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$AutofillRuleMatcher;Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType;Lcom/github/androidpasswordstore/autofillparser/FieldMatcher;ZZILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/AutofillRule$AutofillRuleMatcher; + public fun equals (Ljava/lang/Object;)Z + public final fun getMatchHidden ()Z + public final fun getMatcher ()Lcom/github/androidpasswordstore/autofillparser/FieldMatcher; + public final fun getOptional ()Z + public final fun getType ()Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillRule$Builder { + public static final field Companion Lcom/github/androidpasswordstore/autofillparser/AutofillRule$Builder$Companion; + public fun <init> (ZZ)V + public final fun build ()Lcom/github/androidpasswordstore/autofillparser/AutofillRule; + public final fun currentPassword (ZZLkotlin/jvm/functions/Function1;)V + public static synthetic fun currentPassword$default (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$Builder;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun genericPassword (ZLkotlin/jvm/functions/Function1;)V + public static synthetic fun genericPassword$default (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$Builder;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun getName ()Ljava/lang/String; + public final fun newPassword (ZLkotlin/jvm/functions/Function1;)V + public static synthetic fun newPassword$default (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$Builder;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun otp (ZLkotlin/jvm/functions/Function1;)V + public static synthetic fun otp$default (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$Builder;ZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V + public final fun setName (Ljava/lang/String;)V + public final fun username (ZZLkotlin/jvm/functions/Function1;)V + public static synthetic fun username$default (Lcom/github/androidpasswordstore/autofillparser/AutofillRule$Builder;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillRule$Builder$Companion { +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType : java/lang/Enum { + public static final field CurrentPassword Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public static final field GenericPassword Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public static final field NewPassword Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public static final field Otp Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public static final field Username Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public static fun valueOf (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; + public static fun values ()[Lcom/github/androidpasswordstore/autofillparser/AutofillRule$FillableFieldType; +} + +public abstract class com/github/androidpasswordstore/autofillparser/AutofillScenario { + public static final field BUNDLE_KEY_CURRENT_PASSWORD_IDS Ljava/lang/String; + public static final field BUNDLE_KEY_FILL_USERNAME Ljava/lang/String; + public static final field BUNDLE_KEY_GENERIC_PASSWORD_IDS Ljava/lang/String; + public static final field BUNDLE_KEY_NEW_PASSWORD_IDS Ljava/lang/String; + public static final field BUNDLE_KEY_OTP_ID Ljava/lang/String; + public static final field BUNDLE_KEY_USERNAME_ID Ljava/lang/String; + public static final field Companion Lcom/github/androidpasswordstore/autofillparser/AutofillScenario$Companion; + public final fun fieldsToFillOn (Lcom/github/androidpasswordstore/autofillparser/AutofillAction;)Ljava/util/List; + public final fun getAllFields ()Ljava/util/List; + public abstract fun getAllPasswordFields ()Ljava/util/List; + public final fun getFieldsToSave ()Ljava/util/List; + public abstract fun getFillUsername ()Z + public abstract fun getOtp ()Ljava/lang/Object; + public abstract fun getPasswordFieldsToFillOnGenerate ()Ljava/util/List; + public abstract fun getPasswordFieldsToFillOnMatch ()Ljava/util/List; + public abstract fun getPasswordFieldsToFillOnSearch ()Ljava/util/List; + public abstract fun getPasswordFieldsToSave ()Ljava/util/List; + public abstract fun getUsername ()Ljava/lang/Object; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillScenario$Builder { + public fun <init> ()V + public final fun build ()Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; + public final fun getCurrentPassword ()Ljava/util/List; + public final fun getFillUsername ()Z + public final fun getGenericPassword ()Ljava/util/List; + public final fun getNewPassword ()Ljava/util/List; + public final fun getOtp ()Ljava/lang/Object; + public final fun getUsername ()Ljava/lang/Object; + public final fun setFillUsername (Z)V + public final fun setOtp (Ljava/lang/Object;)V + public final fun setUsername (Ljava/lang/Object;)V +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillScenario$Companion { + public final fun fromBundle (Landroid/os/Bundle;)Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillScenarioKt { + public static final fun fillWithAutofillId (Landroid/service/autofill/Dataset$Builder;Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;Lcom/github/androidpasswordstore/autofillparser/AutofillAction;Lcom/github/androidpasswordstore/autofillparser/Credentials;)V + public static final fun fillWithFormField (Landroid/service/autofill/Dataset$Builder;Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;Lcom/github/androidpasswordstore/autofillparser/AutofillAction;Lcom/github/androidpasswordstore/autofillparser/Credentials;)V + public static final fun getPasswordValue (Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;)Ljava/lang/String; + public static final fun getUsernameValue (Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;)Ljava/lang/String; + public static final fun map (Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;Lkotlin/jvm/functions/Function1;)Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; + public static final fun passesOriginCheck (Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;Z)Z + public static final fun recoverNodes (Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;Landroid/app/assist/AssistStructure;)Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; + public static final fun toBundleFormField (Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;)Landroid/os/Bundle; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillStrategy { + public synthetic fun <init> (Ljava/util/List;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun match (Ljava/util/List;ZZ)Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillStrategy$Builder { + public fun <init> ()V + public final fun build ()Lcom/github/androidpasswordstore/autofillparser/AutofillStrategy; + public final fun rule (ZZLkotlin/jvm/functions/Function1;)V + public static synthetic fun rule$default (Lcom/github/androidpasswordstore/autofillparser/AutofillStrategy$Builder;ZZLkotlin/jvm/functions/Function1;ILjava/lang/Object;)V +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillStrategyDslKt { + public static final fun strategy (Lkotlin/jvm/functions/Function1;)Lcom/github/androidpasswordstore/autofillparser/AutofillStrategy; +} + +public final class com/github/androidpasswordstore/autofillparser/AutofillStrategyKt { + public static final fun getAutofillStrategy ()Lcom/github/androidpasswordstore/autofillparser/AutofillStrategy; +} + +public final class com/github/androidpasswordstore/autofillparser/BrowserAutofillSupportInfo { + public fun <init> (Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod;Ljava/lang/Integer;)V + public final fun component1 ()Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; + public final fun component2 ()Ljava/lang/Integer; + public final fun copy (Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod;Ljava/lang/Integer;)Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportInfo; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportInfo;Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod;Ljava/lang/Integer;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportInfo; + public fun equals (Ljava/lang/Object;)Z + public final fun getMultiOriginMethod ()Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; + public final fun getSaveFlags ()Ljava/lang/Integer; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel : java/lang/Enum { + public static final field FlakyFill Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; + public static final field GeneralFill Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; + public static final field GeneralFillAndSave Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; + public static final field None Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; + public static final field PasswordFill Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; + public static fun valueOf (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; + public static fun values ()[Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportLevel; +} + +public final class com/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod : java/lang/Enum { + public static final field Field Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; + public static final field None Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; + public static final field WebView Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; + public static fun valueOf (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; + public static fun values ()[Lcom/github/androidpasswordstore/autofillparser/BrowserMultiOriginMethod; +} + +public final class com/github/androidpasswordstore/autofillparser/CertaintyLevel : java/lang/Enum { + public static final field Certain Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public static final field Impossible Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public static final field Likely Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public static final field Possible Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public static fun valueOf (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public static fun values ()[Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; +} + +public final class com/github/androidpasswordstore/autofillparser/ClassifiedAutofillScenario : com/github/androidpasswordstore/autofillparser/AutofillScenario { + public fun <init> (Ljava/lang/Object;ZLjava/lang/Object;Ljava/util/List;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Z + public final fun component3 ()Ljava/lang/Object; + public final fun component4 ()Ljava/util/List; + public final fun component5 ()Ljava/util/List; + public final fun copy (Ljava/lang/Object;ZLjava/lang/Object;Ljava/util/List;Ljava/util/List;)Lcom/github/androidpasswordstore/autofillparser/ClassifiedAutofillScenario; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/ClassifiedAutofillScenario;Ljava/lang/Object;ZLjava/lang/Object;Ljava/util/List;Ljava/util/List;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/ClassifiedAutofillScenario; + public fun equals (Ljava/lang/Object;)Z + public fun getAllPasswordFields ()Ljava/util/List; + public final fun getCurrentPassword ()Ljava/util/List; + public fun getFillUsername ()Z + public final fun getNewPassword ()Ljava/util/List; + public fun getOtp ()Ljava/lang/Object; + public fun getPasswordFieldsToFillOnGenerate ()Ljava/util/List; + public fun getPasswordFieldsToFillOnMatch ()Ljava/util/List; + public fun getPasswordFieldsToFillOnSearch ()Ljava/util/List; + public fun getPasswordFieldsToSave ()Ljava/util/List; + public fun getUsername ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/Credentials { + public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/Credentials; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/Credentials;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/Credentials; + public fun equals (Ljava/lang/Object;)Z + public final fun getOtp ()Ljava/lang/String; + public final fun getPassword ()Ljava/lang/String; + public final fun getUsername ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/FeatureAndTrustDetectionKt { + public static final fun getBrowserAutofillSupportInfoIfTrusted (Landroid/content/Context;Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/BrowserAutofillSupportInfo; + public static final fun getInstalledBrowsersWithAutofillSupportLevel (Landroid/content/Context;)Ljava/util/List; +} + +public abstract interface class com/github/androidpasswordstore/autofillparser/FieldMatcher { + public abstract fun match (Ljava/util/List;Ljava/util/List;)Ljava/util/List; +} + +public final class com/github/androidpasswordstore/autofillparser/FieldMatcher$Builder { + public fun <init> ()V + public final fun breakTieOnPair (Lkotlin/jvm/functions/Function2;)V + public final fun breakTieOnSingle (Lkotlin/jvm/functions/Function2;)V + public final fun build ()Lcom/github/androidpasswordstore/autofillparser/FieldMatcher; + public final fun takePair (Lkotlin/jvm/functions/Function2;)V + public static synthetic fun takePair$default (Lcom/github/androidpasswordstore/autofillparser/FieldMatcher$Builder;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public final fun takeSingle (Lkotlin/jvm/functions/Function2;)V + public static synthetic fun takeSingle$default (Lcom/github/androidpasswordstore/autofillparser/FieldMatcher$Builder;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V +} + +public final class com/github/androidpasswordstore/autofillparser/FillableForm { + public static final field Companion Lcom/github/androidpasswordstore/autofillparser/FillableForm$Companion; + public synthetic fun <init> (Lcom/github/androidpasswordstore/autofillparser/FormOrigin;Lcom/github/androidpasswordstore/autofillparser/AutofillScenario;Ljava/util/List;Ljava/lang/Integer;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getFormOrigin ()Lcom/github/androidpasswordstore/autofillparser/FormOrigin; + public final fun getIgnoredIds ()Ljava/util/List; + public final fun getSaveFlags ()Ljava/lang/Integer; + public final fun getScenario ()Lcom/github/androidpasswordstore/autofillparser/AutofillScenario; + public final fun toClientState ()Landroid/os/Bundle; +} + +public final class com/github/androidpasswordstore/autofillparser/FillableForm$Companion { + public final fun parseAssistStructure (Landroid/content/Context;Landroid/app/assist/AssistStructure;ZLkotlin/sequences/Sequence;)Lcom/github/androidpasswordstore/autofillparser/FillableForm; + public static synthetic fun parseAssistStructure$default (Lcom/github/androidpasswordstore/autofillparser/FillableForm$Companion;Landroid/content/Context;Landroid/app/assist/AssistStructure;ZLkotlin/sequences/Sequence;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/FillableForm; +} + +public final class com/github/androidpasswordstore/autofillparser/FixedSaveCallback { + public fun <init> (Landroid/content/Context;Landroid/service/autofill/SaveCallback;)V + public final fun onFailure (Ljava/lang/CharSequence;)V + public final fun onSuccess (Landroid/content/IntentSender;)V +} + +public final class com/github/androidpasswordstore/autofillparser/FormField { + public static final field Companion Lcom/github/androidpasswordstore/autofillparser/FormField$Companion; + public fun <init> (Landroid/app/assist/AssistStructure$ViewNode;IZLjava/lang/String;)V + public synthetic fun <init> (Landroid/app/assist/AssistStructure$ViewNode;IZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun directlyFollows (Lcom/github/androidpasswordstore/autofillparser/FormField;)Z + public final fun directlyFollows (Ljava/lang/Iterable;)Z + public final fun directlyPrecedes (Lcom/github/androidpasswordstore/autofillparser/FormField;)Z + public final fun directlyPrecedes (Ljava/lang/Iterable;)Z + public fun equals (Ljava/lang/Object;)Z + public final fun getAutofillId ()Landroid/view/autofill/AutofillId; + public final fun getCouldBeTwoStepHiddenPassword ()Z + public final fun getCouldBeTwoStepHiddenUsername ()Z + public final fun getHasAutocompleteHintCurrentPassword ()Z + public final fun getHasAutofillHintPassword ()Z + public final fun getHasHintNewPassword ()Z + public final fun getHasHintOtp ()Z + public final fun getHasHintPassword ()Z + public final fun getHasHintUsername ()Z + public final fun getOtpCertainty ()Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public final fun getPasswordCertainty ()Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public final fun getRelevantField ()Z + public final fun getUsernameCertainty ()Lcom/github/androidpasswordstore/autofillparser/CertaintyLevel; + public final fun getWebOrigin ()Ljava/lang/String; + public final fun getWebOriginToPassDown ()Ljava/lang/String; + public fun hashCode ()I + public final fun isFocused ()Z + public final fun isVisible ()Z + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/FormField$Companion { +} + +public abstract class com/github/androidpasswordstore/autofillparser/FormOrigin { + public static final field Companion Lcom/github/androidpasswordstore/autofillparser/FormOrigin$Companion; + public synthetic fun <init> (Ljava/lang/String;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun getIdentifier ()Ljava/lang/String; + public final fun getPrettyIdentifier (Landroid/content/Context;Z)Ljava/lang/String; + public static synthetic fun getPrettyIdentifier$default (Lcom/github/androidpasswordstore/autofillparser/FormOrigin;Landroid/content/Context;ZILjava/lang/Object;)Ljava/lang/String; + public final fun toBundle ()Landroid/os/Bundle; +} + +public final class com/github/androidpasswordstore/autofillparser/FormOrigin$App : com/github/androidpasswordstore/autofillparser/FormOrigin { + public fun <init> (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/FormOrigin$App; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/FormOrigin$App;Ljava/lang/String;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/FormOrigin$App; + public fun equals (Ljava/lang/Object;)Z + public fun getIdentifier ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/FormOrigin$Companion { + public final fun fromBundle (Landroid/os/Bundle;)Lcom/github/androidpasswordstore/autofillparser/FormOrigin; +} + +public final class com/github/androidpasswordstore/autofillparser/FormOrigin$Web : com/github/androidpasswordstore/autofillparser/FormOrigin { + public fun <init> (Ljava/lang/String;)V + public final fun component1 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;)Lcom/github/androidpasswordstore/autofillparser/FormOrigin$Web; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/FormOrigin$Web;Ljava/lang/String;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/FormOrigin$Web; + public fun equals (Ljava/lang/Object;)Z + public fun getIdentifier ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/GenericAutofillScenario : com/github/androidpasswordstore/autofillparser/AutofillScenario { + public fun <init> (Ljava/lang/Object;ZLjava/lang/Object;Ljava/util/List;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Z + public final fun component3 ()Ljava/lang/Object; + public final fun component4 ()Ljava/util/List; + public final fun copy (Ljava/lang/Object;ZLjava/lang/Object;Ljava/util/List;)Lcom/github/androidpasswordstore/autofillparser/GenericAutofillScenario; + public static synthetic fun copy$default (Lcom/github/androidpasswordstore/autofillparser/GenericAutofillScenario;Ljava/lang/Object;ZLjava/lang/Object;Ljava/util/List;ILjava/lang/Object;)Lcom/github/androidpasswordstore/autofillparser/GenericAutofillScenario; + public fun equals (Ljava/lang/Object;)Z + public fun getAllPasswordFields ()Ljava/util/List; + public fun getFillUsername ()Z + public final fun getGenericPassword ()Ljava/util/List; + public fun getOtp ()Ljava/lang/Object; + public fun getPasswordFieldsToFillOnGenerate ()Ljava/util/List; + public fun getPasswordFieldsToFillOnMatch ()Ljava/util/List; + public fun getPasswordFieldsToFillOnSearch ()Ljava/util/List; + public fun getPasswordFieldsToSave ()Ljava/util/List; + public fun getUsername ()Ljava/lang/Object; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/PublicSuffixListCacheKt { + public static final fun cachePublicSuffixList (Landroid/content/Context;)V + public static final fun getCanonicalSuffix (Landroid/content/Context;Ljava/lang/String;Lkotlin/sequences/Sequence;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun getPublicSuffixPlusOne (Landroid/content/Context;Ljava/lang/String;Lkotlin/sequences/Sequence;)Ljava/lang/String; + public static final fun getSuffixPlusUpToOne (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; +} + +public final class com/github/androidpasswordstore/autofillparser/SingleFieldMatcher : com/github/androidpasswordstore/autofillparser/FieldMatcher { + public fun <init> (Lkotlin/jvm/functions/Function2;Ljava/util/List;)V + public fun match (Ljava/util/List;Ljava/util/List;)Ljava/util/List; +} + +public final class com/github/androidpasswordstore/autofillparser/SingleFieldMatcher$Builder { + public fun <init> ()V + public final fun breakTieOnSingle (Lkotlin/jvm/functions/Function2;)V + public final fun build ()Lcom/github/androidpasswordstore/autofillparser/SingleFieldMatcher; + public final fun takeSingle (Lkotlin/jvm/functions/Function2;)V + public static synthetic fun takeSingle$default (Lcom/github/androidpasswordstore/autofillparser/SingleFieldMatcher$Builder;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V +} + +public final class mozilla/components/lib/publicsuffixlist/PublicSuffixList { + public fun <init> (Landroid/content/Context;Lkotlinx/coroutines/CoroutineDispatcher;Lkotlinx/coroutines/CoroutineScope;)V + public synthetic fun <init> (Landroid/content/Context;Lkotlinx/coroutines/CoroutineDispatcher;Lkotlinx/coroutines/CoroutineScope;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getPublicSuffix (Ljava/lang/String;)Lkotlinx/coroutines/Deferred; + public final fun getPublicSuffixPlusOne (Ljava/lang/String;)Lkotlinx/coroutines/Deferred; + public final fun isPublicSuffix (Ljava/lang/String;)Lkotlinx/coroutines/Deferred; + public final fun prefetch ()Lkotlinx/coroutines/Deferred; + public final fun stripPublicSuffix (Ljava/lang/String;)Lkotlinx/coroutines/Deferred; +} + diff --git a/autofill-parser/build.gradle.kts b/autofill-parser/build.gradle.kts index e668a1f3..7f2a6467 100644 --- a/autofill-parser/build.gradle.kts +++ b/autofill-parser/build.gradle.kts @@ -39,10 +39,37 @@ afterEvaluate { } publications { create<MavenPublication>("apsMaven") { + fun getKey(propertyName: String): String { + return findProperty(propertyName)?.toString() ?: error("Failed to find property for $propertyName") + } + from(components.getByName("release")) - groupId = findProperty("GROUP").toString() - artifactId = findProperty("POM_ARTIFACT_ID").toString() - version = findProperty("VERSION_NAME").toString() + groupId = getKey("GROUP") + artifactId = getKey("POM_ARTIFACT_ID") + version = getKey("VERSION_NAME") + pom { + name.set(getKey("POM_ARTIFACT_ID")) + description.set(getKey("POM_ARTIFACT_DESCRIPTION")) + url.set(getKey("POM_URL")) + licenses { + license { + name.set(getKey("POM_LICENSE_NAME")) + url.set(getKey("POM_LICENSE_URL")) + } + } + developers { + developer { + id.set(getKey("POM_DEVELOPER_ID")) + name.set(getKey("POM_DEVELOPER_NAME")) + email.set(getKey("POM_DEVELOPER_EMAIL")) + } + } + scm { + connection.set(getKey("POM_SCM_CONNECTION")) + developerConnection.set(getKey("POM_SCM_DEV_CONNECTION")) + url.set(getKey("POM_SCM_URL")) + } + } } } } diff --git a/autofill-parser/gradle.properties b/autofill-parser/gradle.properties index 24371c01..fe1b7688 100644 --- a/autofill-parser/gradle.properties +++ b/autofill-parser/gradle.properties @@ -1,6 +1,7 @@ GROUP=com.github.androidpasswordstore VERSION_NAME=1.0.0 POM_ARTIFACT_ID=autofill-parser +POM_ARTIFACT_DESCRIPTION=Android library for low-level parsing of Autofill structures POM_URL=https://github.com/Android-Password-Store/android-password-store POM_SCM_URL=https://github.com/Android-Password-Store/android-password-store @@ -13,3 +14,4 @@ POM_LICENSE_DIST=repo POM_DEVELOPER_ID=android-password-store POM_DEVELOPER_NAME=The Android Password Store Authors +POM_DEVELOPER_EMAIL=aps@msfjarvis.dev diff --git a/autofill-parser/src/main/assets/publicsuffixes b/autofill-parser/src/main/assets/publicsuffixes Binary files differindex 814d4969..074a5607 100644 --- a/autofill-parser/src/main/assets/publicsuffixes +++ b/autofill-parser/src/main/assets/publicsuffixes diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt index 86201be8..f3f6d97d 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/AutofillStrategyDsl.kt @@ -94,6 +94,10 @@ class SingleFieldMatcher( override fun match(fields: List<FormField>, alreadyMatched: List<FormField>): List<FormField>? { return fields.minus(alreadyMatched).filter { take(it, alreadyMatched) }.let { contestants -> + when (contestants.size) { + 1 -> return@let listOf(contestants.single()) + 0 -> return@let null + } var current = contestants for ((i, tieBreaker) in tieBreakers.withIndex()) { // Successively filter matched fields via tie breakers... @@ -127,11 +131,15 @@ private class PairOfFieldsMatcher( return fields.minus(alreadyMatched).zipWithNext() .filter { it.first directlyPrecedes it.second }.filter { take(it, alreadyMatched) } .let { contestants -> + when (contestants.size) { + 1 -> return@let contestants.single().toList() + 0 -> return@let null + } var current = contestants for ((i, tieBreaker) in tieBreakers.withIndex()) { val new = current.filter { tieBreaker(it, alreadyMatched) } if (new.isEmpty()) { - d { "Tie breaker #${i + 1}: Didn't match any field; skipping" } + d { "Tie breaker #${i + 1}: Didn't match any pair of fields; skipping" } continue } // and return if the available options have been narrowed to a single field. @@ -140,7 +148,7 @@ private class PairOfFieldsMatcher( current = new break } - d { "Tie breaker #${i + 1}: Matched ${new.size} fields; continuing" } + d { "Tie breaker #${i + 1}: Matched ${new.size} pairs of fields; continuing" } current = new } current.singleOrNull()?.toList() diff --git a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt index ae16a995..91e51f5c 100644 --- a/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt +++ b/autofill-parser/src/main/java/com/github/androidpasswordstore/autofillparser/FormField.kt @@ -243,7 +243,7 @@ class FormField( // Password field heuristics (based only on the current field) private val isPossiblePasswordField = - notExcluded && (isAndroidPasswordField || isHtmlPasswordField) + notExcluded && (isAndroidPasswordField || isHtmlPasswordField || hasHintPassword) private val isCertainPasswordField = isPossiblePasswordField && hasHintPassword private val isLikelyPasswordField = isPossiblePasswordField && (isCertainPasswordField || PASSWORD_HEURISTIC_TERMS.anyMatchesFieldInfo) diff --git a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt index d4d1c1af..f1cdb12a 100644 --- a/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt +++ b/autofill-parser/src/main/java/mozilla/components/lib/publicsuffixlist/PublicSuffixList.kt @@ -32,7 +32,7 @@ class PublicSuffixList( private val scope: CoroutineScope = CoroutineScope(dispatcher) ) { - private val data: PublicSuffixListData by lazy { PublicSuffixListLoader.load(context) } + private val data: PublicSuffixListData by lazy(LazyThreadSafetyMode.PUBLICATION) { PublicSuffixListLoader.load(context) } /** * Prefetch the public suffix list from disk so that it is available in memory. diff --git a/build.gradle.kts b/build.gradle.kts index c128f531..93b44473 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -4,20 +4,32 @@ */ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import com.android.build.gradle.BaseExtension +import kotlinx.validation.ApiValidationExtension buildscript { repositories { google() jcenter() + // For binary compatibility validator. + maven { url = uri("https://kotlin.bintray.com/kotlinx") } } dependencies { classpath(Plugins.agp) + classpath(Plugins.binaryCompatibilityValidator) classpath(Plugins.kotlin) } } plugins { - id("com.github.ben-manes.versions") version "0.31.0" + id("com.github.ben-manes.versions") version "0.33.0" +} + +apply(plugin = "binary-compatibility-validator") + +extensions.configure<ApiValidationExtension> { + ignoredProjects = mutableSetOf( + "app" + ) } subprojects { diff --git a/buildSrc/src/main/java/Dependencies.kt b/buildSrc/src/main/java/Dependencies.kt index 341cb0ae..0f8c903d 100644 --- a/buildSrc/src/main/java/Dependencies.kt +++ b/buildSrc/src/main/java/Dependencies.kt @@ -7,7 +7,8 @@ private const val KOTLIN_VERSION = "1.4.10" object Plugins { - const val agp = "com.android.tools.build:gradle:4.0.1" + const val agp = "com.android.tools.build:gradle:4.0.2" + const val binaryCompatibilityValidator = "org.jetbrains.kotlinx:binary-compatibility-validator:0.2.3" const val kotlin = "org.jetbrains.kotlin:kotlin-gradle-plugin:$KOTLIN_VERSION" } @@ -23,24 +24,24 @@ object Dependencies { object AndroidX { - private const val lifecycleVersion = "2.3.0-alpha07" + private const val lifecycleVersion = "2.3.0-beta01" - const val activity_ktx = "androidx.activity:activity-ktx:1.2.0-alpha08" + const val activity_ktx = "androidx.activity:activity-ktx:1.2.0-beta01" const val annotation = "androidx.annotation:annotation:1.1.0" const val autofill = "androidx.autofill:autofill:1.1.0-alpha02" const val appcompat = "androidx.appcompat:appcompat:1.3.0-alpha02" - const val biometric = "androidx.biometric:biometric:1.1.0-alpha02" - const val constraint_layout = "androidx.constraintlayout:constraintlayout:2.0.1" - const val core_ktx = "androidx.core:core-ktx:1.5.0-alpha02" + const val biometric = "androidx.biometric:biometric:1.1.0-beta01" + const val constraint_layout = "androidx.constraintlayout:constraintlayout:2.0.2" + const val core_ktx = "androidx.core:core-ktx:1.5.0-alpha04" const val documentfile = "androidx.documentfile:documentfile:1.0.1" - const val fragment_ktx = "androidx.fragment:fragment-ktx:1.3.0-alpha08" + const val fragment_ktx = "androidx.fragment:fragment-ktx:1.3.0-beta01" const val lifecycle_common = "androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion" const val lifecycle_livedata_ktx = "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion" const val lifecycle_viewmodel_ktx = "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycleVersion" const val material = "com.google.android.material:material:1.3.0-alpha02" const val preference = "androidx.preference:preference:1.1.1" - const val recycler_view = "androidx.recyclerview:recyclerview:1.2.0-alpha05" - const val recycler_view_selection = "androidx.recyclerview:recyclerview-selection:1.1.0-rc02" + const val recycler_view = "androidx.recyclerview:recyclerview:1.2.0-alpha06" + const val recycler_view_selection = "androidx.recyclerview:recyclerview-selection:1.1.0-rc03" const val security = "androidx.security:security-crypto:1.1.0-alpha02" const val swiperefreshlayout = "androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01" } @@ -60,8 +61,8 @@ object Dependencies { const val fastscroll = "me.zhanghai.android.fastscroll:library:1.1.4" const val jgit = "org.eclipse.jgit:org.eclipse.jgit:3.7.1.201504261725-r" const val kotlin_result = "com.michael-bull.kotlin-result:kotlin-result:1.1.9" - const val leakcanary = "com.squareup.leakcanary:leakcanary-android:2.4" - const val plumber = "com.squareup.leakcanary:plumber-android:2.4" + const val leakcanary = "com.squareup.leakcanary:leakcanary-android:2.5" + const val plumber = "com.squareup.leakcanary:plumber-android:2.5" const val sshj = "com.hierynomus:sshj:0.30.0" const val ssh_auth = "org.sufficientlysecure:sshauthentication-api:1.0" const val timber = "com.jakewharton.timber:timber:4.7.1" diff --git a/contrib/oisafe2pstore/oisafe2pstore.hs b/contrib/oisafe2pstore/oisafe2pstore.hs deleted file mode 100644 index 0417fc0a..00000000 --- a/contrib/oisafe2pstore/oisafe2pstore.hs +++ /dev/null @@ -1,85 +0,0 @@ -{- - oisafe2psore - Quick and dirty script to convert OI Safe export CSV - into the password-store tree format. - - Copyright 2016 Eugene Crosser - - License: BSD, Apache or GPLv3 - chose whatever suits you. - - You will need to adjust paths to the GnuPG program and the CSV - file produced by OI Safe. Also fill in the PGP key I.D. - Description becomes the file name. '*' in the Description is - converted to '+', '/' - to '-'. If this is not sufficient, - adjust the function `sanitize`. --} - -{-# LANGUAGE OverloadedStrings #-} - -module Main where - ---import Data.Text hiding (head, tail, reverse, length, map) -import Control.Monad -import Text.CSV -import System.Directory -import System.Process -import System.Exit - ---gpg = "/usr/local/bin/gpg2" -gpg = "/usr/bin/gpg2" - -keyid = "01234567" -- !!!Fill in the real I.D. here!!! - -data Entry = Entry { fCategory :: String - , fDescription :: String - , fWebsite :: String - , fUsername :: String - , fPassword :: String - , fNotes :: String - }; - -instance Show Entry where - show e = fPassword e - ++ nonempty "User" (fUsername e) - ++ nonempty "Website" (fWebsite e) - ++ nonempty "Notes" (fNotes e) - where - nonempty :: String -> String -> String - nonempty l v = if length v > 0 then "\n" ++ l ++ ": " ++ v else "" - -pathOf e = (sanitize (fCategory e), sanitize (fDescription e)) - -sanitize = map substsafe - where - substsafe '/' = '-' - substsafe '*' = '+' - substsafe x = x - -record2entry :: Record -> Maybe Entry -record2entry [fCat,fDesc,fWeb,fUser,fPass,fNote,_] = - Just (Entry { fCategory = fCat - , fDescription = fDesc - , fWebsite = fWeb - , fUsername = fUser - , fPassword = fPass - , fNotes = fNote - }) -record2entry _ = Nothing - -main = parseCSVFromFile "oisafe.csv" - >>= either (error . show) ((mapM_ makeEntry) . tail) - -makeEntry :: Record -> IO () -makeEntry = buildFile . record2entry - -buildFile :: Maybe Entry -> IO () -buildFile Nothing = return () -buildFile (Just e) = do - let - (sub, file) = pathOf e - dir = "password-store/" ++ sub - path = dir ++ "/" ++ file ++ ".gpg" - cont = show e - (rc, stdout, stderr) <- readProcessWithExitCode gpg ["-ae", "-r", keyid] cont - when (rc /= ExitSuccess) $ error $ "gpg rc " ++ (show rc) ++ " message " ++ stderr - createDirectoryIfMissing True dir - writeFile path stdout diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index ceaa4564..00000000 --- a/crowdin.yml +++ /dev/null @@ -1,3 +0,0 @@ -files: - - source: /app/src/main/res/values/strings.xml - translation: /app/src/main/res/values-%two_letters_code%/strings.xml diff --git a/release/deploy-github.sh b/release/deploy-github.sh deleted file mode 100755 index 9480604b..00000000 --- a/release/deploy-github.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash -set -ex - -trap 'exit 1' SIGINT SIGTERM - -[ -z "$(command -v hub)" ] && { echo "hub not installed; aborting!"; exit 1; } -[ -z "${1}" ] && { echo "No tag specified!"; exit 1; } -prev_ref="$(git rev-parse --abbrev-ref HEAD)" -git checkout "${1}" || exit 1 -gradle clean bundleRelease assembleRelease -hub release create "${1}" -a app/build/outputs/apk/release/app-release.apk -git checkout "$prev_ref" diff --git a/release/signing-setup.sh b/release/signing-setup.sh index d46504c3..e930e04d 100755 --- a/release/signing-setup.sh +++ b/release/signing-setup.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -set -ex +set -e ENCRYPT_KEY=$1 |