From 231367c3dd60b0e28ba3fa3f7cacfb79bd1c518e Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Thu, 6 Jun 2024 16:41:11 -0500 Subject: UI: Add placeholder for resource roles --- gn2/wqflask/templates/oauth2/view-resource.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index 275fcb24..d17f1ddf 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -230,7 +230,15 @@
-

User Roles

+

Available Resource Roles

+

+ + The resource roles will go here when they are implemented … +

+
+ +
+

Users: Assigned Roles

{%if users_n_roles_error is defined%} {{display_error("Users and Roles", users_n_roles_error)}} {%else%} -- cgit 1.4.1 From bc50d737fcf9ede661760a0dbeee124403962044 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Fri, 7 Jun 2024 12:34:35 -0500 Subject: Update UI: Use resource roles rather than obsolete group roles In a fix to fix a privilege-escalation bug, the `…/group/roles` endpoint was entirely removed and replaced with the less error-prone `…/resource/…/roles` endpoint. This commit updates the code to use the new endpoint's data as appropriate. We also fix typos in some url_for routing arguments. --- gn2/wqflask/oauth2/resources.py | 29 +++++++++++++------------ gn2/wqflask/templates/oauth2/view-resource.html | 18 +++++++-------- 2 files changed, 24 insertions(+), 23 deletions(-) (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/oauth2/resources.py b/gn2/wqflask/oauth2/resources.py index 32efbd2a..afba2526 100644 --- a/gn2/wqflask/oauth2/resources.py +++ b/gn2/wqflask/oauth2/resources.py @@ -67,39 +67,40 @@ def view_resource(resource_id: uuid.UUID): int(request.args.get("page", "1"), base=10)) count_per_page = int(request.args.get("count_per_page", "100"), base=10) def __users_success__( - resource, unlinked_data, users_n_roles, this_user, group_roles, + resource, unlinked_data, users_n_roles, this_user, resource_roles, users): return render_ui( "oauth2/view-resource.html", resource=resource, unlinked_data=unlinked_data, users_n_roles=users_n_roles, - this_user=this_user, group_roles=group_roles, users=users, + this_user=this_user, resource_roles=resource_roles, users=users, page=page, count_per_page=count_per_page) - def __group_roles_success__( - resource, unlinked_data, users_n_roles, this_user, group_roles): + def __resource_roles_success__( + resource, unlinked_data, users_n_roles, this_user, resource_roles): return oauth2_get("auth/user/list").either( lambda err: render_ui( "oauth2/view-resource.html", resource=resource, unlinked_data=unlinked_data, users_n_roles=users_n_roles, - this_user=this_user, group_roles=group_roles, + this_user=this_user, resource_roles=resource_roles, users_error=process_error(err), count_per_page=count_per_page), lambda users: __users_success__( - resource, unlinked_data, users_n_roles, this_user, group_roles, + resource, unlinked_data, users_n_roles, this_user, resource_roles, users)) def __this_user_success__(resource, unlinked_data, users_n_roles, this_user): - return oauth2_get("auth/group/roles").either( + return oauth2_get(f"auth/resource/{resource_id}/roles").either( lambda err: render_ui( - "oauth2/view-resources.html", resource=resource, + "oauth2/view-resource.html", resource=resource, unlinked_data=unlinked_data, users_n_roles=users_n_roles, - this_user=this_user, group_roles_error=process_error(err)), - lambda groles: __group_roles_success__( - resource, unlinked_data, users_n_roles, this_user, groles)) + this_user=this_user, resource_roles_error=process_error(err), + count_per_page=count_per_page), + lambda rroles: __resource_roles_success__( + resource, unlinked_data, users_n_roles, this_user, rroles)) def __users_n_roles_success__(resource, unlinked_data, users_n_roles): return oauth2_get("auth/user/").either( lambda err: render_ui( - "oauth2/view-resources.html", + "oauth2/view-resource.html", this_user_error=process_error(err)), lambda usr_dets: __this_user_success__( resource, unlinked_data, users_n_roles, usr_dets)) @@ -229,7 +230,7 @@ def assign_role(resource_id: uuid.UUID) -> Response: }).either(__assign_error__, __assign_success__) except AssertionError as aserr: flash(aserr.args[0], "alert-danger") - return redirect(url_for("oauth2.resources.view_resource", resource_id=resource_id)) + return redirect(url_for("oauth2.resource.view_resource", resource_id=resource_id)) @resources.route("/user/unassign", methods=["POST"]) @require_oauth2 @@ -260,7 +261,7 @@ def unassign_role(resource_id: uuid.UUID) -> Response: }).either(__unassign_error__, __unassign_success__) except AssertionError as aserr: flash(aserr.args[0], "alert-danger") - return redirect(url_for("oauth2.resources.view_resource", resource_id=resource_id)) + return redirect(url_for("oauth2.resource.view_resource", resource_id=resource_id)) @resources.route("/toggle/", methods=["POST"]) @require_oauth2 diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index d17f1ddf..6ae5af56 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -309,8 +309,8 @@

