Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ dependencies {
implementation "com.facebook.react:react-native:$react_native_version"
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.4.1"
implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:18.0.2"
implementation "com.aheaditec.talsec.security:TalsecSecurity-Community-ReactNative:18.0.4"
}

if (isNewArchitectureEnabled()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.aheaditec.talsec_security.security.api.Talsec
import com.aheaditec.talsec_security.security.api.TalsecConfig
import com.aheaditec.talsec_security.security.api.TalsecMode
import com.aheaditec.talsec_security.security.api.ThreatListener
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.LifecycleEventListener
import com.facebook.react.bridge.Promise
Expand All @@ -19,8 +18,8 @@ import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.UiThreadUtil.runOnUiThread
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.modules.core.DeviceEventManagerModule
import com.freeraspreactnative.events.BaseRaspEvent
import com.freeraspreactnative.events.RaspExecutionStateEvent
import com.freeraspreactnative.events.ThreatEvent
import com.freeraspreactnative.interfaces.PluginExecutionStateListener
Expand All @@ -38,15 +37,24 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex

private val lifecycleListener = object : LifecycleEventListener {
override fun onHostResume() {
PluginThreatHandler.threatDispatcher.onResume()
PluginThreatHandler.executionStateDispatcher.onResume()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
reactContext.currentActivity?.let { ScreenProtector.register(it) }
}
}

override fun onHostPause() {
PluginThreatHandler.threatDispatcher.onPause()
PluginThreatHandler.executionStateDispatcher.onPause()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
reactContext.currentActivity?.let { ScreenProtector.unregister(it) }
}
if (reactContext.currentActivity?.isFinishing == true) {
PluginThreatHandler.threatDispatcher.unregisterListener()
PluginThreatHandler.executionStateDispatcher.unregisterListener()
PluginThreatHandler.unregisterSDKListener(reactContext)
}
}

