From 4df10f64992fb20fb6c8c790463cd9586e3db40c Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Mon, 20 Jan 2025 15:59:24 -0300 Subject: [PATCH] Enhance user management: Added 'Peer Groups' column to user list, updated UserAclForm to include peer group selection, and improved user management view with detailed descriptions for user levels and peer groups. --- templates/user_manager/list.html | 10 +++ user_manager/forms.py | 119 +++++++++++++++++++++++++------ user_manager/views.py | 57 +++++++++++++-- 3 files changed, 158 insertions(+), 28 deletions(-) diff --git a/templates/user_manager/list.html b/templates/user_manager/list.html index 2bf04e3..532a5d3 100644 --- a/templates/user_manager/list.html +++ b/templates/user_manager/list.html @@ -6,6 +6,7 @@ Username User Level + Peer Groups @@ -14,6 +15,15 @@ {{ user_acl.user.username }} {{ user_acl.get_user_level_display }} + + {% if user_acl.peer_groups.all %} + {% for peer_group in user_acl.peer_groups.all %} + {{ peer_group.name }}{% if not forloop.last %}, {% endif %} + {% endfor %} + {% else %} + Any + {% endif %} + diff --git a/user_manager/forms.py b/user_manager/forms.py index 449d0e9..145641d 100644 --- a/user_manager/forms.py +++ b/user_manager/forms.py @@ -8,20 +8,67 @@ from crispy_forms.helper import FormHelper from crispy_forms.layout import Layout, Row, Column, Submit, HTML -class UserAclForm(UserCreationForm): +class UserAclForm(forms.Form): + username = forms.CharField(max_length=150) + password1 = forms.CharField(widget=forms.PasswordInput, required=False, label="Password") + password2 = forms.CharField(widget=forms.PasswordInput, required=False, label="Password confirmation") 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',) + peer_groups = forms.ModelMultipleChoiceField( + queryset=PeerGroup.objects.all(), + required=False, + ) def __init__(self, *args, **kwargs): + self.instance = kwargs.pop('instance', None) 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 + + if self.instance: + self.fields['username'].initial = self.instance.username self.fields['username'].widget.attrs['readonly'] = True + self.fields['peer_groups'].initial = self.instance.useracl.peer_groups.all() + else: + self.fields['password1'].required = True + self.fields['password2'].required = True + + self.helper = FormHelper() + self.helper.form_method = 'post' + + if self.instance: + delete_html = "Delete" + else: + delete_html = '' + + self.helper.layout = Layout( + Row( + Column('username', css_class='form-group col-md-12 mb-0'), + css_class='form-row' + ), + Row( + Column('password1', css_class='form-group col-md-12 mb-0'), + css_class='form-row' + ), + Row( + Column('password2', css_class='form-group col-md-12 mb-0'), + css_class='form-row' + ), + Row( + Column('user_level', css_class='form-group col-md-12 mb-0'), + css_class='form-row' + ), + Row( + Column('peer_groups', css_class='form-group col-md-12 mb-0'), + css_class='form-row' + ), + Row( + Column( + Submit('submit', 'Save', css_class='btn btn-success'), + HTML(' Back '), + HTML(delete_html), + css_class='col-md-12'), + css_class='form-row' + ) + ) def clean_username(self): username = self.cleaned_data.get('username') @@ -29,23 +76,51 @@ class UserAclForm(UserCreationForm): 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() + def clean(self): + cleaned_data = super().clean() + password1 = cleaned_data.get('password1') + password2 = cleaned_data.get('password2') - if commit: - user_acl, created = UserAcl.objects.update_or_create( - user=user, - defaults={'user_level': self.cleaned_data.get('user_level')} + if not self.instance: + if not password1: + raise ValidationError("Password is required for new users.") + if not password2: + raise ValidationError("Password confirmation is required for new users.") + + if password1 or password2: + if password1 != password2: + raise ValidationError("The two password fields didn't match.") + if len(password1) < 8: + raise ValidationError("Password must be at least 8 characters long.") + + return cleaned_data + + def save(self): + username = self.cleaned_data['username'] + password = self.cleaned_data.get('password1') + user_level = self.cleaned_data['user_level'] + peer_groups = self.cleaned_data.get('peer_groups', []) + + if self.instance: + user = self.instance + if password: + user.set_password(password) + user.save() + else: + user = User.objects.create_user( + username=username, + password=password ) + user_acl, created = UserAcl.objects.update_or_create( + user=user, + defaults={ + 'user_level': user_level + } + ) + + user_acl.peer_groups.set(peer_groups) + return user diff --git a/user_manager/views.py b/user_manager/views.py index 2fffd43..6550416 100644 --- a/user_manager/views.py +++ b/user_manager/views.py @@ -80,33 +80,43 @@ def view_user_list(request): 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 + initial_data = {} + 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) + initial_data = { + 'username': user.username, + 'user_level': user_acl.user_level, + 'peer_groups': user_acl.peer_groups.all() + } + form = UserAclForm(initial=initial_data, instance=user, 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/') - + else: + messages.warning(request, 'User not deleted|Invalid confirmation.') return redirect('/user/list/') else: form = UserAclForm() page_title = 'Add User' if request.method == 'POST': - if user_acl: + if user: form = UserAclForm(request.POST, instance=user, user_id=user.id) else: form = UserAclForm(request.POST) if form.is_valid(): - form.save() + saved_user = form.save() if form.cleaned_data.get('password1'): user_disconnected = False if user: @@ -116,10 +126,45 @@ def view_manage_user(request): if not user_disconnected: messages.warning(request, 'User Disconnected|The user '+ user.username +' has been disconnected.') user_disconnected = True - if user_acl: + + if user: 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}) + form_description = { + 'size': '', + 'content': ''' +

User Levels

+
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.

+ +
+

Peer Groups

+

Select which peer groups this user can access. If no peer groups are selected, the user will have access to all peers.

+ + ''' + } + + context = { + 'page_title': page_title, + 'form': form, + 'user_acl': user_acl, + 'instance': user_acl, + 'form_description': form_description, + 'delete_confirmation_message': 'Please type the username to proceed.' + } + return render(request, 'generic_form.html', context) \ No newline at end of file