Support for Time-based One-time Passwords (TOTP) in SMS Communicator
This commit is contained in:
parent
249a9b29a0
commit
0f0452a1c4
|
@ -282,6 +282,9 @@ dependencies {
|
||||||
|
|
||||||
implementation 'com.github.DavidProdinger:weekdays-selector:1.1.0'
|
implementation 'com.github.DavidProdinger:weekdays-selector:1.1.0'
|
||||||
|
|
||||||
|
implementation 'com.github.kenglxn.QRGen:android:2.6.0'
|
||||||
|
implementation 'com.eatthepath:java-otp:0.2.0'
|
||||||
|
|
||||||
testImplementation "junit:junit:4.12"
|
testImplementation "junit:junit:4.12"
|
||||||
testImplementation "org.json:json:20190722"
|
testImplementation "org.json:json:20190722"
|
||||||
testImplementation "org.mockito:mockito-core:2.8.47"
|
testImplementation "org.mockito:mockito-core:2.8.47"
|
||||||
|
|
|
@ -87,4 +87,20 @@ public class Constants {
|
||||||
public static final double STATS_RANGE_HIGH_MMOL = 10.0;
|
public static final double STATS_RANGE_HIGH_MMOL = 10.0;
|
||||||
|
|
||||||
|
|
||||||
|
// One Time Password
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Size of generated key for TOTP Authenticator token, in bits
|
||||||
|
* rfc6238 suggest at least 160 for SHA1 based TOTP, but it ts too weak
|
||||||
|
* with 512 generated QRCode to provision authenticator is too detailed
|
||||||
|
* 256 is chosen as both secure enough and small enough for easy-scannable QRCode
|
||||||
|
*/
|
||||||
|
public static final int OTP_GENERATED_KEY_LENGTH_BITS = 256;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* How many old TOTP tokens still accept.
|
||||||
|
* Each token is 30s valid, but copying and SMS transmision of it can take additional seconds,
|
||||||
|
* so we add leeway to still accept given amount of older tokens
|
||||||
|
*/
|
||||||
|
public static final int OTP_ACCEPT_OLD_TOKENS_COUNT = 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,6 +106,7 @@ import info.nightscout.androidaps.services.Intents;
|
||||||
import info.nightscout.androidaps.utils.ActivityMonitor;
|
import info.nightscout.androidaps.utils.ActivityMonitor;
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy;
|
import info.nightscout.androidaps.utils.FabricPrivacy;
|
||||||
import info.nightscout.androidaps.utils.LocaleHelper;
|
import info.nightscout.androidaps.utils.LocaleHelper;
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePassword;
|
||||||
import info.nightscout.androidaps.utils.SP;
|
import info.nightscout.androidaps.utils.SP;
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
import info.nightscout.androidaps.utils.resources.ResourceHelper;
|
||||||
import io.fabric.sdk.android.Fabric;
|
import io.fabric.sdk.android.Fabric;
|
||||||
|
@ -137,6 +138,7 @@ public class MainApp extends DaggerApplication {
|
||||||
@Inject FabricPrivacy fabricPrivacy;
|
@Inject FabricPrivacy fabricPrivacy;
|
||||||
@Inject ResourceHelper resourceHelper;
|
@Inject ResourceHelper resourceHelper;
|
||||||
@Inject VersionCheckerUtils versionCheckersUtils;
|
@Inject VersionCheckerUtils versionCheckersUtils;
|
||||||
|
@Inject OneTimePassword oneTimePassword;
|
||||||
|
|
||||||
@Inject ActionsPlugin actionsPlugin;
|
@Inject ActionsPlugin actionsPlugin;
|
||||||
@Inject AutomationPlugin automationPlugin;
|
@Inject AutomationPlugin automationPlugin;
|
||||||
|
|
|
@ -27,6 +27,8 @@ import info.nightscout.androidaps.plugins.general.nsclient.NSClientFragment
|
||||||
import info.nightscout.androidaps.plugins.general.overview.OverviewFragment
|
import info.nightscout.androidaps.plugins.general.overview.OverviewFragment
|
||||||
import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog
|
import info.nightscout.androidaps.plugins.general.overview.dialogs.EditQuickWizardDialog
|
||||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorFragment
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorFragment
|
||||||
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.fragments.SmsCommunicatorLogFragment
|
||||||
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.fragments.SmsCommunicatorOtpFragment
|
||||||
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolFragment
|
import info.nightscout.androidaps.plugins.general.tidepool.TidepoolFragment
|
||||||
import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment
|
import info.nightscout.androidaps.plugins.profile.local.LocalProfileFragment
|
||||||
import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment
|
import info.nightscout.androidaps.plugins.profile.ns.NSProfileFragment
|
||||||
|
@ -69,8 +71,9 @@ abstract class FragmentsModule {
|
||||||
@ContributesAndroidInjector abstract fun contributesMedtronicFragment(): MedtronicFragment
|
@ContributesAndroidInjector abstract fun contributesMedtronicFragment(): MedtronicFragment
|
||||||
@ContributesAndroidInjector abstract fun contributesNSProfileFragment(): NSProfileFragment
|
@ContributesAndroidInjector abstract fun contributesNSProfileFragment(): NSProfileFragment
|
||||||
@ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment
|
@ContributesAndroidInjector abstract fun contributesNSClientFragment(): NSClientFragment
|
||||||
@ContributesAndroidInjector
|
@ContributesAndroidInjector abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment
|
||||||
abstract fun contributesSmsCommunicatorFragment(): SmsCommunicatorFragment
|
@ContributesAndroidInjector abstract fun contributesSmsCommunicatorLogFragment(): SmsCommunicatorLogFragment
|
||||||
|
@ContributesAndroidInjector abstract fun contributesSmsCommunicatorOtpFragment(): SmsCommunicatorOtpFragment
|
||||||
|
|
||||||
@ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment
|
@ContributesAndroidInjector abstract fun contributesTidepoolFragment(): TidepoolFragment
|
||||||
@ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment
|
@ContributesAndroidInjector abstract fun contributesTreatmentsFragment(): TreatmentsFragment
|
||||||
|
|
|
@ -5,10 +5,12 @@ import info.nightscout.androidaps.R
|
||||||
import info.nightscout.androidaps.logging.L
|
import info.nightscout.androidaps.logging.L
|
||||||
import info.nightscout.androidaps.logging.StacktraceLoggerWrapper
|
import info.nightscout.androidaps.logging.StacktraceLoggerWrapper
|
||||||
import info.nightscout.androidaps.utils.DateUtil
|
import info.nightscout.androidaps.utils.DateUtil
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePassword
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePasswordValidationResult
|
||||||
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
class AuthRequest internal constructor(val plugin: SmsCommunicatorPlugin, val resourceHelper: ResourceHelper, var requester: Sms, requestText: String, var confirmCode: String, val action: SmsAction) {
|
class AuthRequest internal constructor(val plugin: SmsCommunicatorPlugin, val resourceHelper: ResourceHelper, val otp: OneTimePassword, var requester: Sms, requestText: String, var confirmCode: String, val action: SmsAction) {
|
||||||
private val log = StacktraceLoggerWrapper.getLogger(L.SMS)
|
private val log = StacktraceLoggerWrapper.getLogger(L.SMS)
|
||||||
|
|
||||||
private val date = DateUtil.now()
|
private val date = DateUtil.now()
|
||||||
|
@ -18,12 +20,20 @@ class AuthRequest internal constructor(val plugin: SmsCommunicatorPlugin, val re
|
||||||
plugin.sendSMS(Sms(requester.phoneNumber, requestText))
|
plugin.sendSMS(Sms(requester.phoneNumber, requestText))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun codeIsValid(toValidate: String) : Boolean {
|
||||||
|
if (otp.isEnabled()) {
|
||||||
|
return otp.checkOTP(toValidate) == OneTimePasswordValidationResult.OK
|
||||||
|
} else {
|
||||||
|
return confirmCode.equals(toValidate)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun action(codeReceived: String) {
|
fun action(codeReceived: String) {
|
||||||
if (processed) {
|
if (processed) {
|
||||||
if (L.isEnabled(L.SMS)) log.debug("Already processed")
|
if (L.isEnabled(L.SMS)) log.debug("Already processed")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (confirmCode != codeReceived) {
|
if (!codeIsValid(codeReceived)) {
|
||||||
processed = true
|
processed = true
|
||||||
if (L.isEnabled(L.SMS)) log.debug("Wrong code")
|
if (L.isEnabled(L.SMS)) log.debug("Wrong code")
|
||||||
plugin.sendSMS(Sms(requester.phoneNumber, resourceHelper.gs(R.string.sms_wrongcode)))
|
plugin.sendSMS(Sms(requester.phoneNumber, resourceHelper.gs(R.string.sms_wrongcode)))
|
||||||
|
|
|
@ -4,75 +4,78 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentTransaction
|
||||||
import dagger.android.support.DaggerFragment
|
import dagger.android.support.DaggerFragment
|
||||||
import info.nightscout.androidaps.R
|
import info.nightscout.androidaps.R
|
||||||
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||||
import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSmsCommunicatorUpdateGui
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.fragments.SmsCommunicatorLogFragment
|
||||||
import info.nightscout.androidaps.utils.DateUtil
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.fragments.SmsCommunicatorOtpFragment
|
||||||
import info.nightscout.androidaps.utils.FabricPrivacy
|
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||||
import info.nightscout.androidaps.utils.HtmlHelper
|
import info.nightscout.androidaps.utils.OneTimePassword
|
||||||
import info.nightscout.androidaps.utils.extensions.plusAssign
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
|
||||||
import io.reactivex.disposables.CompositeDisposable
|
|
||||||
import kotlinx.android.synthetic.main.smscommunicator_fragment.*
|
import kotlinx.android.synthetic.main.smscommunicator_fragment.*
|
||||||
import java.util.*
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import kotlin.math.max
|
|
||||||
|
|
||||||
class SmsCommunicatorFragment : DaggerFragment() {
|
class SmsCommunicatorFragment : DaggerFragment() {
|
||||||
@Inject lateinit var fabricPrivacy : FabricPrivacy
|
|
||||||
@Inject lateinit var rxBus: RxBusWrapper
|
@Inject lateinit var rxBus: RxBusWrapper
|
||||||
|
@Inject lateinit var resourceHelper: ResourceHelper
|
||||||
|
@Inject lateinit var fabricPrivacy: FabricPrivacy
|
||||||
|
@Inject lateinit var otp: OneTimePassword
|
||||||
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
|
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
|
||||||
|
|
||||||
private val disposable = CompositeDisposable()
|
|
||||||
|
|
||||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?): View? {
|
savedInstanceState: Bundle?): View? {
|
||||||
return inflater.inflate(R.layout.smscommunicator_fragment, container, false)
|
return inflater.inflate(R.layout.smscommunicator_fragment, container, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
smscomunicator_tab_log.setOnClickListener {
|
||||||
|
setFragment(SmsCommunicatorLogFragment())
|
||||||
|
setBackgroundColorOnSelected(it)
|
||||||
|
}
|
||||||
|
smscomunicator_tab_otp.setOnClickListener {
|
||||||
|
setFragment(SmsCommunicatorOtpFragment())
|
||||||
|
setBackgroundColorOnSelected(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
setFragment(SmsCommunicatorLogFragment())
|
||||||
|
setBackgroundColorOnSelected(smscomunicator_tab_log)
|
||||||
|
}
|
||||||
|
|
||||||
@Synchronized
|
@Synchronized
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
disposable += rxBus
|
|
||||||
.toObservable(EventSmsCommunicatorUpdateGui::class.java)
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribe({ updateGui() }) { fabricPrivacy.logException(it) }
|
|
||||||
updateGui()
|
updateGui()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Synchronized
|
|
||||||
override fun onPause() {
|
|
||||||
super.onPause()
|
|
||||||
disposable.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateGui() {
|
fun updateGui() {
|
||||||
class CustomComparator : Comparator<Sms> {
|
if (otp.isEnabled()) {
|
||||||
override fun compare(object1: Sms, object2: Sms): Int {
|
smscomunicator_tab_otp.visibility = View.VISIBLE
|
||||||
return (object1.date - object2.date).toInt()
|
} else {
|
||||||
|
if (smscomunicator_tab_otp.visibility != View.GONE) {
|
||||||
|
setFragment(SmsCommunicatorLogFragment())
|
||||||
|
setBackgroundColorOnSelected(smscomunicator_tab_log)
|
||||||
|
smscomunicator_tab_otp.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Collections.sort(smsCommunicatorPlugin.messages, CustomComparator())
|
|
||||||
val messagesToShow = 40
|
|
||||||
val start = max(0, smsCommunicatorPlugin.messages.size - messagesToShow)
|
|
||||||
var logText = ""
|
|
||||||
for (x in start until smsCommunicatorPlugin.messages.size) {
|
|
||||||
val sms = smsCommunicatorPlugin.messages[x]
|
|
||||||
when {
|
|
||||||
sms.ignored -> {
|
|
||||||
logText += DateUtil.timeString(sms.date) + " <<< " + "░ " + sms.phoneNumber + " <b>" + sms.text + "</b><br>"
|
|
||||||
}
|
|
||||||
|
|
||||||
sms.received -> {
|
|
||||||
logText += DateUtil.timeString(sms.date) + " <<< " + (if (sms.processed) "● " else "○ ") + sms.phoneNumber + " <b>" + sms.text + "</b><br>"
|
|
||||||
}
|
|
||||||
|
|
||||||
sms.sent -> {
|
|
||||||
logText += DateUtil.timeString(sms.date) + " >>> " + (if (sms.processed) "● " else "○ ") + sms.phoneNumber + " <b>" + sms.text + "</b><br>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
smscommunicator_log?.text = HtmlHelper.fromHtml(logText)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setFragment(selectedFragment: Fragment) {
|
||||||
|
val ft = childFragmentManager.beginTransaction()
|
||||||
|
ft.replace(R.id.smscomunicator_fragment_container, selectedFragment)
|
||||||
|
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
|
||||||
|
ft.addToBackStack(null)
|
||||||
|
ft.commit()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setBackgroundColorOnSelected(selected: View) {
|
||||||
|
smscomunicator_tab_log.setBackgroundColor(resourceHelper.gc(R.color.defaultbackground))
|
||||||
|
smscomunicator_tab_otp.setBackgroundColor(resourceHelper.gc(R.color.defaultbackground))
|
||||||
|
selected.setBackgroundColor(resourceHelper.gc(R.color.tabBgColorSelected))
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -56,7 +56,8 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
private val activePlugin: ActivePluginProvider,
|
private val activePlugin: ActivePluginProvider,
|
||||||
private val commandQueue: CommandQueueProvider,
|
private val commandQueue: CommandQueueProvider,
|
||||||
private val loopPlugin: LoopPlugin,
|
private val loopPlugin: LoopPlugin,
|
||||||
private val iobCobCalculatorPlugin: IobCobCalculatorPlugin
|
private val iobCobCalculatorPlugin: IobCobCalculatorPlugin,
|
||||||
|
private var otp: OneTimePassword
|
||||||
) : PluginBase(PluginDescription()
|
) : PluginBase(PluginDescription()
|
||||||
.mainType(PluginType.GENERAL)
|
.mainType(PluginType.GENERAL)
|
||||||
.fragmentClass(SmsCommunicatorFragment::class.java.name)
|
.fragmentClass(SmsCommunicatorFragment::class.java.name)
|
||||||
|
@ -354,7 +355,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_suspendreplywithcode), duration, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(duration) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(duration) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
commandQueue.cancelTempBasal(true, object : Callback() {
|
commandQueue.cancelTempBasal(true, object : Callback() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -469,7 +470,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_profilereplywithcode), list[pindex - 1], percentage, passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_profilereplywithcode), list[pindex - 1], percentage, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
val finalPercentage = percentage
|
val finalPercentage = percentage
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(list[pindex - 1] as String, finalPercentage) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(list[pindex - 1] as String, finalPercentage) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
activePlugin.activeTreatments.doProfileSwitch(store, list[pindex - 1] as String, 0, finalPercentage, 0, DateUtil.now())
|
activePlugin.activeTreatments.doProfileSwitch(store, list[pindex - 1] as String, 0, finalPercentage, 0, DateUtil.now())
|
||||||
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.profileswitchcreated)))
|
sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.profileswitchcreated)))
|
||||||
|
@ -486,7 +487,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalstopreplywithcode), passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalstopreplywithcode), passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction() {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
commandQueue.cancelTempBasal(true, object : Callback() {
|
commandQueue.cancelTempBasal(true, object : Callback() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -516,7 +517,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalpctreplywithcode), tempBasalPct, duration, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(tempBasalPct, duration) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(tempBasalPct, duration) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
commandQueue.tempBasalPercent(anInteger(), secondInteger(), true, profile, object : Callback() {
|
commandQueue.tempBasalPercent(anInteger(), secondInteger(), true, profile, object : Callback() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -548,7 +549,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_basalreplywithcode), tempBasal, duration, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(tempBasal, duration) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(tempBasal, duration) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
commandQueue.tempBasalAbsolute(aDouble(), secondInteger(), true, profile, object : Callback() {
|
commandQueue.tempBasalAbsolute(aDouble(), secondInteger(), true, profile, object : Callback() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -575,7 +576,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedstopreplywithcode), passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction() {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
commandQueue.cancelExtended(object : Callback() {
|
commandQueue.cancelExtended(object : Callback() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -603,7 +604,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_extendedreplywithcode), extended, duration, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(extended, duration) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(extended, duration) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
commandQueue.extendedBolus(aDouble(), secondInteger(), object : Callback() {
|
commandQueue.extendedBolus(aDouble(), secondInteger(), object : Callback() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
|
@ -638,7 +639,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
else
|
else
|
||||||
String.format(resourceHelper.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode)
|
String.format(resourceHelper.gs(R.string.smscommunicator_bolusreplywithcode), bolus, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(bolus) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(bolus) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val detailedBolusInfo = DetailedBolusInfo()
|
val detailedBolusInfo = DetailedBolusInfo()
|
||||||
detailedBolusInfo.insulin = aDouble()
|
detailedBolusInfo.insulin = aDouble()
|
||||||
|
@ -714,7 +715,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_carbsreplywithcode), grams, DateUtil.timeString(time), passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_carbsreplywithcode), grams, DateUtil.timeString(time), passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(grams, time) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(grams, time) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val detailedBolusInfo = DetailedBolusInfo()
|
val detailedBolusInfo = DetailedBolusInfo()
|
||||||
detailedBolusInfo.carbs = anInteger().toDouble()
|
detailedBolusInfo.carbs = anInteger().toDouble()
|
||||||
|
@ -746,7 +747,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), splitted[1].toUpperCase(Locale.getDefault()), passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetwithcode), splitted[1].toUpperCase(Locale.getDefault()), passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction() {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val units = profileFunction.getUnits()
|
val units = profileFunction.getUnits()
|
||||||
var keyDuration = 0
|
var keyDuration = 0
|
||||||
|
@ -801,7 +802,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetcancel), passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_temptargetcancel), passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction() {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val tempTarget = TempTarget()
|
val tempTarget = TempTarget()
|
||||||
.source(Source.USER)
|
.source(Source.USER)
|
||||||
|
@ -825,7 +826,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_stopsmswithcode), passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_stopsmswithcode), passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction() {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
sp.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)
|
sp.putBoolean(R.string.key_smscommunicator_remotecommandsallowed, false)
|
||||||
val replyText = String.format(resourceHelper.gs(R.string.smscommunicator_stoppedsms))
|
val replyText = String.format(resourceHelper.gs(R.string.smscommunicator_stoppedsms))
|
||||||
|
@ -841,7 +842,7 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
val passCode = generatePasscode()
|
val passCode = generatePasscode()
|
||||||
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode)
|
val reply = String.format(resourceHelper.gs(R.string.smscommunicator_calibrationreplywithcode), cal, passCode)
|
||||||
receivedSms.processed = true
|
receivedSms.processed = true
|
||||||
messageToConfirm = AuthRequest(this, resourceHelper, receivedSms, reply, passCode, object : SmsAction(cal) {
|
messageToConfirm = AuthRequest(this, resourceHelper, otp, receivedSms, reply, passCode, object : SmsAction(cal) {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val result = XdripCalibrations.sendIntent(aDouble)
|
val result = XdripCalibrations.sendIntent(aDouble)
|
||||||
if (result) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_calibrationsent))) else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_calibrationfailed)))
|
if (result) sendSMSToAllNumbers(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_calibrationsent))) else sendSMS(Sms(receivedSms.phoneNumber, resourceHelper.gs(R.string.smscommunicator_calibrationfailed)))
|
||||||
|
@ -898,6 +899,12 @@ class SmsCommunicatorPlugin @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generatePasscode(): String {
|
private fun generatePasscode(): String {
|
||||||
|
|
||||||
|
if (otp.isEnabled()) {
|
||||||
|
// this not realy generate password - rather info to use Authenticator TOTP instead
|
||||||
|
return resourceHelper.gs(R.string.smscommunicator_code_from_authenticator_for, otp.name());
|
||||||
|
}
|
||||||
|
|
||||||
val startChar1 = 'A'.toInt() // on iphone 1st char is uppercase :)
|
val startChar1 = 'A'.toInt() // on iphone 1st char is uppercase :)
|
||||||
var passCode = Character.toString((startChar1 + Math.random() * ('z' - 'a' + 1)).toChar())
|
var passCode = Character.toString((startChar1 + Math.random() * ('z' - 'a' + 1)).toChar())
|
||||||
val startChar2: Int = if (Math.random() > 0.5) 'a'.toInt() else 'A'.toInt()
|
val startChar2: Int = if (Math.random() > 0.5) 'a'.toInt() else 'A'.toInt()
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
package info.nightscout.androidaps.plugins.general.smsCommunicator.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import dagger.android.support.DaggerFragment
|
||||||
|
import info.nightscout.androidaps.R
|
||||||
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||||
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.Sms
|
||||||
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
|
||||||
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.events.EventSmsCommunicatorUpdateGui
|
||||||
|
import info.nightscout.androidaps.utils.DateUtil
|
||||||
|
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||||
|
import info.nightscout.androidaps.utils.HtmlHelper
|
||||||
|
import info.nightscout.androidaps.utils.extensions.plusAssign
|
||||||
|
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||||
|
import io.reactivex.disposables.CompositeDisposable
|
||||||
|
import kotlinx.android.synthetic.main.smscommunicator_fragment_log.*
|
||||||
|
import java.util.*
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
class SmsCommunicatorLogFragment : DaggerFragment() {
|
||||||
|
@Inject lateinit var fabricPrivacy : FabricPrivacy
|
||||||
|
@Inject lateinit var rxBus: RxBusWrapper
|
||||||
|
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
|
||||||
|
|
||||||
|
private val disposable = CompositeDisposable()
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.smscommunicator_fragment_log, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
disposable += rxBus
|
||||||
|
.toObservable(EventSmsCommunicatorUpdateGui::class.java)
|
||||||
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribe({ updateGui() }) { fabricPrivacy.logException(it) }
|
||||||
|
updateGui()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
disposable.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateGui() {
|
||||||
|
class CustomComparator : Comparator<Sms> {
|
||||||
|
override fun compare(object1: Sms, object2: Sms): Int {
|
||||||
|
return (object1.date - object2.date).toInt()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Collections.sort(smsCommunicatorPlugin.messages, CustomComparator())
|
||||||
|
val messagesToShow = 40
|
||||||
|
val start = max(0, smsCommunicatorPlugin.messages.size - messagesToShow)
|
||||||
|
var logText = ""
|
||||||
|
for (x in start until smsCommunicatorPlugin.messages.size) {
|
||||||
|
val sms = smsCommunicatorPlugin.messages[x]
|
||||||
|
when {
|
||||||
|
sms.ignored -> {
|
||||||
|
logText += DateUtil.timeString(sms.date) + " <<< " + "░ " + sms.phoneNumber + " <b>" + sms.text + "</b><br>"
|
||||||
|
}
|
||||||
|
|
||||||
|
sms.received -> {
|
||||||
|
logText += DateUtil.timeString(sms.date) + " <<< " + (if (sms.processed) "● " else "○ ") + sms.phoneNumber + " <b>" + sms.text + "</b><br>"
|
||||||
|
}
|
||||||
|
|
||||||
|
sms.sent -> {
|
||||||
|
logText += DateUtil.timeString(sms.date) + " >>> " + (if (sms.processed) "● " else "○ ") + sms.phoneNumber + " <b>" + sms.text + "</b><br>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smscommunicator_log?.text = HtmlHelper.fromHtml(logText)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,115 @@
|
||||||
|
package info.nightscout.androidaps.plugins.general.smsCommunicator.fragments
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import com.google.common.primitives.Ints.min
|
||||||
|
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||||
|
import dagger.android.support.DaggerFragment
|
||||||
|
import info.nightscout.androidaps.R
|
||||||
|
import info.nightscout.androidaps.plugins.bus.RxBusWrapper
|
||||||
|
import info.nightscout.androidaps.plugins.general.smsCommunicator.SmsCommunicatorPlugin
|
||||||
|
import info.nightscout.androidaps.utils.FabricPrivacy
|
||||||
|
import info.nightscout.androidaps.utils.OKDialog
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePassword
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePasswordValidationResult
|
||||||
|
import info.nightscout.androidaps.utils.ToastUtils
|
||||||
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
|
import kotlinx.android.synthetic.main.smscommunicator_fragment_otp.*
|
||||||
|
import net.glxn.qrgen.android.QRCode
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class SmsCommunicatorOtpFragment : DaggerFragment() {
|
||||||
|
@Inject lateinit var fabricPrivacy: FabricPrivacy
|
||||||
|
@Inject lateinit var rxBus: RxBusWrapper
|
||||||
|
@Inject lateinit var smsCommunicatorPlugin: SmsCommunicatorPlugin
|
||||||
|
@Inject lateinit var otp: OneTimePassword
|
||||||
|
@Inject lateinit var resourceHelper: ResourceHelper
|
||||||
|
|
||||||
|
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?): View? {
|
||||||
|
return inflater.inflate(R.layout.smscommunicator_fragment_otp, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
|
smscommunicator_otp_verify_edit.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
if (s != null) {
|
||||||
|
val checkResult = otp.checkOTP(s.toString())
|
||||||
|
|
||||||
|
smscommunicator_otp_verify_label.text = when (checkResult) {
|
||||||
|
OneTimePasswordValidationResult.OK -> "OK"
|
||||||
|
OneTimePasswordValidationResult.ERROR_WRONG_LENGTH -> "INVALID SIZE!"
|
||||||
|
OneTimePasswordValidationResult.ERROR_WRONG_PIN -> "WRONG PIN"
|
||||||
|
OneTimePasswordValidationResult.ERROR_WRONG_OTP -> "WRONG OTP"
|
||||||
|
}
|
||||||
|
|
||||||
|
smscommunicator_otp_verify_label.setTextColor(when (checkResult) {
|
||||||
|
OneTimePasswordValidationResult.OK -> Color.GREEN
|
||||||
|
OneTimePasswordValidationResult.ERROR_WRONG_LENGTH -> Color.YELLOW
|
||||||
|
OneTimePasswordValidationResult.ERROR_WRONG_PIN -> Color.RED
|
||||||
|
OneTimePasswordValidationResult.ERROR_WRONG_OTP -> Color.RED
|
||||||
|
})
|
||||||
|
|
||||||
|
} else {
|
||||||
|
smscommunicator_otp_verify_label.text = "EMPTY";
|
||||||
|
smscommunicator_otp_verify_label.setTextColor(Color.YELLOW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
actions_smscommunicator_otp_reset.setOnClickListener {
|
||||||
|
if (this.activity != null) {
|
||||||
|
OKDialog.showConfirmation(this.activity!!,
|
||||||
|
resourceHelper.gs(R.string.smscommunicator_otp_reset_title),
|
||||||
|
resourceHelper.gs(R.string.smscommunicator_otp_reset_prompt),
|
||||||
|
Runnable {
|
||||||
|
otp.ensureKey(true)
|
||||||
|
updateGui()
|
||||||
|
ToastUtils.showToastInUiThread(this.context, R.string.smscommunicator_otp_reset_successful)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
updateGui()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateGui() {
|
||||||
|
val displayMetrics = Resources.getSystem().getDisplayMetrics()
|
||||||
|
val width = displayMetrics.widthPixels
|
||||||
|
val height = displayMetrics.heightPixels
|
||||||
|
|
||||||
|
// ensure QRCode is big enough to fit on screen
|
||||||
|
val dim = (min(width, height) * 0.85).toInt()
|
||||||
|
val provURI = otp.provisioningURI();
|
||||||
|
|
||||||
|
if (provURI != null) {
|
||||||
|
val myBitmap = QRCode.from(provURI).withErrorCorrection(ErrorCorrectionLevel.H).withSize(dim, dim).bitmap()
|
||||||
|
smscommunicator_otp_provisioning.setImageBitmap(myBitmap)
|
||||||
|
smscommunicator_otp_provisioning.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
smscommunicator_otp_provisioning.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
smscommunicator_otp_verify_edit.text = smscommunicator_otp_verify_edit.text
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package info.nightscout.androidaps.utils
|
||||||
|
|
||||||
|
enum class OneTimePasswordValidationResult {
|
||||||
|
OK,
|
||||||
|
ERROR_WRONG_LENGTH,
|
||||||
|
ERROR_WRONG_PIN,
|
||||||
|
ERROR_WRONG_OTP
|
||||||
|
}
|
|
@ -0,0 +1,135 @@
|
||||||
|
package info.nightscout.androidaps.utils
|
||||||
|
|
||||||
|
import android.util.Base64
|
||||||
|
import com.eatthepath.otp.TimeBasedOneTimePasswordGenerator
|
||||||
|
import com.google.common.io.BaseEncoding
|
||||||
|
import info.nightscout.androidaps.Constants
|
||||||
|
import info.nightscout.androidaps.R
|
||||||
|
import info.nightscout.androidaps.utils.resources.ResourceHelper
|
||||||
|
import info.nightscout.androidaps.utils.sharedPreferences.SP
|
||||||
|
import org.joda.time.DateTimeUtils
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import javax.crypto.KeyGenerator
|
||||||
|
import javax.crypto.SecretKey
|
||||||
|
import javax.crypto.spec.SecretKeySpec
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class OneTimePassword @Inject constructor(
|
||||||
|
private val sp: SP,
|
||||||
|
private val resourceHelper: ResourceHelper
|
||||||
|
) {
|
||||||
|
|
||||||
|
private var key: SecretKey? = null
|
||||||
|
private var pin: String = ""
|
||||||
|
private val totp = TimeBasedOneTimePasswordGenerator()
|
||||||
|
|
||||||
|
init {
|
||||||
|
instance = this
|
||||||
|
configure()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private lateinit var instance: OneTimePassword
|
||||||
|
@JvmStatic
|
||||||
|
fun getInstance(): OneTimePassword = instance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If OTP Authenticator support is enabled by user
|
||||||
|
*/
|
||||||
|
fun isEnabled(): Boolean {
|
||||||
|
return sp.getBoolean(R.string.key_smscommunicator_otp_enabled, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Name of master device (target of OTP)
|
||||||
|
*/
|
||||||
|
fun name(): String {
|
||||||
|
val defaultUserName = resourceHelper.gs(R.string.smscommunicator_default_user_display_name)
|
||||||
|
var userName = sp.getString(R.string.key_smscommunicator_otp_name, defaultUserName).trim()
|
||||||
|
if (userName.length == 0)
|
||||||
|
userName = defaultUserName
|
||||||
|
return userName
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make sure if private key for TOTP is generated, creating it when necessary or requested
|
||||||
|
*/
|
||||||
|
fun ensureKey(forceNewKey: Boolean = false) {
|
||||||
|
var keyBytes: ByteArray = byteArrayOf()
|
||||||
|
val strSecret = sp.getString(R.string.key_smscommunicator_otp_secret, "").trim()
|
||||||
|
if (strSecret.length == 0 || forceNewKey) {
|
||||||
|
val keyGenerator = KeyGenerator.getInstance(totp.getAlgorithm());
|
||||||
|
keyGenerator.init(Constants.OTP_GENERATED_KEY_LENGTH_BITS);
|
||||||
|
val generatedKey = keyGenerator.generateKey();
|
||||||
|
keyBytes = generatedKey.encoded
|
||||||
|
sp.putString(R.string.key_smscommunicator_otp_secret, Base64.encodeToString(keyBytes, Base64.NO_WRAP + Base64.NO_PADDING))
|
||||||
|
} else {
|
||||||
|
keyBytes = Base64.decode(strSecret, Base64.DEFAULT);
|
||||||
|
}
|
||||||
|
key = SecretKeySpec(keyBytes, 0, keyBytes.size, "SHA1")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun configure() {
|
||||||
|
ensureKey()
|
||||||
|
pin = sp.getString(R.string.key_smscommunicator_otp_pin, "").trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateOneTimePassword(counter: Long): String {
|
||||||
|
if (key != null) {
|
||||||
|
return String.format("%06d", totp.generateOneTimePassword(key, counter))
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if given OTP+PIN is valid
|
||||||
|
*/
|
||||||
|
fun checkOTP(otp: String): OneTimePasswordValidationResult {
|
||||||
|
configure()
|
||||||
|
val normalisedOtp = otp.replace(" ", "").replace("-", "").trim()
|
||||||
|
|
||||||
|
if (pin.length < 3) {
|
||||||
|
return OneTimePasswordValidationResult.ERROR_WRONG_PIN
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalisedOtp.length != (6 + pin.length)) {
|
||||||
|
return OneTimePasswordValidationResult.ERROR_WRONG_LENGTH
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalisedOtp.substring(6).equals(pin)) {
|
||||||
|
return OneTimePasswordValidationResult.ERROR_WRONG_PIN
|
||||||
|
}
|
||||||
|
|
||||||
|
val milis: Long = DateTimeUtils.currentTimeMillis()
|
||||||
|
val counter: Long = (milis / 30000L)
|
||||||
|
|
||||||
|
var acceptableTokens: MutableList<String> = mutableListOf(generateOneTimePassword(counter))
|
||||||
|
for (i in 0 until Constants.OTP_ACCEPT_OLD_TOKENS_COUNT) {
|
||||||
|
acceptableTokens.add(generateOneTimePassword(counter - i - 1))
|
||||||
|
}
|
||||||
|
val candidateOtp = normalisedOtp.substring(0, 6)
|
||||||
|
|
||||||
|
if (acceptableTokens.any { candidate -> candidateOtp.equals(candidate) }) {
|
||||||
|
return OneTimePasswordValidationResult.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
return OneTimePasswordValidationResult.ERROR_WRONG_OTP
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return URI used to provision Authenticator apps
|
||||||
|
*/
|
||||||
|
fun provisioningURI(): String? {
|
||||||
|
val keyImm = key
|
||||||
|
if (keyImm != null) {
|
||||||
|
return "otpauth://totp/AndroidAPS:" + URLEncoder.encode(name(), "utf-8") + "?secret=" + BaseEncoding.base32().encode(keyImm.encoded).replace("=", "") + "&issuer=AndroidAPS"
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -117,6 +117,7 @@ class DefaultEditTextValidator : EditTextValidator {
|
||||||
EditTextValidator.TEST_EMAIL -> toAdd = EmailValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_email_address_not_valid) else testErrorString)
|
EditTextValidator.TEST_EMAIL -> toAdd = EmailValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_email_address_not_valid) else testErrorString)
|
||||||
EditTextValidator.TEST_PHONE -> toAdd = PhoneValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_phone_not_valid) else testErrorString)
|
EditTextValidator.TEST_PHONE -> toAdd = PhoneValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_phone_not_valid) else testErrorString)
|
||||||
EditTextValidator.TEST_MULTI_PHONE -> toAdd = MultiPhoneValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_phone_not_valid) else testErrorString)
|
EditTextValidator.TEST_MULTI_PHONE -> toAdd = MultiPhoneValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_phone_not_valid) else testErrorString)
|
||||||
|
EditTextValidator.TEST_PIN_STRENGTH -> toAdd = PinStrengthValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_pin_not_valid) else testErrorString)
|
||||||
EditTextValidator.TEST_DOMAINNAME -> toAdd = DomainValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_domain_not_valid) else testErrorString)
|
EditTextValidator.TEST_DOMAINNAME -> toAdd = DomainValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_domain_not_valid) else testErrorString)
|
||||||
EditTextValidator.TEST_IPADDRESS -> toAdd = IpAddressValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_ip_not_valid) else testErrorString)
|
EditTextValidator.TEST_IPADDRESS -> toAdd = IpAddressValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_ip_not_valid) else testErrorString)
|
||||||
EditTextValidator.TEST_WEBURL -> toAdd = WebUrlValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_url_not_valid) else testErrorString)
|
EditTextValidator.TEST_WEBURL -> toAdd = WebUrlValidator(if (TextUtils.isEmpty(testErrorString)) context.getString(R.string.error_url_not_valid) else testErrorString)
|
||||||
|
|
|
@ -93,4 +93,6 @@ public interface EditTextValidator {
|
||||||
|
|
||||||
int TEST_MULTI_PHONE = 19;
|
int TEST_MULTI_PHONE = 19;
|
||||||
|
|
||||||
|
int TEST_PIN_STRENGTH = 20;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
package info.nightscout.androidaps.utils.textValidator.validators
|
||||||
|
|
||||||
|
import android.widget.EditText
|
||||||
|
|
||||||
|
class PinStrengthValidator(val _customErrorMessage: String?) : Validator(_customErrorMessage) {
|
||||||
|
|
||||||
|
val regex = "[0-9]{3,6}".toRegex()
|
||||||
|
|
||||||
|
override fun isValid(editText: EditText): Boolean {
|
||||||
|
return try {
|
||||||
|
val value = editText.text.toString()
|
||||||
|
if (!regex.matches(value))
|
||||||
|
return false
|
||||||
|
|
||||||
|
var last = ' '
|
||||||
|
var rising = true
|
||||||
|
var falling = true
|
||||||
|
var same = true
|
||||||
|
value.forEachIndexed { i, c ->
|
||||||
|
if (i > 0) {
|
||||||
|
if (last != c) {
|
||||||
|
same = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.toInt() + 1 != c.toInt()) {
|
||||||
|
falling = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (last.toInt() != c.toInt() + 1) {
|
||||||
|
rising = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
last = c
|
||||||
|
}
|
||||||
|
|
||||||
|
!rising && !falling && !same
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,15 +4,48 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".plugins.general.smsCommunicator.SmsCommunicatorFragment">
|
tools:context=".plugins.general.smsCommunicator.SmsCommunicatorFragment">
|
||||||
|
|
||||||
<ScrollView
|
<LinearLayout
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="fill_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<com.google.android.flexbox.FlexboxLayout xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/smscommunicator_log"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="fill_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="fill_parent"
|
android:background="@color/defaultbackground"
|
||||||
android:text=""/>
|
app:alignContent="stretch"
|
||||||
</ScrollView>
|
app:alignItems="stretch"
|
||||||
|
app:flexDirection="row"
|
||||||
|
app:flexWrap="wrap"
|
||||||
|
app:justifyContent="center">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/smscomunicator_tab_log"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="@string/smscomunicator_tab_log_label" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/smscomunicator_tab_otp"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="30dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical|center_horizontal"
|
||||||
|
android:paddingLeft="5dp"
|
||||||
|
android:paddingRight="5dp"
|
||||||
|
android:text="@string/smscomunicator_tab_otp_label" />
|
||||||
|
|
||||||
|
</com.google.android.flexbox.FlexboxLayout>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:id="@+id/smscomunicator_fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
19
app/src/main/res/layout/smscommunicator_fragment_log.xml
Normal file
19
app/src/main/res/layout/smscommunicator_fragment_log.xml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".plugins.general.smsCommunicator.fragments.SmsCommunicatorLogFragment">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/smscommunicator_log"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
104
app/src/main/res/layout/smscommunicator_fragment_otp.xml
Normal file
104
app/src/main/res/layout/smscommunicator_fragment_otp.xml
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context=".plugins.general.smsCommunicator.fragments.SmsCommunicatorOtpFragment">
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/smscommunicator_otp_layout"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/section_header_label"
|
||||||
|
android:text="@string/smscommunicator_otp_step1_install_header" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5sp"
|
||||||
|
android:layout_marginBottom="5sp"
|
||||||
|
android:paddingStart="15sp"
|
||||||
|
android:paddingEnd="15sp"
|
||||||
|
android:text="@string/smscommunicator_otp_install_info" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/section_header_label"
|
||||||
|
android:text="@string/smscommunicator_otp_step2_provisioning_header" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/smscommunicator_otp_provisioning"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:scaleType="center" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/warning_label"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/section_header_label"
|
||||||
|
android:text="@string/smscommunicator_otp_step3_test_header" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="15dp"
|
||||||
|
android:paddingEnd="5dp"
|
||||||
|
android:text="@string/smscommunicator_otp_verify_label" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/smscommunicator_otp_verify_edit"
|
||||||
|
android:layout_width="140sp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="000 000 000"
|
||||||
|
android:inputType="number"
|
||||||
|
android:maxLength="12"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="19sp"
|
||||||
|
tools:ignore="HardcodedText" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/smscommunicator_otp_verify_label"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:paddingStart="15dp"
|
||||||
|
android:paddingEnd="5dp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/section_header_label"
|
||||||
|
android:text="@string/smscommunicator_otp_reset_header" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
style="@style/warning_label"
|
||||||
|
android:text="@string/smscommunicator_otp_reset_warning" />
|
||||||
|
|
||||||
|
<info.nightscout.androidaps.utils.SingleClickButton
|
||||||
|
android:id="@+id/actions_smscommunicator_otp_reset"
|
||||||
|
style="?android:attr/buttonStyle"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginLeft="10dp"
|
||||||
|
android:layout_marginTop="3dp"
|
||||||
|
android:layout_marginRight="10dp"
|
||||||
|
android:layout_marginBottom="3dp"
|
||||||
|
android:text="@string/smscommunicator_otp_reset_btn"
|
||||||
|
android:textColor="@color/colorTreatmentButton" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</FrameLayout>
|
|
@ -23,6 +23,7 @@
|
||||||
<enum name="httpsUrl" value="17" />
|
<enum name="httpsUrl" value="17" />
|
||||||
<enum name="minLength" value="18" />
|
<enum name="minLength" value="18" />
|
||||||
<enum name="multiPhone" value="19" />
|
<enum name="multiPhone" value="19" />
|
||||||
|
<enum name="pinStrength" value="20" />
|
||||||
</attr>
|
</attr>
|
||||||
<attr name="testErrorString" format="string" />
|
<attr name="testErrorString" format="string" />
|
||||||
<attr name="emptyErrorString" format="string" />
|
<attr name="emptyErrorString" format="string" />
|
||||||
|
|
|
@ -1702,4 +1702,38 @@
|
||||||
<string name="loop_tbrrequest_time_label">Temp basal request time</string>
|
<string name="loop_tbrrequest_time_label">Temp basal request time</string>
|
||||||
<string name="loop_tbrexecution_time_label">Temp basal execution time</string>
|
<string name="loop_tbrexecution_time_label">Temp basal execution time</string>
|
||||||
|
|
||||||
|
<!-- SMS Communicator & OTP Authenticator -->
|
||||||
|
|
||||||
|
<string name="key_smscommunicator_otp_enabled" translatable="false">smscommunicator_otp_enabled</string>
|
||||||
|
<string name="key_smscommunicator_otp_name" translatable="false">smscommunicator_otp_name</string>
|
||||||
|
<string name="key_smscommunicator_otp_pin" translatable="false">smscommunicator_otp_pin</string>
|
||||||
|
<string name="key_smscommunicator_otp_secret" translatable="false">smscommunicator_otp_secret</string>
|
||||||
|
|
||||||
|
<string name="smscommunicator_default_user_display_name" comment="This is default user display name, shown by Authenticators">User</string>
|
||||||
|
<string name="smscommunicator_code_from_authenticator_for" comment="This is continuation of sentence: To [ACTION] reply with code">from Authenticator app for: %1$s</string>
|
||||||
|
|
||||||
|
<string name="smscommunicator_otp_enabled">Enable Authenticator</string>
|
||||||
|
<string name="smscommunicator_otp_enabled_summary">Authenticate commands using One Time Passwords generated by Google Authenticator or similar 2FA apps.</string>
|
||||||
|
<string name="smscommunicator_otp_pin">Additional PIN at token end</string>
|
||||||
|
<string name="smscommunicator_otp_pin_summary">Additional digits that should be memorised and glued at end of each generated One Time Password</string>
|
||||||
|
<string name="smscommunicator_otp_name">User name for Authenticator</string>
|
||||||
|
<string name="smscommunicator_otp_name_summary">User name displayed along with generated OTP on Authenticator App</string>
|
||||||
|
|
||||||
|
<string name="smscomunicator_tab_log_label">Operations Log</string>
|
||||||
|
<string name="smscomunicator_tab_otp_label">Authenticator</string>
|
||||||
|
|
||||||
|
<string name="smscommunicator_otp_verify_label">OTP to check:</string>
|
||||||
|
<string name="smscommunicator_otp_reset_btn">Reset Authenticators</string>
|
||||||
|
<string name="smscommunicator_otp_reset_title">Reset Authenticator Key</string>
|
||||||
|
<string name="smscommunicator_otp_reset_prompt">Are you sure to reset Authenticator key? It will render all currently configured Authenticators invalid, and you will need to set them up again.</string>
|
||||||
|
<string name="smscommunicator_otp_reset_successful">New Authenticator Key was generated! Please use updated QRCode to provision authenticators.</string>
|
||||||
|
|
||||||
|
<string name="smscommunicator_otp_step1_install_header">1. Install Authenticator</string>
|
||||||
|
<string name="smscommunicator_otp_step2_provisioning_header">2. Scan code to setup AndroidAPS OTP codes</string>
|
||||||
|
<string name="smscommunicator_otp_step3_test_header">3. Test One-Time-Password</string>
|
||||||
|
<string name="smscommunicator_otp_reset_header">Reset Authenticators</string>
|
||||||
|
|
||||||
|
<string name="smscommunicator_otp_install_info">On each follower phone install Authenticator app that support RFC 6238 TOTP tokens. Popular free apps are:\n • Google Authenticator\n • LastPass Authenticator\n • FreeOTP Authenticator</string>
|
||||||
|
<string name="smscommunicator_otp_provisioning_warning">DO NOT SHARE this code online!\nUse it only to setup Authenticator App on follower phones.</string>
|
||||||
|
<string name="smscommunicator_otp_reset_warning">By reseting authenticator you make all already provisioned authenticators invalid. You will need to set up them again!</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -49,4 +49,24 @@
|
||||||
|
|
||||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
|
||||||
|
|
||||||
|
<style name="section_header_label">
|
||||||
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:layout_marginTop">15dp</item>
|
||||||
|
<item name="android:paddingStart">15dp</item>
|
||||||
|
<item name="android:paddingEnd">15dp</item>
|
||||||
|
<item name="android:textColor">@color/colorAccent</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style name="warning_label">
|
||||||
|
<item name="android:layout_width">match_parent</item>
|
||||||
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
<item name="android:layout_marginTop">5dp</item>
|
||||||
|
<item name="android:layout_marginBottom">5dp</item>
|
||||||
|
<item name="android:paddingStart">15dp</item>
|
||||||
|
<item name="android:paddingEnd">15dp</item>
|
||||||
|
<item name="android:textAlignment">center</item>
|
||||||
|
<item name="android:textColor">#ff0000</item>
|
||||||
|
</style>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
<string name="error_mustbe4digitnumber">Must be 4 digit number</string>
|
<string name="error_mustbe4digitnumber">Must be 4 digit number</string>
|
||||||
<string name="error_mustbe6digitnumber">Must be 6 digit number</string>
|
<string name="error_mustbe6digitnumber">Must be 6 digit number</string>
|
||||||
<string name="error_not_a_minimum_length">Not a minimum length</string>
|
<string name="error_not_a_minimum_length">Not a minimum length</string>
|
||||||
|
<string name="error_pin_not_valid">Pin should be 3 to 6 digits, not same or in series</string>
|
||||||
|
|
||||||
<string name="fourdigitnumber" translatable="false">^\\d{4}</string>
|
<string name="fourdigitnumber" translatable="false">^\\d{4}</string>
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,25 @@
|
||||||
validate:minNumber="3"
|
validate:minNumber="3"
|
||||||
validate:testType="numericRange" />
|
validate:testType="numericRange" />
|
||||||
|
|
||||||
|
<SwitchPreference
|
||||||
|
android:defaultValue="false"
|
||||||
|
android:key="@string/key_smscommunicator_otp_enabled"
|
||||||
|
android:title="@string/smscommunicator_otp_enabled"
|
||||||
|
android:summary="@string/smscommunicator_otp_enabled_summary"/>
|
||||||
|
|
||||||
|
<info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
|
||||||
|
android:key="@string/key_smscommunicator_otp_pin"
|
||||||
|
android:summary="@string/smscommunicator_otp_pin_summary"
|
||||||
|
android:title="@string/smscommunicator_otp_pin"
|
||||||
|
validate:testType="pinStrength" />
|
||||||
|
|
||||||
|
<info.nightscout.androidaps.utils.textValidator.ValidatingEditTextPreference
|
||||||
|
android:key="@string/key_smscommunicator_otp_name"
|
||||||
|
android:summary="@string/smscommunicator_otp_name_summary"
|
||||||
|
android:title="@string/smscommunicator_otp_name"
|
||||||
|
validate:testType="personName" />
|
||||||
|
|
||||||
|
|
||||||
</PreferenceCategory>
|
</PreferenceCategory>
|
||||||
|
|
||||||
</androidx.preference.PreferenceScreen>
|
</androidx.preference.PreferenceScreen>
|
|
@ -9,6 +9,7 @@ import org.json.JSONObject;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.powermock.api.mockito.PowerMockito;
|
import org.powermock.api.mockito.PowerMockito;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import info.nightscout.androidaps.Constants;
|
import info.nightscout.androidaps.Constants;
|
||||||
|
@ -27,6 +28,7 @@ import info.nightscout.androidaps.plugins.iob.iobCobCalculator.IobCobCalculatorP
|
||||||
import info.nightscout.androidaps.plugins.treatments.TreatmentService;
|
import info.nightscout.androidaps.plugins.treatments.TreatmentService;
|
||||||
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||||
import info.nightscout.androidaps.queue.CommandQueue;
|
import info.nightscout.androidaps.queue.CommandQueue;
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePassword;
|
||||||
import info.nightscout.androidaps.utils.SP;
|
import info.nightscout.androidaps.utils.SP;
|
||||||
|
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
@ -294,4 +296,7 @@ public class AAPSMocker {
|
||||||
return iobCobCalculatorPlugin;
|
return iobCobCalculatorPlugin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void mockOTP() {
|
||||||
|
PowerMockito.when(OneTimePassword.getInstance().isEnabled()).thenReturn(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package info.nightscout.androidaps.plugins.general.smsCommunicator;
|
||||||
|
|
||||||
import android.telephony.SmsManager;
|
import android.telephony.SmsManager;
|
||||||
|
|
||||||
|
import com.google.firebase.auth.OAuthCredential;
|
||||||
|
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
@ -41,6 +43,7 @@ import info.nightscout.androidaps.plugins.treatments.TreatmentsPlugin;
|
||||||
import info.nightscout.androidaps.queue.Callback;
|
import info.nightscout.androidaps.queue.Callback;
|
||||||
import info.nightscout.androidaps.queue.CommandQueue;
|
import info.nightscout.androidaps.queue.CommandQueue;
|
||||||
import info.nightscout.androidaps.utils.DateUtil;
|
import info.nightscout.androidaps.utils.DateUtil;
|
||||||
|
import info.nightscout.androidaps.utils.OneTimePassword;
|
||||||
import info.nightscout.androidaps.utils.SP;
|
import info.nightscout.androidaps.utils.SP;
|
||||||
import info.nightscout.androidaps.utils.T;
|
import info.nightscout.androidaps.utils.T;
|
||||||
import info.nightscout.androidaps.utils.XdripCalibrations;
|
import info.nightscout.androidaps.utils.XdripCalibrations;
|
||||||
|
@ -68,6 +71,7 @@ import static org.powermock.api.mockito.PowerMockito.when;
|
||||||
public class SmsCommunicatorPluginTest {
|
public class SmsCommunicatorPluginTest {
|
||||||
|
|
||||||
private SmsCommunicatorPlugin smsCommunicatorPlugin;
|
private SmsCommunicatorPlugin smsCommunicatorPlugin;
|
||||||
|
private OneTimePassword otp;
|
||||||
private LoopPlugin loopPlugin;
|
private LoopPlugin loopPlugin;
|
||||||
|
|
||||||
private boolean hasBeenRun = false;
|
private boolean hasBeenRun = false;
|
||||||
|
@ -87,7 +91,7 @@ public class SmsCommunicatorPluginTest {
|
||||||
Assert.assertTrue(smsCommunicatorPlugin.isCommand("BOLUS", ""));
|
Assert.assertTrue(smsCommunicatorPlugin.isCommand("BOLUS", ""));
|
||||||
smsCommunicatorPlugin.setMessageToConfirm(null);
|
smsCommunicatorPlugin.setMessageToConfirm(null);
|
||||||
Assert.assertFalse(smsCommunicatorPlugin.isCommand("BLB", ""));
|
Assert.assertFalse(smsCommunicatorPlugin.isCommand("BLB", ""));
|
||||||
smsCommunicatorPlugin.setMessageToConfirm(new AuthRequest(smsCommunicatorPlugin, new Sms("1234", "ddd"), "RequestText", "ccode", new SmsAction() {
|
smsCommunicatorPlugin.setMessageToConfirm(new AuthRequest(smsCommunicatorPlugin, new Sms("1234", "ddd"), otp, "RequestText", "ccode", new SmsAction() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
}
|
}
|
||||||
|
@ -892,6 +896,7 @@ public class SmsCommunicatorPluginTest {
|
||||||
AAPSMocker.mockConfigBuilder();
|
AAPSMocker.mockConfigBuilder();
|
||||||
AAPSMocker.mockCommandQueue();
|
AAPSMocker.mockCommandQueue();
|
||||||
AAPSMocker.mockNSUpload();
|
AAPSMocker.mockNSUpload();
|
||||||
|
AAPSMocker.mockOTP();
|
||||||
ConstraintChecker constraintChecker = AAPSMocker.mockConstraintsChecker();
|
ConstraintChecker constraintChecker = AAPSMocker.mockConstraintsChecker();
|
||||||
|
|
||||||
BgReading reading = new BgReading();
|
BgReading reading = new BgReading();
|
||||||
|
|
Loading…
Reference in a new issue