mirror of
https://github.com/eduardogsilva/wireguard_webadmin.git
synced 2026-03-15 05:26:17 +00:00
add gatekeeper management views, forms, and templates
This commit is contained in:
248
gatekeeper/forms.py
Normal file
248
gatekeeper/forms.py
Normal file
@@ -0,0 +1,248 @@
|
||||
import pyotp
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Submit, HTML, Div, Field
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from gatekeeper.models import GatekeeperUser, GatekeeperGroup, AuthMethod, AuthMethodAllowedDomain, \
|
||||
AuthMethodAllowedEmail
|
||||
|
||||
|
||||
class GatekeeperUserForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = GatekeeperUser
|
||||
fields = ['username', 'email', 'password', 'totp_secret']
|
||||
labels = {
|
||||
'username': _('Username'),
|
||||
'email': _('Email'),
|
||||
'password': _('Password'),
|
||||
'totp_secret': _('TOTP Secret'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cancel_url = kwargs.pop('cancel_url', '#')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div('username', css_class='col-md-6'),
|
||||
Div('email', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(Field('password', type='password'), css_class='col-md-6'),
|
||||
Div('totp_secret', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Submit('submit', _('Save'), css_class='btn btn-primary'),
|
||||
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
|
||||
css_class='col-12 d-flex justify-content-end gap-2 mt-3'
|
||||
),
|
||||
css_class='row'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class GatekeeperGroupForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = GatekeeperGroup
|
||||
fields = ['name', 'users']
|
||||
labels = {
|
||||
'name': _('Group Name'),
|
||||
'users': _('Members'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cancel_url = kwargs.pop('cancel_url', '#')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div('name', css_class='col-md-12'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div('users', css_class='col-md-12'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Submit('submit', _('Save'), css_class='btn btn-primary'),
|
||||
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
|
||||
css_class='col-12 d-flex justify-content-end gap-2 mt-3'
|
||||
),
|
||||
css_class='row'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AuthMethodForm(forms.ModelForm):
|
||||
totp_pin = forms.CharField(
|
||||
label=_('TOTP Validation PIN'),
|
||||
max_length=6,
|
||||
required=False,
|
||||
help_text=_('Enter a 6-digit PIN generated by your authenticator app to validate the secret.')
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = AuthMethod
|
||||
fields = [
|
||||
'name', 'auth_type', 'totp_secret',
|
||||
'oidc_provider', 'oidc_client_id', 'oidc_client_secret'
|
||||
]
|
||||
labels = {
|
||||
'name': _('Name'),
|
||||
'auth_type': _('Authentication Type'),
|
||||
'totp_secret': _('Global TOTP Secret'),
|
||||
'oidc_provider': _('OIDC Provider URL'),
|
||||
'oidc_client_id': _('OIDC Client ID'),
|
||||
'oidc_client_secret': _('OIDC Client Secret'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cancel_url = kwargs.pop('cancel_url', '#')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
if self.instance and self.instance.pk:
|
||||
self.fields['auth_type'].disabled = True
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div('name', css_class='col-md-6'),
|
||||
Div('auth_type', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div('totp_secret', css_class='col-md-6'),
|
||||
Div('totp_pin', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div('oidc_provider', css_class='col-md-12'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div('oidc_client_id', css_class='col-md-6'),
|
||||
Div('oidc_client_secret', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Submit('submit', _('Save'), css_class='btn btn-primary'),
|
||||
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
|
||||
css_class='col-12 d-flex justify-content-end gap-2 mt-3'
|
||||
),
|
||||
css_class='row'
|
||||
)
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
auth_type = cleaned_data.get('auth_type')
|
||||
totp_secret = cleaned_data.get('totp_secret')
|
||||
oidc_provider = cleaned_data.get('oidc_provider')
|
||||
oidc_client_id = cleaned_data.get('oidc_client_id')
|
||||
oidc_client_secret = cleaned_data.get('oidc_client_secret')
|
||||
|
||||
if auth_type == 'local_password':
|
||||
if totp_secret:
|
||||
self.add_error('totp_secret', _('TOTP secret must be empty for Local Password authentication.'))
|
||||
if cleaned_data.get('totp_pin'):
|
||||
self.add_error('totp_pin', _('TOTP validation PIN must be empty for Local Password authentication.'))
|
||||
if oidc_provider or oidc_client_id or oidc_client_secret:
|
||||
self.add_error(None, _('OIDC fields must be empty for Local Password authentication.'))
|
||||
|
||||
existing_local = AuthMethod.objects.filter(auth_type='local_password')
|
||||
if self.instance and self.instance.pk:
|
||||
existing_local = existing_local.exclude(pk=self.instance.pk)
|
||||
if existing_local.exists():
|
||||
self.add_error('auth_type', _('Only one Local Password authentication method can be configured.'))
|
||||
elif auth_type == 'totp':
|
||||
if oidc_provider or oidc_client_id or oidc_client_secret:
|
||||
self.add_error(None, _('OIDC fields must be empty for TOTP authentication.'))
|
||||
if not totp_secret:
|
||||
self.add_error('totp_secret', _('TOTP secret is required for TOTP authentication.'))
|
||||
else:
|
||||
totp_pin = cleaned_data.get('totp_pin')
|
||||
if not totp_pin:
|
||||
self.add_error('totp_pin', _('Please provide a PIN to validate the TOTP secret.'))
|
||||
else:
|
||||
try:
|
||||
totp = pyotp.TOTP(totp_secret)
|
||||
if not totp.verify(totp_pin):
|
||||
self.add_error('totp_pin', _('Invalid TOTP PIN.'))
|
||||
except Exception:
|
||||
self.add_error('totp_secret', _('Invalid TOTP secret format. Must be a valid Base32 string.'))
|
||||
elif auth_type == 'oidc':
|
||||
if totp_secret:
|
||||
self.add_error('totp_secret', _('TOTP secret must be empty for OIDC authentication.'))
|
||||
if cleaned_data.get('totp_pin'):
|
||||
self.add_error('totp_pin', _('TOTP validation PIN must be empty for OIDC authentication.'))
|
||||
|
||||
return cleaned_data
|
||||
|
||||
class AuthMethodAllowedDomainForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = AuthMethodAllowedDomain
|
||||
fields = ['auth_method', 'domain']
|
||||
labels = {
|
||||
'auth_method': _('Authentication Method'),
|
||||
'domain': _('Domain'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cancel_url = kwargs.pop('cancel_url', '#')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div('auth_method', css_class='col-md-6'),
|
||||
Div('domain', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Submit('submit', _('Save'), css_class='btn btn-primary'),
|
||||
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
|
||||
css_class='col-12 d-flex justify-content-end gap-2 mt-3'
|
||||
),
|
||||
css_class='row'
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class AuthMethodAllowedEmailForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = AuthMethodAllowedEmail
|
||||
fields = ['auth_method', 'email']
|
||||
labels = {
|
||||
'auth_method': _('Authentication Method'),
|
||||
'email': _('Email'),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
cancel_url = kwargs.pop('cancel_url', '#')
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.layout = Layout(
|
||||
Div(
|
||||
Div('auth_method', css_class='col-md-6'),
|
||||
Div('email', css_class='col-md-6'),
|
||||
css_class='row'
|
||||
),
|
||||
Div(
|
||||
Div(
|
||||
Submit('submit', _('Save'), css_class='btn btn-primary'),
|
||||
HTML(f'<a href="{cancel_url}" class="btn btn-secondary">{_("Cancel")}</a>'),
|
||||
css_class='col-12 d-flex justify-content-end gap-2 mt-3'
|
||||
),
|
||||
css_class='row'
|
||||
)
|
||||
)
|
||||
28
gatekeeper/urls.py
Normal file
28
gatekeeper/urls.py
Normal file
@@ -0,0 +1,28 @@
|
||||
from django.urls import path
|
||||
|
||||
from gatekeeper import views
|
||||
|
||||
urlpatterns = [
|
||||
# Main Dashboard / List
|
||||
path('', views.view_gatekeeper_list, name='gatekeeper_list'),
|
||||
|
||||
# Gatekeeper Users
|
||||
path('user/manage/', views.view_manage_gatekeeper_user, name='manage_gatekeeper_user'),
|
||||
path('user/delete/', views.view_delete_gatekeeper_user, name='delete_gatekeeper_user'),
|
||||
|
||||
# Gatekeeper Groups
|
||||
path('group/manage/', views.view_manage_gatekeeper_group, name='manage_gatekeeper_group'),
|
||||
path('group/delete/', views.view_delete_gatekeeper_group, name='delete_gatekeeper_group'),
|
||||
|
||||
# Auth Methods
|
||||
path('auth_method/manage/', views.view_manage_auth_method, name='manage_gatekeeper_auth_method'),
|
||||
path('auth_method/delete/', views.view_delete_auth_method, name='delete_gatekeeper_auth_method'),
|
||||
|
||||
# Auth Method Allowed Domains
|
||||
path('domain/manage/', views.view_manage_auth_domain, name='manage_gatekeeper_domain'),
|
||||
path('domain/delete/', views.view_delete_auth_domain, name='delete_gatekeeper_domain'),
|
||||
|
||||
# Auth Method Allowed Emails
|
||||
path('email/manage/', views.view_manage_auth_email, name='manage_gatekeeper_email'),
|
||||
path('email/delete/', views.view_delete_auth_email, name='delete_gatekeeper_email'),
|
||||
]
|
||||
@@ -1 +1,335 @@
|
||||
# Create your views here.
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render, get_object_or_404, redirect
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext as _
|
||||
|
||||
from gatekeeper.forms import GatekeeperUserForm, GatekeeperGroupForm, AuthMethodForm, AuthMethodAllowedDomainForm, \
|
||||
AuthMethodAllowedEmailForm
|
||||
from gatekeeper.models import GatekeeperUser, GatekeeperGroup, AuthMethod, AuthMethodAllowedDomain, \
|
||||
AuthMethodAllowedEmail
|
||||
from user_manager.models import UserAcl
|
||||
|
||||
|
||||
@login_required
|
||||
def view_gatekeeper_list(request):
|
||||
"""Main list view containing tabs for Users, Groups, and Auth Methods"""
|
||||
if not UserAcl.objects.filter(user=request.user).filter(user_level__gte=20).exists():
|
||||
return render(request, 'access_denied.html', {'page_title': _('Access Denied')})
|
||||
|
||||
users = GatekeeperUser.objects.all().order_by('username')
|
||||
groups = GatekeeperGroup.objects.all().order_by('name')
|
||||
auth_methods = AuthMethod.objects.all().order_by('name')
|
||||
auth_domains = AuthMethodAllowedDomain.objects.all().order_by('domain')
|
||||
auth_emails = AuthMethodAllowedEmail.objects.all().order_by('email')
|
||||
|
||||
tab = request.GET.get('tab', 'users')
|
||||
|
||||
context = {
|
||||
'users': users,
|
||||
'groups': groups,
|
||||
'auth_methods': auth_methods,
|
||||
'auth_domains': auth_domains,
|
||||
'auth_emails': auth_emails,
|
||||
'active_tab': tab,
|
||||
}
|
||||
return render(request, 'gatekeeper/gatekeeper_list.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_manage_gatekeeper_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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
|
||||
if obj_uuid:
|
||||
obj = get_object_or_404(GatekeeperUser, uuid=obj_uuid)
|
||||
title = _('Edit Gatekeeper User')
|
||||
else:
|
||||
obj = None
|
||||
title = _('Create Gatekeeper User')
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=users'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = GatekeeperUserForm(request.POST, instance=obj, cancel_url=cancel_url)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _('Gatekeeper User saved successfully.'))
|
||||
return redirect(cancel_url)
|
||||
else:
|
||||
form = GatekeeperUserForm(instance=obj, cancel_url=cancel_url)
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': title,
|
||||
'page_title': title,
|
||||
}
|
||||
return render(request, 'generic_form.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_delete_gatekeeper_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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
obj = get_object_or_404(GatekeeperUser, uuid=obj_uuid)
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=users'
|
||||
|
||||
if request.method == 'POST':
|
||||
obj.delete()
|
||||
messages.success(request, _('Gatekeeper User deleted successfully.'))
|
||||
return redirect(cancel_url)
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'title': _('Delete Gatekeeper User'),
|
||||
'cancel_url': cancel_url,
|
||||
'text': _('Are you sure you want to delete the user "%(username)s"?') % {'username': obj.username}
|
||||
}
|
||||
return render(request, 'generic_delete_confirmation.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_manage_gatekeeper_group(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
|
||||
if obj_uuid:
|
||||
obj = get_object_or_404(GatekeeperGroup, uuid=obj_uuid)
|
||||
title = _('Edit Gatekeeper Group')
|
||||
else:
|
||||
obj = None
|
||||
title = _('Create Gatekeeper Group')
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=groups'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = GatekeeperGroupForm(request.POST, instance=obj, cancel_url=cancel_url)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _('Gatekeeper Group saved successfully.'))
|
||||
return redirect(cancel_url)
|
||||
else:
|
||||
form = GatekeeperGroupForm(instance=obj, cancel_url=cancel_url)
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': title,
|
||||
'page_title': title,
|
||||
}
|
||||
return render(request, 'generic_form.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_delete_gatekeeper_group(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
obj = get_object_or_404(GatekeeperGroup, uuid=obj_uuid)
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=groups'
|
||||
|
||||
if request.method == 'POST':
|
||||
obj.delete()
|
||||
messages.success(request, _('Gatekeeper Group deleted successfully.'))
|
||||
return redirect(cancel_url)
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'title': _('Delete Gatekeeper Group'),
|
||||
'cancel_url': cancel_url,
|
||||
'text': _('Are you sure you want to delete the group "%(name)s"?') % {'name': obj.name}
|
||||
}
|
||||
return render(request, 'generic_delete_confirmation.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_manage_auth_method(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
|
||||
if obj_uuid:
|
||||
obj = get_object_or_404(AuthMethod, uuid=obj_uuid)
|
||||
title = _('Edit Authentication Method')
|
||||
else:
|
||||
obj = None
|
||||
title = _('Create Authentication Method')
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=auth_methods'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AuthMethodForm(request.POST, instance=obj, cancel_url=cancel_url)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _('Authentication Method saved successfully.'))
|
||||
return redirect(cancel_url)
|
||||
else:
|
||||
form = AuthMethodForm(instance=obj, cancel_url=cancel_url)
|
||||
|
||||
form_description = {
|
||||
'size': '',
|
||||
'content': _('''
|
||||
<h5>Authentication Types</h5>
|
||||
<p>Select how users will authenticate through this method.</p>
|
||||
<ul>
|
||||
<li><strong>Local Password</strong>: Users will authenticate using a standard username and password stored locally. Only one of this type can be created.</li>
|
||||
<li><strong>TOTP (Time-Based One-Time Password)</strong>: Users will need to enter a rotating token from an authenticator app. Requires setting a Global TOTP Secret.</li>
|
||||
<li><strong>OIDC (OpenID Connect)</strong>: Users will authenticate via an external identity provider (like Keycloak, Google, or Authelia). Requires Provider URL, Client ID, and Client Secret.</li>
|
||||
</ul>
|
||||
''')
|
||||
}
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': title,
|
||||
'page_title': title,
|
||||
'form_description': form_description,
|
||||
}
|
||||
return render(request, 'generic_form.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_delete_auth_method(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
obj = get_object_or_404(AuthMethod, uuid=obj_uuid)
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=auth_methods'
|
||||
|
||||
if request.method == 'POST':
|
||||
obj.delete()
|
||||
messages.success(request, _('Authentication Method deleted successfully.'))
|
||||
return redirect(cancel_url)
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'title': _('Delete Authentication Method'),
|
||||
'cancel_url': cancel_url,
|
||||
'text': _('Are you sure you want to delete the authentication method "%(name)s"?') % {'name': obj.name}
|
||||
}
|
||||
return render(request, 'generic_delete_confirmation.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_manage_auth_domain(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
|
||||
if obj_uuid:
|
||||
obj = get_object_or_404(AuthMethodAllowedDomain, uuid=obj_uuid)
|
||||
title = _('Edit Allowed Domain')
|
||||
else:
|
||||
obj = None
|
||||
title = _('Add Allowed Domain')
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=allowed_identities'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AuthMethodAllowedDomainForm(request.POST, instance=obj, cancel_url=cancel_url)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _('Allowed Domain saved successfully.'))
|
||||
return redirect(cancel_url)
|
||||
else:
|
||||
form = AuthMethodAllowedDomainForm(instance=obj, cancel_url=cancel_url)
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': title,
|
||||
'page_title': title,
|
||||
}
|
||||
return render(request, 'generic_form.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_delete_auth_domain(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
obj = get_object_or_404(AuthMethodAllowedDomain, uuid=obj_uuid)
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=allowed_identities'
|
||||
|
||||
if request.method == 'POST':
|
||||
obj.delete()
|
||||
messages.success(request, _('Allowed Domain deleted successfully.'))
|
||||
return redirect(cancel_url)
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'title': _('Delete Allowed Domain'),
|
||||
'cancel_url': cancel_url,
|
||||
'text': _('Are you sure you want to delete the allowed domain "%(domain)s"?') % {'domain': obj.domain}
|
||||
}
|
||||
return render(request, 'generic_delete_confirmation.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_manage_auth_email(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
|
||||
if obj_uuid:
|
||||
obj = get_object_or_404(AuthMethodAllowedEmail, uuid=obj_uuid)
|
||||
title = _('Edit Allowed Email')
|
||||
else:
|
||||
obj = None
|
||||
title = _('Add Allowed Email')
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=allowed_identities'
|
||||
|
||||
if request.method == 'POST':
|
||||
form = AuthMethodAllowedEmailForm(request.POST, instance=obj, cancel_url=cancel_url)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, _('Allowed Email saved successfully.'))
|
||||
return redirect(cancel_url)
|
||||
else:
|
||||
form = AuthMethodAllowedEmailForm(instance=obj, cancel_url=cancel_url)
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': title,
|
||||
'page_title': title,
|
||||
}
|
||||
return render(request, 'generic_form.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def view_delete_auth_email(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')})
|
||||
|
||||
obj_uuid = request.GET.get('uuid')
|
||||
obj = get_object_or_404(AuthMethodAllowedEmail, uuid=obj_uuid)
|
||||
|
||||
cancel_url = reverse('gatekeeper_list') + '?tab=allowed_identities'
|
||||
|
||||
if request.method == 'POST':
|
||||
obj.delete()
|
||||
messages.success(request, _('Allowed Email deleted successfully.'))
|
||||
return redirect(cancel_url)
|
||||
|
||||
context = {
|
||||
'object': obj,
|
||||
'title': _('Delete Allowed Email'),
|
||||
'cancel_url': cancel_url,
|
||||
'text': _('Are you sure you want to delete the allowed email "%(email)s"?') % {'email': obj.email}
|
||||
}
|
||||
return render(request, 'generic_delete_confirmation.html', context)
|
||||
|
||||
@@ -15,6 +15,7 @@ packaging==26.0
|
||||
pillow==12.1.1
|
||||
polib==1.2.0
|
||||
pycparser==3.0
|
||||
pyotp==2.9.0
|
||||
pypng==0.20220715.0
|
||||
pyproject_hooks==1.2.0
|
||||
pytz==2025.2
|
||||
|
||||
244
templates/gatekeeper/gatekeeper_list.html
Normal file
244
templates/gatekeeper/gatekeeper_list.html
Normal file
@@ -0,0 +1,244 @@
|
||||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card card-primary card-outline">
|
||||
<div class="card-body">
|
||||
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'users' or active_tab == 'groups' %}active{% endif %}"
|
||||
href="{% url 'gatekeeper_list' %}?tab=users" role="tab">
|
||||
{% trans 'Gatekeeper Users' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'auth_methods' %}active{% endif %}"
|
||||
href="{% url 'gatekeeper_list' %}?tab=auth_methods" role="tab">
|
||||
{% trans 'Authentication Methods' %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% if active_tab == 'allowed_identities' %}active{% endif %}"
|
||||
href="{% url 'gatekeeper_list' %}?tab=allowed_identities" role="tab">
|
||||
{% trans 'Allowed Emails & Domains' %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content mt-4">
|
||||
{% if active_tab == 'users' or active_tab == 'groups' %}
|
||||
<div class="mb-3">
|
||||
<div class="btn-group mb-3">
|
||||
<a href="{% url 'gatekeeper_list' %}?tab=users" class="btn {% if active_tab == 'users' %}btn-primary{% else %}btn-outline-primary{% endif %}">
|
||||
{% trans 'Users' %}
|
||||
</a>
|
||||
<a href="{% url 'gatekeeper_list' %}?tab=groups" class="btn {% if active_tab == 'groups' %}btn-primary{% else %}btn-outline-primary{% endif %}">
|
||||
{% trans 'Groups' %}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if active_tab == 'users' %}
|
||||
<div class="mb-3">
|
||||
<a href="{% url 'manage_gatekeeper_user' %}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> {% trans 'Add User' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
|
||||
{% if users %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Username' %}</th>
|
||||
<th>{% trans 'Email' %}</th>
|
||||
<th>{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.username }}</td>
|
||||
<td>{{ user.email }}</td>
|
||||
<td style="width: 15%">
|
||||
<a href="{% url 'manage_gatekeeper_user' %}?uuid={{ user.uuid }}"
|
||||
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_gatekeeper_user' %}?uuid={{ user.uuid }}"
|
||||
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
{% trans 'No Gatekeeper Users found.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% elif active_tab == 'groups' %}
|
||||
<div class="mb-3">
|
||||
<a href="{% url 'manage_gatekeeper_group' %}" class="btn btn-outline-primary btn-sm">
|
||||
<i class="fas fa-plus"></i> {% trans 'Add Group' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if groups %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Group Name' %}</th>
|
||||
<th>{% trans 'Members' %}</th>
|
||||
<th>{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for group in groups %}
|
||||
<tr>
|
||||
<td>{{ group.name }}</td>
|
||||
<td>{{ group.users.count }}</td>
|
||||
<td style="width: 15%">
|
||||
<a href="{% url 'manage_gatekeeper_group' %}?uuid={{ group.uuid }}"
|
||||
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_gatekeeper_group' %}?uuid={{ group.uuid }}"
|
||||
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
{% trans 'No Gatekeeper Groups found.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
|
||||
{% elif active_tab == 'auth_methods' %}
|
||||
<div class="mb-3">
|
||||
<a href="{% url 'manage_gatekeeper_auth_method' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> {% trans 'Add Auth Method' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if auth_methods %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Name' %}</th>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for method in auth_methods %}
|
||||
<tr>
|
||||
<td>{{ method.name }}</td>
|
||||
<td>{{ method.get_auth_type_display }}</td>
|
||||
<td style="width: 15%">
|
||||
<a href="{% url 'manage_gatekeeper_auth_method' %}?uuid={{ method.uuid }}"
|
||||
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_gatekeeper_auth_method' %}?uuid={{ method.uuid }}"
|
||||
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
{% trans 'No Authentication Methods found.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% elif active_tab == 'allowed_identities' %}
|
||||
<div class="mb-3">
|
||||
<a href="{% url 'manage_gatekeeper_email' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> {% trans 'Add Allowed Email' %}
|
||||
</a>
|
||||
<a href="{% url 'manage_gatekeeper_domain' %}" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> {% trans 'Add Allowed Domain' %}
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{% if auth_emails or auth_domains %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans 'Type' %}</th>
|
||||
<th>{% trans 'Identity' %}</th>
|
||||
<th>{% trans 'Auth Method' %}</th>
|
||||
<th>{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for email in auth_emails %}
|
||||
<tr>
|
||||
<td><span class="badge badge-info">{% trans 'Email' %}</span></td>
|
||||
<td>{{ email.email }}</td>
|
||||
<td>{{ email.auth_method.name }}</td>
|
||||
<td style="width: 15%">
|
||||
<a href="{% url 'manage_gatekeeper_email' %}?uuid={{ email.uuid }}"
|
||||
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_gatekeeper_email' %}?uuid={{ email.uuid }}"
|
||||
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% for domain in auth_domains %}
|
||||
<tr>
|
||||
<td><span class="badge badge-secondary">{% trans 'Domain' %}</span></td>
|
||||
<td>{{ domain.domain }}</td>
|
||||
<td>{{ domain.auth_method.name }}</td>
|
||||
<td style="width: 15%">
|
||||
<a href="{% url 'manage_gatekeeper_domain' %}?uuid={{ domain.uuid }}"
|
||||
class="btn btn-sm btn-info" title="{% trans 'Edit' %}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a href="{% url 'delete_gatekeeper_domain' %}?uuid={{ domain.uuid }}"
|
||||
class="btn btn-sm btn-danger" title="{% trans 'Delete' %}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-info">
|
||||
{% trans 'No Allowed Emails or Domains found.' %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -22,6 +22,7 @@ urlpatterns = [
|
||||
path('firewall/', include('firewall.urls')),
|
||||
path('peer/', include('wireguard_peer.urls')),
|
||||
path('routing-templates/', include('routing_templates.urls')),
|
||||
path('gatekeeper/', include('gatekeeper.urls')),
|
||||
path('scheduler/', include('scheduler.urls')),
|
||||
path('server/', include('wireguard.urls')),
|
||||
path('tools/', include('wireguard_tools.urls')),
|
||||
|
||||
Reference in New Issue
Block a user