CredentialProviderUnlockActivity.kt

5.8 KB · raw

1package com.peachpasswords.credentials23import android.content.Intent4import android.graphics.drawable.Icon5import android.os.Bundle6import com.peachpasswords.BuildConfig7import androidx.activity.compose.setContent8import com.peachpasswords.ui.components.QaFlags9import com.peachpasswords.ui.components.SecureWindowPolicy10import androidx.credentials.provider.BeginGetCredentialRequest11import androidx.credentials.provider.BeginGetPasswordOption12import androidx.credentials.provider.BeginGetPublicKeyCredentialOption13import androidx.credentials.provider.BeginGetCredentialResponse14import androidx.credentials.provider.CredentialEntry15import androidx.credentials.provider.PasswordCredentialEntry16import androidx.credentials.provider.PendingIntentHandler17import androidx.credentials.provider.PublicKeyCredentialEntry18import androidx.fragment.app.FragmentActivity19import com.peachpasswords.R20import com.peachpasswords.passkeys.PasskeyCallerOrigin21import com.peachpasswords.passkeys.PasskeyCredentialPlanner22import com.peachpasswords.passkeys.PasskeyCredentialSelectionActivity23import com.peachpasswords.passkeys.PasskeyUnlockIntent24import com.peachpasswords.autofill.appLinkDomains25import com.peachpasswords.shared.extractDomain26import com.peachpasswords.ui.lock.LockScreen27import com.peachpasswords.ui.theme.ACCENT_COLORS28import com.peachpasswords.ui.theme.DEFAULT_ACCENT29import com.peachpasswords.ui.theme.PeachTheme30import com.peachpasswords.vault.VaultRepository31import dagger.hilt.android.AndroidEntryPoint32import java.time.Instant33import javax.inject.Inject3435@AndroidEntryPoint36class CredentialProviderUnlockActivity : FragmentActivity() {37    @Inject lateinit var vault: VaultRepository38    private var debugSecureLease = false3940    override fun onCreate(savedInstanceState: Bundle?) {41        super.onCreate(savedInstanceState)42        if (!BuildConfig.DEBUG) SecureWindowPolicy.setUserSecure(window, true)43        else if (!intent.getBooleanExtra(QaFlags.EXTRA_DISABLE_SECURE_CAPTURE, false)) {44            SecureWindowPolicy.acquire(window)45            debugSecureLease = true46        }47        val request = intent.getBundleExtra(PasskeyUnlockIntent.EXTRA_REQUEST)48            ?.let(BeginGetCredentialRequest::fromBundle)49        if (request == null) {50            finish()51            return52        }53        val settings = vault.settings.value54        val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT55        setContent {56            PeachTheme(accent = accent, textSize = settings.textSize) {57                LockScreen(58                    onUnlocked = { complete(request) },59                    onNavigate = { finish() },60                )61            }62        }63    }6465    override fun onDestroy() {66        if (debugSecureLease) SecureWindowPolicy.release(window)67        super.onDestroy()68    }6970    private fun complete(request: BeginGetCredentialRequest) {71        val info = request.callingAppInfo72        val allowlist = PasskeyCallerOrigin.privilegedAllowlist(this)73        val webOrigin = info?.let { runCatching { it.getOrigin(allowlist) }.getOrNull() }74        val domains = if (!webOrigin.isNullOrBlank()) {75            setOfNotNull(extractDomain(webOrigin))76        } else {77            info?.let { appLinkDomains(this, it.packageName).toSet() }.orEmpty()78        }79        val packageName = info?.packageName?.takeIf { webOrigin.isNullOrBlank() }80        val icon = Icon.createWithResource(this, R.drawable.ic_peach_mark)81        val callerOrigin = info?.let {82            PasskeyCallerOrigin.resolve(it, allowlist)83        }84        val entries = request.beginGetCredentialOptions.flatMap { option ->85            when (option) {86                is BeginGetPasswordOption -> PasswordCredentialPlanner.candidates(87                    vault.getEntries(),88                    domains,89                    packageName = packageName,90                    allowedUserIds = option.allowedUserIds,91                ).map { candidate ->92                    PasswordCredentialEntry.Builder(93                        this,94                        candidate.username,95                        PasswordCredentialSelectionActivity.pendingIntent(this, candidate.entryId),96                        option,97                    )98                        .setDisplayName(candidate.title)99                        .setLastUsedTime(Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L))100                        .setIcon(icon)101                        .setAutoSelectAllowed(false)102                        .build()103                }104                is BeginGetPublicKeyCredentialOption -> PasskeyCredentialPlanner.candidates(105                    vault.getEntries(),106                    option.requestJson,107                    callerOrigin,108                ).map { candidate ->109                    PublicKeyCredentialEntry(110                        context = this,111                        username = candidate.username,112                        pendingIntent = PasskeyCredentialSelectionActivity.pendingIntent(113                            this,114                            candidate.entryId,115                            option.requestJson,116                            callerOrigin,117                        ),118                        beginGetPublicKeyCredentialOption = option,119                        displayName = candidate.displayName,120                        lastUsedTime = Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L),121                        icon = icon,122                        isAutoSelectAllowed = false,123                    )124                }125                else -> emptyList<CredentialEntry>()126            }127        }128129        val result = Intent()130        PendingIntentHandler.setBeginGetCredentialResponse(131            result,132            BeginGetCredentialResponse.Builder().setCredentialEntries(entries).build(),133        )134        setResult(RESULT_OK, result)135        finish()136    }137}