feat(scripting): permissions

- Java Interfaces binding
This commit is contained in:
rhunk
2023-12-25 12:10:29 +01:00
parent 3edd6ed723
commit 37becec350
5 changed files with 96 additions and 8 deletions

View File

@ -4,11 +4,13 @@ import android.os.Handler
import android.widget.Toast
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
import me.rhunk.snapenhance.common.scripting.bindings.BindingsContext
import me.rhunk.snapenhance.common.scripting.impl.JavaInterfaces
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
import me.rhunk.snapenhance.common.scripting.ktx.putFunction
import me.rhunk.snapenhance.common.scripting.ktx.scriptable
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
import me.rhunk.snapenhance.common.scripting.type.ModuleInfo
import me.rhunk.snapenhance.common.scripting.type.Permissions
import org.mozilla.javascript.Function
import org.mozilla.javascript.NativeJavaObject
import org.mozilla.javascript.ScriptableObject
@ -49,6 +51,10 @@ class JSModule(
})
})
registerBindings(
JavaInterfaces(),
)
moduleObject.putFunction("setField") { args ->
val obj = args?.get(0) as? NativeJavaObject ?: return@putFunction Undefined.instance
val name = args[1].toString()
@ -74,8 +80,12 @@ class JSModule(
moduleObject.putFunction("findClass") {
val className = it?.get(0).toString()
val useModClassLoader = it?.getOrNull(1) as? Boolean ?: false
if (useModClassLoader) moduleInfo.ensurePermissionGranted(Permissions.UNSAFE_CLASSLOADER)
runCatching {
classLoader.loadClass(className)
if (useModClassLoader) this::class.java.classLoader?.loadClass(className)
else classLoader.loadClass(className)
}.onFailure { throwable ->
scriptRuntime.logger.error("Failed to load class $className", throwable)
}.getOrNull()
@ -83,7 +93,12 @@ class JSModule(
moduleObject.putFunction("type") { args ->
val className = args?.get(0).toString()
val clazz = runCatching { classLoader.loadClass(className) }.getOrNull() ?: return@putFunction Undefined.instance
val useModClassLoader = args?.getOrNull(1) as? Boolean ?: false
if (useModClassLoader) moduleInfo.ensurePermissionGranted(Permissions.UNSAFE_CLASSLOADER)
val clazz = runCatching {
if (useModClassLoader) this::class.java.classLoader?.loadClass(className) else classLoader.loadClass(className)
}.getOrNull() ?: return@putFunction Undefined.instance
scriptableObject("JavaClassWrapper") {
putFunction("newInstance") newInstance@{ args ->
@ -116,7 +131,7 @@ class JSModule(
}
moduleObject.putFunction("logError") { args ->
scriptRuntime.logger.error(argsToString(arrayOf(args?.get(0))), args?.get(1) as? Throwable ?: Throwable())
scriptRuntime.logger.error(argsToString(arrayOf(args?.get(0))), args?.getOrNull(1) as? Throwable ?: Throwable())
Undefined.instance
}
@ -179,8 +194,8 @@ class JSModule(
contextScope {
name.split(".").also { split ->
val function = split.dropLast(1).fold(moduleObject) { obj, key ->
obj.get(key, obj) as? ScriptableObject ?: return@contextScope
}.get(split.last(), moduleObject) as? Function ?: return@contextScope
obj.get(key, obj) as? ScriptableObject ?: return@contextScope Unit
}.get(split.last(), moduleObject) as? Function ?: return@contextScope Unit
runCatching {
function.call(this, moduleObject, moduleObject, args)

View File

@ -0,0 +1,45 @@
package me.rhunk.snapenhance.common.scripting.impl
import me.rhunk.snapenhance.common.scripting.bindings.AbstractBinding
import me.rhunk.snapenhance.common.scripting.bindings.BindingSide
import me.rhunk.snapenhance.common.scripting.ktx.contextScope
import me.rhunk.snapenhance.common.scripting.ktx.putFunction
import me.rhunk.snapenhance.common.scripting.ktx.scriptableObject
import java.lang.reflect.Proxy
class JavaInterfaces : AbstractBinding("java-interfaces", BindingSide.COMMON) {
override fun getObject() = scriptableObject {
putFunction("runnable") {
val function = it?.get(0) as? org.mozilla.javascript.Function ?: return@putFunction null
Runnable {
contextScope {
function.call(
this,
this@scriptableObject,
this@scriptableObject,
emptyArray()
)
}
}
}
putFunction("newProxy") { arguments ->
val javaInterface = arguments?.get(0) as? Class<*> ?: return@putFunction null
val function = arguments[1] as? org.mozilla.javascript.Function ?: return@putFunction null
Proxy.newProxyInstance(
javaInterface.classLoader,
arrayOf(javaInterface)
) { instance, method, args ->
contextScope {
function.call(
this,
this@scriptableObject,
this@scriptableObject,
arrayOf(instance, method.name, (args ?: emptyArray<Any>()).toList())
)
}
}
}
}
}

View File

@ -4,12 +4,17 @@ import org.mozilla.javascript.Context
import org.mozilla.javascript.Function
import org.mozilla.javascript.Scriptable
import org.mozilla.javascript.ScriptableObject
import org.mozilla.javascript.Wrapper
fun contextScope(f: Context.() -> Unit) {
fun contextScope(f: Context.() -> Any?): Any? {
val context = Context.enter()
context.optimizationLevel = -1
try {
context.f()
return context.f().let {
if (it is Wrapper) {
it.unwrap()
} else it
}
} finally {
Context.exit()
}

View File

@ -9,4 +9,20 @@ data class ModuleInfo(
val minSnapchatVersion: Long? = null,
val minSEVersion: Long? = null,
val grantedPermissions: List<String>,
)
) {
override fun equals(other: Any?): Boolean {
if (other !is ModuleInfo) return false
if (other === this) return true
return name == other.name &&
version == other.version &&
displayName == other.displayName &&
description == other.description &&
author == other.author
}
fun ensurePermissionGranted(permission: Permissions) {
if (!grantedPermissions.contains(permission.key)) {
throw AssertionError("Permission $permission is not granted")
}
}
}

View File

@ -0,0 +1,7 @@
package me.rhunk.snapenhance.common.scripting.type
enum class Permissions(
val key: String,
) {
UNSAFE_CLASSLOADER("unsafe-classloader"),
}