From f036daf7797210779637f772bc19b5ed1e7559a2 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 15 Feb 2024 11:02:22 -0300 Subject: [PATCH] User and permission management --- accounts/views.py | 4 +- templates/user_manager/list.html | 22 ++++- templates/user_manager/manage_user.html | 97 +++++++++++++++++++ user_manager/admin.py | 8 +- user_manager/forms.py | 46 +++++++++ user_manager/migrations/0001_initial.py | 25 +++++ ...seracl_created_useracl_updated_and_more.py | 35 +++++++ user_manager/models.py | 7 +- user_manager/views.py | 59 ++++++++++- wireguard_webadmin/urls.py | 3 +- 10 files changed, 296 insertions(+), 10 deletions(-) create mode 100644 templates/user_manager/manage_user.html create mode 100644 user_manager/forms.py create mode 100644 user_manager/migrations/0001_initial.py create mode 100644 user_manager/migrations/0002_remove_useracl_id_useracl_created_useracl_updated_and_more.py diff --git a/accounts/views.py b/accounts/views.py index c6b5f2f..d413cd4 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -3,6 +3,7 @@ from django.contrib.auth.models import User from django.contrib import auth from .forms import CreateUserForm, LoginForm from django.http import HttpResponse +from user_manager.models import UserAcl def view_create_first_user(request): @@ -13,7 +14,8 @@ def view_create_first_user(request): if form.is_valid(): username = form.cleaned_data['username'] password = form.cleaned_data['password'] - User.objects.create_superuser(username=username, password=password) + new_user = User.objects.create_superuser(username=username, password=password) + UserAcl.objects.create(user=new_user, user_level=50) return render(request, 'accounts/superuser_created.html') else: form = CreateUserForm() diff --git a/templates/user_manager/list.html b/templates/user_manager/list.html index d141e4a..68c0075 100644 --- a/templates/user_manager/list.html +++ b/templates/user_manager/list.html @@ -1,5 +1,25 @@ {% extends "base.html" %} {% block content %} -this page is just a placeholder for the moment + + + + + + + + + + {% for user_acl in user_acl_list %} + + + + + + {% endfor %} + +
UsernameUser Level
{{ user_acl.user.username }}{{ user_acl.get_user_level_display }} + +
+ {% endblock %} diff --git a/templates/user_manager/manage_user.html b/templates/user_manager/manage_user.html new file mode 100644 index 0000000..c1e2aca --- /dev/null +++ b/templates/user_manager/manage_user.html @@ -0,0 +1,97 @@ +{% extends "base.html" %} + +{% block content %} +
+
+
+

{{ form.instance.pk|yesno:"Edit User,Create New User" }}

+
+
+
+
+
+ {% csrf_token %} + + +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ +
+ + Back + {% if user_acl %}Delete User{% endif %} + +
+
+
+ + +
+ +
Debugging Analyst
+

Access to basic system information and logs for troubleshooting. No access to modify settings or view sensitive data such as peer keys.

+ +
View Only User
+

Full view access, including peer keys and configuration files. Cannot modify any settings or configurations.

+ +
Peer Manager
+

Permissions to add, edit, and remove peers and IP addresses. Does not include access to modify WireGuard instance configurations or higher-level settings.

+ +
Wireguard Manager
+

Authority to add, edit, and remove configurations of WireGuard instances.

+ +
Administrator
+

Full access across the system. Can view and modify all settings, configurations and manage users.

