From c9d4e0312d4149a5d4de3cbadae3b9bb8826dc3e Mon Sep 17 00:00:00 2001 From: Luca Beltrame Date: Sat, 12 Feb 2022 10:19:06 +0100 Subject: [PATCH] Support for ACLs Users in the "admin" level can set aliases, rebuild packages, and trigger services. On the other hand, users in the "user" level can only perform read-only operations (reading status at this point). --- base-config.yaml | 5 +++++ oscbot/__init__.py | 46 ++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/base-config.yaml b/base-config.yaml index 96c03f6..cf823be 100644 --- a/base-config.yaml +++ b/base-config.yaml @@ -34,3 +34,8 @@ repo_aliases: repository: "KDE_Unstable_Frameworks_openSUSE_Factory" state: "failed" arch: "all" +acl: + admin: + - "@CHANGE_ME" + user: [] + diff --git a/oscbot/__init__.py b/oscbot/__init__.py index 02ff417..73735b5 100644 --- a/oscbot/__init__.py +++ b/oscbot/__init__.py @@ -83,6 +83,7 @@ class Config(BaseProxyConfig): helper.copy("username") helper.copy("repo_aliases") + helper.copy("acl") class OSCBot(Plugin): @@ -114,6 +115,18 @@ class OSCBot(Plugin): return (project, package, repository, state, arch) + def check_acl(self, user_id: str, need_admin: bool = False) -> bool: + + acls = self.config["acl"] + if not need_admin: + if user_id in acls["admin"] or acls["user"]: + return True + else: + if user_id in acls["admin"]: + return True + self.log.debug(f"Denied operation as {user_id}") + return False + async def parse_rebuilpac( self, project: str, @@ -222,6 +235,10 @@ class OSCBot(Plugin): @alias.subcommand("list", help="List configured aliases") async def list_aliases(self, evt: MessageEvent) -> None: + if not self.check_acl(evt.sender, need_admin=False): + await evt.reply("You are not authorized to perform this action.") + return + body = self.template.from_string(ALIAS_TEMPLATE) if self.config.get("repo_aliases", None) is None: @@ -246,6 +263,10 @@ class OSCBot(Plugin): arch: Optional[str] = None, state: Optional[str] = None) -> None: + if not self.check_acl(evt.sender, need_admin=True): + await evt.reply("You are not authorized to perform this action.") + return + if alias not in self.config["repo_aliases"]: await evt.respond(f"Unknown alias {alias}") return @@ -286,6 +307,10 @@ class OSCBot(Plugin): arch: Optional[str] = None, state: Optional[str] = None) -> None: + if not self.check_acl(evt.sender, need_admin=True): + await evt.reply("You are not authorized to perform this action.") + return + repository = "all" if not repository else repository package = "all" if not package else package arch = "all" if not arch else arch @@ -304,6 +329,11 @@ class OSCBot(Plugin): @alias.subcommand("delete", help="Delete an alias", aliases=("rm", )) @command.argument("alias", "alias name") async def delete_alias(self, evt: MessageEvent, alias: str): + + if not self.check_acl(evt.sender, need_admin=True): + await evt.reply("You are not authorized to perform this action.") + return + if alias not in self.config["repo_aliases"]: await evt.respond(f"Unknown alias {alias}") return @@ -325,6 +355,10 @@ class OSCBot(Plugin): repository: Optional[str] = None, arch: Optional[str] = None) -> None: + if not self.check_acl(evt.sender, need_admin=True): + await evt.reply("You are not authorized to perform this action.") + return + if project in self.config["repo_aliases"]: # We're not interested in state and we query package explicitly project, _, repository, _, arch = self.get_alias(project) @@ -353,6 +387,10 @@ class OSCBot(Plugin): project: str, package: str) -> None: + if not self.check_acl(evt.sender, need_admin=True): + await evt.reply("You are not authorized to perform this action.") + return + token = self.config["trigger_token"] trigger_url = f"{self.config['api_url']}/trigger/runservice" params = {"project": project, "package": package} @@ -382,6 +420,10 @@ class OSCBot(Plugin): repository: Optional[str] = None, arch: Optional[str] = None) -> None: + if not self.check_acl(evt.sender, need_admin=False): + await evt.reply("You are not authorized to perform this action.") + return + if project in self.config["repo_aliases"]: project, package, repository, state, arch = self.get_alias(project) else: @@ -428,5 +470,5 @@ class OSCBot(Plugin): message = body.render(packages=packagelist, base_url=base_url, project=project, repo=repository) await evt.respond(message, markdown=True) - # Wait 100 milliseconds to avoid triggering rate limiting - sleep(0.1) + # Wait 200 milliseconds to avoid triggering rate limiting + sleep(0.2)