From 641011760defb30994d83d5400799c1b4e6ab0c8 Mon Sep 17 00:00:00 2001 From: Tomas Kopecek Date: Apr 08 2020 11:00:15 +0000 Subject: PR#2054: editSideTag API call Merges #2054 https://pagure.io/koji/pull-request/2054 Fixes: #1998 https://pagure.io/koji/issue/1998 un/block packages in sidetags --- diff --git a/docs/source/plugins.rst b/docs/source/plugins.rst index 28bbf7c..c1e9d09 100644 --- a/docs/source/plugins.rst +++ b/docs/source/plugins.rst @@ -93,6 +93,14 @@ Example for `/etc/koji-hub/hub.conf`: # forbid everything else all :: deny + package_list = + # allow blocking for owners in their sidetags + match action block && is_sidetag_owner :: allow + all :: deny + +There are two special policy tests `is_sidetag` and `is_sidetag_owner` with +expectable behaviour. + Now Sidetag Koji plugin should be installed. To verify that, run `koji list-api` command -- it should now display `createSideTag` as one of available API calls. diff --git a/plugins/cli/sidetag_cli.py b/plugins/cli/sidetag_cli.py index cc55157..f44ac40 100644 --- a/plugins/cli/sidetag_cli.py +++ b/plugins/cli/sidetag_cli.py @@ -10,7 +10,7 @@ from argparse import ArgumentParser import koji from koji.plugin import export_cli from koji_cli.commands import anon_handle_wait_repo -from koji_cli.lib import _, activate_session +from koji_cli.lib import _, activate_session, arg_filter @export_cli @@ -93,3 +93,43 @@ def handle_list_sidetags(options, session, args): for tag in session.listSideTags(basetag=opts.basetag, user=user): print(tag["name"]) + + +@export_cli +def handle_edit_sidetag(options, session, args): + "Edit sidetag" + usage = _("usage: %(prog)s edit-sidetag [options]") + usage += _("\n(Specify the --help global option for a list of other help options)") + parser = ArgumentParser(usage=usage) + parser.add_argument("sidetag", help="name of sidetag") + parser.add_argument("--debuginfo", action="store_true", default=None, + help=_("Generate debuginfo repository")) + parser.add_argument("--no-debuginfo", action="store_false", dest="debuginfo") + parser.add_argument("--rpm-macro", action="append", default=[], metavar="key=value", + dest="rpm_macros", help=_("Set tag-specific rpm macros")) + parser.add_argument("--remove-rpm-macro", action="append", default=[], metavar="key", + dest="remove_rpm_macros", help=_("Remove rpm macros")) + + opts = parser.parse_args(args) + + if opts.debuginfo is None and not opts.rpm_macros and not opts.remove_rpm_macros: + parser.error("At least one option needs to be specified") + + activate_session(session, options) + + kwargs = {} + if opts.debuginfo is not None: + kwargs['debuginfo'] = opts.debuginfo + + if opts.rpm_macros: + rpm_macros = {} + for xopt in opts.rpm_macros: + key, value = xopt.split('=', 1) + value = arg_filter(value) + rpm_macros[key] = value + kwargs['rpm_macros'] = rpm_macros + + if opts.remove_rpm_macros: + kwargs['remove_rpm_macros'] = opts.remove_rpm_macros + + session.editSideTag(opts.sidetag, **kwargs) diff --git a/plugins/hub/sidetag_hub.py b/plugins/hub/sidetag_hub.py index 6b29e1b..13cf878 100644 --- a/plugins/hub/sidetag_hub.py +++ b/plugins/hub/sidetag_hub.py @@ -4,6 +4,7 @@ import sys import koji +import koji.policy from koji.context import context from koji.plugin import callback, export sys.path.insert(0, "/usr/share/koji-hub/") @@ -13,17 +14,56 @@ from kojihub import ( # noqa: F402 _create_tag, _delete_build_target, _delete_tag, + _edit_tag, assert_policy, get_build_target, + readInheritanceData, get_tag, get_user, - nextval + nextval, + policy_get_user ) CONFIG_FILE = "/etc/koji-hub/plugins/sidetag.conf" CONFIG = None +def is_sidetag(taginfo, raise_error=False): + """Check, that given tag is sidetag""" + result = bool(taginfo['extra'].get('sidetag')) + if not result and raise_error: + raise koji.GenericError("Not a sidetag: %(name)s" % taginfo) + + +def is_sidetag_owner(taginfo, user, raise_error=False): + """Check, that given user is owner of the sidetag""" + result = (taginfo['extra'].get('sidetag') and + taginfo['extra'].get('sidetag_user_id') == user['id']) + if not result and raise_error: + raise koji.ActionNotAllowed("This is not your sidetag") + + +# Policy tests +class SidetagTest(koji.policy.MatchTest): + """Checks, if tag is a sidetag""" + name = 'is_sidetag' + + def run(self, data): + tag = get_tag(data['tag']) + return is_sidetag(tag) + + +class SidetagOwnerTest(koji.policy.MatchTest): + """Checks, if user is a real owner of sidetag""" + name = 'is_sidetag_owner' + + def run(self, data): + user = policy_get_user(data) + tag = get_tag(data['tag']) + return is_sidetag_owner(tag, user) + + +# API calls @export def createSideTag(basetag, debuginfo=False): """Create a side tag. @@ -94,11 +134,9 @@ def removeSideTag(sidetag): sidetag = get_tag(sidetag, strict=True) # sanity/access - if not sidetag["extra"].get("sidetag"): - raise koji.GenericError("Not a sidetag: %(name)s" % sidetag) - if sidetag["extra"].get("sidetag_user_id") != user["id"]: - if not context.session.hasPerm("admin"): - raise koji.ActionNotAllowed("This is not your sidetag") + is_sidetag(sidetag, raise_error=True) + is_sidetag_owner(sidetag, user, raise_error=True) + _remove_sidetag(sidetag) @@ -170,6 +208,54 @@ def listSideTags(basetag=None, user=None, queryOpts=None): return query.execute() +@export +def editSideTag(sidetag, debuginfo=None, rpm_macros=None, remove_rpm_macros=None): + """Restricted ability to modify sidetags, parent tag must have: + sidetag_debuginfo_allowed: 1 + sidetag_rpm_macros_allowed: 1 + in extra, if modifying functions should work. For blocking/unblocking + further policy must be compatible with these operations. + + :param sidetag: sidetag id or name + :type sidetag: int or str + :param debuginfo: set or disable debuginfo repo generation + :type debuginfo: bool + :param rpm_macros: add/update rpms macros in extra + :type rpm_macros: dict + :param remove_rpm_macros: remove rpm macros from extra + :type remove_rpm_macros: list of str + """ + + context.session.assertLogin() + user = get_user(context.session.user_id, strict=True) + sidetag = get_tag(sidetag, strict=True) + + # sanity/access + is_sidetag(sidetag, raise_error=True) + is_sidetag_owner(sidetag, user, raise_error=True) + + parent_id = readInheritanceData(sidetag['id'])[0]['parent_id'] + parent = get_tag(parent_id) + + if debuginfo is not None and not parent['extra'].get('sidetag_debuginfo_allowed'): + raise koji.GenericError("Debuginfo setting is not allowed in parent tag.") + + if (rpm_macros is not None or remove_rpm_macros is not None) \ + and not parent['extra'].get('sidetag_rpm_macros_allowed'): + raise koji.GenericError("RPM macros change is not allowed in parent tag.") + + kwargs = {'extra': {}} + if debuginfo is not None: + kwargs['extra']['with_debuginfo'] = bool(debuginfo) + if rpm_macros is not None: + for macro, value in rpm_macros.items(): + kwargs['extra']['rpm.macro.%s' % macro] = value + if remove_rpm_macros is not None: + kwargs['remove_extra'] = ['rpm.macro.%s' % m for m in remove_rpm_macros] + + _edit_tag(sidetag['id'], **kwargs) + + def handle_sidetag_untag(cbtype, *args, **kws): """Remove a side tag when its last build is untagged @@ -184,8 +270,7 @@ def handle_sidetag_untag(cbtype, *args, **kws): if not tag: # also shouldn't happen, but just in case return - if not tag["extra"].get("sidetag"): - # not a side tag + if not is_sidetag(tag): return # is the tag now empty? query = QueryProcessor(