Skip to content
This repository has been archived by the owner on Jun 17, 2024. It is now read-only.

Commit

Permalink
[components] For mozilla-mobile/android-components#7134 - Adds login …
Browse files Browse the repository at this point in the history
…selection to prompt feature
  • Loading branch information
ekager committed Aug 21, 2020
1 parent 92d4e20 commit d3f1d35
Show file tree
Hide file tree
Showing 24 changed files with 1,255 additions and 140 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import android.content.Context
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.storage.Login
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.Login
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.Autocomplete
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate
Expand All @@ -29,7 +30,6 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.MON
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.TIME
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEEK
import org.mozilla.geckoview.GeckoSession.PromptDelegate.PromptResponse
import org.mozilla.geckoview.Autocomplete
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
Expand Down Expand Up @@ -106,19 +106,25 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
prompt: PromptDelegate.AutocompleteRequest<Autocomplete.LoginSelectOption>
): GeckoResult<PromptResponse>? {
val geckoResult = GeckoResult<PromptResponse>()
val onConfirmSave: (Login) -> Unit = { login ->
val onConfirmSelect: (Login) -> Unit = { login ->
geckoResult.complete(prompt.confirm(Autocomplete.LoginSelectOption(login.toLoginEntry())))
}
val onDismiss: () -> Unit = {
geckoResult.complete(prompt.dismiss())
}

// Currently no-op will be addressed in https://github.com/mozilla-mobile/android-components/issues/7134
// Exactly one of `httpRealm` and `formSubmitURL` must be present to be a valid login entry.
val loginList = prompt.options.filter { option ->
option.value.formActionOrigin != null || option.value.httpRealm != null
}.map { option ->
option.value.toLogin()
}

geckoEngineSession.notifyObservers {
onPromptRequest(
PromptRequest.SelectLoginPrompt(
logins = listOf(),
onConfirm = onConfirmSave,
logins = loginList,
onConfirm = onConfirmSelect,
onDismiss = onDismiss
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import android.content.Context
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.storage.Login
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.Login
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.Autocomplete
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate
Expand All @@ -29,7 +30,6 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.MON
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.TIME
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEEK
import org.mozilla.geckoview.GeckoSession.PromptDelegate.PromptResponse
import org.mozilla.geckoview.Autocomplete
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
Expand Down Expand Up @@ -106,19 +106,25 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
prompt: PromptDelegate.AutocompleteRequest<Autocomplete.LoginSelectOption>
): GeckoResult<PromptResponse>? {
val geckoResult = GeckoResult<PromptResponse>()
val onConfirmSave: (Login) -> Unit = { login ->
val onConfirmSelect: (Login) -> Unit = { login ->
geckoResult.complete(prompt.confirm(Autocomplete.LoginSelectOption(login.toLoginEntry())))
}
val onDismiss: () -> Unit = {
geckoResult.complete(prompt.dismiss())
}

// Currently no-op will be addressed in https://github.com/mozilla-mobile/android-components/issues/7134
// Exactly one of `httpRealm` and `formSubmitURL` must be present to be a valid login entry.
val loginList = prompt.options.filter { option ->
option.value.formActionOrigin != null || option.value.httpRealm != null
}.map { option ->
option.value.toLogin()
}

geckoEngineSession.notifyObservers {
onPromptRequest(
PromptRequest.SelectLoginPrompt(
logins = listOf(),
onConfirm = onConfirmSave,
logins = loginList,
onConfirm = onConfirmSelect,
onDismiss = onDismiss
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.storage.Login
import mozilla.components.support.ktx.kotlin.toDate
import mozilla.components.support.test.any
import mozilla.components.support.test.mock
Expand All @@ -25,9 +26,10 @@ import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.spy
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.spy
import org.mozilla.gecko.util.GeckoBundle
import org.mozilla.geckoview.Autocomplete
import org.mozilla.geckoview.GeckoRuntime
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.DATE
Expand All @@ -43,6 +45,7 @@ import java.security.InvalidParameterException
import java.util.Calendar
import java.util.Calendar.YEAR
import java.util.Date

typealias GeckoChoice = GeckoSession.PromptDelegate.ChoicePrompt.Choice
typealias GECKO_AUTH_LEVEL = GeckoSession.PromptDelegate.AuthPrompt.AuthOptions.Level
typealias GECKO_PROMPT_CHOICE_TYPE = GeckoSession.PromptDelegate.ChoicePrompt.Type
Expand Down Expand Up @@ -142,11 +145,11 @@ class GeckoPromptDelegateTest {
)

mockSession.register(
object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
promptRequestSingleChoice = promptRequest
}
})
object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
promptRequestSingleChoice = promptRequest
}
})

val geckoResult = gecko.onChoicePrompt(mock(), geckoPrompt)
geckoResult!!.accept {
Expand Down Expand Up @@ -475,7 +478,8 @@ class GeckoPromptDelegateTest {
dateRequest = promptRequest
}
})
val geckoResult = promptDelegate.onDateTimePrompt(mock(), GeckoDateTimePrompt(type = DATETIME_LOCAL))
val geckoResult =
promptDelegate.onDateTimePrompt(mock(), GeckoDateTimePrompt(type = DATETIME_LOCAL))
geckoResult!!.accept {
confirmCalled = true
}
Expand Down Expand Up @@ -618,6 +622,125 @@ class GeckoPromptDelegateTest {
)
}

@Test
fun `Calling onLoginSave must provide an SaveLoginPrompt PromptRequest`() {
val mockSession = GeckoEngineSession(runtime)
var onLoginSaved = false
var onDismissWasCalled = false

var loginSaveRequest: PromptRequest.SaveLoginPrompt = mock()

val promptDelegate = spy(GeckoPromptDelegate(mockSession))

mockSession.register(object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
loginSaveRequest = promptRequest as PromptRequest.SaveLoginPrompt
}
})

val login = createLogin()
val saveOption = Autocomplete.LoginSaveOption(login.toLoginEntry())

var geckoResult =
promptDelegate.onLoginSave(mock(), GeckoLoginSavePrompt(arrayOf(saveOption)))

geckoResult!!.accept {
onDismissWasCalled = true
}

loginSaveRequest.onDismiss()
assertTrue(onDismissWasCalled)

geckoResult = promptDelegate.onLoginSave(mock(), GeckoLoginSavePrompt(arrayOf(saveOption)))

geckoResult!!.accept {
onLoginSaved = true
}

loginSaveRequest.onConfirm(login)
assertTrue(onLoginSaved)
}

