Skip to content

Commit

Permalink
Add 'Permissions' panel to UI
Browse files Browse the repository at this point in the history
Add a panel to the UI of each detail screen of Service/Project/Farm object
to assign or remove permissions for users. User needs to have MANAGE permissions
to assign or remove permissions for users through this panel.
  • Loading branch information
hoangpn committed Feb 7, 2025
1 parent e5b079a commit 74a5024
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 1 deletion.
37 changes: 37 additions & 0 deletions promgen/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@

from dateutil import parser
from django import forms
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from guardian.shortcuts import get_perms_for_model

from promgen import errors, models, plugins, prometheus, validators

Expand Down Expand Up @@ -254,3 +256,38 @@ def clean(self):
if not hosts:
raise ValidationError("No valid hosts")
self.cleaned_data["hosts"] = list(hosts)


class UserPermissionForm(forms.Form):
permission = forms.ChoiceField(
required=True,
label="Permission",
)

username = forms.ChoiceField(
required=True,
label="Username",
)

def __init__(self, *args, **kwargs):
input_object = kwargs.pop("input_object", None)
super(UserPermissionForm, self).__init__(*args, **kwargs)
if input_object:
self.fields["permission"].choices = self.get_permission_choices(input_object)
self.fields["username"].choices = self.get_user_choices()

def get_permission_choices(self, input_object):
permissions = get_perms_for_model(input_object)
for permission in permissions:
yield (permission.codename, permission.name)

def get_user_choices(self):
for u in (User.objects.filter(is_active=True, is_superuser=False)
.exclude(username="AnonymousUser")
.order_by("username")):
if u.first_name:
yield (u.username, f"{u.username} ({u.first_name} {u.last_name})")
elif u.email:
yield (u.username, f"{u.username} ({u.email})")
else:
yield (u.username, u.username)
5 changes: 5 additions & 0 deletions promgen/templates/promgen/farm_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,9 @@ <h1>Farm: {{ farm.name }} ({{ farm.source }})

</div>

<div class="panel panel-default">
<div class="panel-heading">Permissions</div>
{% include "promgen/permission_block.html" with object=farm %}
</div>

{% endblock %}
38 changes: 38 additions & 0 deletions promgen/templates/promgen/permission_block.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{% load i18n %}
{% load promgen %}

<table class="table table-bordered table-condensed{% if collapse %} collapse"
id="{{ collapse }}{% endif %}">
<tr>
<th>User</th>
<th>Email</th>
<th>
<div data-toggle="tooltip" data-placement="top" data-html="true">
Permission
</div>
</th>
<th>Actions</th>
</tr>
{% for user,perms in object|get_users_permissions %}
{% include 'promgen/permission_row.html' %}
{% endfor %}
</table>


<div class="panel-footer">
<form method="post" action="{% url 'permission-assign'%}"
onsubmit="return confirm('{% trans 'Assign permission for user?' %}')">
{% csrf_token %}
<input name="next" type="hidden" value="{{ request.get_full_path }}"/>
<input name="model" type="hidden" value="{{ object | klass }}"/>
<input name="id" type="hidden" value="{{object.id}}"/>
<table class="table">
{{ permission_form.as_table }}
</table>
<div>
<button class="btn btn-primary">
Assign
</button>
</div>
</form>
</div>
26 changes: 26 additions & 0 deletions promgen/templates/promgen/permission_row.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{% load i18n %}
{% load promgen %}
<tr>
<td class="col-xs-3" v-pre>{{ user.username }}</td>
<td class="col-xs-3" v-pre>{{ user.email }}</td>
<td class="col-xs-3" style="word-break: break-all;" v-pre>
{% for perm in perms %}
<span class="label label-info">{{ perm|upper }}</span>
{% endfor %}
</td>
<td class="col-xs-3" style="word-break: break-all;" v-pre>
<form method="post" action="{% url 'permission-delete' %}"
onsubmit="return confirm('{% trans 'Delete all permissions for user?' %}')">
{% csrf_token %}
<input type="hidden" name="username" value="{{ user.username }}">
<input name="next" type="hidden" value="{{ request.get_full_path }}"/>
<input name="model" type="hidden" value="{{ object | klass }}"/>
<input name="id" type="hidden" value="{{object.id}}"/>
<button type="submit" class="btn btn-danger btn-xs">Delete</button>
</form>
</td>
</tr>




1 change: 1 addition & 0 deletions promgen/templates/promgen/project_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -66,5 +66,6 @@ <h1>
</div>

{% include "promgen/project_detail_rules.html" %}
{% include "promgen/project_detail_permissions.html" %}

{% endblock %}
6 changes: 6 additions & 0 deletions promgen/templates/promgen/project_detail_permissions.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{% load i18n %}

