CredentialProviderUnlockActivity.kt

6.3 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.Action15import androidx.credentials.provider.CredentialEntry16import androidx.credentials.provider.PasswordCredentialEntry17import androidx.credentials.provider.PendingIntentHandler18import androidx.credentials.provider.PublicKeyCredentialEntry19import androidx.fragment.app.FragmentActivity20import com.peachpasswords.R21import com.peachpasswords.passkeys.PasskeyCallerOrigin22import com.peachpasswords.passkeys.PasskeyCredentialPlanner23import com.peachpasswords.passkeys.PasskeyCredentialSelectionActivity24import com.peachpasswords.passkeys.PasskeyUnlockIntent25import com.peachpasswords.autofill.appLinkDomains26import com.peachpasswords.shared.extractDomain27import com.peachpasswords.ui.lock.LockScreen28import com.peachpasswords.ui.theme.ACCENT_COLORS29import com.peachpasswords.ui.theme.DEFAULT_ACCENT30import com.peachpasswords.ui.theme.PeachTheme31import com.peachpasswords.vault.VaultRepository32import dagger.hilt.android.AndroidEntryPoint33import java.time.Instant34import javax.inject.Inject3536@AndroidEntryPoint37class CredentialProviderUnlockActivity : FragmentActivity() {38    @Inject lateinit var vault: VaultRepository39    private var debugSecureLease = false4041    override fun onCreate(savedInstanceState: Bundle?) {42        super.onCreate(savedInstanceState)43        if (!BuildConfig.DEBUG) SecureWindowPolicy.setUserSecure(window, true)44        else if (!intent.getBooleanExtra(QaFlags.EXTRA_DISABLE_SECURE_CAPTURE, false)) {45            SecureWindowPolicy.acquire(window)46            debugSecureLease = true47        }48        val request = intent.getBundleExtra(PasskeyUnlockIntent.EXTRA_REQUEST)49            ?.let(BeginGetCredentialRequest::fromBundle)50        if (request == null) {51            finish()52            return53        }54        val settings = vault.settings.value55        val accent = ACCENT_COLORS[settings.accentColor] ?: DEFAULT_ACCENT56        setContent {57            PeachTheme(accent = accent, textSize = settings.textSize) {58                LockScreen(59                    onUnlocked = { complete(request) },60                    onNavigate = { finish() },61                )62            }63        }64    }6566    override fun onDestroy() {67        if (debugSecureLease) SecureWindowPolicy.release(window)68        super.onDestroy()69    }7071    private fun complete(request: BeginGetCredentialRequest) {72        val info = request.callingAppInfo73        val allowlist = PasskeyCallerOrigin.privilegedAllowlist(this)74        val webOrigin = info?.let { runCatching { it.getOrigin(allowlist) }.getOrNull() }75        val domains = if (!webOrigin.isNullOrBlank()) {76            setOfNotNull(extractDomain(webOrigin))77        } else {78            info?.let { appLinkDomains(this, it.packageName).toSet() }.orEmpty()79        }80        val packageName = info?.packageName?.takeIf { webOrigin.isNullOrBlank() }81        val icon = Icon.createWithResource(this, R.drawable.ic_peach_mark)82        val callerOrigin = info?.let {83            PasskeyCallerOrigin.resolve(it, allowlist)84        }85        val entries = request.beginGetCredentialOptions.flatMap { option ->86            when (option) {87                is BeginGetPasswordOption -> PasswordCredentialPlanner.candidates(88                    vault.getEntries(),89                    domains,90                    packageName = packageName,91                    allowedUserIds = option.allowedUserIds,92                ).map { candidate ->93                    PasswordCredentialEntry.Builder(94                        this,95                        candidate.username,96                        PasswordCredentialSelectionActivity.pendingIntent(this, candidate.entryId),97                        option,98                    )99                        .setDisplayName(candidate.title)100                        .setLastUsedTime(Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L))101                        .setIcon(icon)102                        .setAutoSelectAllowed(false)103                        .build()104                }105                is BeginGetPublicKeyCredentialOption -> PasskeyCredentialPlanner.candidates(106                    vault.getEntries(),107                    option.requestJson,108                    callerOrigin,109                ).map { candidate ->110                    PublicKeyCredentialEntry(111                        context = this,112                        username = candidate.username,113                        pendingIntent = PasskeyCredentialSelectionActivity.pendingIntent(114                            this,115                            candidate.entryId,116                            option.requestJson,117                            callerOrigin,118                            option.clientDataHash,119                        ),120                        beginGetPublicKeyCredentialOption = option,121                        displayName = candidate.displayName,122                        lastUsedTime = Instant.ofEpochMilli(candidate.lastUsedAt ?: 0L),123                        icon = icon,124                        isAutoSelectAllowed = false,125                    )126                }127                else -> emptyList<CredentialEntry>()128            }129        }130131        val result = Intent()132        val hasPasskeyEntries = entries.any { it is PublicKeyCredentialEntry }133        val builder = BeginGetCredentialResponse.Builder().setCredentialEntries(entries)134        if (hasPasskeyEntries) {135            builder.addAction(136                Action(137                    "Choose another Peach passkey",138                    PasskeyCredentialSelectionActivity.choosePendingIntent(this, request, callerOrigin),139                    "Search eligible passkeys",140                ),141            )142        }143        PendingIntentHandler.setBeginGetCredentialResponse(144            result,145            builder.build(),146        )147        setResult(RESULT_OK, result)148        finish()149    }150}