@Test
fun `Calling onLoginSelect must provide an SelectLoginPrompt PromptRequest`() {
val mockSession = GeckoEngineSession(runtime)
var onLoginSelected = false
var onDismissWasCalled = false

var loginSelectRequest: PromptRequest.SelectLoginPrompt = mock()

val promptDelegate = spy(GeckoPromptDelegate(mockSession))

mockSession.register(object : EngineSession.Observer {
override fun onPromptRequest(promptRequest: PromptRequest) {
loginSelectRequest = promptRequest as PromptRequest.SelectLoginPrompt
}
})

val login = createLogin()
val loginSelectOption = Autocomplete.LoginSelectOption(login.toLoginEntry())

val secondLogin = createLogin(username = "username2")
val secondLoginSelectOption = Autocomplete.LoginSelectOption(secondLogin.toLoginEntry())

var geckoResult =
promptDelegate.onLoginSelect(
mock(),
GeckoLoginSelectPrompt(arrayOf(loginSelectOption, secondLoginSelectOption))
)

geckoResult!!.accept {
onDismissWasCalled = true
}

loginSelectRequest.onDismiss()
assertTrue(onDismissWasCalled)

geckoResult = promptDelegate.onLoginSelect(
mock(),
GeckoLoginSelectPrompt(arrayOf(loginSelectOption, secondLoginSelectOption))
)

geckoResult!!.accept {
onLoginSelected = true
}

loginSelectRequest.onConfirm(login)
assertTrue(onLoginSelected)
}

fun createLogin(
guid: String = "id",
password: String = "password",
username: String = "username",
origin: String = "https://www.origin.com",
httpRealm: String = "httpRealm",
formActionOrigin: String = "https://www.origin.com",
usernameField: String = "usernameField",
passwordField: String = "passwordField"
) = Login(
guid = guid,
origin = origin,
password = password,
username = username,
httpRealm = httpRealm,
formActionOrigin = formActionOrigin,
usernameField = usernameField,
passwordField = passwordField
)

/**
* Converts an Android Components [Login] to a GeckoView [LoginStorage.LoginEntry]
*/
private fun Login.toLoginEntry() = Autocomplete.LoginEntry.Builder()
.guid(guid)
.origin(origin)
.formActionOrigin(formActionOrigin)
.httpRealm(httpRealm)
.username(username)
.password(password)
.build()

