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).
This commit is contained in:
Luca Beltrame 2022-02-12 10:19:06 +01:00
parent aea53325ad
commit c9d4e0312d
Signed by: einar
GPG key ID: 4707F46E9EC72DEC
2 changed files with 49 additions and 2 deletions

View file

@ -34,3 +34,8 @@ repo_aliases:
repository: "KDE_Unstable_Frameworks_openSUSE_Factory" repository: "KDE_Unstable_Frameworks_openSUSE_Factory"
state: "failed" state: "failed"
arch: "all" arch: "all"
acl:
admin:
- "@CHANGE_ME"
user: []

View file

@ -83,6 +83,7 @@ class Config(BaseProxyConfig):
helper.copy("username") helper.copy("username")
helper.copy("repo_aliases") helper.copy("repo_aliases")
helper.copy("acl")
class OSCBot(Plugin): class OSCBot(Plugin):
@ -114,6 +115,18 @@ class OSCBot(Plugin):
return (project, package, repository, state, arch) 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( async def parse_rebuilpac(
self, self,
project: str, project: str,
@ -222,6 +235,10 @@ class OSCBot(Plugin):
@alias.subcommand("list", help="List configured aliases") @alias.subcommand("list", help="List configured aliases")
async def list_aliases(self, evt: MessageEvent) -> None: 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) body = self.template.from_string(ALIAS_TEMPLATE)
if self.config.get("repo_aliases", None) is None: if self.config.get("repo_aliases", None) is None:
@ -246,6 +263,10 @@ class OSCBot(Plugin):
arch: Optional[str] = None, arch: Optional[str] = None,
state: Optional[str] = None) -> 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"]: if alias not in self.config["repo_aliases"]:
await evt.respond(f"Unknown alias {alias}") await evt.respond(f"Unknown alias {alias}")
return return
@ -286,6 +307,10 @@ class OSCBot(Plugin):
arch: Optional[str] = None, arch: Optional[str] = None,
state: Optional[str] = None) -> 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 repository = "all" if not repository else repository
package = "all" if not package else package package = "all" if not package else package
arch = "all" if not arch else arch arch = "all" if not arch else arch
@ -304,6 +329,11 @@ class OSCBot(Plugin):
@alias.subcommand("delete", help="Delete an alias", aliases=("rm", )) @alias.subcommand("delete", help="Delete an alias", aliases=("rm", ))
@command.argument("alias", "alias name") @command.argument("alias", "alias name")
async def delete_alias(self, evt: MessageEvent, alias: str): 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"]: if alias not in self.config["repo_aliases"]:
await evt.respond(f"Unknown alias {alias}") await evt.respond(f"Unknown alias {alias}")
return return
@ -325,6 +355,10 @@ class OSCBot(Plugin):
repository: Optional[str] = None, repository: Optional[str] = None,
arch: Optional[str] = None) -> 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"]: if project in self.config["repo_aliases"]:
# We're not interested in state and we query package explicitly # We're not interested in state and we query package explicitly
project, _, repository, _, arch = self.get_alias(project) project, _, repository, _, arch = self.get_alias(project)
@ -353,6 +387,10 @@ class OSCBot(Plugin):
project: str, project: str,
package: str) -> None: 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"] token = self.config["trigger_token"]
trigger_url = f"{self.config['api_url']}/trigger/runservice" trigger_url = f"{self.config['api_url']}/trigger/runservice"
params = {"project": project, "package": package} params = {"project": project, "package": package}
@ -382,6 +420,10 @@ class OSCBot(Plugin):
repository: Optional[str] = None, repository: Optional[str] = None,
arch: Optional[str] = None) -> 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"]: if project in self.config["repo_aliases"]:
project, package, repository, state, arch = self.get_alias(project) project, package, repository, state, arch = self.get_alias(project)
else: else:
@ -428,5 +470,5 @@ class OSCBot(Plugin):
message = body.render(packages=packagelist, base_url=base_url, message = body.render(packages=packagelist, base_url=base_url,
project=project, repo=repository) project=project, repo=repository)
await evt.respond(message, markdown=True) await evt.respond(message, markdown=True)
# Wait 100 milliseconds to avoid triggering rate limiting # Wait 200 milliseconds to avoid triggering rate limiting
sleep(0.1) sleep(0.2)