From c5e8a94d84abf1b80cb2355e2943dbc848478545 Mon Sep 17 00:00:00 2001 From: Martin Hradil Date: Thu, 18 Aug 2022 01:33:09 +0200 Subject: [PATCH] feature/rbac-roles - add role commands; namespaces.addgroup - use object_roles, not object_permissions (#39) * gitignore vim swapfiles * namespaces.addgroup - use object_roles, not object_permissions no object_permissions since https://github.com/ansible/galaxy_ng/pull/1057 * galaxykit groups & roles - add roles, move perms to roles ```diff +galaxykit group role * +galaxykit role * -galaxykit group perm * +galaxykit role perm * ``` * remove client.set_permissions (not sure if unused, or if we just need to change it to call roles.set_permissions instead of groups.set_permissions) * fix regex typo * galaxykit role create: add -p alias for --permissions --- .gitignore | 1 + galaxykit/client.py | 6 --- galaxykit/command.py | 115 ++++++++++++++++++++++++++++++++-------- galaxykit/groups.py | 67 +++++++++++------------ galaxykit/namespaces.py | 2 +- galaxykit/roles.py | 93 ++++++++++++++++++++++++++++++++ 6 files changed, 222 insertions(+), 62 deletions(-) create mode 100644 galaxykit/roles.py diff --git a/.gitignore b/.gitignore index d69f342..a42b41f 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,4 @@ venv.bak/ # Editors .vscode/ .idea/ +.*.sw[po] diff --git a/galaxykit/client.py b/galaxykit/client.py index 4f3c643..b935e93 100644 --- a/galaxykit/client.py +++ b/galaxykit/client.py @@ -277,12 +277,6 @@ def delete_group(self, group_name): """ return groups.delete_group(self, group_name) - def set_permissions(self, group_name, permissions): - """ - Assigns the given permissions to the group - """ - return groups.set_permissions(self, group_name, permissions) - def get_container_readme(self, container): return containers.get_readme(self, container) diff --git a/galaxykit/command.py b/galaxykit/command.py index 0d9e419..b332f03 100644 --- a/galaxykit/command.py +++ b/galaxykit/command.py @@ -5,13 +5,14 @@ from .client import GalaxyClient from .utils import GalaxyClientError -from . import containers from . import collections +from . import container_images +from . import containers from . import groups from . import namespaces -from . import users -from . import container_images from . import registries +from . import roles +from . import users from . import __version__ as VERSION EXIT_OK = 0 @@ -248,31 +249,73 @@ def report_error(resp): "create": { "args": { "name": {}, - } + }, }, "delete": { "args": { "name": {}, - } + }, }, - "perm": { + "role": { "subops": { "list": { "args": { "groupname": {}, - } + }, }, "add": { "args": { "groupname": {}, - "perm": {}, - } + "rolename": {}, + }, }, "remove": { "args": { "groupname": {}, + "rolename": {}, + }, + }, + }, + }, + }, + }, + "role": { + "help": "RBAC Role", + "ops": { + "list": {"args": None}, + "create": { + "args": { + "name": {}, + "description": {}, + "--permissions": { + "help": "Comma-separated list of permissions", + }, + "-p": {"dest": "permissions"}, + }, + }, + "delete": { + "args": { + "name": {}, + }, + }, + "perm": { + "subops": { + "list": { + "args": { + "rolename": {}, + }, + }, + "add": { + "args": { + "rolename": {}, "perm": {}, - } + }, + }, + "remove": { + "args": { + "rolename": {}, + "perm": {}, + }, }, }, }, @@ -534,22 +577,50 @@ def main(): if not args.ignore: print(e) sys.exit(EXIT_NOT_FOUND) + + elif args.operation == "role": + if args.subop == "list": + resp = groups.get_roles(client, args.groupname) + print(format_list(resp["results"], "role")) + elif args.subop == "add": + resp = groups.add_role(client, args.groupname, args.rolename) + elif args.subop == "remove": + resp = groups.remove_role(client, args.groupname, args.rolename) + + elif args.kind == "role": + if args.operation == "list": + resp = roles.get_role_list(client) + print(format_list(resp["results"], "name")) + elif args.operation == "create": + permissions = args.permissions.split(",") if args.permissions else [] + try: + resp = roles.create_role( + client, args.name, args.description, permissions + ) + except ValueError as e: + if not args.ignore: + print(e) + sys.exit(EXIT_NOT_FOUND) + elif args.operation == "delete": + try: + resp = roles.delete_role(client, args.name) + except ValueError as e: + if not args.ignore: + print(e) + sys.exit(EXIT_NOT_FOUND) + elif args.operation == "perm": if args.subop == "list": - groupname = args.groupname - resp = groups.get_permissions(client, groupname) - print(format_list(resp["data"], "permission")) + resp = roles.get_permissions(client, args.rolename) + print(resp) elif args.subop == "add": - groupname, perm = args.groupname, args.perm - perms = [ - p["permission"] - for p in groups.get_permissions(client, groupname)["data"] - ] - perms = list(set(perms) | set([perm])) - resp = groups.set_permissions(client, groupname, perms) + resp = roles.set_permissions( + client, args.rolename, add_permissions=[args.perm] + ) elif args.subop == "remove": - groupname, perm = args.groupname, args.perm - resp = groups.delete_permission(client, groupname, perm) + resp = roles.set_permissions( + client, args.rolename, remove_permissions=[args.perm] + ) elif args.kind == "namespace": if args.operation == "get": diff --git a/galaxykit/groups.py b/galaxykit/groups.py index 20ef543..a7425af 100644 --- a/galaxykit/groups.py +++ b/galaxykit/groups.py @@ -1,6 +1,4 @@ -import requests -import json -from pprint import pprint +from . import roles def get_group(client, group_name): @@ -38,46 +36,49 @@ def delete_group(client, group_name): return client.delete(delete_url, parse_json=False) -def get_permissions(client, group_name): - group_id = get_group(client, group_name)["id"] - permissions_url = f"_ui/v1/groups/{group_id}/model-permissions/" - return client.get(permissions_url) +def get_roles(client, group_name): + group_id = get_group_id(client, group_name) + roles_url = f"pulp/api/v3/groups/{group_id}/roles/?content_object=null" + return client.get(roles_url) -def set_permissions(client, group_name, permissions): +def add_role(client, group_name, role_name): """ - Assigns the given permissions to the group. - `permissions` must be a list of strings, each one recognized as a permission by the backend. See - them listed at the link below: - https://github.com/ansible/galaxy_ng/blob/ca503375077a225a5fb215e6fb2c6ae47e09cfd7/galaxy_ng/app/api/ui/serializers/user.py#L122 - - Container permissions are in another file: - https://github.com/ansible/galaxy_ng/blob/009385fb3a1a34d1df9ff369e2e15c3fa27869b3/galaxy_ng/app/access_control/statements/pulp_container.py#L139 + Assigns the given role to the group. + The roles should match the "galaxy.role-name" format. + """ + group_id = get_group_id(client, group_name) + roles_url = f"pulp/api/v3/groups/{group_id}/roles/" + payload = { + "content_object": None, + "role": role_name, + } + return client.post(roles_url, payload) - The permissions are the ones that match the "namespace.permission-name" format. +def get_group_role_id(client, group_name, role_name): + """ + Returns the id for a given role in a group """ - group_id = get_group(client, group_name)["id"] - permissions_url = f"_ui/v1/groups/{group_id}/model-permissions/" - for perm in permissions: - payload = {"permission": perm} - client.post(permissions_url, payload) - # TODO: Check the results of each and aggregate for a return value + group_id = get_group_id(client, group_name) + roles_url = ( + f"pulp/api/v3/groups/{group_id}/roles/?content_object=null&role={role_name}" + ) + resp = client.get(roles_url) + if resp["results"]: + return roles.pulp_href_to_id(resp["results"][0]["pulp_href"]) + else: + raise ValueError(f"No role '{role_name}' found in group '{group_name}'.") -def delete_permission(client, group_name, permission): +def remove_role(client, group_name, role_name): """ - Removes a permission from a group. - + Removes a role from a group. """ - group_id = get_group(client, group_name)["id"] - permissions_url = f"_ui/v1/groups/{group_id}/model-permissions/" - resp = client.get(permissions_url) - for perm in resp["data"]: - if perm["permission"] == permission: - perm_id = perm["id"] - perm_url = f"_ui/v1/groups/{group_id}/model-permissions/{perm_id}/" - client.delete(perm_url, parse_json=False) + group_id = get_group_id(client, group_name) + role_id = get_group_role_id(client, group_name, role_name) + roles_url = f"pulp/api/v3/groups/{group_id}/roles/{role_id}/" + return client.delete(roles_url, parse_json=False) def get_group_list(client): diff --git a/galaxykit/namespaces.py b/galaxykit/namespaces.py index 81e81b7..358ac20 100644 --- a/galaxykit/namespaces.py +++ b/galaxykit/namespaces.py @@ -59,7 +59,7 @@ def add_group(client, ns_name, group_name): { "id": group["id"], "name": group["name"], - "object_permissions": ["change_namespace", "upload_to_namespace"], + "object_roles": ["galaxy.namespace_owner"], } ) return update_namespace(client, namespace) diff --git a/galaxykit/roles.py b/galaxykit/roles.py new file mode 100644 index 0000000..3a17ea7 --- /dev/null +++ b/galaxykit/roles.py @@ -0,0 +1,93 @@ +import re + + +def pulp_href_to_id(href): + uuid_regex = "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" + + for section in href.split("/"): + if re.match(uuid_regex, section): + return section + + return None + + +def get_role_list(client): + """ + Returns list of galaxy rbac roles in the system + """ + return client.get(f"pulp/api/v3/roles/?name__startswith=galaxy.") + + +def get_role(client, role_name): + """ + Returns the data of the role with role_name + """ + roles_url = f"pulp/api/v3/roles/?name={role_name}" + return client.get(roles_url)["results"][0] + + +def get_role_id(client, role_name): + """ + Returns the id for a given role + """ + roles_url = f"pulp/api/v3/roles/?name={role_name}" + resp = client.get(roles_url) + if resp["results"]: + return pulp_href_to_id(resp["results"][0]["pulp_href"]) + else: + raise ValueError(f"No role '{role_name}' found.") + + +def create_role(client, role_name, description, permissions): + """ + Creates an rbac role + """ + payload = { + "description": description, + "name": role_name, + "permissions": permissions or [], + } + resp = client.post("pulp/api/v3/roles/", payload, parse_json=False) + if resp.status_code == 400: + raise ValueError(resp.json()) + return resp + + +def delete_role(client, role_name): + role_id = get_role_id(client, role_name) + delete_url = f"pulp/api/v3/roles/{role_id}/" + return client.delete(delete_url, parse_json=False) + + +# `permissions` must be a list of strings, each one recognized as a permission by the backend. +# +# See them listed at the link below: +# https://github.com/ansible/galaxy_ng/blob/ca503375077a225a5fb215e6fb2c6ae47e09cfd7/galaxy_ng/app/api/ui/serializers/user.py#L122 +# +# Container permissions are in another file: +# https://github.com/ansible/galaxy_ng/blob/009385fb3a1a34d1df9ff369e2e15c3fa27869b3/galaxy_ng/app/access_control/statements/pulp_container.py#L139 +# +# The permissions are the ones that match the "namespace.permission-name" format. + + +def get_permissions(client, role_name): + return get_role(client, role_name)["permissions"] + + +def set_permissions(client, role_name, add_permissions=[], remove_permissions=[]): + """ + Assigns the given permissions to the role. + """ + role = get_role(client, role_name) + role_id = pulp_href_to_id(role["pulp_href"]) + role_url = f"pulp/api/v3/roles/{role_id}/" + + permissions = set(role["permissions"]) + permissions = permissions | set(add_permissions) + permissions = permissions - set(remove_permissions) + + payload = {"permissions": list(permissions)} + resp = client.patch(role_url, payload, parse_json=False) + if resp.status_code == 400: + raise ValueError(resp.json()) + return resp