mirror of
https://github.com/afollestad/nock-nock.git
synced 2025-08-05 15:48:37 +00:00
Basic certificate URI validation
This commit is contained in:
parent
26d6d9abf8
commit
cd1651672f
7 changed files with 84 additions and 31 deletions
|
@ -28,7 +28,7 @@ import com.afollestad.nocknock.data.model.ValidationMode
|
||||||
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
|
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
|
||||||
import com.afollestad.nocknock.ui.viewsite.KEY_SITE
|
import com.afollestad.nocknock.ui.viewsite.KEY_SITE
|
||||||
import com.afollestad.nocknock.utilities.ext.onTextChanged
|
import com.afollestad.nocknock.utilities.ext.onTextChanged
|
||||||
import com.afollestad.nocknock.utilities.ext.toUri
|
import com.afollestad.nocknock.utilities.ext.setTextAndMaintainSelection
|
||||||
import com.afollestad.nocknock.utilities.livedata.distinct
|
import com.afollestad.nocknock.utilities.livedata.distinct
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.onScroll
|
import com.afollestad.nocknock.viewcomponents.ext.onScroll
|
||||||
|
@ -141,6 +141,13 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
minutesData = viewModel.retryPolicyMinutes
|
minutesData = viewModel.retryPolicyMinutes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SSL certificate
|
||||||
|
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
|
||||||
|
viewModel.certificateUri.distinct()
|
||||||
|
.observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
|
||||||
|
viewModel.onCertificateError()
|
||||||
|
.toViewError(this, sslCertificateInput)
|
||||||
|
|
||||||
// Headers
|
// Headers
|
||||||
headersLayout.attach(viewModel.headers)
|
headersLayout.attach(viewModel.headers)
|
||||||
}
|
}
|
||||||
|
@ -179,9 +186,6 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SSL certificate
|
// SSL certificate
|
||||||
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it.toUri() }
|
|
||||||
viewModel.certificateUri.distinct()
|
|
||||||
.observe(this, Observer { sslCertificateInput.setText(it.toString()) })
|
|
||||||
sslCertificateBrowse.setOnClickListener {
|
sslCertificateBrowse.setOnClickListener {
|
||||||
val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
|
val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
|
||||||
addCategory(CATEGORY_OPENABLE)
|
addCategory(CATEGORY_OPENABLE)
|
||||||
|
@ -193,12 +197,11 @@ class AddSiteActivity : DarkModeSwitchActivity() {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
appToolbar.elevation =
|
appToolbar.elevation = if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
|
||||||
if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
|
appToolbar.dimenFloat(R.dimen.default_elevation)
|
||||||
appToolbar.dimenFloat(R.dimen.default_elevation)
|
} else {
|
||||||
} else {
|
0f
|
||||||
0f
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(
|
override fun onActivityResult(
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.afollestad.nocknock.ui.addsite
|
package com.afollestad.nocknock.ui.addsite
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.VisibleForTesting.PRIVATE
|
import androidx.annotation.VisibleForTesting.PRIVATE
|
||||||
|
@ -40,6 +39,7 @@ import com.afollestad.nocknock.data.putSite
|
||||||
import com.afollestad.nocknock.engine.validation.ValidationExecutor
|
import com.afollestad.nocknock.engine.validation.ValidationExecutor
|
||||||
import com.afollestad.nocknock.ui.ScopedViewModel
|
import com.afollestad.nocknock.ui.ScopedViewModel
|
||||||
import com.afollestad.nocknock.utilities.ext.MINUTE
|
import com.afollestad.nocknock.utilities.ext.MINUTE
|
||||||
|
import com.afollestad.nocknock.utilities.ext.toUri
|
||||||
import com.afollestad.nocknock.utilities.livedata.map
|
import com.afollestad.nocknock.utilities.livedata.map
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
|
import com.afollestad.nocknock.viewcomponents.ext.isNullOrLessThan
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
@ -69,7 +69,7 @@ class AddSiteViewModel(
|
||||||
val retryPolicyTimes = MutableLiveData<Int>()
|
val retryPolicyTimes = MutableLiveData<Int>()
|
||||||
val retryPolicyMinutes = MutableLiveData<Int>()
|
val retryPolicyMinutes = MutableLiveData<Int>()
|
||||||
val headers = MutableLiveData<List<Header>>()
|
val headers = MutableLiveData<List<Header>>()
|
||||||
val certificateUri = MutableLiveData<Uri>()
|
val certificateUri = MutableLiveData<String>()
|
||||||
|
|
||||||
@OnLifecycleEvent(ON_START)
|
@OnLifecycleEvent(ON_START)
|
||||||
fun setDefaults() {
|
fun setDefaults() {
|
||||||
|
@ -91,6 +91,7 @@ class AddSiteViewModel(
|
||||||
private val validationSearchTermError = MutableLiveData<Int?>()
|
private val validationSearchTermError = MutableLiveData<Int?>()
|
||||||
private val validationScriptError = MutableLiveData<Int?>()
|
private val validationScriptError = MutableLiveData<Int?>()
|
||||||
private val checkIntervalValueError = MutableLiveData<Int?>()
|
private val checkIntervalValueError = MutableLiveData<Int?>()
|
||||||
|
private val certificateError = MutableLiveData<Int?>()
|
||||||
|
|
||||||
// Expose private properties or calculated properties
|
// Expose private properties or calculated properties
|
||||||
@CheckResult fun onIsLoading(): LiveData<Boolean> = isLoading
|
@CheckResult fun onIsLoading(): LiveData<Boolean> = isLoading
|
||||||
|
@ -130,6 +131,8 @@ class AddSiteViewModel(
|
||||||
|
|
||||||
@CheckResult fun onCheckIntervalError(): LiveData<Int?> = checkIntervalValueError
|
@CheckResult fun onCheckIntervalError(): LiveData<Int?> = checkIntervalValueError
|
||||||
|
|
||||||
|
@CheckResult fun onCertificateError(): LiveData<Int?> = certificateError
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
fun commit(done: () -> Unit) {
|
fun commit(done: () -> Unit) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
|
@ -228,6 +231,25 @@ class AddSiteViewModel(
|
||||||
validationScriptError.value = null
|
validationScriptError.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate SSL certificate
|
||||||
|
val certString = certificateUri.value
|
||||||
|
if (certString != null) {
|
||||||
|
val rawCertUri = certString.toUri()
|
||||||
|
val certUri = if (rawCertUri.scheme == null) {
|
||||||
|
rawCertUri.buildUpon()
|
||||||
|
.scheme("file")
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
rawCertUri
|
||||||
|
}
|
||||||
|
if (certUri.scheme != "content" && certUri.scheme != "file") {
|
||||||
|
errorCount++
|
||||||
|
certificateError.value = R.string.please_enter_validCertUri
|
||||||
|
} else {
|
||||||
|
certificateError.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errorCount > 0) {
|
if (errorCount > 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,7 +28,7 @@ import com.afollestad.nocknock.data.model.Site
|
||||||
import com.afollestad.nocknock.data.model.ValidationMode
|
import com.afollestad.nocknock.data.model.ValidationMode
|
||||||
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
|
import com.afollestad.nocknock.ui.DarkModeSwitchActivity
|
||||||
import com.afollestad.nocknock.utilities.ext.onTextChanged
|
import com.afollestad.nocknock.utilities.ext.onTextChanged
|
||||||
import com.afollestad.nocknock.utilities.ext.toUri
|
import com.afollestad.nocknock.utilities.ext.setTextAndMaintainSelection
|
||||||
import com.afollestad.nocknock.utilities.livedata.distinct
|
import com.afollestad.nocknock.utilities.livedata.distinct
|
||||||
import com.afollestad.nocknock.utilities.providers.IntentProvider
|
import com.afollestad.nocknock.utilities.providers.IntentProvider
|
||||||
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
import com.afollestad.nocknock.viewcomponents.ext.dimenFloat
|
||||||
|
@ -158,6 +158,13 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
|
||||||
minutesData = viewModel.retryPolicyMinutes
|
minutesData = viewModel.retryPolicyMinutes
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// SSL certificate
|
||||||
|
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it }
|
||||||
|
viewModel.certificateUri.distinct()
|
||||||
|
.observe(this, Observer { sslCertificateInput.setTextAndMaintainSelection(it) })
|
||||||
|
viewModel.onCertificateError()
|
||||||
|
.toViewError(this, sslCertificateInput)
|
||||||
|
|
||||||
// Headers
|
// Headers
|
||||||
headersLayout.attach(viewModel.headers)
|
headersLayout.attach(viewModel.headers)
|
||||||
|
|
||||||
|
@ -214,7 +221,7 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
|
||||||
.isVisible = it
|
.isVisible = it
|
||||||
})
|
})
|
||||||
|
|
||||||
// Done button
|
// Done item text
|
||||||
viewModel.onDoneButtonText()
|
viewModel.onDoneButtonText()
|
||||||
.observe(this, Observer {
|
.observe(this, Observer {
|
||||||
toolbar.menu.findItem(R.id.commit)
|
toolbar.menu.findItem(R.id.commit)
|
||||||
|
@ -222,9 +229,6 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
|
||||||
})
|
})
|
||||||
|
|
||||||
// SSL certificate
|
// SSL certificate
|
||||||
sslCertificateInput.onTextChanged { viewModel.certificateUri.value = it.toUri() }
|
|
||||||
viewModel.certificateUri.distinct()
|
|
||||||
.observe(this, Observer { sslCertificateInput.setText(it.toString()) })
|
|
||||||
sslCertificateBrowse.setOnClickListener {
|
sslCertificateBrowse.setOnClickListener {
|
||||||
val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
|
val intent = Intent(ACTION_OPEN_DOCUMENT).apply {
|
||||||
addCategory(CATEGORY_OPENABLE)
|
addCategory(CATEGORY_OPENABLE)
|
||||||
|
@ -236,12 +240,11 @@ class ViewSiteActivity : DarkModeSwitchActivity() {
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
appToolbar.elevation =
|
appToolbar.elevation = if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
|
||||||
if (scrollView.scrollY > appToolbar.measuredHeight / 2) {
|
appToolbar.dimenFloat(R.dimen.default_elevation)
|
||||||
appToolbar.dimenFloat(R.dimen.default_elevation)
|
} else {
|
||||||
} else {
|
0f
|
||||||
0f
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onActivityResult(
|
override fun onActivityResult(
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
*/
|
*/
|
||||||
package com.afollestad.nocknock.ui.viewsite
|
package com.afollestad.nocknock.ui.viewsite
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.annotation.CheckResult
|
import androidx.annotation.CheckResult
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.annotation.VisibleForTesting.PRIVATE
|
import androidx.annotation.VisibleForTesting.PRIVATE
|
||||||
|
@ -24,9 +23,9 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import com.afollestad.nocknock.R
|
import com.afollestad.nocknock.R
|
||||||
import com.afollestad.nocknock.data.AppDatabase
|
import com.afollestad.nocknock.data.AppDatabase
|
||||||
import com.afollestad.nocknock.data.model.RetryPolicy
|
|
||||||
import com.afollestad.nocknock.data.deleteSite
|
import com.afollestad.nocknock.data.deleteSite
|
||||||
import com.afollestad.nocknock.data.model.Header
|
import com.afollestad.nocknock.data.model.Header
|
||||||
|
import com.afollestad.nocknock.data.model.RetryPolicy
|
||||||
import com.afollestad.nocknock.data.model.Site
|
import com.afollestad.nocknock.data.model.Site
|
||||||
import com.afollestad.nocknock.data.model.Status
|
import com.afollestad.nocknock.data.model.Status
|
||||||
import com.afollestad.nocknock.data.model.Status.WAITING
|
import com.afollestad.nocknock.data.model.Status.WAITING
|
||||||
|
@ -41,6 +40,7 @@ import com.afollestad.nocknock.engine.validation.ValidationExecutor
|
||||||
import com.afollestad.nocknock.notifications.NockNotificationManager
|
import com.afollestad.nocknock.notifications.NockNotificationManager
|
||||||
import com.afollestad.nocknock.ui.ScopedViewModel
|
import com.afollestad.nocknock.ui.ScopedViewModel
|
||||||
import com.afollestad.nocknock.utilities.ext.formatDate
|
import com.afollestad.nocknock.utilities.ext.formatDate
|
||||||
|
import com.afollestad.nocknock.utilities.ext.toUri
|
||||||
import com.afollestad.nocknock.utilities.livedata.map
|
import com.afollestad.nocknock.utilities.livedata.map
|
||||||
import com.afollestad.nocknock.utilities.livedata.zip
|
import com.afollestad.nocknock.utilities.livedata.zip
|
||||||
import com.afollestad.nocknock.utilities.providers.StringProvider
|
import com.afollestad.nocknock.utilities.providers.StringProvider
|
||||||
|
@ -77,7 +77,7 @@ class ViewSiteViewModel(
|
||||||
val retryPolicyTimes = MutableLiveData<Int>()
|
val retryPolicyTimes = MutableLiveData<Int>()
|
||||||
val retryPolicyMinutes = MutableLiveData<Int>()
|
val retryPolicyMinutes = MutableLiveData<Int>()
|
||||||
val headers = MutableLiveData<List<Header>>()
|
val headers = MutableLiveData<List<Header>>()
|
||||||
val certificateUri = MutableLiveData<Uri>()
|
val certificateUri = MutableLiveData<String>()
|
||||||
internal val disabled = MutableLiveData<Boolean>()
|
internal val disabled = MutableLiveData<Boolean>()
|
||||||
internal val lastResult = MutableLiveData<ValidationResult?>()
|
internal val lastResult = MutableLiveData<ValidationResult?>()
|
||||||
|
|
||||||
|
@ -89,6 +89,7 @@ class ViewSiteViewModel(
|
||||||
private val validationSearchTermError = MutableLiveData<Int?>()
|
private val validationSearchTermError = MutableLiveData<Int?>()
|
||||||
private val validationScriptError = MutableLiveData<Int?>()
|
private val validationScriptError = MutableLiveData<Int?>()
|
||||||
private val checkIntervalValueError = MutableLiveData<Int?>()
|
private val checkIntervalValueError = MutableLiveData<Int?>()
|
||||||
|
private val certificateError = MutableLiveData<Int?>()
|
||||||
|
|
||||||
// Expose private properties or calculated properties
|
// Expose private properties or calculated properties
|
||||||
@CheckResult fun onIsLoading(): LiveData<Boolean> = isLoading
|
@CheckResult fun onIsLoading(): LiveData<Boolean> = isLoading
|
||||||
|
@ -131,6 +132,8 @@ class ViewSiteViewModel(
|
||||||
@CheckResult fun onDisableChecksVisibility(): LiveData<Boolean> =
|
@CheckResult fun onDisableChecksVisibility(): LiveData<Boolean> =
|
||||||
disabled.map { !it }
|
disabled.map { !it }
|
||||||
|
|
||||||
|
@CheckResult fun onCertificateError(): LiveData<Int?> = certificateError
|
||||||
|
|
||||||
@CheckResult fun onDoneButtonText(): LiveData<Int> =
|
@CheckResult fun onDoneButtonText(): LiveData<Int> =
|
||||||
disabled.map {
|
disabled.map {
|
||||||
if (it) R.string.renable_and_save_changes
|
if (it) R.string.renable_and_save_changes
|
||||||
|
@ -307,6 +310,25 @@ class ViewSiteViewModel(
|
||||||
validationScriptError.value = null
|
validationScriptError.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate SSL certificate
|
||||||
|
val certString = certificateUri.value
|
||||||
|
if (certString != null) {
|
||||||
|
val rawCertUri = certString.toUri()
|
||||||
|
val certUri = if (rawCertUri.scheme == null) {
|
||||||
|
rawCertUri.buildUpon()
|
||||||
|
.scheme("file")
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
rawCertUri
|
||||||
|
}
|
||||||
|
if (certUri.scheme != "content" && certUri.scheme != "file") {
|
||||||
|
errorCount++
|
||||||
|
certificateError.value = R.string.please_enter_validCertUri
|
||||||
|
} else {
|
||||||
|
certificateError.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (errorCount > 0) {
|
if (errorCount > 0) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,6 @@ import com.afollestad.nocknock.utilities.ext.DAY
|
||||||
import com.afollestad.nocknock.utilities.ext.HOUR
|
import com.afollestad.nocknock.utilities.ext.HOUR
|
||||||
import com.afollestad.nocknock.utilities.ext.MINUTE
|
import com.afollestad.nocknock.utilities.ext.MINUTE
|
||||||
import com.afollestad.nocknock.utilities.ext.WEEK
|
import com.afollestad.nocknock.utilities.ext.WEEK
|
||||||
import com.afollestad.nocknock.utilities.ext.toUri
|
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
|
||||||
fun ViewSiteViewModel.setModel(site: Site) {
|
fun ViewSiteViewModel.setModel(site: Site) {
|
||||||
|
@ -56,9 +55,7 @@ fun ViewSiteViewModel.setModel(site: Site) {
|
||||||
setCheckInterval(settings.validationIntervalMs)
|
setCheckInterval(settings.validationIntervalMs)
|
||||||
setRetryPolicy(site.retryPolicy)
|
setRetryPolicy(site.retryPolicy)
|
||||||
headers.value = site.headers
|
headers.value = site.headers
|
||||||
if (settings.certificate != null) {
|
certificateUri.value = settings.certificate
|
||||||
certificateUri.value = settings.certificate!!.toUri()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.disabled.value = settings.disabled
|
this.disabled.value = settings.disabled
|
||||||
this.lastResult.value = site.lastResult
|
this.lastResult.value = site.lastResult
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
<string name="please_enter_search_term">Please input a search term.</string>
|
<string name="please_enter_search_term">Please input a search term.</string>
|
||||||
<string name="please_enter_javaScript">Please input a validation script.</string>
|
<string name="please_enter_javaScript">Please input a validation script.</string>
|
||||||
<string name="please_enter_networkTimeout">Please enter a network timeout greater than 0.</string>
|
<string name="please_enter_networkTimeout">Please enter a network timeout greater than 0.</string>
|
||||||
|
<string name="please_enter_validCertUri">Certificate should be a valid file or content URI.</string>
|
||||||
|
|
||||||
<string name="options">Options</string>
|
<string name="options">Options</string>
|
||||||
<string name="remove_site">Remove Site</string>
|
<string name="remove_site">Remove Site</string>
|
||||||
|
|
|
@ -21,7 +21,12 @@ import android.widget.EditText
|
||||||
import androidx.annotation.IntRange
|
import androidx.annotation.IntRange
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
fun EditText.setTextAndMaintainSelection(text: CharSequence) {
|
fun EditText.setTextAndMaintainSelection(text: CharSequence?) {
|
||||||
|
if (text == null) {
|
||||||
|
setText("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val formerStart = min(selectionStart, text.length)
|
val formerStart = min(selectionStart, text.length)
|
||||||
val formerEnd = min(selectionEnd, text.length)
|
val formerEnd = min(selectionEnd, text.length)
|
||||||
setText(text)
|
setText(text)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue