Skip to content

Commit 8f41eb9

Browse files
authored
Merge commit from fork
* fix incorrect permissions on plugin directories * chown plugin dirs too * fix the stupid * cleanup useless comments
1 parent 670ae7d commit 8f41eb9

File tree

12 files changed

+94
-64
lines changed

12 files changed

+94
-64
lines changed

backend/decky_loader/browser.py

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,10 @@
1818
from typing import Dict, List, TypedDict
1919

2020
# Local modules
21-
from .localplatform.localplatform import chown, chmod
21+
from .localplatform.localplatform import chown, chmod, get_chown_plugin_path
2222
from .loader import Loader, Plugins
2323
from .helpers import get_ssl_context, download_remote_binary_to_path
24+
from .enums import UserType
2425
from .settings import SettingsManager
2526

2627
logger = getLogger("Browser")
@@ -60,13 +61,6 @@ def _unzip_to_plugin_dir(self, zip: BytesIO, name: str, hash: str):
6061
return False
6162
zip_file = ZipFile(zip)
6263
zip_file.extractall(self.plugin_path)
63-
plugin_folder = self.find_plugin_folder(name)
64-
assert plugin_folder is not None
65-
plugin_dir = path.join(self.plugin_path, plugin_folder)
66-
67-
if not chown(plugin_dir) or not chmod(plugin_dir, 555):
68-
logger.error(f"chown/chmod exited with a non-zero exit code")
69-
return False
7064
return True
7165

7266
async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: str):
@@ -101,8 +95,6 @@ async def _download_remote_binaries_for_plugin_with_name(self, pluginBasePath: s
10195
rv = False
10296
raise Exception(f"Error Downloading Remote Binary {binName}@{binURL} with hash {binHash} to {path.join(pluginBinPath, binName)}")
10397

104-
chown(self.plugin_path)
105-
chmod(pluginBasePath, 555)
10698
else:
10799
rv = True
108100
logger.info(f"No Remote Binaries to Download")
@@ -124,6 +116,25 @@ def find_plugin_folder(self, name: str) -> str | None:
124116
return folder
125117
except:
126118
logger.debug(f"skipping {folder}")
119+
120+
def set_plugin_dir_permissions(self, plugin_dir: str) -> bool:
121+
plugin_json_path = path.join(plugin_dir, 'plugin.json')
122+
logger.debug(f"Checking plugin.json at {plugin_json_path}")
123+
124+
root_plugin = False
125+
126+
if access(plugin_json_path, R_OK):
127+
with open(plugin_json_path, "r", encoding="utf-8") as f:
128+
plugin_json = json.load(f)
129+
if "flags" in plugin_json and "root" in plugin_json["flags"]:
130+
root_plugin = True
131+
132+
logger.debug("root_plugin %d, dir %s", root_plugin, plugin_dir)
133+
if get_chown_plugin_path():
134+
return chown(plugin_dir, UserType.EFFECTIVE_USER if root_plugin else UserType.HOST_USER, True) and chown(plugin_dir, UserType.EFFECTIVE_USER, False) and chmod(plugin_dir, 755) and chown(plugin_json_path, UserType.EFFECTIVE_USER, False) and chmod(plugin_json_path, 755)
135+
else:
136+
logger.debug("chown disabled by environment")
137+
return True
127138

128139
async def uninstall_plugin(self, name: str):
129140
if self.loader.watcher:
@@ -266,6 +277,7 @@ async def _install(self, artifact: str, name: str, version: str, hash: str):
266277
plugin_dir = path.join(self.plugin_path, plugin_folder)
267278
await self.loader.ws.emit("loader/plugin_download_info", 95, "Store.download_progress_info.download_remote")
268279
ret = await self._download_remote_binaries_for_plugin_with_name(plugin_dir)
280+
chown_ret = self.set_plugin_dir_permissions(plugin_dir)
269281
if ret:
270282
logger.info(f"Installed {name} (Version: {version})")
271283
if name in self.loader.plugins:
@@ -278,6 +290,9 @@ async def _install(self, artifact: str, name: str, version: str, hash: str):
278290
self.settings.setSetting("pluginOrder", current_plugin_order)
279291
logger.debug("Plugin %s was added to the pluginOrder setting", name)
280292
await self.loader.import_plugin(path.join(plugin_dir, "main.py"), plugin_folder)
293+
elif not chown_ret:
294+
logger.error("Could not chown plugin")
295+
return
281296
else:
282297
logger.error("Could not download remote binaries")
283298
return

backend/decky_loader/enums.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from enum import IntEnum
22

33
class UserType(IntEnum):
4-
HOST_USER = 1
5-
EFFECTIVE_USER = 2
6-
ROOT = 3
4+
HOST_USER = 1 # usually deck
5+
EFFECTIVE_USER = 2 # usually root
76

87
class PluginLoadType(IntEnum):
98
LEGACY_EVAL_IIFE = 0 # legacy, uses legacy serverAPI

backend/decky_loader/helpers.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ def get_user_group_id() -> int:
181181

182182
# Get the default home path unless a user is specified
183183
def get_home_path(username: str | None = None) -> str:
184-
return localplatform.get_home_path(UserType.ROOT if username == "root" else UserType.HOST_USER)
184+
# TODO hardcoded root is kinda a hack
185+
return localplatform.get_home_path(UserType.EFFECTIVE_USER if username == "root" else UserType.HOST_USER)
185186

186187
async def is_systemd_unit_active(unit_name: str) -> bool:
187188
return await localplatform.service_active(unit_name)

backend/decky_loader/localplatform/localplatformlinux.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,6 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
5959
user_str = _get_user()+":"+_get_user_group()
6060
elif user == UserType.EFFECTIVE_USER:
6161
user_str = _get_effective_user()+":"+_get_effective_user_group()
62-
elif user == UserType.ROOT:
63-
user_str = "root:root"
6462
else:
6563
raise Exception("Unknown User Type")
6664

@@ -87,7 +85,7 @@ def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
8785

8886
return True
8987

90-
def folder_owner(path : str) -> UserType|None:
88+
def file_owner(path : str) -> UserType|None:
9189
user_owner = _get_user_owner(path)
9290

9391
if (user_owner == _get_user()):
@@ -106,13 +104,14 @@ def get_home_path(user : UserType = UserType.HOST_USER) -> str:
106104
user_name = _get_user()
107105
elif user == UserType.EFFECTIVE_USER:
108106
user_name = _get_effective_user()
109-
elif user == UserType.ROOT:
110-
pass
111107
else:
112108
raise Exception("Unknown User Type")
113109

114110
return pwd.getpwnam(user_name).pw_dir
115111

112+
def get_effective_username() -> str:
113+
return _get_effective_user()
114+
116115
def get_username() -> str:
117116
return _get_user()
118117

@@ -121,8 +120,8 @@ def setgid(user : UserType = UserType.HOST_USER):
121120

122121
if user == UserType.HOST_USER:
123122
user_id = _get_user_group_id()
124-
elif user == UserType.ROOT:
125-
pass
123+
elif user == UserType.EFFECTIVE_USER:
124+
pass # we already are
126125
else:
127126
raise Exception("Unknown user type")
128127

@@ -133,8 +132,8 @@ def setuid(user : UserType = UserType.HOST_USER):
133132

134133
if user == UserType.HOST_USER:
135134
user_id = _get_user_id()
136-
elif user == UserType.ROOT:
137-
pass
135+
elif user == UserType.EFFECTIVE_USER:
136+
pass # we already are
138137
else:
139138
raise Exception("Unknown user type")
140139

backend/decky_loader/localplatform/localplatformwin.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ def chown(path : str, user : UserType = UserType.HOST_USER, recursive : bool =
77
def chmod(path : str, permissions : int, recursive : bool = True) -> bool:
88
return True # Stubbed
99

10-
def folder_owner(path : str) -> UserType|None:
10+
def file_owner(path : str) -> UserType|None:
1111
return UserType.HOST_USER # Stubbed
1212

1313
def get_home_path(user : UserType = UserType.HOST_USER) -> str:
@@ -34,6 +34,9 @@ async def service_restart(service_name : str, block : bool = True) -> bool:
3434

3535
return True # Stubbed
3636

37+
def get_effective_username() -> str:
38+
return os.getlogin()
39+
3740
def get_username() -> str:
3841
return os.getlogin()
3942

backend/decky_loader/main.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def chown_plugin_dir():
5050
if not path.exists(plugin_path): # For safety, create the folder before attempting to do anything with it
5151
mkdir_as_user(plugin_path)
5252

53-
if not chown(plugin_path, UserType.HOST_USER) or not chmod(plugin_path, 555):
53+
if not chown(plugin_path, UserType.EFFECTIVE_USER, False) or not chmod(plugin_path, 755, False):
5454
logger.error(f"chown/chmod exited with a non-zero exit code")
5555

5656
if get_chown_plugin_path() == True:

backend/decky_loader/plugin/plugin.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88

99
from .sandboxed_plugin import SandboxedPlugin
1010
from .messages import MethodCallRequest, SocketMessageType
11-
from ..enums import PluginLoadType
11+
from ..enums import PluginLoadType, UserType
12+
from ..localplatform.localplatform import file_owner, chown, chmod, get_chown_plugin_path
1213
from ..localplatform.localsocket import LocalSocket
1314
from ..helpers import get_homebrew_path, mkdir_as_user
1415

@@ -26,9 +27,12 @@ def __init__(self, file: str, plugin_directory: str, plugin_path: str, emit_call
2627

2728
self.load_type = PluginLoadType.LEGACY_EVAL_IIFE.value
2829

29-
json = load(open(path.join(plugin_path, plugin_directory, "plugin.json"), "r", encoding="utf-8"))
30-
if path.isfile(path.join(plugin_path, plugin_directory, "package.json")):
31-
package_json = load(open(path.join(plugin_path, plugin_directory, "package.json"), "r", encoding="utf-8"))
30+
plugin_dir_path = path.join(plugin_path, plugin_directory)
31+
plugin_json_path = path.join(plugin_dir_path, "plugin.json")
32+
33+
json = load(open(plugin_json_path, "r", encoding="utf-8"))
34+
if path.isfile(path.join(plugin_dir_path, "package.json")):
35+
package_json = load(open(path.join(plugin_dir_path, "package.json"), "r", encoding="utf-8"))
3236
self.version = package_json["version"]
3337
if ("type" in package_json and package_json["type"] == "module"):
3438
self.load_type = PluginLoadType.ESMODULE_V1.value
@@ -42,6 +46,17 @@ def __init__(self, file: str, plugin_directory: str, plugin_path: str, emit_call
4246

4347
self.log = getLogger("plugin")
4448

49+
if get_chown_plugin_path():
50+
# ensure plugin folder ownership
51+
if file_owner(plugin_dir_path) != UserType.EFFECTIVE_USER:
52+
chown(plugin_dir_path, UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER, True)
53+
chown(plugin_dir_path, UserType.EFFECTIVE_USER, False)
54+
chmod(plugin_dir_path, 755, True)
55+
# fix plugin.json permissions
56+
if file_owner(plugin_json_path) != UserType.EFFECTIVE_USER:
57+
chown(plugin_json_path, UserType.EFFECTIVE_USER, False)
58+
chmod(plugin_json_path, 755, False)
59+
4560
self.sandboxed_plugin = SandboxedPlugin(self.name, self.passive, self.flags, self.file, self.plugin_directory, self.plugin_path, self.version, self.author, self.api_version)
4661
self.proc: Process | None = None
4762
self._socket = LocalSocket()

backend/decky_loader/plugin/sandboxed_plugin.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from ..localplatform.localsocket import LocalSocket
1414
from ..localplatform.localplatform import setgid, setuid, get_username, get_home_path, ON_LINUX
1515
from ..enums import UserType
16-
from .. import helpers, settings, injector # pyright: ignore [reportUnusedImport]
16+
from .. import helpers
1717

1818
from typing import List, TypeVar, Any
1919

@@ -61,10 +61,10 @@ def initialize(self, socket: LocalSocket):
6161
if self.passive:
6262
return
6363

64-
setgid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
65-
setuid(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
64+
setgid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
65+
setuid(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
6666
# export a bunch of environment variables to help plugin developers
67-
environ["HOME"] = get_home_path(UserType.ROOT if "root" in self.flags else UserType.HOST_USER)
67+
environ["HOME"] = get_home_path(UserType.EFFECTIVE_USER if "root" in self.flags else UserType.HOST_USER)
6868
environ["USER"] = "root" if "root" in self.flags else get_username()
6969
environ["DECKY_VERSION"] = helpers.get_loader_version()
7070
environ["DECKY_USER"] = get_username()

backend/decky_loader/settings.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from json import dump, load
22
from os import mkdir, path, listdir, rename
33
from typing import Any, Dict
4-
from .localplatform.localplatform import chown, folder_owner, get_chown_plugin_path
4+
from .localplatform.localplatform import chown, file_owner, get_chown_plugin_path
55
from .enums import UserType
66

77
from .helpers import get_homebrew_path
@@ -28,8 +28,8 @@ def __init__(self, name: str, settings_directory: str | None = None) -> None:
2828

2929

3030
#If the owner of the settings directory is not the user, then set it as the user:
31-
expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.ROOT
32-
if folder_owner(settings_directory) != expected_user:
31+
expected_user = UserType.HOST_USER if get_chown_plugin_path() else UserType.EFFECTIVE_USER
32+
if file_owner(settings_directory) != expected_user:
3333
chown(settings_directory, expected_user, False)
3434

3535
self.settings: Dict[str, Any] = {}

backend/decky_loader/wsrouter.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from enum import IntEnum
99

10-
from typing import Callable, Coroutine, Dict, Any, cast, TypeVar
10+
from typing import Callable, Coroutine, Dict, Any, cast
1111

1212
from traceback import format_exc
1313

@@ -29,8 +29,6 @@ class WSMessageExtra(WSMessage):
2929

3030
# see wsrouter.ts for typings
3131

32-
DataType = TypeVar("DataType")
33-
3432
Route = Callable[..., Coroutine[Any, Any, Any]]
3533

3634
class WSRouter:

0 commit comments

Comments
 (0)