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.

This commit is contained in:
Eduardo Silva 2025-01-20 15:59:24 -03:00
parent 8eb0a6d852
commit 4df10f6499
3 changed files with 158 additions and 28 deletions

View File

@ -6,6 +6,7 @@
<tr> <tr>
<th>Username</th> <th>Username</th>
<th>User Level</th> <th>User Level</th>
<th>Peer Groups</th>
<th></th> <th></th>
</tr> </tr>
</thead> </thead>
@ -14,6 +15,15 @@
<tr> <tr>
<td>{{ user_acl.user.username }}</td> <td>{{ user_acl.user.username }}</td>
<td>{{ user_acl.get_user_level_display }}</td> <td>{{ user_acl.get_user_level_display }}</td>
<td>
{% if user_acl.peer_groups.all %}
{% for peer_group in user_acl.peer_groups.all %}
<a href="/user/peer-group/manage/?uuid={{ peer_group.uuid }}">{{ peer_group.name }}</a>{% if not forloop.last %}, {% endif %}
{% endfor %}
{% else %}
Any
{% endif %}
</td>
<td style="width: 1%; white-space: nowrap;"> <td style="width: 1%; white-space: nowrap;">
<a href="/user/manage/?uuid={{ user_acl.uuid }}" ><i class="far fa-edit"></i></a> <a href="/user/manage/?uuid={{ user_acl.uuid }}" ><i class="far fa-edit"></i></a>
</td> </td>

View File

@ -8,20 +8,67 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column, Submit, HTML 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") user_level = forms.ChoiceField(choices=UserAcl.user_level.field.choices, required=True, label="User Level")
peer_groups = forms.ModelMultipleChoiceField(
class Meta(UserCreationForm.Meta): queryset=PeerGroup.objects.all(),
model = User required=False,
fields = UserCreationForm.Meta.fields + ('user_level',) )
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.instance = kwargs.pop('instance', None)
self.user_id = kwargs.pop('user_id', None) self.user_id = kwargs.pop('user_id', None)
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.instance and self.instance.pk:
self.fields['password1'].required = False if self.instance:
self.fields['password2'].required = False self.fields['username'].initial = self.instance.username
self.fields['username'].widget.attrs['readonly'] = True 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 = "<a href='javascript:void(0)' class='btn btn-outline-danger' data-command='delete' onclick='openCommandDialog(this)'>Delete</a>"
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(' <a class="btn btn-secondary" href="/user/list/">Back</a> '),
HTML(delete_html),
css_class='col-md-12'),
css_class='form-row'
)
)
def clean_username(self): def clean_username(self):
username = self.cleaned_data.get('username') username = self.cleaned_data.get('username')
@ -29,23 +76,51 @@ class UserAclForm(UserCreationForm):
raise ValidationError("A user with that username already exists.") raise ValidationError("A user with that username already exists.")
return username return username
def save(self, commit=True): def clean(self):
user = super().save(commit=False) cleaned_data = super().clean()
new_password = self.cleaned_data.get("password1") password1 = cleaned_data.get('password1')
password2 = cleaned_data.get('password2')
if new_password: if not self.instance:
user.set_password(new_password) 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() user.save()
else: else:
if not user.id: user = User.objects.create_user(
user.save() username=username,
password=password
)
if commit:
user_acl, created = UserAcl.objects.update_or_create( user_acl, created = UserAcl.objects.update_or_create(
user=user, user=user,
defaults={'user_level': self.cleaned_data.get('user_level')} defaults={
'user_level': user_level
}
) )
user_acl.peer_groups.set(peer_groups)
return user return user

View File

@ -80,33 +80,43 @@ def view_user_list(request):
def view_manage_user(request): def view_manage_user(request):
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists(): if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=50).exists():
return render(request, 'access_denied.html', {'page_title': 'Access Denied'}) return render(request, 'access_denied.html', {'page_title': 'Access Denied'})
user_acl = None user_acl = None
user = None user = None
initial_data = {}
if 'uuid' in request.GET: if 'uuid' in request.GET:
user_acl = get_object_or_404(UserAcl, uuid=request.GET['uuid']) user_acl = get_object_or_404(UserAcl, uuid=request.GET['uuid'])
user = user_acl.user 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 page_title = 'Edit User '+ user.username
if request.GET.get('action') == 'delete': if request.GET.get('action') == 'delete':
username = user.username username = user.username
if request.GET.get('confirmation') == username: if request.GET.get('confirmation') == username:
user.delete() user.delete()
messages.success(request, 'User deleted|The user '+ username +' has been deleted.') messages.success(request, 'User deleted|The user '+ username +' has been deleted.')
return redirect('/user/list/') return redirect('/user/list/')
else:
messages.warning(request, 'User not deleted|Invalid confirmation.')
return redirect('/user/list/') return redirect('/user/list/')
else: else:
form = UserAclForm() form = UserAclForm()
page_title = 'Add User' page_title = 'Add User'
if request.method == 'POST': if request.method == 'POST':
if user_acl: if user:
form = UserAclForm(request.POST, instance=user, user_id=user.id) form = UserAclForm(request.POST, instance=user, user_id=user.id)
else: else:
form = UserAclForm(request.POST) form = UserAclForm(request.POST)
if form.is_valid(): if form.is_valid():
form.save() saved_user = form.save()
if form.cleaned_data.get('password1'): if form.cleaned_data.get('password1'):
user_disconnected = False user_disconnected = False
if user: if user:
@ -116,10 +126,45 @@ def view_manage_user(request):
if not user_disconnected: if not user_disconnected:
messages.warning(request, 'User Disconnected|The user '+ user.username +' has been disconnected.') messages.warning(request, 'User Disconnected|The user '+ user.username +' has been disconnected.')
user_disconnected = True user_disconnected = True
if user_acl:
if user:
messages.success(request, 'User updated|The user '+ form.cleaned_data['username'] +' has been updated.') messages.success(request, 'User updated|The user '+ form.cleaned_data['username'] +' has been updated.')
else: else:
messages.success(request, 'User added|The user '+ form.cleaned_data['username'] +' has been added.') messages.success(request, 'User added|The user '+ form.cleaned_data['username'] +' has been added.')
return redirect('/user/list/') 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': '''
<h4>User Levels</h4>
<h5>Debugging Analyst</h5>
<p>Access to basic system information and logs for troubleshooting. No access to modify settings or view sensitive data such as peer keys.</p>
<h5>View Only User</h5>
<p>Full view access, including peer keys and configuration files. Cannot modify any settings or configurations.</p>
<h5>Peer Manager</h5>
<p>Permissions to add, edit, and remove peers and IP addresses. Does not include access to modify WireGuard instance configurations or higher-level settings.</p>
<h5>Wireguard Manager</h5>
<p>Authority to add, edit, and remove configurations of WireGuard instances.</p>
<h5>Administrator</h5>
<p>Full access across the system. Can view and modify all settings, configurations and manage users. </p>
<br>
<h4>Peer Groups</h4>
<p>Select which peer groups this user can access. If no peer groups are selected, the user will have access to all peers.</p>
'''
}
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)