<div class="panel panel-primary">
<div class="panel-heading">Permissions</div>
{% include "promgen/permission_block.html" with object=project %}
</div>
8 changes: 8 additions & 0 deletions promgen/templates/promgen/service_detail.html
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ <h1>Service: {{ service.name }}</h1>
<li role="presentation" class="active"><a href="#projects" data-toggle="tab">Projects</a></li>
<li role="presentation"><a href="#rules" data-toggle="tab">Rules</a></li>
<li role="presentation"><a href="#notifiers" data-toggle="tab">Notifiers</a></li>
<li role="presentation"><a href="#permissions" data-toggle="tab">Permissions</a></li>
</ul>

<div class="well">
Expand All @@ -51,6 +52,13 @@ <h1>Service: {{ service.name }}</h1>
{% include "promgen/service_block_panel_notifiers.inc.html" %}

</div>

<div role="tabpanel" class="tab-pane" id="permissions">
<div class="panel panel-default">
{% include "promgen/permission_block.html" with object=service %}
</div>
</div>

</div>

</div>
Expand Down
7 changes: 7 additions & 0 deletions promgen/templatetags/promgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext as _
from guardian.shortcuts import get_users_with_perms

register = template.Library()

Expand Down Expand Up @@ -214,3 +215,9 @@ def urlqs(view, **kwargs):
This only works for views that do not need additional parameters
"""
return reverse(view) + "?" + urlencode(kwargs)


@register.filter()
def get_users_permissions(object):
return get_users_with_perms(object, attach_perms=True, with_superusers=False,
with_group_users=False).items()
3 changes: 3 additions & 0 deletions promgen/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
path("rule/<int:pk>/toggle", views.RuleToggle.as_view(), name="rule-toggle"),
path("rule/<int:pk>/test", csrf_exempt(views.RuleTest.as_view()), name="rule-test"),
path("rule/<int:pk>/duplicate", views.RulesCopy.as_view(), name="rule-overwrite"),
# Permissions
path("permission/assign", views.PermissionAssign.as_view(), name="permission-assign"),
path("permission/delete", views.PermissionDelete.as_view(), name="permission-delete"),
# Generic Rules
path("<content_type>/<object_id>/rule", views.AlertRuleRegister.as_view(), name="rule-new"),
# Other miscellaneous
Expand Down
64 changes: 63 additions & 1 deletion promgen/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
tasks,
util,
)
from promgen.forms import UserPermForm
from promgen.forms import UserPermissionForm
from promgen.mixins import PromgenGuardianPermissionMixin
from promgen.shortcuts import resolve_domain

Expand Down Expand Up @@ -286,6 +286,10 @@ class ServiceDetail(LoginRequiredMixin, DetailView):
"project_set__notifiers__owner",
)

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["permission_form"] = UserPermissionForm(input_object=self.object)
return context


class ServiceDelete(PromgenGuardianPermissionMixin, DeleteView):
Expand Down Expand Up @@ -496,6 +500,7 @@ def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["sources"] = models.Farm.driver_set()
context["url_form"] = forms.URLForm()
context["permission_form"] = UserPermissionForm(input_object=self.object)
return context


Expand All @@ -510,6 +515,10 @@ class FarmList(LoginRequiredMixin, ListView):
class FarmDetail(LoginRequiredMixin, DetailView):
model = models.Farm

def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["permission_form"] = UserPermissionForm(input_object=self.object)
return context


class FarmUpdate(PromgenGuardianPermissionMixin, UpdateView):
Expand Down Expand Up @@ -1508,3 +1517,56 @@ def get(self, request):
return util.proxy_error(response)

return HttpResponse(response.content, content_type="application/json")


class PermissionAssign(PromgenGuardianPermissionMixin, View):
permission_required = ["manage_service", "manage_project", "manage_farm"]

def post(self, request):
user = User.objects.get_by_natural_key(request.POST["username"])
permission = request.POST["permission"]
obj = self.get_object()

# User should only have one permission MANAGE or EDIT for an object
# So we remove all permissions before assigning new one
permissions = get_perms(user, obj)
for perm in permissions:
remove_perm(perm, user, obj)

assign_perm(permission, user, obj)
messages.success(
request,
"Assigned permission: {} for user: {} on: {}".format(permission, user.username,
obj.name),
)
return redirect(request.POST["next"])

def get_object(self):
id = self.request.POST["id"]
model = self.request.POST["model"]
models = ContentType.objects.get(app_label="promgen", model=model)
obj = models.get_object_for_this_type(pk=id)
return obj


class PermissionDelete(PromgenGuardianPermissionMixin, View):
permission_required = ["manage_service", "manage_project", "manage_farm"]

def post(self, request):
user = User.objects.get_by_natural_key(request.POST["username"])
obj = self.get_object()
permissions = get_perms(user, obj)
for perm in permissions:
remove_perm(perm, user, obj)
messages.success(
request,
"Removed all permissions of user: {} on: {}".format(user.username, obj.name),
)
return redirect(request.POST["next"])

def get_object(self):
id = self.request.POST["id"]
model = self.request.POST["model"]
models = ContentType.objects.get(app_label="promgen", model=model)
obj = models.get_object_for_this_type(pk=id)
return obj

0 comments on commit 74a5024

Please sign in to comment.