From c104a12df0450ce0b63860b7162149b4b57d4602 Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Tue, 12 Mar 2024 10:49:49 -0300 Subject: [PATCH] import user_manager and accounts from project wireguard_webadmin --- accounts/__init__.py | 0 accounts/admin.py | 3 + accounts/apps.py | 6 ++ accounts/forms.py | 41 ++++++++++ accounts/migrations/__init__.py | 0 accounts/models.py | 3 + accounts/tests.py | 3 + accounts/views.py | 42 +++++++++++ dashboard/views.py | 3 + requirements.txt | 1 + routerfleet/settings.py | 7 +- routerfleet/urls.py | 9 ++- templates/accounts/create_first_user.html | 71 +++++++++++++++++ templates/accounts/login.html | 62 +++++++++++++++ templates/accounts/logout.html | 35 +++++++++ templates/accounts/superuser_created.html | 32 ++++++++ templates/base.html | 11 ++- templates/user_manager/list.html | 27 +++++++ templates/user_manager/manage_user.html | 92 +++++++++++++++++++++++ user_manager/__init__.py | 0 user_manager/admin.py | 3 + user_manager/apps.py | 6 ++ user_manager/forms.py | 45 +++++++++++ user_manager/migrations/0001_initial.py | 28 +++++++ user_manager/migrations/__init__.py | 0 user_manager/models.py | 21 ++++++ user_manager/tests.py | 3 + user_manager/views.py | 68 +++++++++++++++++ 28 files changed, 618 insertions(+), 4 deletions(-) create mode 100644 accounts/__init__.py create mode 100644 accounts/admin.py create mode 100644 accounts/apps.py create mode 100644 accounts/forms.py create mode 100644 accounts/migrations/__init__.py create mode 100644 accounts/models.py create mode 100644 accounts/tests.py create mode 100644 accounts/views.py create mode 100644 templates/accounts/create_first_user.html create mode 100644 templates/accounts/login.html create mode 100644 templates/accounts/logout.html create mode 100644 templates/accounts/superuser_created.html create mode 100644 templates/user_manager/list.html create mode 100644 templates/user_manager/manage_user.html create mode 100644 user_manager/__init__.py create mode 100644 user_manager/admin.py create mode 100644 user_manager/apps.py create mode 100644 user_manager/forms.py create mode 100644 user_manager/migrations/0001_initial.py create mode 100644 user_manager/migrations/__init__.py create mode 100644 user_manager/models.py create mode 100644 user_manager/tests.py create mode 100644 user_manager/views.py diff --git a/accounts/__init__.py b/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts/admin.py b/accounts/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/accounts/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/accounts/apps.py b/accounts/apps.py new file mode 100644 index 0000000..3e3c765 --- /dev/null +++ b/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'accounts' diff --git a/accounts/forms.py b/accounts/forms.py new file mode 100644 index 0000000..3e0b621 --- /dev/null +++ b/accounts/forms.py @@ -0,0 +1,41 @@ +from typing import Any +from django import forms +from django.core.exceptions import ValidationError +from django.contrib.auth import authenticate + + +class CreateUserForm(forms.Form): + username = forms.CharField(label='Username') + password = forms.CharField(label='Password', widget=forms.PasswordInput) + password2 = forms.CharField(label='Confirm Password', widget=forms.PasswordInput) # Adicione este campo para a confirmação da senha + + def clean(self): + cleaned_data = super().clean() + + username = cleaned_data.get("username") + if username and ' ' in username: + self.add_error('username', ValidationError("Username cannot contain spaces.")) + cleaned_data['username'] = username.lower() + password = cleaned_data.get("password") + password2 = cleaned_data.get("password2") + + if password and password2 and password != password2: + self.add_error('password2', ValidationError("The two password fields didn't match.")) + return cleaned_data + + +class LoginForm(forms.Form): + username = forms.CharField(label='Username') + password = forms.CharField(label='Password', widget=forms.PasswordInput) + + def clean(self): + cleaned_data = super().clean() + username = cleaned_data.get("username") + password = cleaned_data.get("password") + if username and password: + user = authenticate(username=username, password=password) + if not user: + self.add_error(None, ValidationError("Invalid username or password.")) + else: + self.add_error(None, ValidationError("Both fields are required.")) + return cleaned_data \ No newline at end of file diff --git a/accounts/migrations/__init__.py b/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/accounts/models.py b/accounts/models.py new file mode 100644 index 0000000..71a8362 --- /dev/null +++ b/accounts/models.py @@ -0,0 +1,3 @@ +from django.db import models + +# Create your models here. diff --git a/accounts/tests.py b/accounts/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/accounts/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/accounts/views.py b/accounts/views.py new file mode 100644 index 0000000..d413cd4 --- /dev/null +++ b/accounts/views.py @@ -0,0 +1,42 @@ +from django.shortcuts import render, Http404, redirect +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): + if User.objects.filter().all(): + raise Http404('Superuser already exists') + if request.method == 'POST': + form = CreateUserForm(request.POST) + if form.is_valid(): + username = form.cleaned_data['username'] + password = form.cleaned_data['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() + return render(request, 'accounts/create_first_user.html', {'form': form}) + + +def view_login(request): + if not User.objects.filter().all(): + return redirect('/accounts/create_first_user/') + if request.method == 'POST': + form = LoginForm(request.POST) + if form.is_valid(): + username = form.cleaned_data['username'] + user = User.objects.get(username=username) + auth.login(request, user) + return redirect('/') + else: + form = LoginForm() + return render(request, 'accounts/login.html', {'form': form}) + + +def view_logout(request): + auth.logout(request) + return render(request, 'accounts/logout.html') \ No newline at end of file diff --git a/dashboard/views.py b/dashboard/views.py index 7b82de0..5e8a5ce 100644 --- a/dashboard/views.py +++ b/dashboard/views.py @@ -1,11 +1,14 @@ from django.shortcuts import render +from django.contrib.auth.decorators import login_required +@login_required def view_dashboard(request): context = {'page_title': 'Welcome to routerfleet'} return render(request, 'dashboard/welcome.html', context=context) +@login_required def view_status(request): context = {'page_title': 'Welcome to routerfleet'} return render(request, 'dashboard/status.html', context=context) diff --git a/requirements.txt b/requirements.txt index fe0cdf8..d3ca6f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ asgiref==3.7.2 +crispy-bootstrap4==2024.1 crispy-bootstrap5==2024.2 Django==5.0.3 django-crispy-forms==2.1 diff --git a/routerfleet/settings.py b/routerfleet/settings.py index 7f98a0f..0bf7e62 100644 --- a/routerfleet/settings.py +++ b/routerfleet/settings.py @@ -37,7 +37,9 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', - 'crispy_forms' + 'crispy_forms', + 'crispy_bootstrap4', + 'user_manager' ] MIDDLEWARE = [ @@ -50,6 +52,9 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +CRISPY_ALLOWED_TEMPLATE_PACKS = "bootstrap4" +CRISPY_TEMPLATE_PACK = "bootstrap4" + ROOT_URLCONF = 'routerfleet.urls' TEMPLATES = [ diff --git a/routerfleet/urls.py b/routerfleet/urls.py index c2f42d0..cafb318 100644 --- a/routerfleet/urls.py +++ b/routerfleet/urls.py @@ -1,10 +1,17 @@ from django.contrib import admin from django.urls import path from dashboard.views import view_dashboard, view_status +from user_manager.views import view_manage_user, view_user_list +from accounts.views import view_login, view_logout, view_create_first_user urlpatterns = [ path('admin/', admin.site.urls), path('', view_dashboard, name='dashboard'), - path('status/', view_status, name='status') + path('status/', view_status, name='status'), + path('user/list/', view_user_list, name='user_list'), + path('user/manage/', view_manage_user, name='manage_user'), + path('accounts/create_first_user/', view_create_first_user, name='create_first_user'), + path('accounts/login/', view_login, name='login'), + path('accounts/logout/', view_logout, name='logout'), ] diff --git a/templates/accounts/create_first_user.html b/templates/accounts/create_first_user.html new file mode 100644 index 0000000..3b5aa69 --- /dev/null +++ b/templates/accounts/create_first_user.html @@ -0,0 +1,71 @@ +{% extends "base_login.html" %} + +{% block content %} + +
+ + +
+
+ + +
+ {% csrf_token %} +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+ + +
+
+
+
+ +
+
+
+ + + +
+ +
+
+ + + + + + + + + {% endblock %} +``` diff --git a/templates/accounts/login.html b/templates/accounts/login.html new file mode 100644 index 0000000..afd880a --- /dev/null +++ b/templates/accounts/login.html @@ -0,0 +1,62 @@ +{% extends "base_login.html" %} + +{% block content %} + +
+ + +
+
+
+ {% csrf_token %} +
+ +
+
+ +
+
+
+ +
+ +
+
+ +
+
+
+ + + +
+
+
+
+ +
+
+
+ + + +
+ +
+
+ + + + + + + + + {% endblock %} +``` diff --git a/templates/accounts/logout.html b/templates/accounts/logout.html new file mode 100644 index 0000000..9ff426d --- /dev/null +++ b/templates/accounts/logout.html @@ -0,0 +1,35 @@ +{% extends "base_login.html" %} + +{% block content %} + +
+ + +
+
+ + + + + +
+ + +
+ + +
+ +
+
+ + + + + + {% endblock %} +``` diff --git a/templates/accounts/superuser_created.html b/templates/accounts/superuser_created.html new file mode 100644 index 0000000..ab705a2 --- /dev/null +++ b/templates/accounts/superuser_created.html @@ -0,0 +1,32 @@ +{% extends "base_login.html" %} + +{% block content %} + +
+ + +
+
+ + +
+ + +
+ + +
+ +
+
+ + + + + + + {% endblock %} diff --git a/templates/base.html b/templates/base.html index 7ab2739..2308d65 100644 --- a/templates/base.html +++ b/templates/base.html @@ -3,7 +3,7 @@ - {% if page_title %}{{page_title}} | {% endif %}routerfleet + {% if page_title %}{{ page_title }} | {% endif %}routerfleet @@ -93,8 +93,15 @@ + - diff --git a/templates/user_manager/list.html b/templates/user_manager/list.html new file mode 100644 index 0000000..d1928f7 --- /dev/null +++ b/templates/user_manager/list.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block content %} + + + + + + + + + + {% for user_acl in user_acl_list %} + + + + + + {% endfor %} + +
UsernameUser Level
{{ user_acl.user.username }}{{ user_acl.get_user_level_display }} + +
+ + Add User + +{% endblock %} diff --git a/templates/user_manager/manage_user.html b/templates/user_manager/manage_user.html new file mode 100644 index 0000000..4b84439 --- /dev/null +++ b/templates/user_manager/manage_user.html @@ -0,0 +1,92 @@ +{% 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.

+ +
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/__init__.py b/user_manager/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user_manager/admin.py b/user_manager/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/user_manager/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/user_manager/apps.py b/user_manager/apps.py new file mode 100644 index 0000000..83164c9 --- /dev/null +++ b/user_manager/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class UserManagerConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'user_manager' diff --git a/user_manager/forms.py b/user_manager/forms.py new file mode 100644 index 0000000..7c6fe11 --- /dev/null +++ b/user_manager/forms.py @@ -0,0 +1,45 @@ +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..cd0ce97 --- /dev/null +++ b/user_manager/migrations/0001_initial.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.3 on 2024-03-12 13:05 + +import django.db.models.deletion +import uuid +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=[ + ('user_level', models.PositiveIntegerField(choices=[(10, 'Debugging Analyst'), (20, 'View Only User'), (30, 'Peer Manager'), (40, 'Manager'), (50, 'Administrator')], default=0)), + ('created', models.DateTimeField(auto_now_add=True)), + ('updated', models.DateTimeField(auto_now=True)), + ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/user_manager/migrations/__init__.py b/user_manager/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/user_manager/models.py b/user_manager/models.py new file mode 100644 index 0000000..10b0d90 --- /dev/null +++ b/user_manager/models.py @@ -0,0 +1,21 @@ +from django.db import models +from django.contrib.auth.models import User +import uuid + + +class UserAcl(models.Model): + user = models.OneToOneField(User, on_delete=models.CASCADE) + user_level = models.PositiveIntegerField(default=0, choices=( + (10, 'Debugging Analyst'), + (20, 'View Only User'), + (30, 'Peer Manager'), + (40, '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/tests.py b/user_manager/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/user_manager/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/user_manager/views.py b/user_manager/views.py new file mode 100644 index 0000000..5bace4f --- /dev/null +++ b/user_manager/views.py @@ -0,0 +1,68 @@ +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): + if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists(): + return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) + page_title = 'User Manager' + 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): + if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists(): + return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) + 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('confirmation') == username: + user.delete() + messages.success(request, 'User deleted|The user ' + username + ' has been deleted.') + return redirect('/user/list/') + + 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 + if user: + 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})