1.修复漏洞:BaseDao 中的 SQL 注入

2.移除ACCESS_MOCK_LOCATION  权限
3.MMKV启用AES-256加密保存
This commit is contained in:
2026-07-02 17:47:59 +08:00
parent 5ac14912bb
commit 815464ef20
22 changed files with 350 additions and 150 deletions

View File

@@ -43,9 +43,9 @@
<uses-permission android:name="android.permission.READ_PHONE_STATE" /> <!-- 读取缓存数据 -->
<!-- 蓝牙扫描权限 -->
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> <!-- 获取模拟定位信息 -->
<uses-permission
android:name="android.permission.ACCESS_MOCK_LOCATION"
tools:ignore="MockLocation,ProtectedPermissions" />
<!-- <uses-permission-->
<!-- android:name="android.permission.ACCESS_MOCK_LOCATION"-->
<!-- tools:ignore="MockLocation,ProtectedPermissions" />-->
<permission
android:name="android.permission.INTERACT_ACROSS_USERS_FULL"
@@ -95,7 +95,8 @@
<application
android:name=".MyApplication"
android:allowBackup="true"
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/logo_abbidot"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_security_config"

View File

@@ -578,10 +578,11 @@ abstract class BaseActivity<T : ViewBinding>(val inflater: (inflater: LayoutInfl
fun <T> dealRequestResult(
it: Result<T>,
getResultCallback: GetResultCallback,
showLoading: Boolean = false,
isShowNoDataTip: Boolean = true,
isShowRequestErrorTip: Boolean = true,
isShowCodeError: Boolean = true,
showLoading: Boolean = false,//是否显示loading弹窗
isShowNoDataTip: Boolean = true,//是否显示无数据提示
isShowRequestErrorTip: Boolean = true,//是否显示请求错误提示
isShowCodeError: Boolean = true,//是否显示错误码提示
isShowNetCodeMsg: Boolean = false,//是否显示服务器返回message提示
errorTip: String = getString(R.string.txt_net_error)
) {
try {
@@ -614,7 +615,9 @@ abstract class BaseActivity<T : ViewBinding>(val inflater: (inflater: LayoutInfl
val data = it.getOrNull()
if (null == data) {
if (isShowRequestErrorTip) {
if (isShowNetCodeMsg && exceptionCode == ErrorCode.ERROR.toString()) {
showToast(exceptionMessage ?: errorTip)
} else if (isShowRequestErrorTip) {
if (NetworkUtil.isNetworkAvailable(mContext)) {
showToast(errorTip)
} else {

View File

@@ -179,10 +179,11 @@ abstract class BaseFragment<T : ViewBinding>(
fun <T> dealRequestResult(
it: Result<T>,
getResultCallback: GetResultCallback,
showLoading: Boolean = false,
isShowNoDataTip: Boolean = true,
isRequestErrorTip: Boolean = true,
isShowCodeError: Boolean = true,
showLoading: Boolean = false,//是否显示loading弹窗
isShowNoDataTip: Boolean = true,//是否显示无数据提示
isRequestErrorTip: Boolean = true,//是否显示请求错误提示
isShowCodeError: Boolean = true,//是否显示错误码提示
isShowNetCodeMsg: Boolean = false,//是否显示服务器返回message提示
errorTip: String = getString(R.string.txt_net_error)
) {
(activity as BaseActivity<*>).dealRequestResult(
@@ -191,8 +192,8 @@ abstract class BaseFragment<T : ViewBinding>(
showLoading,
isShowNoDataTip,
isRequestErrorTip,
isShowCodeError,
errorTip
isShowCodeError, isShowNetCodeMsg,
errorTip,
)
}
}

View File

@@ -41,7 +41,7 @@ abstract class MyDatabase : RoomDatabase() {
private val db: MyDatabase by lazy {
Room.databaseBuilder(MyApplication.instance, MyDatabase::class.java, "tracker_dp")
// .addMigrations(MIGRATION_1_2)
.fallbackToDestructiveMigration()//删除旧表格并创建新表格,的方式升级数据库版本
.fallbackToDestructiveMigration(true)//删除旧表格并创建新表格,的方式升级数据库版本
.build()
}

View File

@@ -1,9 +1,13 @@
package com.abbidot.tracker.database.dao
import android.util.Log
import androidx.room.*
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.RawQuery
import androidx.room.Update
import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteQuery
import com.abbidot.baselibrary.util.LogUtil
/**
*Created by .yzq on 2022/4/1/001.
@@ -28,43 +32,43 @@ interface BaseDao<T> {
* OnConflictStrategy.REPLACE新数据替换旧数据
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(obj: T): Long
fun insert(obj: T): Long
/**
* 添加数组对象数据
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg objs: T): LongArray?
fun insert(vararg objs: T): LongArray?
/**
* 添加对象集合
*/
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(personList: List<T>): List<Long>
fun insert(personList: List<T>): List<Long>
/**
* 根据对象中的主键删除(主键是自动增长的,无需手动赋值)
*/
@Delete
fun delete(obj: T)
fun delete(obj: T)
/**
* 根据对象中的主键更新(主键是自动增长的,无需手动赋值)
*/
@Update
fun update(vararg obj: T): Int
fun update(vararg obj: T): Int
fun deleteAll(tableName:String): Int {
fun deleteAll(tableName: String): Int {
val query = SimpleSQLiteQuery("delete from $tableName")
return doDeleteAll(query)
}
fun findAll(tableName:String): List<T>? {
fun findAll(tableName: String): List<T>? {
val query = SimpleSQLiteQuery("select * from $tableName")
return doFindAll(query)
}
fun find(id: Long,tableName:String): T? {
fun find(id: Long, tableName: String): T? {
val query = SimpleSQLiteQuery("select * from $tableName where id = ?", arrayOf<Any>(id))
return doFind(query)
}
@@ -73,9 +77,9 @@ interface BaseDao<T> {
* [params] 列名
* [value] 列的值
*/
fun deleteByParams(params: String, value: String,tableName:String): Int {
val query = SimpleSQLiteQuery("delete from $tableName where $params='${value}'")
Log.d("ez", "deleteByParams: ${"delete from $tableName where $params='${value}'"}")
fun deleteByParams(params: String, value: String, tableName: String): Int {
val query = SimpleSQLiteQuery("delete from $tableName where $params= ?", arrayOf(value))
LogUtil.d("ez", "deleteByParams: ${"delete from $tableName where $params = ?"}")
return doDeleteByParams(query)
}
@@ -83,18 +87,26 @@ interface BaseDao<T> {
* 分页查询,支持传入多个字段,但必须要按照顺序传入
* key = valuekey = value 的形式,一一对应(可以使用 stringbuilder 去构造一下,这里就不演示了)
*/
fun doQueryByLimit(tableName:String,vararg string: String, limit: Int = 10, offset: Int = 0): List<T>? {
fun doQueryByLimit(
tableName: String, vararg string: String, limit: Int = 10, offset: Int = 0
): List<T>? {
val query = SimpleSQLiteQuery(
"SELECT * FROM $tableName WHERE ${string[0]} = '${string[1]}' limit $limit offset $offset")
"SELECT * FROM $tableName WHERE ${string[0]} = ? limit $limit offset $offset",
arrayOf(string[1])
)
return doQueryByLimit(query)
}
/**
* 降序分页查询
*/
fun doQueryByOrder(tableName:String,vararg string: String, limit: Int = 10, offset: Int = 10): List<T>? {
fun doQueryByOrder(
tableName: String, vararg string: String, limit: Int = 10, offset: Int = 10
): List<T>? {
// 修复 SQL 注入1. 去掉 limit 和 offset 的单引号 2. 确保排序字段安全(建议增加白名单验证)
val query = SimpleSQLiteQuery(
"SELECT * FROM $tableName ORDER BY ${string[0]} desc limit '${limit}' offset '${offset}'")
"SELECT * FROM $tableName ORDER BY ${string[0]} desc limit $limit offset $offset"
)
return doQueryByOrder(query)
}

View File

@@ -10,7 +10,6 @@ import com.abbidot.tracker.R
import com.abbidot.tracker.base.BaseActivity
import com.abbidot.tracker.bean.CreditCardBean
import com.abbidot.tracker.constant.ConstantString
import com.abbidot.tracker.database.MyDatabase
import com.abbidot.tracker.databinding.ActivityAddCreditCardBinding
import com.abbidot.tracker.util.Util
import kotlinx.coroutines.Dispatchers
@@ -123,7 +122,7 @@ class AddCreditCardActivity :
card.firstName = firstName
card.expirationDate = creditCardValidity
card.cvv = cvv
MyDatabase.creditCardDao().insert(card)
// MyDatabase.creditCardDao().insert(card)
runOnUiThread {
val intent = Intent()
intent.putExtra(ConstantString.Card, card)

View File

@@ -20,16 +20,13 @@ import com.abbidot.tracker.R
import com.abbidot.tracker.base.BaseActivity
import com.abbidot.tracker.constant.ConstantInt
import com.abbidot.tracker.constant.GetResultCallback
import com.abbidot.tracker.database.MyDatabase
import com.abbidot.tracker.databinding.ActivityLoginV2Binding
import com.abbidot.tracker.ui.activity.set.FirstPermissionsActivity
import com.abbidot.tracker.util.Util
import com.abbidot.tracker.util.ViewUtil
import com.abbidot.tracker.vm.LoginViewModel
import com.abbidot.tracker.vm.TrackerInfoViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class LoginV2Activity : BaseActivity<ActivityLoginV2Binding>(ActivityLoginV2Binding::inflate) {
@@ -136,14 +133,14 @@ class LoginV2Activity : BaseActivity<ActivityLoginV2Binding>(ActivityLoginV2Bind
)
}
}
withContext(Dispatchers.IO) {
//保存本地数据库
for (device in list) {
if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
MyDatabase.deviceDao().insert(device)
}
}
}
// withContext(Dispatchers.IO) {
// //保存本地数据库
// for (device in list) {
// if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
// MyDatabase.deviceDao().insert(device)
// }
// }
// }
showToast(R.string.txt_login_successful, gravity = Gravity.CENTER)

View File

@@ -166,7 +166,7 @@ class MyTrackerV2Activity :
//保存本地数据库
if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
bindDeviceNumber++
MyDatabase.deviceDao().insert(device)
// MyDatabase.deviceDao().insert(device)
} else if (!TextUtils.isEmpty(device.deviceOutId) && null == device.pet) {
//删除本地没绑定的设备
MyDatabase.deviceDao().deleteByParams(

View File

@@ -118,18 +118,18 @@ class FirstPetProfileActivity :
mTrackerViewModel.getDeviceListLiveData.observe(this) {
dealRequestResult(it, object : GetResultCallback {
override fun onResult(any: Any) {
it.getOrNull()?.let { list ->
lifecycleScope.launch {
withContext(Dispatchers.IO) {
for (device in list) {
//保存本地数据库
if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
MyDatabase.deviceDao().insert(device)
}
}
}
}
}
// it.getOrNull()?.let { list ->
// lifecycleScope.launch {
// withContext(Dispatchers.IO) {
// for (device in list) {
// //保存本地数据库
// if (!TextUtils.isEmpty(device.deviceOutId) && null != device.pet) {
// MyDatabase.deviceDao().insert(device)
// }
// }
// }
// }
// }
showToast(R.string.txt_bind_success, isFinish = true)
mViewBinding.root.postDelayed({

View File

@@ -1,5 +1,6 @@
package com.abbidot.tracker.ui.activity.subscribe
import android.content.Intent
import android.text.InputFilter
import android.text.InputFilter.LengthFilter
import android.text.InputType
@@ -143,7 +144,7 @@ class AddCreditCardPaymentActivity :
override fun onRequestError(exceptionCode: String?) {
setButtonEnabled(mViewBinding.btnSaveNewCreditCard, ConstantInt.Type1)
}
})
}, isShowNetCodeMsg = true)
}
}
@@ -336,7 +337,7 @@ class AddCreditCardPaymentActivity :
it.cardName = cardName
it.expirationDate = creditCardValidity
it.cvv = cvv
MyDatabase.creditCardDao().insert(it)
// MyDatabase.creditCardDao().insert(it)
}
runOnUiThread {
@@ -410,28 +411,21 @@ class AddCreditCardPaymentActivity :
override fun onSuccess(result: PaymentMethod) {
LogUtil.e(result.toString())
MyDatabase.creditCardDao()
.deleteAll(MyDatabase.CREDIT_CARD_TABLE)
it.cardNumber = cardNumber
it.cardName = cardName
it.expirationDate = creditCardValidity
it.cvv = cvv
MyDatabase.creditCardDao().insert(it)
val paymentMethodID = "${result.id}"
mSubscriptionPayViewModel.savePaymentMethodInfo(
paymentMethodID
)
mSubscriptionPayViewModel.savePaymentMethodInfo(paymentMethodID)
}
})
} else {
MyDatabase.creditCardDao().deleteAll(MyDatabase.CREDIT_CARD_TABLE)
// MyDatabase.creditCardDao().deleteAll(MyDatabase.CREDIT_CARD_TABLE)
it.cardNumber = cardNumber
it.cardName = cardName
it.expirationDate = creditCardValidity
it.cvv = cvv
MyDatabase.creditCardDao().insert(it)
// MyDatabase.creditCardDao().insert(it)
runOnUiThread {
setResult(ResultCode.ResultCode_1, null)
val intent = Intent()
intent.putExtra(ConstantString.Card, it)
setResult(ResultCode.ResultCode_1, intent)
showToast(R.string.txt_save_successful, isFinish = true)
}
}

View File

@@ -6,7 +6,6 @@ import android.text.TextUtils
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.abbidot.baselibrary.constant.ResultCode
import com.abbidot.baselibrary.list.BaseRecyclerAdapter
import com.abbidot.baselibrary.util.AppUtils
@@ -17,20 +16,18 @@ import com.abbidot.tracker.base.BaseFragment
import com.abbidot.tracker.bean.CreditCardBean
import com.abbidot.tracker.constant.ConstantString
import com.abbidot.tracker.constant.GetResultCallback
import com.abbidot.tracker.database.MyDatabase
import com.abbidot.tracker.databinding.FragmentCreditCardPaymentBinding
import com.abbidot.tracker.ui.activity.subscribe.AddCreditCardPaymentActivity
import com.abbidot.tracker.ui.activity.subscribe.PaymentMethodActivity
import com.abbidot.tracker.ui.activity.subscribe.PaymentSuccessActivity
import com.abbidot.tracker.util.ThirdPartyUtil
import com.abbidot.tracker.util.Util
import com.abbidot.tracker.util.ViewUtil
import com.abbidot.tracker.vm.SubscriptionPayViewModel
import com.stripe.android.ApiResultCallback
import com.stripe.android.Stripe
import com.stripe.android.model.PaymentMethod
import com.stripe.android.model.PaymentMethodCreateParams
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
/**
@@ -87,6 +84,7 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
mContext!!, rvCreditCardPaymentList, mCreditCardAdapter, top = AppUtils.dpToPx(10)
)
ilCreditCardPaymentNoData.btnSureSubscribePlan1Continue.visibility = View.GONE
setOnClickListenerViews(btnCreditCardPaymentAdd, btnMakePaymentCreditCard)
}
// getCardData()
@@ -94,17 +92,28 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
fun getCardData(stripeMethodInfo: CreditCardBean) {
if (TextUtils.isEmpty(stripeMethodInfo.lastFourNumber)) {
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
val list = MyDatabase.creditCardDao().findAll(MyDatabase.CREDIT_CARD_TABLE)
list?.let {
mActivity?.runOnUiThread {
val sortedByList =
list.sortedByDescending { l -> l.isPrimary }.toMutableList()
setCheckAndPrimaryState(sortedByList)
mCreditCardAdapter.setData(sortedByList, true)
}
// viewLifecycleOwner.lifecycleScope.launch(Dispatchers.IO) {
// val list = MyDatabase.creditCardDao().findAll(MyDatabase.CREDIT_CARD_TABLE)
// list?.let {
// mActivity?.runOnUiThread {
// val sortedByList =
// list.sortedByDescending { l -> l.isPrimary }.toMutableList()
// setCheckAndPrimaryState(sortedByList)
// mCreditCardAdapter.setData(sortedByList, true)
// }
// }
// }
//没有卡,显示无卡提示 不用去添加卡
mViewBinding.apply {
if (null == getHostActivity(PaymentMethodActivity::class.java)?.mOrderBean) {
ilCreditCardPaymentNoData.root.visibility = View.VISIBLE
rvCreditCardPaymentList.visibility = View.GONE
} else {
ilCreditCardPaymentNoData.root.visibility = View.GONE
rvCreditCardPaymentList.visibility = View.VISIBLE
}
}
} else {
stripeMethodInfo.apply {
expirationDate = "$expMonth/$expYear"
@@ -127,8 +136,9 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
add(stripeMethodInfo)
mCreditCardAdapter.setData(this, true)
}
mViewBinding.ilCreditCardPaymentNoData.root.visibility = View.GONE
ViewUtil.instance.viewShow(mViewBinding.rvCreditCardPaymentList)
}
ViewUtil.instance.viewShow(mViewBinding.rvCreditCardPaymentList)
}
fun goEditCard(cardBean: CreditCardBean) {
@@ -162,7 +172,7 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
override fun onRequestError(exceptionCode: String?) {
showNoCancelableLoading(false)
}
})
}, isShowNetCodeMsg = true)
}
mCreateStripeOrderLiveData.observe(viewLifecycleOwner) {
@@ -245,7 +255,19 @@ class CreditCardPaymentFragment : BaseFragment<FragmentCreditCardPaymentBinding>
// }
if (it.resultCode == ResultCode.ResultCode_1) {
// getCardData()
getHostActivity(PaymentMethodActivity::class.java)?.getUserPaymentMethodID()
if (null != it.data) {
val card = Util.getParcelableAdaptive(
it.data!!, ConstantString.Card, CreditCardBean::class.java
)
if (null != card) {
val list = mutableListOf<CreditCardBean>()
list.add(card)
setCheckAndPrimaryState(list)
mCreditCardAdapter.setData(list, true)
} else getHostActivity(PaymentMethodActivity::class.java)?.getUserPaymentMethodID()
} else {
getHostActivity(PaymentMethodActivity::class.java)?.getUserPaymentMethodID()
}
}
}

View File

@@ -19,11 +19,10 @@ import com.easysocket.interfaces.conn.ISocketActionListener
*/
class SocketUtilManage {
private val iP = "3.101.8.36"
// private val iP = "192.168.0.69"
private val iP = ""
//默认国外服务器端口
private var port = 8868
private var port = 0
@OptIn(ExperimentalUnsignedTypes::class)
private val crcTable = ushortArrayOf(
@@ -51,7 +50,7 @@ class SocketUtilManage {
fun initEasySocket(context: Context) {
if (AppUtils.isChina()) {
//国内socket端口号
port = 9168
port = 1
}
//socket配置
val options = EasySocketOptions.Builder().setSocketAddress(SocketAddress(iP, port))

View File

@@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.abbidot.baselibrary.constant.MMKVKey
import com.abbidot.baselibrary.util.LogUtil
import com.abbidot.baselibrary.util.MMKVUtil
import com.abbidot.baselibrary.util.Utils
import com.abbidot.baselibrary.util.rsa.RSA
import com.abbidot.tracker.bean.CreditCardBean
import com.abbidot.tracker.bean.PayResultBean
@@ -33,7 +34,7 @@ class SubscriptionPayViewModel : ViewModel() {
/**
* Stripe支付创建消费用户
*/
fun createCustomer(card: CreditCardBean,paymentMethodID: String) {
fun createCustomer(card: CreditCardBean, paymentMethodID: String) {
// activity.showLoading(true)
viewModelScope.launch(Dispatchers.IO) {
val userId = MMKVUtil.getString(MMKVKey.UserId)
@@ -48,12 +49,10 @@ class SubscriptionPayViewModel : ViewModel() {
cardParamsMap["cardName"] = card.cardName
cardParamsMap["cardnum"] = card.cardNumber
val cardJso = Gson().toJson(cardParamsMap)
val b =
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAofuJbnkwyF7SWg3eRadSYT+ttfHN+CUmCzH0WW1EWTUQOG+01Nv8ux+UJUlxbZA2X7959Et/+VKE03+4OY0/26dlgkROMlXCUnzsucuaoHW+9whGi6xklRZrDLi22QBae/8XV1YDEU2vfYXtcvwf66J0t+bRIhGytQUZvUrL7OEaAHvwJU0QwCy3wQGWcCgSkPpDruIe4ar19iZMleE+BdPGIfKfVdPvWFls63g1eMFECb8F9zN6ydGTCvlqvx/4gfDuFQnF+JuTSre8euSpTRTQn/Q0s3K6KiROVcAYCqDW6cKxNUr2ha1C9ZFxMws9C0qhx5wYzfSlIjFthPC1qwIDAQAB"
val rsaKey = RSA.encrypt(cardJso, b)
val rsaKey = RSA.encrypt(cardJso, Utils.CARD_RSA_KEY)
LogUtil.e(cardJso)
val result = NetworkApi.createCustomer(rsaKey, userId, userName, email,paymentMethodID)
val result = NetworkApi.createCustomer(rsaKey, userId, userName, email, paymentMethodID)
mSubscriptionCustomerLiveData.postValue(result)
}
}

View File

@@ -37,12 +37,17 @@
app:qmui_radius="@dimen/dp_64"
app:typeface="@string/roboto_bold_font" />
<include
android:id="@+id/il_credit_card_payment_no_data"
android:visibility="gone"
layout="@layout/layout_credit_card_no_data" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_credit_card_payment_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:layout_above="@id/btn_make_payment_credit_card"
android:layout_below="@id/btn_credit_card_payment_add"
android:layout_marginTop="@dimen/dp_12" />
android:layout_marginTop="@dimen/dp_12"
android:visibility="gone" />
</RelativeLayout>

View File

@@ -2,5 +2,5 @@
<!-- 解决 Android P 禁用 http 请求的问题 -->
<network-security-config>
<!-- 允许明文通信 -->
<base-config cleartextTrafficPermitted="true" />
<base-config cleartextTrafficPermitted="false" />
</network-security-config>