aboutsummaryrefslogtreecommitdiffstats
path: root/app/src/main/java/dev/msfjarvis/aps/ui/autofill/AutofillSaveActivity.kt
blob: 46234c4f5baa80f62f2862fd830e437659d9acd4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
/*
 * Copyright © 2014-2020 The Android Password Store Authors. All Rights Reserved.
 * SPDX-License-Identifier: GPL-3.0-only
 */
package dev.msfjarvis.aps.ui.autofill

import android.app.PendingIntent
import android.content.Context
import android.content.Intent
import android.content.IntentSender
import android.os.Build
import android.os.Bundle
import android.view.autofill.AutofillManager
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import com.github.ajalt.timberkt.e
import com.github.androidpasswordstore.autofillparser.AutofillAction
import com.github.androidpasswordstore.autofillparser.Credentials
import com.github.androidpasswordstore.autofillparser.FormOrigin
import dev.msfjarvis.aps.util.autofill.AutofillMatcher
import dev.msfjarvis.aps.util.autofill.AutofillPreferences
import dev.msfjarvis.aps.util.autofill.AutofillResponseBuilder
import dev.msfjarvis.aps.ui.crypto.PasswordCreationActivity
import dev.msfjarvis.aps.data.repo.PasswordRepository
import java.io.File

@RequiresApi(Build.VERSION_CODES.O)
class AutofillSaveActivity : AppCompatActivity() {

    companion object {

        private const val EXTRA_FOLDER_NAME =
            "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_FOLDER_NAME"
        private const val EXTRA_PASSWORD = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_PASSWORD"
        private const val EXTRA_NAME = "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_NAME"
        private const val EXTRA_SHOULD_MATCH_APP =
            "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_APP"
        private const val EXTRA_SHOULD_MATCH_WEB =
            "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_SHOULD_MATCH_WEB"
        private const val EXTRA_GENERATE_PASSWORD =
            "dev.msfjarvis.aps.autofill.oreo.ui.EXTRA_GENERATE_PASSWORD"

        private var saveRequestCode = 1

        fun makeSaveIntentSender(
            context: Context,
            credentials: Credentials?,
            formOrigin: FormOrigin
        ): IntentSender {
            val identifier = formOrigin.getPrettyIdentifier(context, untrusted = false)
            // Prevent directory traversals
            val sanitizedIdentifier = identifier.replace('\\', '_')
                .replace('/', '_')
                .trimStart('.')
                .takeUnless { it.isBlank() } ?: formOrigin.identifier
            val directoryStructure = AutofillPreferences.directoryStructure(context)
            val folderName = directoryStructure.getSaveFolderName(
                sanitizedIdentifier = sanitizedIdentifier,
                username = credentials?.username
            )
            val fileName = directoryStructure.getSaveFileName(username = credentials?.username, identifier = identifier)
            val intent = Intent(context, AutofillSaveActivity::class.java).apply {
                putExtras(
                    bundleOf(
                        EXTRA_FOLDER_NAME to folderName,
                        EXTRA_NAME to fileName,
                        EXTRA_PASSWORD to credentials?.password,
                        EXTRA_SHOULD_MATCH_APP to formOrigin.identifier.takeIf { formOrigin is FormOrigin.App },
                        EXTRA_SHOULD_MATCH_WEB to formOrigin.identifier.takeIf { formOrigin is FormOrigin.Web },
                        EXTRA_GENERATE_PASSWORD to (credentials == null)
                    )
                )
            }
            return PendingIntent.getActivity(
                context,
                saveRequestCode++,
                intent,
                PendingIntent.FLAG_CANCEL_CURRENT
            ).intentSender
        }
    }

    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) {
            FormOrigin.App(shouldMatchApp)
        } else if (shouldMatchApp == null && shouldMatchWeb != null) {
            FormOrigin.Web(shouldMatchWeb)
        } else {
            null
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val repo = PasswordRepository.getRepositoryDirectory()
        val saveIntent = Intent(this, PasswordCreationActivity::class.java).apply {
            putExtras(
                bundleOf(
                    "REPO_PATH" to repo.absolutePath,
                    "FILE_PATH" to repo.resolve(intent.getStringExtra(EXTRA_FOLDER_NAME)!!).absolutePath,
                    PasswordCreationActivity.EXTRA_FILE_NAME to intent.getStringExtra(EXTRA_NAME),
                    PasswordCreationActivity.EXTRA_PASSWORD to intent.getStringExtra(EXTRA_PASSWORD),
                    PasswordCreationActivity.EXTRA_GENERATE_PASSWORD to intent.getBooleanExtra(EXTRA_GENERATE_PASSWORD, false)
                )
            )
        }
        registerForActivityResult(StartActivityForResult()) { result ->
            val data = result.data
            if (result.resultCode == RESULT_OK && data != null) {
                val createdPath = data.getStringExtra("CREATED_FILE")!!
                formOrigin?.let {
                    AutofillMatcher.addMatchFor(this, it, File(createdPath))
                }
                val password = data.getStringExtra("PASSWORD")
                val resultIntent = if (password != null) {
                    // Password was generated and should be filled into a form.
                    val username = data.getStringExtra("USERNAME")
                    val clientState =
                        intent?.getBundleExtra(AutofillManager.EXTRA_CLIENT_STATE) ?: run {
                            e { "AutofillDecryptActivity started without EXTRA_CLIENT_STATE" }
                            finish()
                            return@registerForActivityResult
                        }
                    val credentials = Credentials(username, password, null)
                    val fillInDataset = AutofillResponseBuilder.makeFillInDataset(
                        this,
                        credentials,
                        clientState,
                        AutofillAction.Generate
                    )
                    Intent().apply {
                        putExtra(AutofillManager.EXTRA_AUTHENTICATION_RESULT, fillInDataset)
                    }
                } else {
                    // Password was extracted from a form, there is nothing to fill.
                    Intent()
                }
                setResult(RESULT_OK, resultIntent)
            } else {
                setResult(RESULT_CANCELED)
            }
            finish()
        }.launch(saveIntent)
    }
}