override fun onHostDestroy() {
Expand All @@ -61,16 +69,21 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
init {
reactContext.addLifecycleEventListener(lifecycleListener)
initializeEventKeys()
PluginThreatHandler.initializeDispatchers(PluginListener(reactContext))
}

@ReactMethod
fun talsecStart(
options: ReadableMap, promise: Promise
) {
if (talsecStarted) {
promise.resolve("freeRASP started")
return
}

try {
val config = buildTalsecConfig(options)
PluginThreatHandler.registerListener(reactContext)
PluginThreatHandler.registerSDKListener(reactContext)
runOnUiThread {
Talsec.start(reactContext, config, TalsecMode.BACKGROUND)
mainHandler.post {
Expand Down Expand Up @@ -149,10 +162,10 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
@ReactMethod
fun addListener(eventName: String) {
if (eventName == ThreatEvent.CHANNEL_NAME) {
PluginThreatHandler.threatDispatcher.listener = PluginListener(reactContext)
PluginThreatHandler.threatDispatcher.registerListener()
}
if (eventName == RaspExecutionStateEvent.CHANNEL_NAME) {
PluginThreatHandler.executionStateDispatcher.listener = PluginListener(reactContext)
PluginThreatHandler.executionStateDispatcher.registerListener()
}
}

Expand All @@ -165,10 +178,10 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
@ReactMethod
fun removeListenerForEvent(eventName: String, promise: Promise) {
if (eventName == ThreatEvent.CHANNEL_NAME) {
PluginThreatHandler.threatDispatcher.listener = null
PluginThreatHandler.threatDispatcher.unregisterListener()
}
if (eventName == RaspExecutionStateEvent.CHANNEL_NAME) {
PluginThreatHandler.executionStateDispatcher.listener = null
PluginThreatHandler.executionStateDispatcher.unregisterListener()
}
promise.resolve("Listener unregistered")
}
Expand Down Expand Up @@ -302,48 +315,40 @@ class FreeraspReactNativeModule(private val reactContext: ReactApplicationContex
private val mainHandler = Handler(Looper.getMainLooper())

internal var talsecStarted = false
}

internal class PluginListener(private val reactContext: ReactApplicationContext) :
PluginThreatListener, PluginExecutionStateListener {

private fun notifyEvent(event: BaseRaspEvent, appReactContext: ReactApplicationContext) {
override fun threatDetected(threatEventType: ThreatEvent) {
val params = Arguments.createMap()
params.putInt(event.channelKey, event.value)
appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(event.channelName, params)
params.putInt(threatEventType.channelKey, threatEventType.value)
notifyEvent(ThreatEvent.CHANNEL_NAME, params)
}

/**
* Sends malware detected event to React Native
*/
private fun notifyMalware(suspiciousApps: MutableList<SuspiciousAppInfo>, appReactContext: ReactApplicationContext) {
// Perform the malware encoding on a background thread
override fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>) {
backgroundHandler.post {

val encodedSuspiciousApps = suspiciousApps.toEncodedWritableArray(appReactContext)

val encodedSuspiciousApps = suspiciousApps.toEncodedWritableArray(reactContext)
mainHandler.post {
val params = Arguments.createMap()
params.putInt(ThreatEvent.CHANNEL_KEY, ThreatEvent.Malware.value)
params.putArray(
ThreatEvent.MALWARE_CHANNEL_KEY, encodedSuspiciousApps
)

appReactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(ThreatEvent.CHANNEL_NAME, params)
notifyEvent(ThreatEvent.CHANNEL_NAME, params)
}
}
}
}

internal class PluginListener(private val reactContext: ReactApplicationContext) : PluginThreatListener, PluginExecutionStateListener {
override fun threatDetected(threatEventType: ThreatEvent) {
notifyEvent(threatEventType, reactContext)
}

override fun malwareDetected(suspiciousApps: MutableList<SuspiciousAppInfo>) {
notifyMalware(suspiciousApps, reactContext)
override fun raspExecutionStateChanged(event: RaspExecutionStateEvent) {
val params = Arguments.createMap()
params.putInt(event.channelKey, event.value)
notifyEvent(event.channelName, params)
}

override fun raspExecutionStateChanged(event: RaspExecutionStateEvent) {
notifyEvent(event, reactContext)
private fun notifyEvent(eventName: String, params: WritableMap) {
reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
.emit(eventName, params)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ import com.freeraspreactnative.events.ThreatEvent

internal object PluginThreatHandler {

internal val threatDispatcher = ThreatDispatcher()
internal val executionStateDispatcher = ExecutionStateDispatcher()
internal lateinit var threatDispatcher: ThreatDispatcher
internal lateinit var executionStateDispatcher: ExecutionStateDispatcher

fun initializeDispatchers(listener: FreeraspReactNativeModule.PluginListener) {
threatDispatcher = ThreatDispatcher(listener)
executionStateDispatcher = ExecutionStateDispatcher(listener)
}

private val threatDetected = object : ThreatListener.ThreatDetected() {

Expand Down Expand Up @@ -111,11 +116,11 @@ internal object PluginThreatHandler {

private val internalListener = ThreatListener(threatDetected, deviceState, raspExecutionState)

internal fun registerListener(context: Context) {
internal fun registerSDKListener(context: Context) {
internalListener.registerListener(context)
}

internal fun unregisterListener(context: Context) {
internal fun unregisterSDKListener(context: Context) {
internalListener.unregisterListener(context)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,36 +3,52 @@ package com.freeraspreactnative.dispatchers
import com.freeraspreactnative.events.RaspExecutionStateEvent
import com.freeraspreactnative.interfaces.PluginExecutionStateListener

internal class ExecutionStateDispatcher {
internal class ExecutionStateDispatcher(private val listener: PluginExecutionStateListener) {
private val cache = mutableSetOf<RaspExecutionStateEvent>()

var listener: PluginExecutionStateListener? = null
set(value) {
field = value
if (value != null) {
flushCache(value)
}
private var isAppInForeground = false
private var isListenerRegistered = false

fun registerListener() {
isListenerRegistered = true
isAppInForeground = true
flushCache()
}

fun unregisterListener() {
isListenerRegistered = false
isAppInForeground = false
}

fun onResume() {
isAppInForeground = true
if (isListenerRegistered) {
flushCache()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please remove this empty line.

}

fun onPause() {
isAppInForeground = false
}

fun dispatch(event: RaspExecutionStateEvent) {
val currentListener = listener
if (currentListener != null) {
currentListener.raspExecutionStateChanged(event)
if (isAppInForeground && isListenerRegistered) {
listener.raspExecutionStateChanged(event)
} else {
synchronized(cache) {
val checkedListener = listener
checkedListener?.raspExecutionStateChanged(event) ?: cache.add(event)
cache.add(event)
}
}
}

private fun flushCache(registeredListener: PluginExecutionStateListener) {
private fun flushCache() {
val events = synchronized(cache) {
val snapshot = cache.toSet()
cache.clear()
snapshot
}
events.forEach { registeredListener.raspExecutionStateChanged(it) }
events.forEach { listener.raspExecutionStateChanged(it) }
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -4,57 +4,71 @@ import com.aheaditec.talsec_security.security.api.SuspiciousAppInfo
import com.freeraspreactnative.events.ThreatEvent
import com.freeraspreactnative.interfaces.PluginThreatListener

internal class ThreatDispatcher {
internal class ThreatDispatcher(private val listener: PluginThreatListener) {
private val threatCache = mutableSetOf<ThreatEvent>()
private val malwareCache = mutableSetOf<SuspiciousAppInfo>()

var listener: PluginThreatListener? = null
set(value) {
field = value
if (value != null) {
flushCache(value)
}
private var isAppInForeground = false
private var isListenerRegistered = false

fun registerListener() {
isListenerRegistered = true
isAppInForeground = true
flushCache()
}

fun unregisterListener() {
isListenerRegistered = false
isAppInForeground = false
}

fun onResume() {
isAppInForeground = true
if (isListenerRegistered) {
flushCache()
}
}

fun onPause() {
isAppInForeground = false
}

fun dispatchThreat(event: ThreatEvent) {
val currentListener = listener
if (currentListener != null) {
currentListener.threatDetected(event)
if (isAppInForeground && isListenerRegistered) {
listener.threatDetected(event)
} else {
synchronized(threatCache) {
val checkedListener = listener
checkedListener?.threatDetected(event) ?: threatCache.add(event)
threatCache.add(event)
}
}
}

fun dispatchMalware(apps: MutableList<SuspiciousAppInfo>) {
val currentListener = listener
if (currentListener != null) {
currentListener.malwareDetected(apps)
} else {
if (isAppInForeground && isListenerRegistered) {
listener.malwareDetected(apps)
}
else {
synchronized(malwareCache) {
val checkedListener = listener
checkedListener?.malwareDetected(apps) ?: malwareCache.addAll(apps)
malwareCache.addAll(apps)
}
}
}

private fun flushCache(registeredListener: PluginThreatListener) {
private fun flushCache() {
val threats = synchronized(threatCache) {
val snapshot = threatCache.toSet()
threatCache.clear()
snapshot
}
threats.forEach { registeredListener.threatDetected(it) }
threats.forEach { listener.threatDetected(it) }

val malware = synchronized(malwareCache) {
val snapshot = malwareCache.toMutableList()
malwareCache.clear()
snapshot
}
if (malware.isNotEmpty()) {
registeredListener.malwareDetected(malware)
listener.malwareDetected(malware)
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -221,7 +221,7 @@ const App = () => {
)
);
},
// Android only
// Android & iOS
timeSpoofing: () => {
setAppChecks((currentState) =>
currentState.map((threat) =>
Expand Down
2 changes: 1 addition & 1 deletion example/src/checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const commonChecks = [
{ name: 'Unofficial Store', status: 'ok' },
{ name: 'Screenshot', status: 'ok' },
{ name: 'Screen Recording', status: 'ok' },
{ name: 'Time Spoofing', status: 'ok' },
];

export const iosChecks = [{ name: 'Device ID', status: 'ok' }];
Expand All @@ -21,7 +22,6 @@ export const androidChecks = [
{ name: 'Malware', status: 'ok' },
{ name: 'ADB Enabled', status: 'ok' },
{ name: 'Multi Instance', status: 'ok' },
{ name: 'Time Spoofing', status: 'ok' },
{ name: 'Location Spoofing', status: 'ok' },
{ name: 'Unsecure Wifi', status: 'ok' },
{ name: 'Automation', status: 'ok' },
Expand Down
Binary file modified ios/TalsecRuntime.xcframework/_CodeSignature/CodeDirectory
Binary file not shown.
Binary file not shown.
Loading
Loading