@Test
fun `Calling onAuthPrompt must provide an Authentication PromptRequest`() {
val mockSession = GeckoEngineSession(runtime)
Expand Down Expand Up @@ -979,7 +1102,7 @@ class GeckoPromptDelegateTest {
title: String = "title",
type: Int,
capture: Int = 0,
mimeTypes: Array<out String > = emptyArray()
mimeTypes: Array<out String> = emptyArray()
) : GeckoSession.PromptDelegate.FilePrompt(title, type, capture, mimeTypes)

class GeckoAuthPrompt(
Expand Down Expand Up @@ -1016,6 +1139,14 @@ class GeckoPromptDelegateTest {
message: String = "message"
) : GeckoSession.PromptDelegate.ButtonPrompt(title, message)

class GeckoLoginSelectPrompt(
loginArray: Array<Autocomplete.LoginSelectOption>
) : GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.LoginSelectOption>(loginArray)

class GeckoLoginSavePrompt(
login: Array<Autocomplete.LoginSaveOption>
) : GeckoSession.PromptDelegate.AutocompleteRequest<Autocomplete.LoginSaveOption>(login)

class GeckoAuthOptions : GeckoSession.PromptDelegate.AuthPrompt.AuthOptions()

private fun GeckoSession.PromptDelegate.BasePrompt.getGeckoResult(): GeckoBundle {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,18 @@ import android.content.Context
import android.net.Uri
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.engine.gecko.GeckoEngineSession
import mozilla.components.concept.storage.Login
import mozilla.components.concept.engine.prompt.Choice
import mozilla.components.concept.engine.prompt.PromptRequest
import mozilla.components.concept.engine.prompt.PromptRequest.MenuChoice
import mozilla.components.concept.engine.prompt.PromptRequest.MultipleChoice
import mozilla.components.concept.engine.prompt.PromptRequest.SingleChoice
import mozilla.components.concept.engine.prompt.ShareData
import mozilla.components.concept.storage.Login
import mozilla.components.support.base.log.logger.Logger
import mozilla.components.support.ktx.android.net.getFileName
import mozilla.components.support.ktx.kotlin.toDate
import org.mozilla.geckoview.AllowOrDeny
import org.mozilla.geckoview.Autocomplete
import org.mozilla.geckoview.GeckoResult
import org.mozilla.geckoview.GeckoSession
import org.mozilla.geckoview.GeckoSession.PromptDelegate
Expand All @@ -29,7 +30,6 @@ import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.MON
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.TIME
import org.mozilla.geckoview.GeckoSession.PromptDelegate.DateTimePrompt.Type.WEEK
import org.mozilla.geckoview.GeckoSession.PromptDelegate.PromptResponse
import org.mozilla.geckoview.Autocomplete
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
Expand Down Expand Up @@ -106,19 +106,25 @@ internal class GeckoPromptDelegate(private val geckoEngineSession: GeckoEngineSe
prompt: PromptDelegate.AutocompleteRequest<Autocomplete.LoginSelectOption>
): GeckoResult<PromptResponse>? {
val geckoResult = GeckoResult<PromptResponse>()
val onConfirmSave: (Login) -> Unit = { login ->
val onConfirmSelect: (Login) -> Unit = { login ->
geckoResult.complete(prompt.confirm(Autocomplete.LoginSelectOption(login.toLoginEntry())))
}
val onDismiss: () -> Unit = {
geckoResult.complete(prompt.dismiss())
}

// Currently no-op will be addressed in https://github.com/mozilla-mobile/android-components/issues/7134
// Exactly one of `httpRealm` and `formSubmitURL` must be present to be a valid login entry.
val loginList = prompt.options.filter { option ->
option.value.formActionOrigin != null || option.value.httpRealm != null
}.map { option ->
option.value.toLogin()
}

geckoEngineSession.notifyObservers {
onPromptRequest(
PromptRequest.SelectLoginPrompt(
logins = listOf(),
onConfirm = onConfirmSave,
logins = loginList,
onConfirm = onConfirmSelect,
onDismiss = onDismiss
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.map
import mozilla.components.feature.logins.exceptions.adapter.LoginExceptionAdapter
import mozilla.components.feature.logins.exceptions.db.LoginExceptionDatabase
import mozilla.components.feature.logins.exceptions.db.LoginExceptionEntity
import mozilla.components.feature.prompts.LoginExceptions
import mozilla.components.feature.prompts.login.LoginExceptions

/**
* A storage implementation for organizing login exceptions.
Expand Down
Loading

0 comments on commit d3f1d35

Please sign in to comment.