From dcfdf7fabd24d4a8bb0b07e5d84b8761297bf94b Mon Sep 17 00:00:00 2001 From: boatbomber Date: Tue, 4 Jul 2023 19:21:32 -0700 Subject: [PATCH] Prevent misuse of RequestAccess --- plugin/src/App/init.lua | 10 +++++++++- plugin/src/HeadlessAPI.lua | 25 ++++++++++++++++++++----- 2 files changed, 29 insertions(+), 6 deletions(-) diff --git a/plugin/src/App/init.lua b/plugin/src/App/init.lua index 4e5e939d..6cb47d75 100644 --- a/plugin/src/App/init.lua +++ b/plugin/src/App/init.lua @@ -314,9 +314,16 @@ function App:releaseSyncLock() Log.trace("Could not release sync lock because it is owned by {}", lock.Value) end -function App:requestPermission(source: string, name: string, apis: {string}, initialState: {[string]: boolean?}): {[string]: boolean} +function App:requestPermission(plugin: Plugin, source: string, name: string, apis: {string}, initialState: {[string]: boolean?}): {[string]: boolean} local responseEvent = Instance.new("BindableEvent") + Log.info("The third-party plugin '{}' is requesting permission to use the API!", name) + + local unloadProtection = plugin.Unloading:Connect(function() + Log.warn("Cancelling API permission request for '{}' because the third-party plugin has been removed.", name) + responseEvent:Fire(initialState) + end) + self:setState(function(state) state.popups[source .. " Permissions"] = { name = name, @@ -338,6 +345,7 @@ function App:requestPermission(source: string, name: string, apis: {string}, ini local response = responseEvent.Event:Wait() responseEvent:Destroy() + unloadProtection:Disconnect() self:setState(function(state) state.popups[source .. " Permissions"] = nil diff --git a/plugin/src/HeadlessAPI.lua b/plugin/src/HeadlessAPI.lua index 83e735ea..1221be7e 100644 --- a/plugin/src/HeadlessAPI.lua +++ b/plugin/src/HeadlessAPI.lua @@ -23,7 +23,9 @@ function API.new(app) local Rojo = {} Rojo._rateLimit = {} + Rojo._sourceToPlugin = {} Rojo._permissions = Settings:get("apiPermissions") or {} + Rojo._activePermissionRequests = {} Rojo._changedEvent = Instance.new("BindableEvent") Rojo._apiDescriptions = {} @@ -55,7 +57,7 @@ function API.new(app) local traceback = string.split(debug.traceback(), "\n") local topLevel = traceback[#traceback - 1] - local localPlugin = string.match(topLevel, "(user_.-)%.") + local localPlugin = string.match(topLevel, "user_.-%.%w+") if localPlugin then return localPlugin end @@ -188,15 +190,26 @@ function API.new(app) end Rojo._apiDescriptions.RequestAccess = "Used to gain access to Rojo API members" - function Rojo:RequestAccess(apis: {string}): {[string]: boolean} - assert(type(apis) == "table", "Rojo:RequestAccess expects an array of valid API names") + function Rojo:RequestAccess(plugin: Plugin, apis: {string}): {[string]: boolean} + assert(type(apis) == "table", "Rojo:RequestAccess expects an array of valid API names as the second argument") + assert(typeof(plugin) == "Instance" and plugin:IsA("Plugin"), "Rojo:RequestAccess expects a Plugin as the first argument") + + local source, name = Rojo:_getCallerSource(), Rojo:_getCallerName() + Rojo._sourceToPlugin[source] = plugin if Rojo:_checkRateLimit("RequestAccess") then -- Because this opens a popup, we dont want to let users get spammed by it return {} end - local source, name = Rojo:_getCallerSource(), Rojo:_getCallerName() + if Rojo._activePermissionRequests[source] then + -- If a request is already active, exit + error( + "Rojo:RequestAccess cannot be called in multiple threads at once. Please call it once and wait for the response before calling it again.", + 2 + ) + end + Rojo._activePermissionRequests[source] = true if Rojo._permissions[source] == nil then Rojo._permissions[source] = {} @@ -226,10 +239,11 @@ function API.new(app) for _, api in sanitizedApis do response[api] = true end + Rojo._activePermissionRequests[source] = nil return response end - local response = app:requestPermission(source, name, sanitizedApis, Rojo._permissions[source]) + local response = app:requestPermission(plugin, source, name, sanitizedApis, Rojo._permissions[source]) for api, granted in response do Log.warn(string.format( @@ -240,6 +254,7 @@ function API.new(app) end Settings:set("apiPermissions", Rojo._permissions) + Rojo._activePermissionRequests[source] = nil return response end