Assign

- {%if group_roles_error is defined%} - {{display_error("Group Roles", group_roles_error)}} + {%if resource_roles_error is defined%} + {{display_error("Resource Roles", resource_roles_error)}} {%elif users_error is defined%} {{display_error("Users", users_error)}} {%else%} @@ -320,13 +320,13 @@ method="POST" autocomplete="off">
- - + > + {%for rrole in resource_roles%} + {%endfor%} -- cgit 1.4.1 From 37f4995185653678d54c3f2af6a12d4ebca41b73 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Fri, 7 Jun 2024 15:26:41 -0500 Subject: PoC: Display resource roles on View-Resource page. --- gn2/wqflask/templates/oauth2/view-resource.html | 56 +++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 4 deletions(-) (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index 6ae5af56..2d11a302 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -231,10 +231,58 @@

Available Resource Roles

-

- - The resource roles will go here when they are implemented … -

+ + + + + + + + + + + + {%for role in resource_roles%} + {%for priv in role.privileges%} + {%if loop.index == 1%} + + + + + + + + {%else%} + + + + + {%endif%} + {%endfor%} + {%else%} + + + + {%endfor%} + +
Roles Acting on this Resource
Role NamePrivilegePrivilege DescriptionActions
+ {{role.role_name}} + {{priv.privilege_id}}{{priv.privilege_description}} + Delete + + Edit +
{{priv.privilege_id}}{{priv.privilege_description}}
+

+ + No roles defined for this resource. +

+
-- cgit 1.4.1 From 7fa4cdd16ff3604b90bb58dbd3d099bd2a91169a Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 10 Jun 2024 09:36:09 -0500 Subject: Simplify 'View-Resource page' UI Get rid of the resource role details and provide button-like elements to help navigate to view more details. --- gn2/wqflask/static/new/css/pills.css | 25 ++++++++++ gn2/wqflask/static/new/css/resource-roles.css | 5 ++ gn2/wqflask/templates/oauth2/view-resource.html | 63 +++++-------------------- 3 files changed, 41 insertions(+), 52 deletions(-) create mode 100644 gn2/wqflask/static/new/css/pills.css create mode 100644 gn2/wqflask/static/new/css/resource-roles.css (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/static/new/css/pills.css b/gn2/wqflask/static/new/css/pills.css new file mode 100644 index 00000000..57c84204 --- /dev/null +++ b/gn2/wqflask/static/new/css/pills.css @@ -0,0 +1,25 @@ +a.pill{ + border: 1px; + color: #3071A9; + text-decoration: none; + border: 1px solid #3071A9; + border-radius: 5px; + padding: 0.3em; + box-shadow: 3px 3px #DEDEDE; + text-align: center; +} + +a.pill:active { + box-shadow: 1px 1px aquamarine; + position: relative; + left: 2px; + top: 2px; +} + +a.pill:hover { + text-decoration: none; +} + +a.pill:focus { + text-decoration: none; +} diff --git a/gn2/wqflask/static/new/css/resource-roles.css b/gn2/wqflask/static/new/css/resource-roles.css new file mode 100644 index 00000000..f6f5e54b --- /dev/null +++ b/gn2/wqflask/static/new/css/resource-roles.css @@ -0,0 +1,5 @@ +.resource_roles { + display: grid; + grid-template-columns: 150px 150px 150px 150px 150px; + grid-gap: 5px; +} diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index 2d11a302..451bfbd7 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -2,6 +2,10 @@ {%from "oauth2/profile_nav.html" import profile_nav%} {%from "oauth2/display_error.html" import display_error%} {%block title%}View User{%endblock%} +{%block css%} + + +{%endblock%} {%block content%}
{{profile_nav("resources", user_privileges)}} @@ -231,58 +235,13 @@

Available Resource Roles

- - - - - - - - - - - - {%for role in resource_roles%} - {%for priv in role.privileges%} - {%if loop.index == 1%} - - - - - - - - {%else%} - - - - - {%endif%} - {%endfor%} - {%else%} - - - - {%endfor%} - -
Roles Acting on this Resource
Role NamePrivilegePrivilege DescriptionActions
- {{role.role_name}} - {{priv.privilege_id}}{{priv.privilege_description}} - Delete - - Edit -
{{priv.privilege_id}}{{priv.privilege_description}}
-

- - No roles defined for this resource. -

-
+
+ {%for role in resource_roles%} + + {{role.role_name}} + + {%endfor%} +
-- cgit 1.4.1 From 8d25673a2256e1fb0a45d62e582865bcfd84ec35 Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 10 Jun 2024 12:34:12 -0500 Subject: Implement "Resource Role Page" Show the page, providing all UI elements necessary, even if the elements themselves are not active. --- gn2/wqflask/oauth2/resources.py | 35 ++++++++ .../templates/oauth2/view-resource-role.html | 98 ++++++++++++++++++++++ gn2/wqflask/templates/oauth2/view-resource.html | 6 +- 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 gn2/wqflask/templates/oauth2/view-resource-role.html (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/oauth2/resources.py b/gn2/wqflask/oauth2/resources.py index 42fdae37..70b49375 100644 --- a/gn2/wqflask/oauth2/resources.py +++ b/gn2/wqflask/oauth2/resources.py @@ -296,3 +296,38 @@ def edit_resource(resource_id: uuid.UUID): def delete_resource(resource_id: uuid.UUID): """Delete the given resource.""" return "WOULD DELETE THE GIVEN RESOURCE" + +@resources.route("//role/", methods=["GET"]) +@require_oauth2 +def view_resource_role(resource_id: uuid.UUID, role_id: uuid.UUID): + """View resource role page.""" + def __render_template__(**kwargs): + return render_ui("oauth2/view-resource-role.html", **kwargs) + + def __fetch_all_roles__(resource, role): + return oauth2_get(f"auth/resource/{resource_id}/roles").either( + lambda error: __render_template__( + all_roles_error=process_error(error)), + lambda all_roles: __render_template__( + resource=resource, + role=role, + unassigned_privileges=[ + priv for role in all_roles + for priv in role["privileges"] + if priv not in role["privileges"] + ])) + + def __fetch_resource_role__(resource): + return oauth2_get( + f"auth/resource/{resource_id}/role/{role_id}").either( + lambda error: __render_template__( + resource=resource, + role_id=role_id, + role_error=process_error(error)), + lambda role: __fetch_all_roles__(resource, role)) + + return oauth2_get( + f"auth/resource/view/{resource_id}").either( + lambda error: __render_template__( + resource_error=process_error(error)), + lambda resource: __fetch_resource_role__(resource=resource)) diff --git a/gn2/wqflask/templates/oauth2/view-resource-role.html b/gn2/wqflask/templates/oauth2/view-resource-role.html new file mode 100644 index 00000000..05df41d6 --- /dev/null +++ b/gn2/wqflask/templates/oauth2/view-resource-role.html @@ -0,0 +1,98 @@ +{%extends "base.html"%} +{%from "oauth2/profile_nav.html" import profile_nav%} +{%from "oauth2/display_error.html" import display_error%} +{%block title%}View User{%endblock%} +{%block content%} + +{%macro unassign_button(resource_id, role_id, privilege_id)%} + +{%endmacro%} + +
+ {{profile_nav(uipages, user_privileges)}} + {%if resource_error is defined%} + {{display_error("Resource", resource_error)}} + {%else%} +

Role for Resource '{{resource.resource_name}}'

+ {%if role_error is defined%} + {{display_error("Role", role_error)}} + {%else%} + + + + + + + + + + + + {%for priv in role.privileges%} + {%if loop.index0 == 0%} + + + + + + {%else%} + + + + + {%endif%} + {%else%} + + + + {%endfor%} + +
Role '{{role.role_name}}' for resource '{{resource.resource_name}}'
Role NamePrivilegeAction
+ {{role.role_name}}{{priv.privilege_description}}{{unassign_button(resource.resource_id, role.role_id, priv.privilege_id)}}
{{priv.privilege_description}}{{unassign_button(resource.resource_id, role.role_id, priv.privilege_id)}}
+

+ {{title}}: + +   + This role has no privileges. +

+
+ +
+ + + {%if unassigned_privileges | length == 0%} +

+ {{title}}: + +   + There are no more privileges left to assign. +

+ {%else%} +
+ Select privileges to assign to this role + {%for priv in unassigned_privileges%} +
+ +
+ {%endfor%} +
+ + + {%endif%} +
+ {%endif%} + {%endif%} +
+ +{%endblock%} diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index 451bfbd7..25cac6ff 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -237,7 +237,11 @@

Available Resource Roles

{%for role in resource_roles%} - + {{role.role_name}} {%endfor%} -- cgit 1.4.1 From c1efb9f57be588137ae3093d3c4aa7badff63b5f Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Mon, 17 Jun 2024 13:55:57 -0500 Subject: Create a new resource role. --- gn2/wqflask/oauth2/resources.py | 48 +++++++++++++++++++++++++ gn2/wqflask/templates/oauth2/create-role.html | 38 +++++++++++++------- gn2/wqflask/templates/oauth2/view-resource.html | 5 +++ 3 files changed, 78 insertions(+), 13 deletions(-) (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/oauth2/resources.py b/gn2/wqflask/oauth2/resources.py index 7a705856..cf600b51 100644 --- a/gn2/wqflask/oauth2/resources.py +++ b/gn2/wqflask/oauth2/resources.py @@ -397,3 +397,51 @@ def unassign_privilege_from_resource_role(resource_id: UUID, role_id: UUID): f"auth/resource/view/{resource_id}").either( with_flash_error(returnto), __fetch_resource_role__) + + +@resources.route("//roles/create-role", + methods=["GET", "POST"]) +@require_oauth2 +def create_resource_role(resource_id: UUID): + """Create new role for the resource.""" + def __render__(**kwargs): + return render_ui("oauth2/create-role.html", **kwargs) + + def __fetch_resource_roles__(resource): + return oauth2_get(f"auth/resource/{resource_id}/roles").either( + lambda error: __render__(resource_role_error=error), + lambda roles: {"resource": resource, "roles": roles}) + + if request.method == "GET": + return oauth2_get(f"auth/resource/view/{resource_id}").map( + __fetch_resource_roles__).either( + lambda error: __render__(resource_error=error), + lambda kwargs: __render__(**kwargs)) + + formdata = request.form + privileges = formdata.getlist("privileges[]") + if not bool(privileges): + flash( + "You must provide at least one privilege for creation of the new " + "role.", + "alert-danger") + return redirect(url_for("oauth2.resource.create_resource_role", + resource_id=resource_id)) + + def __handle_error__(error): + flash_error(process_error(error)) + return redirect(url_for( + "oauth2.resource.create_resource_role", resource_id=resource_id)) + + def __handle_success__(success): + flash("Role successfully created.", "alert-success") + return redirect(url_for( + "oauth2.resource.view_resource", resource_id=resource_id)) + + return oauth2_post( + f"auth/resource/{resource_id}/roles/create", + json={ + "role_name": formdata["role_name"], + "privileges": privileges + }).either( + __handle_error__, __handle_success__) diff --git a/gn2/wqflask/templates/oauth2/create-role.html b/gn2/wqflask/templates/oauth2/create-role.html index f2bff7b4..198eacdd 100644 --- a/gn2/wqflask/templates/oauth2/create-role.html +++ b/gn2/wqflask/templates/oauth2/create-role.html @@ -7,31 +7,43 @@ {{profile_nav("roles", user_privileges)}}

Create Role

- {{flash_me()}} +

Create a new role to act on resource "{{resource.resource_name}}"

{%if group_privileges_error is defined%} {{display_error("Group Privileges", group_privileges_error)}} {%else%} - {%if "group:role:create-role" in user_privileges%} -
- Create Group Role + {%if "resource:role:create-role" in (user_privileges|map(attribute="privilege_id")) %} + + create resource role + + {{flash_me()}} +
- +
+ + {{resource.resource_name|replace(" ", "_")}}:: + + +
+ + The name of the role will have the resource's name appended. +
- {%for priv in group_privileges%} + {%for priv in user_privileges%}
-
{%endfor%} diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index 25cac6ff..cfc769c4 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -246,6 +246,11 @@ {%endfor%}
+
+ New Role
-- cgit 1.4.1 From 0ca86869d8acaa8a820df45fc1debfbe1b7936df Mon Sep 17 00:00:00 2001 From: Frederick Muriuki Muriithi Date: Tue, 25 Jun 2024 13:19:26 -0500 Subject: Remove flawed "group role" idea: use just "role". --- gn2/wqflask/oauth2/resources.py | 12 ++++++------ gn2/wqflask/templates/oauth2/view-resource.html | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) (limited to 'gn2/wqflask/templates/oauth2/view-resource.html') diff --git a/gn2/wqflask/oauth2/resources.py b/gn2/wqflask/oauth2/resources.py index 7ebb82ad..66545802 100644 --- a/gn2/wqflask/oauth2/resources.py +++ b/gn2/wqflask/oauth2/resources.py @@ -213,10 +213,10 @@ def unlink_data_from_resource(): @require_oauth2 def assign_role(resource_id: UUID) -> Response: form = request.form - group_role_id = form.get("group_role_id", "") + role_id = form.get("role_id", "") user_email = form.get("user_email", "") try: - assert bool(group_role_id), "The role must be provided." + assert bool(role_id), "The role must be provided." assert bool(user_email), "The user email must be provided." def __assign_error__(error): @@ -233,7 +233,7 @@ def assign_role(resource_id: UUID) -> Response: return oauth2_post( f"auth/resource/{resource_id}/user/assign", json={ - "group_role_id": group_role_id, + "role_id": role_id, "user_email": user_email }).either(__assign_error__, __assign_success__) except AssertionError as aserr: @@ -244,10 +244,10 @@ def assign_role(resource_id: UUID) -> Response: @require_oauth2 def unassign_role(resource_id: UUID) -> Response: form = request.form - group_role_id = form.get("group_role_id", "") + role_id = form.get("role_id", "") user_id = form.get("user_id", "") try: - assert bool(group_role_id), "The role must be provided." + assert bool(role_id), "The role must be provided." assert bool(user_id), "The user id must be provided." def __unassign_error__(error): @@ -264,7 +264,7 @@ def unassign_role(resource_id: UUID) -> Response: return oauth2_post( f"auth/resource/{resource_id}/user/unassign", json={ - "group_role_id": group_role_id, + "role_id": role_id, "user_id": user_id }).either(__unassign_error__, __unassign_success__) except AssertionError as aserr: diff --git a/gn2/wqflask/templates/oauth2/view-resource.html b/gn2/wqflask/templates/oauth2/view-resource.html index cfc769c4..0788e30c 100644 --- a/gn2/wqflask/templates/oauth2/view-resource.html +++ b/gn2/wqflask/templates/oauth2/view-resource.html @@ -278,14 +278,14 @@ Role Action - {%for grole in user_row.roles%} + {%for role in user_row.roles%} - {{grole.role_name}} + role_id=role.role_id)}}" + title="Details for '{{role.role_name}}' role"> + {{role.role_name}} @@ -294,8 +294,8 @@ method="POST"> - +