feature/enforce_login (#32)

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#32
This commit is contained in:
2025-07-23 07:27:57 +12:00
parent df896878f1
commit 43c95e3da7
8 changed files with 126 additions and 76 deletions

View File

@ -62,6 +62,9 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
] ]
if os.environ.get('REGISTRATION_MANDATORY', False) == 'True':
MIDDLEWARE.append('epdb.middleware.login_required_middleware.LoginRequiredMiddleware')
ROOT_URLCONF = 'envipath.urls' ROOT_URLCONF = 'envipath.urls'
TEMPLATES = [ TEMPLATES = [
@ -147,7 +150,7 @@ ADMIN_APPROVAL_REQUIRED = os.environ.get('ADMIN_APPROVAL_REQUIRED', 'False') ==
# SESAME_MAX_AGE = 300 # SESAME_MAX_AGE = 300
# # TODO set to "home" # # TODO set to "home"
# LOGIN_REDIRECT_URL = "/" # LOGIN_REDIRECT_URL = "/"
# LOGIN_URL = '/login/' LOGIN_URL = '/login/'
SERVER_URL = os.environ.get('SERVER_URL', 'http://localhost:8000') SERVER_URL = os.environ.get('SERVER_URL', 'http://localhost:8000')

View File

View File

@ -0,0 +1,21 @@
from django.conf import settings
from django.shortcuts import redirect
from django.urls import reverse
class LoginRequiredMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.exempt_urls = [
reverse('login'),
reverse('logout'),
reverse('admin:login'),
reverse('admin:index'),
] + getattr(settings, 'LOGIN_EXEMPT_URLS', [])
def __call__(self, request):
if not request.user.is_authenticated:
path = request.path_info
if not any(path.startswith(url) for url in self.exempt_urls):
return redirect(settings.LOGIN_URL)
return self.get_response(request)

View File

@ -13,7 +13,8 @@ urlpatterns = [
# Home # Home
re_path(r'^$', v.index, name='index'), re_path(r'^$', v.index, name='index'),
# re_path(r'^login', v.login, name='login'), re_path(r'^login', v.login, name='login'),
re_path(r'^logout', v.logout, name='logout'),
# Top level urls # Top level urls
re_path(r'^package$', v.packages, name='packages'), re_path(r'^package$', v.packages, name='packages'),

View File

@ -8,7 +8,6 @@ from django.contrib.auth import get_user_model
from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest
from django.shortcuts import render, redirect from django.shortcuts import render, redirect
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth.decorators import login_required
from utilities.chem import FormatConverter, IndigoUtils from utilities.chem import FormatConverter, IndigoUtils
from .logic import GroupManager, PackageManager, UserManager, SettingManager, SearchManager from .logic import GroupManager, PackageManager, UserManager, SettingManager, SearchManager
@ -18,12 +17,81 @@ from .models import Package, GroupPackagePermission, Group, CompoundStructure, C
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def log_post_params(request): def log_post_params(request):
if s.DEBUG: if s.DEBUG:
for k, v in request.POST.items(): for k, v in request.POST.items():
logger.debug(f"{k}\t{v}") logger.debug(f"{k}\t{v}")
def login(request):
current_user = _anonymous_or_real(request)
context = get_base_context(request)
if request.method == 'GET':
context['title'] = 'enviPath'
return render(request, 'login.html', context)
elif request.method == 'POST':
is_login = bool(request.POST.get('login', False))
is_register = bool(request.POST.get('register', False))
if is_login:
from django.contrib.auth import authenticate
from django.contrib.auth import login
username = request.POST.get('username')
password = request.POST.get('password')
# Get email for username and check if account is active
try:
temp_user = get_user_model().objects.get(username=username)
if not temp_user.is_active:
context['message'] = "User account is not activated yet!"
return render(request, 'login.html', context)
email = temp_user.email
except get_user_model().DoesNotExist:
context['message'] = "Login failed!"
return render(request, 'login.html', context)
user = authenticate(username=email, password=password)
if user is not None:
login(request, user)
return redirect(s.SERVER_URL)
else:
context['message'] = "Login failed!"
return render(request, 'login.html', context)
elif is_register:
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
rpassword = request.POST.get('rpassword')
if password != rpassword:
pass
u = UserManager.create_user(username, email, password)
context['message'] = "Your account has been created! An admin will activate it soon!"
return render(request, 'login.html', context)
def logout(request):
if request.method == 'POST':
is_logout = bool(request.POST.get('logout', False))
if is_logout:
from django.contrib.auth import logout
logout(request)
return redirect(s.SERVER_URL)
return HttpResponseBadRequest()
def catch_exceptions(view_func): def catch_exceptions(view_func):
@wraps(view_func) @wraps(view_func)
def _wrapped_view(request, *args, **kwargs): def _wrapped_view(request, *args, **kwargs):
@ -38,6 +106,7 @@ def catch_exceptions(view_func):
) )
else: else:
return render(request, 'errors/error.html', get_base_context(request)) return render(request, 'errors/error.html', get_base_context(request))
return _wrapped_view return _wrapped_view
@ -60,7 +129,6 @@ def editable(request, user):
return False return False
def get_base_context(request) -> Dict[str, Any]: def get_base_context(request) -> Dict[str, Any]:
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -121,16 +189,6 @@ def index(request):
return render(request, 'index/index.html', context) return render(request, 'index/index.html', context)
# def login(request):
# current_user = _anonymous_or_real(request)
# if request.method == 'GET':
# context = get_base_context(request)
# context['title'] = 'enviPath'
# return render(request, 'login.html', context)
# else:
# return HttpResponseBadRequest()
#
# @login_required(login_url='/login')
def packages(request): def packages(request):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -688,6 +746,7 @@ def package(request, package_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/compound # https://envipath.org/package/<id>/compound
def package_compounds(request, package_uuid): def package_compounds(request, package_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -814,6 +873,7 @@ def package_compound_structures(request, package_uuid, compound_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/compound/<id>/structure/<id> # https://envipath.org/package/<id>/compound/<id>/structure/<id>
def package_compound_structure(request, package_uuid, compound_uuid, structure_uuid): def package_compound_structure(request, package_uuid, compound_uuid, structure_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -893,12 +953,14 @@ def package_rules(request, package_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
r = Rule.create(rule_type=rule_type, package=current_package, name=rule_name, description=rule_description, **params) r = Rule.create(rule_type=rule_type, package=current_package, name=rule_name, description=rule_description,
**params)
return redirect(r.url) return redirect(r.url)
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/rule/<id> # https://envipath.org/package/<id>/rule/<id>
def package_rule(request, package_uuid, rule_uuid): def package_rule(request, package_uuid, rule_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -945,6 +1007,7 @@ def package_rule(request, package_uuid, rule_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/reaction # https://envipath.org/package/<id>/reaction
def package_reactions(request, package_uuid): def package_reactions(request, package_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -995,6 +1058,7 @@ def package_reactions(request, package_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/reaction/<id> # https://envipath.org/package/<id>/reaction/<id>
def package_reaction(request, package_uuid, reaction_uuid): def package_reaction(request, package_uuid, reaction_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -1039,6 +1103,7 @@ def package_reaction(request, package_uuid, reaction_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway # https://envipath.org/package/<id>/pathway
def package_pathways(request, package_uuid): def package_pathways(request, package_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -1115,6 +1180,7 @@ def package_pathways(request, package_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id> # https://envipath.org/package/<id>/pathway/<id>
def package_pathway(request, package_uuid, pathway_uuid): def package_pathway(request, package_uuid, pathway_uuid):
current_user: User = _anonymous_or_real(request) current_user: User = _anonymous_or_real(request)
@ -1186,6 +1252,7 @@ def package_pathway(request, package_uuid, pathway_uuid):
else: else:
return HttpResponseBadRequest() return HttpResponseBadRequest()
# https://envipath.org/package/<id>/pathway/<id>/node # https://envipath.org/package/<id>/pathway/<id>/node
def package_pathway_nodes(request, package_uuid, pathway_uuid): def package_pathway_nodes(request, package_uuid, pathway_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -1452,49 +1519,6 @@ def users(request):
return render(request, 'collections/objects_list.html', context) return render(request, 'collections/objects_list.html', context)
if request.method == 'POST':
is_login = bool(request.POST.get('login', False))
is_register = bool(request.POST.get('register', False))
if is_login:
from django.contrib.auth import authenticate
from django.contrib.auth import login
username = request.POST.get('username')
password = request.POST.get('password')
# Get email for username and check if account is active
try:
temp_user = get_user_model().objects.get(username=username)
if not temp_user.is_active:
return render(request, 'errors/user_account_inactive.html', status=403)
email = temp_user.email
except get_user_model().DoesNotExist:
return HttpResponseBadRequest()
user = authenticate(username=email, password=password)
if user is not None:
login(request, user)
return redirect(s.SERVER_URL)
else:
return HttpResponseBadRequest()
elif is_register:
username = request.POST.get('username')
email = request.POST.get('email')
password = request.POST.get('password')
rpassword = request.POST.get('rpassword')
if password != rpassword:
pass
u = UserManager.create_user(username, email, password)
return redirect(s.SERVER_URL)
def user(request, user_uuid): def user(request, user_uuid):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
@ -1551,14 +1575,6 @@ def user(request, user_uuid):
return HttpResponse("success") return HttpResponse("success")
is_logout = bool(request.POST.get('logout', False))
if is_logout:
from django.contrib.auth import logout
logout(request)
return redirect(s.SERVER_URL)
default_package = request.POST.get('default-package') default_package = request.POST.get('default-package')
default_group = request.POST.get('default-group') default_group = request.POST.get('default-group')
default_prediction_setting = request.POST.get('default-prediction-setting') default_prediction_setting = request.POST.get('default-prediction-setting')
@ -1652,7 +1668,8 @@ def group(request, group_uuid):
context['users'] = get_user_model().objects.exclude(id__in=current_group.user_member.all()) context['users'] = get_user_model().objects.exclude(id__in=current_group.user_member.all())
context['groups'] = Group.objects.exclude(id__in=current_group.group_member.all()).exclude(id=current_group.pk) context['groups'] = Group.objects.exclude(id__in=current_group.group_member.all()).exclude(id=current_group.pk)
context['packages'] = Package.objects.filter(id__in=GroupPackagePermission.objects.filter(group=current_group).values('package').distinct()) context['packages'] = Package.objects.filter(
id__in=GroupPackagePermission.objects.filter(group=current_group).values('package').distinct())
return render(request, 'objects/group.html', context) return render(request, 'objects/group.html', context)
@ -1682,6 +1699,7 @@ def group(request, group_uuid):
return redirect(current_group.url) return redirect(current_group.url)
def settings(request): def settings(request):
current_user = _anonymous_or_real(request) current_user = _anonymous_or_real(request)
context = get_base_context(request) context = get_base_context(request)
@ -1705,8 +1723,10 @@ def settings(request):
description = request.POST.get('prediction-setting-description') description = request.POST.get('prediction-setting-description')
new_default = request.POST.get('prediction-setting-new-default', 'off') == 'on' new_default = request.POST.get('prediction-setting-new-default', 'off') == 'on'
max_nodes = min(max(int(request.POST.get('prediction-setting-max-nodes', 1)), s.DEFAULT_MAX_NUMBER_OF_NODES), s.DEFAULT_MAX_NUMBER_OF_NODES) max_nodes = min(max(int(request.POST.get('prediction-setting-max-nodes', 1)), s.DEFAULT_MAX_NUMBER_OF_NODES),
max_depth = min(max(int(request.POST.get('prediction-setting-max-depth', 1)), s.DEFAULT_MAX_DEPTH), s.DEFAULT_MAX_DEPTH) s.DEFAULT_MAX_NUMBER_OF_NODES)
max_depth = min(max(int(request.POST.get('prediction-setting-max-depth', 1)), s.DEFAULT_MAX_DEPTH),
s.DEFAULT_MAX_DEPTH)
tp_gen_method = request.POST.get('tp-generation-method') tp_gen_method = request.POST.get('tp-generation-method')

View File

@ -152,7 +152,7 @@
</li> </li>
<li class="divider"></li> <li class="divider"></li>
<form class="navbar-form navbar-left navbar-left-framework" role="logout" <form class="navbar-form navbar-left navbar-left-framework" role="logout"
action="{{ meta.user.url }}" method="post"> action="{% url 'logout' %}" method="post">
{% csrf_token %} {% csrf_token %}
<div class="form-group"> <div class="form-group">
<input type="hidden" name="logout" value="true"> <input type="hidden" name="logout" value="true">

View File

@ -5,7 +5,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Login Modal with Blur</title> <title>enviPath - Login</title>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Bootstrap 3.3.7 CSS --> <!-- Bootstrap 3.3.7 CSS -->
@ -55,6 +55,12 @@
<div class="bg-blur"></div> <div class="bg-blur"></div>
<div class="bg-dim"></div> <div class="bg-dim"></div>
{% if message %}
<div class="alert alert-danger" role="alert">
{{ message }}
</div>
{% endif %}
<!-- Trigger Button --> <!-- Trigger Button -->
<div class="center-button"> <div class="center-button">
<button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#signupmodal">Login / Sign Up</button> <button class="btn btn-primary btn-lg" data-toggle="modal" data-target="#signupmodal">Login / Sign Up</button>
@ -83,7 +89,7 @@
<div class="modal-body"> <div class="modal-body">
<div id="myTabContent" class="tab-content"> <div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="signin"> <div class="tab-pane fade active in" id="signin">
<form class="form-horizontal" method="post" action="{{ meta.server_url }}/user"> <form class="form-horizontal" method="post" action="{% url 'login' %}">
{% csrf_token %} {% csrf_token %}
<fieldset> <fieldset>
<input type="hidden" name="login" id="login" value="true"/> <input type="hidden" name="login" id="login" value="true"/>
@ -140,8 +146,7 @@
</div> </div>
<form id="signup-action" class="form-horizontal" action="{{ meta.server_url }}/user" <form id="signup-action" class="form-horizontal" action="{% url 'login' %}" method="post">
method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="register" id="register" value="true"/> <input type="hidden" name="register" id="register" value="true"/>

View File

@ -19,7 +19,7 @@
<div class="modal-body"> <div class="modal-body">
<div id="myTabContent" class="tab-content"> <div id="myTabContent" class="tab-content">
<div class="tab-pane fade active in" id="signin"> <div class="tab-pane fade active in" id="signin">
<form class="form-horizontal" method="post" action="{{ meta.server_url }}/user"> <form class="form-horizontal" method="post" action="{% url 'login' %}">
{% csrf_token %} {% csrf_token %}
<fieldset> <fieldset>
<input type="hidden" name="login" id="login" value="true"/> <input type="hidden" name="login" id="login" value="true"/>
@ -73,7 +73,7 @@
</div> </div>
<form id="signup-action" class="form-horizontal" action="{{ meta.server_url }}/user" method="post"> <form id="signup-action" class="form-horizontal" action="{% url 'login' %}" method="post">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="register" id="register" value="true"/> <input type="hidden" name="register" id="register" value="true"/>