提交 5e5d74cd 编写于 作者: 楼国栋

Merge branch 'feature/android_password_encrypt' into 'develop'

android v5.2.0版本

See merge request o2oa/o2oa!1764
......@@ -36,7 +36,8 @@
<application
android:name=".O2App"
android:allowBackup="true"
android:allowBackup="false"
tools:replace="android:allowBackup"
android:hardwareAccelerated="true"
android:icon="@mipmap/logo"
android:label="@string/app_name"
......
......@@ -2,6 +2,7 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base
import android.content.Context
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import android.widget.TextView
......@@ -50,6 +51,7 @@ abstract class BaseMVPActivity<in V: BaseView, T: BasePresenter<V>>: AppCompatAc
super.onCreate(savedInstanceState)
beforeSetContentView()
setContentView(layoutResId())
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
// 沉浸式状态栏
ImmersedStatusBarUtils.setImmersedStatusBar(this)
......
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import android.widget.TextView
......@@ -37,6 +38,7 @@ abstract class BaseO2Activity : AppCompatActivity() {
super.onCreate(savedInstanceState)
beforeSetContentView()
setContentView(layoutResId())
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
// 沉浸式状态栏
ImmersedStatusBarUtils.setImmersedStatusBar(this)
afterSetContentView(savedInstanceState)
......
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base
import android.os.Bundle
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.Toolbar
import android.widget.TextView
......@@ -37,6 +38,7 @@ abstract class BaseO2BindActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
beforeSetContentView()
bindView(savedInstanceState)
window.addFlags(WindowManager.LayoutParams.FLAG_SECURE)
// 沉浸式状态栏
ImmersedStatusBarUtils.setImmersedStatusBar(this)
afterSetContentView(savedInstanceState)
......
......@@ -16,6 +16,7 @@ import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.bbs.main.BBSMainActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.calendar.CalendarMainActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.clouddrive.CloudDriveActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.clouddrive.v2.CloudDiskActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.cms.index.CMSIndexActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.meeting.main.MeetingMainActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.webview.TaskWebViewActivity
......@@ -122,7 +123,8 @@ class O2InstantMessageActivity : BaseMVPActivity<O2InstantMessageContract.View,
}
}else if (type.startsWith("attachment_")) {
setLinkStyle(textView) {
go<CloudDriveActivity>()
// go<CloudDriveActivity>()
go<CloudDiskActivity>()
}
}else if (type.startsWith("calendar_")) {
setLinkStyle(textView) {
......
......@@ -120,21 +120,25 @@ class LaunchActivity : BaseMVPActivity<LaunchContract.View, LaunchContract.Prese
fun start() {
tv_launch_status.text = getString(R.string.launch_start) //开始启动
circleProgressBar_launch.visible()
PermissionRequester(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.o2Subscribe {
onNext { (granted, shouldShowRequestPermissionRationale, deniedPermissions) ->
Log.d("LaunchActivity", "granted:$granted, show:$shouldShowRequestPermissionRationale, deniedList:$deniedPermissions")
if (!granted) {
O2DialogSupport.openAlertDialog(this@LaunchActivity, "非常抱歉,应用需要存储权限才能正常运行, 马上去设置", { permissionSetting() })
} else {
checkNetwork()
if (CheckRoot.isDeviceRooted()) {
O2DialogSupport.openAlertDialog(this, "当前是Root环境,App禁止使用!")
}else {
PermissionRequester(this)
.request(Manifest.permission.WRITE_EXTERNAL_STORAGE)
.o2Subscribe {
onNext { (granted, shouldShowRequestPermissionRationale, deniedPermissions) ->
Log.d("LaunchActivity", "granted:$granted, show:$shouldShowRequestPermissionRationale, deniedList:$deniedPermissions")
if (!granted) {
O2DialogSupport.openAlertDialog(this@LaunchActivity, "非常抱歉,应用需要存储权限才能正常运行, 马上去设置", { permissionSetting() })
} else {
checkNetwork()
}
}
onError { e, _ ->
Log.e("LaunchActivity", "检查权限出错", e)
}
}
onError { e, _ ->
Log.e("LaunchActivity", "检查权限出错", e)
}
}
}
}
/**
......
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.login
import android.content.Context
import android.graphics.BitmapFactory
import android.media.AudioManager
import android.media.MediaPlayer
import android.os.Bundle
import androidx.core.content.ContextCompat
import android.os.Looper
import android.text.InputType
import android.text.TextUtils
import android.view.KeyEvent
import android.view.View
import android.view.WindowManager
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_login.*
import net.muliba.changeskin.FancySkinManager
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.*
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseMVPActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.bind.BindPhoneActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.main.MainActivity
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.CaptchaImgData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.LoginModeData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.LoginWithCaptchaForm
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.AuthenticationInfoJson
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.BitmapUtil
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.DateHelper
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XToast
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.*
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.biometric.BioConstants
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.biometric.BiometryManager
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.biometric.OnBiometryAuthCallback
......@@ -85,11 +88,21 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
private var loginType = 0 // 0默认的用户名验证码登录 1用户名密码登录
private var canBioAuth = false //是否有指纹认证
//验证码
private var useCaptcha = true
private var captcha : CaptchaImgData? = null
override fun afterSetContentView(savedInstanceState: Bundle?) {
receivePhone = intent.extras?.getString(REQUEST_PHONE_KEY) ?: ""
override fun afterSetContentView(savedInstanceState: Bundle?) {
//获取加密key
mPresenter.getRSAPublicKey()
//获取图片验证码
mPresenter.getCaptcha()
//
mPresenter.getLoginMode()
receivePhone = intent.extras?.getString(REQUEST_PHONE_KEY) ?: ""
setDefaultLogo()
login_edit_username_id.setText(receivePhone)
tv_login_copyright.text = getString(R.string.copy_right).plus(" ")
......@@ -114,23 +127,29 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
view_login_password_bottom.setBackgroundColor(FancySkinManager.instance().getColor(this, R.color.z_color_input_line_blur))
}
}
edit_login_captcha_input.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
view_login_captcha_input_bottom.setBackgroundColor(FancySkinManager.instance().getColor(this, R.color.z_color_input_line_focus))
}else {
view_login_captcha_input_bottom.setBackgroundColor(FancySkinManager.instance().getColor(this, R.color.z_color_input_line_blur))
}
}
btn_login_submit.setOnClickListener(this)
btn_bio_auth_login.setOnClickListener(this)
tv_user_fallback_btn.setOnClickListener(this)
tv_bioauth_btn.setOnClickListener(this)
image_login_captcha.setOnClickListener(this)
//是否开启了指纹识别登录
checkBioAuthLogin()
if (BuildConfig.InnerServer) {
login_edit_password_id.setHint(R.string.activity_login_password)
login_edit_password_id.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
// InputType.TYPE_TEXT_VARIATION_PASSWORD
button_login_phone_code.gone()
tv_rebind_btn.gone()
tv_bioauth_btn.gone()
ll_login_captcha.visible()
}else {
tv_bioauth_btn.visible()
login_edit_password_id.setHint(R.string.login_code)
......@@ -139,6 +158,8 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
button_login_phone_code.setOnClickListener(this)
tv_rebind_btn.visible()
tv_rebind_btn.setOnClickListener(this)
//不需要图片验证码
ll_login_captcha.gone()
}
}
......@@ -166,6 +187,10 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
override fun onResume() {
super.onResume()
//清除用户名密码
login_edit_username_id.setText("")
login_edit_password_id.setText("")
edit_login_captcha_input.setText("")
playBeep = true
val audioService = getSystemService(Context.AUDIO_SERVICE) as AudioManager
......@@ -175,6 +200,26 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
initBeepSound()
}
override fun onStop() {
super.onStop()
//Activity反劫持检测工具
Thread(Runnable { // 白名单
val safe = AntiHijackingUtil.checkActivity(applicationContext)
// 系统桌面
val isHome = AntiHijackingUtil.isHome(applicationContext)
// 锁屏操作
val isReflectScreen = AntiHijackingUtil.isReflectScreen(applicationContext)
// 判断程序是否当前显示
if (!safe && !isHome && !isReflectScreen) {
Looper.prepare()
Toast.makeText(applicationContext, R.string.activity_safe_warning,
Toast.LENGTH_LONG).show()
Looper.loop()
}
}).start()
}
override fun onDestroy() {
super.onDestroy()
countDownHelper.destroy()
......@@ -202,6 +247,10 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
reBindService()
})
}
R.id.image_login_captcha -> {
showLoadingDialog()
mPresenter.getCaptcha()
}
}
}
......@@ -228,11 +277,13 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
}
private fun changeLoginType() {
if (loginType == 0) {
ll_login_captcha.visible()
login_edit_password_id.setHint(R.string.activity_login_password)
login_edit_password_id.inputType = InputType.TYPE_TEXT_VARIATION_PASSWORD
button_login_phone_code.gone()
loginType = 1
}else {
ll_login_captcha.gone()
login_edit_password_id.setHint(R.string.login_code)
login_edit_password_id.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_VARIATION_NORMAL
button_login_phone_code.visible()
......@@ -242,6 +293,32 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
}
override fun loginMode(mode: LoginModeData?) {
if (mode != null) {
useCaptcha = mode.captchaLogin
if (useCaptcha) {
ll_login_captcha.visible()
}else {
ll_login_captcha.gone()
}
}
}
override fun showCaptcha(data: CaptchaImgData) {
hideLoadingDialog()
captcha = data
val stream = Base64ImageUtil.generateBase642Inputstream(data.image)
if (stream != null) {
image_login_captcha.setImageBitmap(BitmapFactory.decodeStream(stream))
}
}
override fun getCaptchaError(err: String) {
hideLoadingDialog()
//todo 图片验证码没有
XLog.error(err)
}
override fun loginSuccess(data: AuthenticationInfoJson) {
if (login_main_biometry.visibility == View.VISIBLE) {
playBeepSound()
......@@ -280,16 +357,72 @@ class LoginActivity: BaseMVPActivity<LoginContract.View, LoginContract.Presenter
XToast.toastShort(this, "$label 不能为空!")
return
}
showLoadingDialog()
if (BuildConfig.InnerServer) {
mPresenter.loginByPassword(credential, code)
if (useCaptcha) {
if (BuildConfig.InnerServer) {
// mPresenter.loginByPassword(credential, code)
val captchaCode = edit_login_captcha_input.text.toString()
if (TextUtils.isEmpty(captchaCode)) {
XToast.toastShort(this, "图片验证码 不能为空!")
return
}
if (captcha == null) {
XToast.toastShort(this, "图片验证码 不能为空!")
return
}
val form = LoginWithCaptchaForm()
form.credential = credential
form.password = code
form.captcha = captcha!!.id
form.captchaAnswer = captchaCode
showLoadingDialog()
mPresenter.loginWithCaptcha(form)
}else {
if (loginType == 0) {
showLoadingDialog()
mPresenter.login(credential, code)
}else {
// mPresenter.loginByPassword(credential, code)
val captchaCode = edit_login_captcha_input.text.toString()
if (TextUtils.isEmpty(captchaCode)) {
XToast.toastShort(this, "图片验证码 不能为空!")
return
}
if (captcha == null) {
XToast.toastShort(this, "图片验证码 不能为空!")
return
}
val form = LoginWithCaptchaForm()
form.credential = credential
form.password = code
form.captcha = captcha!!.id
form.captchaAnswer = captchaCode
showLoadingDialog()
mPresenter.loginWithCaptcha(form)
}
}
}else {
if (loginType == 0) {
mPresenter.login(credential, code)
if (BuildConfig.InnerServer) {
// mPresenter.loginByPassword(credential, code)
val form = LoginWithCaptchaForm()
form.credential = credential
form.password = code
showLoadingDialog()
mPresenter.loginWithCaptcha(form)
}else {
mPresenter.loginByPassword(credential, code)
if (loginType == 0) {
showLoadingDialog()
mPresenter.login(credential, code)
}else {
// mPresenter.loginByPassword(credential, code)
val form = LoginWithCaptchaForm()
form.credential = credential
form.password = code
showLoadingDialog()
mPresenter.loginWithCaptcha(form)
}
}
}
}
......
......@@ -2,6 +2,9 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.login
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenter
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BaseView
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.CaptchaImgData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.LoginModeData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.LoginWithCaptchaForm
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.AuthenticationInfoJson
/**
......@@ -13,11 +16,19 @@ object LoginContract {
fun getCodeError()
fun loginSuccess(data: AuthenticationInfoJson)
fun loginFail()
fun showCaptcha(data: CaptchaImgData)
fun getCaptchaError(err: String)
fun loginMode(mode: LoginModeData?)
}
interface Presenter: BasePresenter<View> {
/**
* 登录方式
*/
fun getLoginMode()
/**
* 获取验证码
* @param value 用户名或者手机号码
......@@ -37,6 +48,21 @@ object LoginContract {
fun ssoLogin(userId: String)
/**
* 获取图片验证码
*/
fun getCaptcha()
/**
* 图片验证码登录
*/
fun loginWithCaptcha(form: LoginWithCaptchaForm)
/**
*
*/
fun getRSAPublicKey()
}
}
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.login
import android.text.TextUtils
import net.muliba.accounting.app.ExceptionHandler
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.O2
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.LoginWithCaptchaForm
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.Base64ImageUtil
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.CryptRSA
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
import rx.android.schedulers.AndroidSchedulers
......@@ -17,8 +20,90 @@ import rx.schedulers.Schedulers
class LoginPresenter : BasePresenterImpl<LoginContract.View>(), LoginContract.Presenter {
//rsa加密公钥
private var publicKey: String = ""
override fun getLoginMode() {
getAssembleAuthenticationService(mView?.getContext())?.let { service ->
service.loginMode()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.o2Subscribe {
onNext {
mView?.loginMode(it.data)
}
onError { e, _ ->
XLog.error("", e)
mView?.loginMode(null)
}
}
}
}
override fun getCaptcha() {
getAssembleAuthenticationService(mView?.getContext())?.let { service ->
service.getCaptchaCodeImg(120, 50)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.o2Subscribe {
onNext {
if (it.data != null) {
mView?.showCaptcha(it.data)
}else {
mView?.getCaptchaError("没有获取到图片验证码")
}
}
onError { e, _ ->
XLog.error("获取图片验证码错误,", e)
mView?.getCaptchaError("没有获取到图片验证码")
}
}
}
}
override fun loginWithCaptcha(form: LoginWithCaptchaForm) {
getAssembleAuthenticationService(mView?.getContext())?.let { service ->
//加密
if (!TextUtils.isEmpty(publicKey)) {
XLog.debug("key:$publicKey")
val newPwd = CryptRSA.rsaEncryptByPublicKey(form.password, publicKey)
if (!TextUtils.isEmpty(newPwd)) {
form.password = newPwd
form.isEncrypted = "y"
XLog.debug("加密成功。。。。。$newPwd")
}
}
service.loginWithCaptchaCode(form)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(ResponseHandler { data -> mView?.loginSuccess(data) },
ExceptionHandler(mView?.getContext()) { e ->
XLog.error("", e)
mView?.loginFail()
})
}
}
override fun getRSAPublicKey() {
getAssembleAuthenticationService(mView?.getContext())?.let { service ->
service.getRSAPublicKey()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.o2Subscribe {
onNext {
if (it.data!=null) {
publicKey = it.data.publicKey
XLog.debug("public key is ok.lllll ")
}
}
onError { e, _ ->
XLog.error("public key is error ", e)
}
}
}
}
override fun getVerificationCode(value: String) {
getAssembleAuthenticationService(mView?.getContext())?.let { service ->
service.getPhoneCode(value)
......
......@@ -81,7 +81,8 @@ class IndexFragment : BaseMVPViewPagerFragment<IndexContract.View, IndexContract
ApplicationEnum.READCOMPLETED.key -> activity.go<ReadCompletedListActivity>()
ApplicationEnum.BBS.key -> activity.go<BBSMainActivity>()
ApplicationEnum.CMS.key -> activity.go<CMSIndexActivity>()
ApplicationEnum.YUNPAN.key -> activity.go<CloudDriveActivity>()
ApplicationEnum.YUNPAN.key -> activity.go<CloudDiskActivity>()
// ApplicationEnum.YUNPAN.key -> activity.go<CloudDriveActivity>()
ApplicationEnum.clouddisk.key -> activity.go<CloudDiskActivity>()
ApplicationEnum.MEETING.key -> activity.go<MeetingMainActivity>()
ApplicationEnum.ATTENDANCE.key -> activity.go<AttendanceMainActivity>()
......
......@@ -114,7 +114,10 @@ class IndexPortalFragment : BaseMVPViewPagerFragment<IndexPortalContract.View, I
}
override fun lazyLoad() {
//页面显示的时候调用一个js方法 这个方法可以用来刷新数据之类的
web_view_portal_content.evaluateJavascript("window.o2Reload()") { value ->
XLog.info("执行o2Reload , result: $value")
}
}
override fun loadCmsCategoryListByAppId(categoryList: List<CMSCategoryInfoJson>) {
......
......@@ -35,10 +35,10 @@ class MyAppPresenter : BasePresenterImpl<MyAppContract.View>(), MyAppContract.Pr
obj.appTitle = it.name
result.add(obj)
}
val newCloudDiskApp = MyAppListObject()
newCloudDiskApp.appId = ApplicationEnum.clouddisk.key
newCloudDiskApp.appTitle = ApplicationEnum.clouddisk.appName
result.add(newCloudDiskApp)
// val newCloudDiskApp = MyAppListObject()
// newCloudDiskApp.appId = ApplicationEnum.clouddisk.key
// newCloudDiskApp.appTitle = ApplicationEnum.clouddisk.appName
// result.add(newCloudDiskApp)
service.findAllPortalList()
}
?.flatMap { list ->
......@@ -66,10 +66,10 @@ class MyAppPresenter : BasePresenterImpl<MyAppContract.View>(), MyAppContract.Pr
obj.appTitle = it.name
result.add(obj)
}
val newCloudDiskApp = MyAppListObject()
newCloudDiskApp.appId = ApplicationEnum.clouddisk.key
newCloudDiskApp.appTitle = ApplicationEnum.clouddisk.appName
result.add(newCloudDiskApp)
// val newCloudDiskApp = MyAppListObject()
// newCloudDiskApp.appId = ApplicationEnum.clouddisk.key
// newCloudDiskApp.appTitle = ApplicationEnum.clouddisk.appName
// result.add(newCloudDiskApp)
portalList.filter { portal -> portal.enable }.map {
val obj = MyAppListObject()
obj.appId = it.id
......
......@@ -23,6 +23,7 @@ class AccountSecurityActivity : BaseMVPActivity<AccountSecurityContract.View, Ac
override var mPresenter: AccountSecurityContract.Presenter = AccountSecurityPresenter()
override fun layoutResId(): Int = R.layout.activity_account_security
override fun afterSetContentView(savedInstanceState: Bundle?) {
mPresenter.getRSAPublicKey()
setupToolBar(getString(R.string.title_activity_account_security), true)
account_name_id.text = O2SDKManager.instance().cName
......
......@@ -15,5 +15,10 @@ object AccountSecurityContract {
interface Presenter : BasePresenter<View> {
fun logout(deviceId:String)
fun updateMyPassword(old: String, newPwd: String, newPwdConfirm: String)
/**
*
*/
fun getRSAPublicKey()
}
}
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.o2.security
import android.text.TextUtils
import net.muliba.accounting.app.ExceptionHandler
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.app.base.BasePresenterImpl
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.ResponseHandler
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.exception.O2ResponseException
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.IdData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person.PersonPwdForm
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.CryptRSA
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.XLog
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils.extension.o2Subscribe
import rx.android.schedulers.AndroidSchedulers
......@@ -13,6 +15,28 @@ import rx.schedulers.Schedulers
class AccountSecurityPresenter : BasePresenterImpl<AccountSecurityContract.View>(), AccountSecurityContract.Presenter {
//rsa加密公钥
private var publicKey: String = ""
override fun getRSAPublicKey() {
getAssembleAuthenticationService(mView?.getContext())?.let { service ->
service.getRSAPublicKey()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.o2Subscribe {
onNext {
if (it.data!=null) {
publicKey = it.data.publicKey
XLog.debug("public key is ok.lllll ")
}
}
onError { e, _ ->
XLog.error("public key is error ", e)
}
}
}
}
override fun logout(deviceId: String) {
getAssembleAuthenticationService(mView?.getContext())?.let { service->
......@@ -29,6 +53,24 @@ class AccountSecurityPresenter : BasePresenterImpl<AccountSecurityContract.View>
val service = getAssemblePersonalApi(mView?.getContext())
if (service != null) {
val form = PersonPwdForm(old, newPwd, newPwdConfirm)
if (!TextUtils.isEmpty(publicKey)) {
XLog.debug("key:$publicKey")
val newOld = CryptRSA.rsaEncryptByPublicKey(old, publicKey)
if (!TextUtils.isEmpty(newOld)) {
form.oldPassword = newOld
}
val newNewPwd = CryptRSA.rsaEncryptByPublicKey(newPwd, publicKey)
if (!TextUtils.isEmpty(newNewPwd)) {
form.newPassword = newNewPwd
}
val newNewPwdConfirm = CryptRSA.rsaEncryptByPublicKey(newPwdConfirm, publicKey)
if (!TextUtils.isEmpty(newNewPwdConfirm)) {
form.confirmPassword = newNewPwdConfirm
}
form.isEncrypted = "y"
}
service.modifyCurrentPersonPassword(form)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
......
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Description: Activity反劫持检测工具
* author: zs
* Date: 2018/7/8 16:31
*/
public class AntiHijackingUtil {
public static final String TAG = "AntiHijackingUtil";
/**
* 检测当前Activity是否安全
*/
public static boolean checkActivity(Context context) {
PackageManager pm = context.getPackageManager();
// 查询所有已经安装的应用程序
List<ApplicationInfo> listAppcations =
pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
Collections.sort(listAppcations, new ApplicationInfo.DisplayNameComparator(pm));// 排序
List<String> safePackages = new ArrayList<>();
for (ApplicationInfo app : listAppcations) {// 这个排序必须有.
if ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
safePackages.add(app.packageName);
}
}
// 得到所有的系统程序包名放进白名单里面.
ActivityManager activityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
String runningActivityPackageName;
int sdkVersion;
try {
sdkVersion = Integer.valueOf(android.os.Build.VERSION.SDK);
} catch (NumberFormatException e) {
sdkVersion = 0;
}
if (sdkVersion >= 21) {// 获取系统api版本号,如果是5x系统就用这个方法获取当前运行的包名
runningActivityPackageName = getCurrentPkgName(context);
} else {
runningActivityPackageName =
activityManager.getRunningTasks(1).get(0).topActivity.getPackageName();
}
// 如果是4x及以下,用这个方法.
if (runningActivityPackageName != null) {
// 有些情况下在5x的手机中可能获取不到当前运行的包名,所以要非空判断。
if (runningActivityPackageName.equals(context.getPackageName())) {
return true;
}
// 白名单比对
for (String safePack : safePackages) {
if (safePack.equals(runningActivityPackageName)) {
return true;
}
}
}
return false;
}
private static String getCurrentPkgName(Context context) {
// 5x系统以后利用反射获取当前栈顶activity的包名.
ActivityManager.RunningAppProcessInfo currentInfo = null;
Field field = null;
int START_TASK_TO_FRONT = 2;
String pkgName = null;
try {
// 通过反射获取进程状态字段.
field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
} catch (Exception e) {
e.printStackTrace();
}
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List appList = am.getRunningAppProcesses();
ActivityManager.RunningAppProcessInfo app;
for (int i = 0; i < appList.size(); i++) {
//ActivityManager.RunningAppProcessInfo app : appList
app = (ActivityManager.RunningAppProcessInfo) appList.get(i);
//表示前台运行进程.
if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
Integer state = null;
try {
state = field.getInt(app);// 反射调用字段值的方法,获取该进程的状态.
} catch (Exception e) {
e.printStackTrace();
}
// 根据这个判断条件从前台中获取当前切换的进程对象
if (state != null && state == START_TASK_TO_FRONT) {
currentInfo = app;
break;
}
}
}
if (currentInfo != null) {
pkgName = currentInfo.processName;
}
return pkgName;
}
/**
* 判断当前是否在桌面
*
* @param context 上下文
*/
public static boolean isHome(Context context) {
ActivityManager mActivityManager =
(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
return getHomes(context).contains(rti.get(0).topActivity.getPackageName());
}
/**
* 获得属于桌面的应用的应用包名称
*
* @return 返回包含所有包名的字符串列表
*/
private static List<String> getHomes(Context context) {
List<String> names = new ArrayList<String>();
PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_HOME);
List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo ri : resolveInfo) {
names.add(ri.activityInfo.packageName);
}
return names;
}
/**
* 判断当前是否在锁屏再解锁状态
*
* @param context 上下文
*/
public static boolean isReflectScreen(Context context) {
KeyguardManager mKeyguardManager =
(KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
return mKeyguardManager.inKeyguardRestrictedInputMode();
}
}
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils;
/**
* Created by fancyLou on 2020-09-28.
* Copyright © 2020 O2. All rights reserved.
*/
import android.util.Log;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
public class CheckRoot {
private static String LOG_TAG = CheckRoot.class.getName();
public static boolean isDeviceRooted() {
if (checkDeviceDebuggable()) {
return true;
}//check buildTags
if (checkSuperuserApk()) {
return true;
}//Superuser.apk
//if (checkRootPathSU()){return true;}//find su in some path
//if (checkRootWhichSU()){return true;}//find su use 'which'
if (checkBusybox()) {
return true;
}//find su use 'which'
if (checkAccessRootData()) {
return true;
}//find su use 'which'
if (checkGetRootAuth()) {
return true;
}//exec su
return false;
}
public static boolean checkDeviceDebuggable() {
String buildTags = android.os.Build.TAGS;
if (buildTags != null && buildTags.contains("test-keys")) {
Log.i(LOG_TAG, "buildTags=" + buildTags);
return true;
}
return false;
}
public static boolean checkSuperuserApk() {
try {
File file = new File("/system/app/Superuser.apk");
if (file.exists()) {
Log.i(LOG_TAG, "/system/app/Superuser.apk exist");
return true;
}
} catch (Exception e) {
}
return false;
}
public static synchronized boolean checkGetRootAuth() {
Process process = null;
DataOutputStream os = null;
try {
Log.i(LOG_TAG, "to exec su");
process = Runtime.getRuntime().exec("su");
os = new DataOutputStream(process.getOutputStream());
os.writeBytes("exit\n");
os.flush();
int exitValue = process.waitFor();
Log.i(LOG_TAG, "exitValue=" + exitValue);
if (exitValue == 0) {
return true;
} else {
return false;
}
} catch (Exception e) {
Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage());
return false;
} finally {
try {
if (os != null) {
os.close();
}
process.destroy();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static synchronized boolean checkBusybox() {
try {
Log.i(LOG_TAG, "to exec busybox df");
String[] strCmd = new String[]{"busybox", "df"};
ArrayList<String> execResult = executeCommand(strCmd);
if (execResult != null) {
Log.i(LOG_TAG, "execResult=" + execResult.toString());
return true;
} else {
Log.i(LOG_TAG, "execResult=null");
return false;
}
} catch (Exception e) {
Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage());
return false;
}
}
public static ArrayList<String> executeCommand(String[] shellCmd) {
String line = null;
ArrayList<String> fullResponse = new ArrayList<String>();
Process localProcess = null;
try {
Log.i(LOG_TAG, "to shell exec which for find su :");
localProcess = Runtime.getRuntime().exec(shellCmd);
} catch (Exception e) {
return null;
}
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(localProcess.getOutputStream()));
BufferedReader in = new BufferedReader(new InputStreamReader(localProcess.getInputStream()));
try {
while ((line = in.readLine()) != null) {
Log.i(LOG_TAG, "–> Line received: " + line);
fullResponse.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}
Log.i(LOG_TAG, "–> Full response was: " + fullResponse);
return fullResponse;
}
public static synchronized boolean checkAccessRootData() {
try {
Log.i(LOG_TAG, "to write /data");
String fileContent = "test_ok";
Boolean writeFlag = writeFile("/data/su_test", fileContent);
if (writeFlag) {
Log.i(LOG_TAG, "write ok");
} else {
Log.i(LOG_TAG, "write failed");
}
Log.i(LOG_TAG, "to read /data");
String strRead = readFile("/data/su_test");
Log.i(LOG_TAG, "strRead=" + strRead);
if (fileContent.equals(strRead)) {
return true;
} else {
return false;
}
} catch (Exception e) {
Log.i(LOG_TAG, "Unexpected error - Here is what I know: "
+ e.getMessage());
return false;
}
}
//写文件
public static Boolean writeFile(String fileName, String message) {
try {
FileOutputStream fout = new FileOutputStream(fileName);
byte[] bytes = message.getBytes();
fout.write(bytes);
fout.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
//读文件
public static String readFile(String fileName) {
File file = new File(fileName);
try {
FileInputStream fis = new FileInputStream(file);
byte[] bytes = new byte[1024];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int len;
while ((len = fis.read(bytes)) > 0) {
bos.write(bytes, 0, len);
}
String result = new String(bos.toByteArray());
Log.i(LOG_TAG, result);
return result;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
\ No newline at end of file
......@@ -153,6 +153,47 @@
android:background="@color/z_color_input_line_blur"
/>
<LinearLayout
android:id="@+id/ll_login_captcha"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent"
android:orientation="vertical">
<net.zoneland.x.bpm.mobile.v1.zoneXBPM.widgets.ClearTextEdit
android:id="@+id/edit_login_captcha_input"
android:layout_width="match_parent"
android:layout_height="49dp"
android:layout_gravity="center_vertical"
android:background="@null"
android:hint="@string/login_captcha"
android:maxLines="1"
android:inputType="number"
android:textColor="@color/z_color_text_primary"
android:textColorHint="@color/z_color_text_hint"
android:textSize="13sp" />
<View
android:id="@+id/view_login_captcha_input_bottom"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/z_color_input_line_blur"
/>
</LinearLayout>
<ImageView
android:id="@+id/image_login_captcha"
android:layout_width="120dp"
android:layout_height="50dp"
android:scaleType="fitCenter"
/>
</LinearLayout>
<Button
android:id="@+id/btn_login_submit"
android:layout_width="match_parent"
......
......@@ -2,6 +2,7 @@
<string name="app_name">O2OA</string>
<string name="app_about">关于O2OA</string>
<string name="app_name_about">O2OA</string>
<string name="activity_safe_warning">已切换至后台运行!</string>
<string name="copy_right">&#169;</string>
<string name="reserved">All right reserved.</string>
<string name="version">版本:</string>
......@@ -13,6 +14,8 @@
<string name="bind_phone_choose_company">选择单位</string>
<string name="app_error">应用异常</string>
<string name="launch_start">准备中&#8230;</string>
<string name="launch_network_is_not_connected">网络未连接,请检查网络连接情况!</string>
<string name="launch_check_bind">正在连接O2云&#8230;</string>
......@@ -112,6 +115,7 @@
<string name="activity_login_username">用户名或手机号码</string>
<string name="login_phone">输入手机号码</string>
<string name="login_code">验证码</string>
<string name="login_captcha">图片验证码</string>
<string name="activity_login_password">密码</string>
<string name="activity_login_submit">登 录</string>
<string name="activity_login_other">其他登录方式</string>
......
......@@ -23,6 +23,6 @@ android.enableJetifier=true
# o2
o2.versionName=5.1.9
o2.versionCode=119
o2.versionName=5.2.0
o2.versionCode=120
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.core.component.api.service
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.ApiResponse
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.IdData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.ValueData
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.*
import net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.AuthenticationInfoJson
import retrofit2.http.*
import rx.Observable
......@@ -15,6 +13,13 @@ import rx.Observable
interface OrgAssembleAuthenticationService{
/**
* 登录状态
*/
@GET("jaxrs/authentication/mode")
fun loginMode(): Observable<ApiResponse<LoginModeData>>
/**
* 登陆
* @param json
......@@ -62,6 +67,21 @@ interface OrgAssembleAuthenticationService{
fun getPhoneCode(@Path("credential") credential: String): Observable<ApiResponse<ValueData>>
/**
* 获取图片验证码
*/
@GET("jaxrs/authentication/captcha/width/{width}/height/{height}")
fun getCaptchaCodeImg(@Path("width") width: Int, @Path("height") height: Int): Observable<ApiResponse<CaptchaImgData>>
/**
* 用图片验证码登录
*/
@Headers("Content-Type:application/json;charset=UTF-8")
@POST("jaxrs/authentication/captcha")
fun loginWithCaptchaCode(@Body form:LoginWithCaptchaForm): Observable<ApiResponse<AuthenticationInfoJson>>
/**
* 扫一扫 确认登录
* @param meta
......@@ -72,6 +92,13 @@ interface OrgAssembleAuthenticationService{
@POST("jaxrs/authentication/bind/meta/{meta}")
fun scanConfirmWebLogin(@Path("meta") meta: String): Observable<ApiResponse<AuthenticationInfoJson>>
/**
* 获取RSA 加密公钥
*/
@GET("jaxrs/authentication/captchaRSAPublicKey")
fun getRSAPublicKey(): Observable<ApiResponse<RSAPublicKeyData>>
/**
* 登出
* @return
......
......@@ -51,6 +51,6 @@ interface OrgAssemblePersonalService {
/**
* 更新当前用户密码
*/
@PUT("jaxrs/password")
@PUT("jaxrs/person/password")
fun modifyCurrentPersonPassword(@Body body: PersonPwdForm): Observable<ApiResponse<ValueData>>
}
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api
/**
* Created by fancyLou on 2020-09-27.
* Copyright © 2020 O2. All rights reserved.
*/
data class CaptchaImgData (
var id: String = "",
var image: String = "" //验证码图片的base64
)
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api
/**
* Created by fancyLou on 2020-09-27.
* Copyright © 2020 O2. All rights reserved.
*/
data class LoginModeData (
var captchaLogin: Boolean = false, //验证码登录
var codeLogin: Boolean = false, //短信验证码登录
var bindLogin: Boolean = false,
var faceLogin: Boolean = false
)
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api
/**
* Created by fancyLou on 2020-09-27.
* Copyright © 2020 O2. All rights reserved.
*/
data class LoginWithCaptchaForm (
var credential: String = "",
var password: String = "",
var captcha: String ="", //图片认证编号id
var captchaAnswer: String = "",//图片认证码
var isEncrypted: String = "" //是否启用加密
)
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api
/**
* Created by fancyLou on 2020-09-27.
* Copyright © 2020 O2. All rights reserved.
*/
data class RSAPublicKeyData (
var publicKey: String = "" //公钥
)
\ No newline at end of file
......@@ -9,5 +9,6 @@ package net.zoneland.x.bpm.mobile.v1.zoneXBPM.model.bo.api.main.person
class PersonPwdForm(
var oldPassword: String = "",
var newPassword: String = "",
var confirmPassword: String = ""
var confirmPassword: String = "",
var isEncrypted: String = ""
)
\ No newline at end of file
package net.zoneland.x.bpm.mobile.v1.zoneXBPM.utils;
import android.util.Base64;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;
/**
* Created by fancyLou on 2020-09-27.
* Copyright © 2020 O2. All rights reserved.
*/
public class CryptRSA {
/**RSA算法*/
private static final String RSA = "RSA";
private static KeyFactory keyFactory;
static {
//实例化密钥工厂
try {
keyFactory = KeyFactory.getInstance(RSA);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
/**
* 公钥加密
*
* @param content 待加密数据
* @param publicKey 密钥
* @return string 加密后数据
*/
public static String rsaEncryptByPublicKey(String content, String publicKey) throws Exception {
byte[] keyBytes = Base64.decode(publicKey, Base64.NO_WRAP);
//初始化公钥
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//数据加密
//PKCS1Padding
Cipher cipher = Cipher.getInstance("RSA/None/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return new String(Base64.encode(cipher.doFinal(content.trim().getBytes("utf-8")), Base64.NO_WRAP));
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册