Skip to content

Commit d70d5f9

Browse files
committed
Add 'Permissions' panel to UI
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.
1 parent dd52e89 commit d70d5f9

10 files changed

+194
-1
lines changed

promgen/forms.py

+37
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@
66

77
from dateutil import parser
88
from django import forms
9+
from django.contrib.auth.models import User
910
from django.core.exceptions import ValidationError
11+
from guardian.shortcuts import get_perms_for_model
1012

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

@@ -254,3 +256,38 @@ def clean(self):
254256
if not hosts:
255257
raise ValidationError("No valid hosts")
256258
self.cleaned_data["hosts"] = list(hosts)
259+
260+
261+
class UserPermissionForm(forms.Form):
262+
permission = forms.ChoiceField(
263+
required=True,
264+
label="Permission",
265+
)
266+
267+
username = forms.ChoiceField(
268+
required=True,
269+
label="Username",
270+
)
271+
272+
def __init__(self, *args, **kwargs):
273+
input_object = kwargs.pop("input_object", None)
274+
super(UserPermissionForm, self).__init__(*args, **kwargs)
275+
if input_object:
276+
self.fields["permission"].choices = self.get_permission_choices(input_object)
277+
self.fields["username"].choices = self.get_user_choices()
278+
279+
def get_permission_choices(self, input_object):
280+
permissions = get_perms_for_model(input_object)
281+
for permission in permissions:
282+
yield (permission.codename, permission.name)
283+
284+
def get_user_choices(self):
285+
for u in (User.objects.filter(is_active=True, is_superuser=False)
286+
.exclude(username="AnonymousUser")
287+
.order_by("username")):
288+
if u.first_name:
289+
yield (u.username, f"{u.username} ({u.first_name} {u.last_name})")
290+
elif u.email:
291+
yield (u.username, f"{u.username} ({u.email})")
292+
else:
293+
yield (u.username, u.username)

promgen/templates/promgen/farm_detail.html

+5
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,9 @@ <h1>Farm: {{ farm.name }} ({{ farm.source }})
7171

7272
</div>
7373

74+
<div class="panel panel-default">
75+
<div class="panel-heading">Permissions</div>
76+
{% include "promgen/permission_block.html" with object=farm %}
77+
</div>
78+
7479
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{% load i18n %}
2+
{% load promgen %}
3+
4+
<table class="table table-bordered table-condensed{% if collapse %} collapse"
5+
id="{{ collapse }}{% endif %}">
6+
<tr>
7+
<th>User</th>
8+
<th>Email</th>
9+
<th>
10+
<div data-toggle="tooltip" data-placement="top" data-html="true">
11+
Permission
12+
</div>
13+
</th>
14+
<th>Actions</th>
15+
</tr>
16+
{% for user,perms in object|get_users_permissions %}
17+
{% include 'promgen/permission_row.html' %}
18+
{% endfor %}
19+
</table>
20+
21+
22+
<div class="panel-footer">
23+
<form method="post" action="{% url 'permission-assign'%}"
24+
onsubmit="return confirm('{% trans 'Assign permission for user?' %}')">
25+
{% csrf_token %}
26+
<input name="next" type="hidden" value="{{ request.get_full_path }}"/>
27+
<input name="model" type="hidden" value="{{ object | klass }}"/>
28+
<input name="id" type="hidden" value="{{object.id}}"/>
29+
<table class="table">
30+
{{ permission_form.as_table }}
31+
</table>
32+
<div>
33+
<button class="btn btn-primary">
34+
Assign
35+
</button>
36+
</div>
37+
</form>
38+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{% load i18n %}
2+
{% load promgen %}
3+
<tr>
4+
<td class="col-xs-3" v-pre>{{ user.username }}</td>
5+
<td class="col-xs-3" v-pre>{{ user.email }}</td>
6+
<td class="col-xs-3" style="word-break: break-all;" v-pre>
7+
{% for perm in perms %}
8+
<span class="label label-info">{{ perm|upper }}</span>
9+
{% endfor %}
10+
</td>
11+
<td class="col-xs-3" style="word-break: break-all;" v-pre>
12+
<form method="post" action="{% url 'permission-delete' %}"
13+
onsubmit="return confirm('{% trans 'Delete all permissions for user?' %}')">
14+
{% csrf_token %}
15+
<input type="hidden" name="username" value="{{ user.username }}">
16+
<input name="next" type="hidden" value="{{ request.get_full_path }}"/>
17+
<input name="model" type="hidden" value="{{ object | klass }}"/>
18+
<input name="id" type="hidden" value="{{object.id}}"/>
19+
<button type="submit" class="btn btn-danger btn-xs">Delete</button>
20+
</form>
21+
</td>
22+
</tr>
23+
24+
25+
26+

promgen/templates/promgen/project_detail.html

+1
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,6 @@ <h1>
6666
</div>
6767

6868
{% include "promgen/project_detail_rules.html" %}
69+
{% include "promgen/project_detail_permissions.html" %}
6970

