Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New authorization for Promgen #562

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
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.
hoangpn committed Mar 14, 2025
commit d1e91ebf56621b87b57ff72f755ed0687755fa12
39 changes: 39 additions & 0 deletions promgen/forms.py
Original file line number Diff line number Diff line change
@@ -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

@@ -254,3 +256,40 @@ 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
@@ -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
@@ -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
@@ -63,6 +63,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">
@@ -89,6 +90,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>
8 changes: 8 additions & 0 deletions promgen/templatetags/promgen.py
Original file line number Diff line number Diff line change
@@ -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()

@@ -214,3 +215,10 @@ 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
@@ -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
65 changes: 65 additions & 0 deletions promgen/views.py
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@
from django.views.generic.base import RedirectView, TemplateView
from django.views.generic.detail import SingleObjectMixin
from django.views.generic.edit import CreateView, DeleteView, FormView
from guardian.shortcuts import assign_perm, get_perms, remove_perm
from prometheus_client.core import CounterMetricFamily, GaugeMetricFamily
from prometheus_client.parser import text_string_to_metric_families
from requests.exceptions import HTTPError
@@ -45,6 +46,7 @@
tasks,
util,
)
from promgen.forms import UserPermissionForm
from promgen.mixins import PromgenGuardianPermissionMixin
from promgen.shortcuts import resolve_domain

@@ -291,6 +293,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):
@@ -503,6 +509,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


@@ -517,6 +524,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):
@@ -1552,3 +1563,57 @@ 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