@@ -62,6 +62,10 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
6262 val source: String?
6363 get() = intent.getStringExtra(KEY_SOURCE )
6464
65+ enum class AllowedInstantLevel {
66+ NONE , PRESELECT , INSTANT
67+ }
68+
6569 private val service: GmsService
6670 get() = GmsService .byServiceId(intent.getIntExtra(KEY_SERVICE , GmsService .UNKNOWN .SERVICE_ID ))
6771 private val database by lazy { Database (this ) }
@@ -79,6 +83,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
7983 lateinit var callerSignature: String
8084 private lateinit var navHostFragment: NavHostFragment
8185 private var preselectedCredentialId: String? = null
86+ private var preselectedTransport: Transport ? = null
8287
8388 private inline fun <reified T : TransportHandler > getTransportHandler (): T ? =
8489 transportHandlers.filterIsInstance<T >().firstOrNull { it.isSupported }
@@ -106,14 +111,23 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
106111 this .callerSignature = packageManager.getFirstSignatureDigest(callerPackage, " SHA-256" )?.toBase64()
107112 ? : return finishWithError(UNKNOWN_ERR , " Could not determine signature of app" )
108113 this .preselectedCredentialId = intent.getStringExtra(KEY_CREDENTIAL_ID )
114+ this .preselectedTransport = intent.getStringExtra(KEY_PRESELECTED_TRANSPORT )?.let {
115+ runCatching { Transport .valueOf(it) }.getOrNull()
116+ }
109117
110118 Log .d(TAG , " onCreate caller=$callerPackage options=$options preselectedCredentialId=$preselectedCredentialId " )
111119
120+ // By default, we allow instants. This extra may be used by app using fido-core library
121+ // To avoid instant.
122+ val allowInstant = AllowedInstantLevel .entries.getOrNull(
123+ intent.getIntExtra(KEY_ALLOW_INSTANT , AllowedInstantLevel .INSTANT .ordinal)
124+ ) ? : AllowedInstantLevel .INSTANT
125+
112126 val requiresPrivilege =
113127 source == SOURCE_BROWSER && ! database.isPrivileged(callerPackage, callerSignature)
114128
115129 // Check if we can directly open screen lock handling
116- if (! requiresPrivilege) {
130+ if (allowInstant == AllowedInstantLevel . INSTANT && ! requiresPrivilege) {
117131 val instantTransport = transportHandlers.firstOrNull {
118132 it.isSupported && it.shouldBeUsedInstantly(options, preselectedCredentialId)
119133 }
@@ -128,7 +142,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
128142 setContentView(R .layout.fido_authenticator_activity)
129143
130144 lifecycleScope.launchWhenCreated {
131- handleRequest(options)
145+ handleRequest(options, allowInstant )
132146 }
133147 } catch (e: RequestHandlingException ) {
134148 finishWithError(e.errorCode, e.message ? : e.errorCode.name)
@@ -139,7 +153,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
139153 }
140154
141155 @RequiresApi(24 )
142- suspend fun handleRequest (options : RequestOptions , allowInstant : Boolean = true ) {
156+ suspend fun handleRequest (options : RequestOptions , allowInstantLevel : AllowedInstantLevel = AllowedInstantLevel . INSTANT ) {
143157 try {
144158 val origin = getOrigin(this , options, callerPackage)
145159 options.checkIsValid(this , origin, callerPackage)
@@ -154,7 +168,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
154168 val noLocalUserForSignInstantBlock = options.type == RequestOptionsType .SIGN && database.getKnownRegistrationInfo(options.rpId).isEmpty()
155169
156170 // Check if we can directly open screen lock handling
157- if (! requiresPrivilege && allowInstant && ! noLocalUserForSignInstantBlock) {
171+ if (! requiresPrivilege && allowInstantLevel == AllowedInstantLevel . INSTANT && ! noLocalUserForSignInstantBlock) {
158172 val instantTransport = transportHandlers.firstOrNull {
159173 it.isSupported && it.shouldBeUsedInstantly(options, preselectedCredentialId)
160174 }
@@ -171,7 +185,11 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
171185 this .requiresPrivilege = requiresPrivilege
172186 this .supportedTransports = transportHandlers.filter { it.isSupported }.map { it.transport }.toSet()
173187 }.arguments
174- val next = if (! requiresPrivilege) {
188+ val next = if (requiresPrivilege) {
189+ null
190+ } else if (allowInstantLevel == AllowedInstantLevel .NONE ) {
191+ R .id.transportSelectionFragment
192+ } else {
175193 val knownRegistrationTransports = mutableSetOf<Transport >()
176194 val allowedTransports = mutableSetOf<Transport >()
177195 if (options.type == RequestOptionsType .SIGN ) {
@@ -203,7 +221,12 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
203221 }
204222 }
205223 }
206- val preselectedTransport = knownRegistrationTransports.singleOrNull() ? : allowedTransports.singleOrNull()
224+ // We do not control if preselectedTransport is in allowedTransports, or if
225+ // allowedTransports is empty, as allowedTransports is a *hint*.
226+ // This is particularly useful if the application sends a credential with
227+ // transport=internal, but we login with another device (via hybrid connection)
228+ val preselectedTransport = preselectedTransport
229+ ? : knownRegistrationTransports.singleOrNull() ? : allowedTransports.singleOrNull()
207230 if (database.wasUsed()) {
208231 when (preselectedTransport) {
209232 USB -> R .id.usbFragment
@@ -215,8 +238,6 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
215238 } else {
216239 null
217240 }
218- } else {
219- null
220241 }
221242 navHostFragment = NavHostFragment ()
222243 supportFragmentManager.commit {
@@ -321,7 +342,7 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
321342 } catch (e: SecurityException ) {
322343 Log .w(TAG , e)
323344 if (instant) {
324- handleRequest(options, false )
345+ handleRequest(options, AllowedInstantLevel . PRESELECT )
325346 } else {
326347 finishWithError(SECURITY_ERR , e.message ? : e.javaClass.simpleName)
327348 }
@@ -381,9 +402,11 @@ class AuthenticatorActivity : AppCompatActivity(), TransportHandlerCallback {
381402 const val KEY_SERVICE = " service"
382403 const val KEY_SOURCE = " source"
383404 const val KEY_TYPE = " type"
405+ const val KEY_ALLOW_INSTANT = " allowInstant"
384406 const val KEY_OPTIONS = " options"
385407 const val KEY_USER_JSON = " userInfo"
386408 const val KEY_CREDENTIAL_ID = " credential"
409+ const val KEY_PRESELECTED_TRANSPORT = " transport"
387410 val REQUIRED_EXTRAS = setOf (KEY_SOURCE , KEY_TYPE , KEY_OPTIONS )
388411
389412 const val SOURCE_BROWSER = " browser"
0 commit comments