+ +
+ + +
+
+
+ +
+
+ +
+
+
+{% endblock %} + + +{% block custom_page_scripts %} + + + +{% endblock %} \ No newline at end of file diff --git a/user_manager/admin.py b/user_manager/admin.py index 8c38f3f..f70e211 100644 --- a/user_manager/admin.py +++ b/user_manager/admin.py @@ -1,3 +1,9 @@ from django.contrib import admin +from user_manager.models import UserAcl -# Register your models here. + +class UserAclAdmin(admin.ModelAdmin): + list_display = ('user', 'user_level', 'created', 'updated') + search_fields = ('user__username', 'user__email') + +admin.site.register(UserAcl, UserAclAdmin) diff --git a/user_manager/forms.py b/user_manager/forms.py new file mode 100644 index 0000000..392b8d6 --- /dev/null +++ b/user_manager/forms.py @@ -0,0 +1,46 @@ +from django import forms +from django.contrib.auth.forms import UserCreationForm +from django.contrib.auth.models import User +from .models import UserAcl +from django.core.exceptions import ValidationError + + +class UserAclForm(UserCreationForm): + user_level = forms.ChoiceField(choices=UserAcl.user_level.field.choices, required=True, label="User Level") + + class Meta(UserCreationForm.Meta): + model = User + fields = UserCreationForm.Meta.fields + ('user_level',) + + def __init__(self, *args, **kwargs): + self.user_id = kwargs.pop('user_id', None) + super().__init__(*args, **kwargs) + if self.instance and self.instance.pk: + self.fields['password1'].required = False + self.fields['password2'].required = False + self.fields['username'].widget.attrs['readonly'] = True + + def clean_username(self): + username = self.cleaned_data.get('username') + if User.objects.filter(username=username).exclude(pk=self.user_id).exists(): + raise ValidationError("A user with that username already exists.") + return username + + def save(self, commit=True): + user = super().save(commit=False) + new_password = self.cleaned_data.get("password1") + + if new_password: + user.set_password(new_password) + user.save() + else: + if not user.id: + user.save() + + if commit: + user_acl, created = UserAcl.objects.update_or_create( + user=user, + defaults={'user_level': self.cleaned_data.get('user_level')} + ) + + return user diff --git a/user_manager/migrations/0001_initial.py b/user_manager/migrations/0001_initial.py new file mode 100644 index 0000000..dafdce0 --- /dev/null +++ b/user_manager/migrations/0001_initial.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.1 on 2024-02-14 20:54 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='UserAcl', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('user_level', models.PositiveIntegerField(choices=[(10, 'Debugging Analyst'), (20, 'View Only User'), (30, 'Peer Manager'), (40, 'Wireguard Manager'), (50, 'Administrator')], default=0)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/user_manager/migrations/0002_remove_useracl_id_useracl_created_useracl_updated_and_more.py b/user_manager/migrations/0002_remove_useracl_id_useracl_created_useracl_updated_and_more.py new file mode 100644 index 0000000..6d33cf3 --- /dev/null +++ b/user_manager/migrations/0002_remove_useracl_id_useracl_created_useracl_updated_and_more.py @@ -0,0 +1,35 @@ +# Generated by Django 5.0.1 on 2024-02-14 21:23 + +import django.utils.timezone +import uuid +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_manager', '0001_initial'), + ] + + operations = [ + migrations.RemoveField( + model_name='useracl', + name='id', + ), + migrations.AddField( + model_name='useracl', + name='created', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), + preserve_default=False, + ), + migrations.AddField( + model_name='useracl', + name='updated', + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name='useracl', + name='uuid', + field=models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False), + ), + ] diff --git a/user_manager/models.py b/user_manager/models.py index 6907e78..1768846 100644 --- a/user_manager/models.py +++ b/user_manager/models.py @@ -1,6 +1,6 @@ from django.db import models from django.contrib.auth.models import User -# Create your models here. +import uuid class UserAcl(models.Model): @@ -11,8 +11,11 @@ class UserAcl(models.Model): (30, 'Peer Manager'), (40, 'Wireguard Manager'), (50, 'Administrator'), - )) + created = models.DateTimeField(auto_now_add=True) + updated = models.DateTimeField(auto_now=True) + uuid = models.UUIDField(primary_key=True, editable=False, default=uuid.uuid4) + def __str__(self): return self.user.username diff --git a/user_manager/views.py b/user_manager/views.py index 73b7c62..fd188b4 100644 --- a/user_manager/views.py +++ b/user_manager/views.py @@ -1,9 +1,60 @@ -from django.shortcuts import render from django.contrib.auth.decorators import login_required - +from django.shortcuts import render, redirect, get_object_or_404 +from user_manager.models import UserAcl +from .forms import UserAclForm +from django.contrib.auth.models import User +from django.contrib import messages +from django.contrib.sessions.models import Session @login_required def view_user_list(request): page_title = 'User Manager' - context = {'page_title': page_title} - return render(request, 'user_manager/list.html', context) \ No newline at end of file + user_acl_list = UserAcl.objects.all().order_by('user__username') + context = {'page_title': page_title, 'user_acl_list': user_acl_list} + return render(request, 'user_manager/list.html', context) + + +@login_required +def view_manage_user(request): + user_acl = None + user = None + if 'uuid' in request.GET: + user_acl = get_object_or_404(UserAcl, uuid=request.GET['uuid']) + user = user_acl.user + form = UserAclForm(instance=user, initial={'user_level': user_acl.user_level}, user_id=user.id) + page_title = 'Edit User '+ user.username + if request.GET.get('action') == 'delete': + username = user.username + if request.GET.get('confirm') == user.username: + user.delete() + messages.success(request, 'User deleted|The user '+ username +' has been deleted.') + return redirect('/user/list/') + user_acl.delete() + return redirect('/user/list/') + else: + form = UserAclForm() + page_title = 'Add User' + + if request.method == 'POST': + if user_acl: + form = UserAclForm(request.POST, instance=user, user_id=user.id) + else: + form = UserAclForm(request.POST) + + if form.is_valid(): + form.save() + if form.cleaned_data.get('password1'): + user_disconnected = False + for session in Session.objects.all(): + if str(user.id) == session.get_decoded().get('_auth_user_id'): + session.delete() + if not user_disconnected: + messages.warning(request, 'User Disconnected|The user '+ user.username +' has been disconnected.') + user_disconnected = True + if user_acl: + messages.success(request, 'User updated|The user '+ form.cleaned_data['username'] +' has been updated.') + else: + messages.success(request, 'User added|The user '+ form.cleaned_data['username'] +' has been added.') + return redirect('/user/list/') + + return render(request, 'user_manager/manage_user.html', {'form': form, 'page_title': page_title, 'user_acl': user_acl}) diff --git a/wireguard_webadmin/urls.py b/wireguard_webadmin/urls.py index c958f49..891ee64 100644 --- a/wireguard_webadmin/urls.py +++ b/wireguard_webadmin/urls.py @@ -19,7 +19,7 @@ from django.urls import path from wireguard.views import view_welcome, view_wireguard_status, view_wireguard_manage_instance from wireguard_peer.views import view_wireguard_peer_list, view_wireguard_peer_manage, view_manage_ip_address from console.views import view_console -from user_manager.views import view_user_list +from user_manager.views import view_user_list, view_manage_user from accounts.views import view_create_first_user, view_login, view_logout @@ -32,6 +32,7 @@ urlpatterns = [ path('peer/manage_ip_address/', view_manage_ip_address, name='manage_ip_address'), path('console/', view_console, name='console'), path('user/list/', view_user_list, name='user_list'), + path('user/manage/', view_manage_user, name='manage_user'), path('server/manage/', view_wireguard_manage_instance, name='wireguard_manage_instance'), path('accounts/create_first_user/', view_create_first_user, name='create_first_user'), path('accounts/login/', view_login, name='login'),