7071
{% endblock %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{% load i18n %}
2+
3+
<div class="panel panel-primary">
4+
<div class="panel-heading">Permissions</div>
5+
{% include "promgen/permission_block.html" with object=project %}
6+
</div>

promgen/templates/promgen/service_detail.html

+8
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ <h1>Service: {{ service.name }}</h1>
2525
<li role="presentation" class="active"><a href="#projects" data-toggle="tab">Projects</a></li>
2626
<li role="presentation"><a href="#rules" data-toggle="tab">Rules</a></li>
2727
<li role="presentation"><a href="#notifiers" data-toggle="tab">Notifiers</a></li>
28+
<li role="presentation"><a href="#permissions" data-toggle="tab">Permissions</a></li>
2829
</ul>
2930

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

5354
</div>
55+
56+
<div role="tabpanel" class="tab-pane" id="permissions">
57+
<div class="panel panel-default">
58+
{% include "promgen/permission_block.html" with object=service %}
59+
</div>
60+
</div>
61+
5462
</div>
5563

5664
</div>

promgen/templatetags/promgen.py

+7
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from django.utils.html import format_html
1515
from django.utils.safestring import mark_safe
1616
from django.utils.translation import gettext as _
17+
from guardian.shortcuts import get_users_with_perms
1718

1819
register = template.Library()
1920

@@ -214,3 +215,9 @@ def urlqs(view, **kwargs):
214215
This only works for views that do not need additional parameters
215216
"""
216217
return reverse(view) + "?" + urlencode(kwargs)
218+
219+
220+
@register.filter()
221+
def get_users_permissions(object):
222+
return get_users_with_perms(object, attach_perms=True, with_superusers=False,
223+
with_group_users=False).items()

promgen/urls.py

+3
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@
8787
path("rule/<int:pk>/toggle", views.RuleToggle.as_view(), name="rule-toggle"),
8888
path("rule/<int:pk>/test", csrf_exempt(views.RuleTest.as_view()), name="rule-test"),
8989
path("rule/<int:pk>/duplicate", views.RulesCopy.as_view(), name="rule-overwrite"),
90+
# Permissions
91+
path("permission/assign", views.PermissionAssign.as_view(), name="permission-assign"),
92+
path("permission/delete", views.PermissionDelete.as_view(), name="permission-delete"),
9093
# Generic Rules
9194
path("<content_type>/<object_id>/rule", views.AlertRuleRegister.as_view(), name="rule-new"),
9295
# Other miscellaneous

promgen/views.py

+63-1
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
tasks,
4646
util,
4747
)
48-
from promgen.forms import UserPermForm
48+
from promgen.forms import UserPermissionForm
4949
from promgen.mixins import PromgenGuardianPermissionMixin
5050
from promgen.shortcuts import resolve_domain
5151

@@ -286,6 +286,10 @@ class ServiceDetail(LoginRequiredMixin, DetailView):
286286
"project_set__notifiers__owner",
287287
)
288288

289+
def get_context_data(self, **kwargs):
290+
context = super().get_context_data(**kwargs)
291+
context["permission_form"] = UserPermissionForm(input_object=self.object)
292+
return context
289293

290294

291295
class ServiceDelete(PromgenGuardianPermissionMixin, DeleteView):
@@ -496,6 +500,7 @@ def get_context_data(self, **kwargs):
496500
context = super().get_context_data(**kwargs)
497501
context["sources"] = models.Farm.driver_set()
498502
context["url_form"] = forms.URLForm()
503+
context["permission_form"] = UserPermissionForm(input_object=self.object)
499504
return context
500505

501506

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

518+
def get_context_data(self, **kwargs):
519+
context = super().get_context_data(**kwargs)
520+
context["permission_form"] = UserPermissionForm(input_object=self.object)
521+
return context
513522

514523

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

15101519
return HttpResponse(response.content, content_type="application/json")
1520+
1521+
1522+
class PermissionAssign(PromgenGuardianPermissionMixin, View):
1523+
permission_required = ["manage_service", "manage_project", "manage_farm"]
1524+
1525+
def post(self, request):
1526+
user = User.objects.get_by_natural_key(request.POST["username"])
1527+
permission = request.POST["permission"]
1528+
obj = self.get_object()
1529+
1530+
# User should only have one permission MANAGE or EDIT for an object
1531+
# So we remove all permissions before assigning new one
1532+
permissions = get_perms(user, obj)
1533+
for perm in permissions:
1534+
remove_perm(perm, user, obj)
1535+
1536+
assign_perm(permission, user, obj)
1537+
messages.success(
1538+
request,
1539+
"Assigned permission: {} for user: {} on: {}".format(permission, user.username,
1540+
obj.name),
1541+
)
1542+
return redirect(request.POST["next"])
1543+
1544+
def get_object(self):
1545+
id = self.request.POST["id"]
1546+
model = self.request.POST["model"]
1547+
models = ContentType.objects.get(app_label="promgen", model=model)
1548+
obj = models.get_object_for_this_type(pk=id)
1549+
return obj
1550+
1551+
1552+
class PermissionDelete(PromgenGuardianPermissionMixin, View):
1553+
permission_required = ["manage_service", "manage_project", "manage_farm"]
1554+
1555+
def post(self, request):
1556+
user = User.objects.get_by_natural_key(request.POST["username"])
1557+
obj = self.get_object()
1558+
permissions = get_perms(user, obj)
1559+
for perm in permissions:
1560+
remove_perm(perm, user, obj)
1561+
messages.success(
1562+
request,
1563+
"Removed all permissions of user: {} on: {}".format(user.username, obj.name),
1564+
)
1565+
return redirect(request.POST["next"])
1566+
1567+
def get_object(self):
1568+
id = self.request.POST["id"]
1569+
model = self.request.POST["model"]
1570+
models = ContentType.objects.get(app_label="promgen", model=model)
1571+
obj = models.get_object_for_this_type(pk=id)
1572+
return obj

0 commit comments

Comments
 (0)