From 43c95e3da7872076f90ece880212c223ecc88987 Mon Sep 17 00:00:00 2001 From: jebus Date: Wed, 23 Jul 2025 07:27:57 +1200 Subject: [PATCH] feature/enforce_login (#32) Co-authored-by: Tim Lorsbach Reviewed-on: https://git.envipath.com/enviPath/enviPy/pulls/32 --- envipath/settings.py | 5 +- epdb/middleware/__init__.py | 0 epdb/middleware/login_required_middleware.py | 21 +++ epdb/urls.py | 3 +- epdb/views.py | 154 +++++++++++-------- templates/framework.html | 2 +- templates/login.html | 13 +- templates/modals/signup_modal.html | 4 +- 8 files changed, 126 insertions(+), 76 deletions(-) create mode 100644 epdb/middleware/__init__.py create mode 100644 epdb/middleware/login_required_middleware.py diff --git a/envipath/settings.py b/envipath/settings.py index 2ca661de..d891bb07 100644 --- a/envipath/settings.py +++ b/envipath/settings.py @@ -62,6 +62,9 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] +if os.environ.get('REGISTRATION_MANDATORY', False) == 'True': + MIDDLEWARE.append('epdb.middleware.login_required_middleware.LoginRequiredMiddleware') + ROOT_URLCONF = 'envipath.urls' TEMPLATES = [ @@ -147,7 +150,7 @@ ADMIN_APPROVAL_REQUIRED = os.environ.get('ADMIN_APPROVAL_REQUIRED', 'False') == # SESAME_MAX_AGE = 300 # # TODO set to "home" # LOGIN_REDIRECT_URL = "/" -# LOGIN_URL = '/login/' +LOGIN_URL = '/login/' SERVER_URL = os.environ.get('SERVER_URL', 'http://localhost:8000') diff --git a/epdb/middleware/__init__.py b/epdb/middleware/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/epdb/middleware/login_required_middleware.py b/epdb/middleware/login_required_middleware.py new file mode 100644 index 00000000..0c33001c --- /dev/null +++ b/epdb/middleware/login_required_middleware.py @@ -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) diff --git a/epdb/urls.py b/epdb/urls.py index 86839530..c79ebf41 100644 --- a/epdb/urls.py +++ b/epdb/urls.py @@ -13,7 +13,8 @@ urlpatterns = [ # Home 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 re_path(r'^package$', v.packages, name='packages'), diff --git a/epdb/views.py b/epdb/views.py index 9973611e..387b461c 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -8,7 +8,6 @@ from django.contrib.auth import get_user_model from django.http import JsonResponse, HttpResponse, HttpResponseNotAllowed, HttpResponseBadRequest from django.shortcuts import render, redirect from django.views.decorators.csrf import csrf_exempt -from django.contrib.auth.decorators import login_required from utilities.chem import FormatConverter, IndigoUtils from .logic import GroupManager, PackageManager, UserManager, SettingManager, SearchManager @@ -18,12 +17,81 @@ from .models import Package, GroupPackagePermission, Group, CompoundStructure, C logger = logging.getLogger(__name__) + def log_post_params(request): if s.DEBUG: for k, v in request.POST.items(): 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): @wraps(view_func) def _wrapped_view(request, *args, **kwargs): @@ -38,6 +106,7 @@ def catch_exceptions(view_func): ) else: return render(request, 'errors/error.html', get_base_context(request)) + return _wrapped_view @@ -60,7 +129,6 @@ def editable(request, user): return False - def get_base_context(request) -> Dict[str, Any]: current_user = _anonymous_or_real(request) @@ -121,16 +189,6 @@ def index(request): 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): current_user = _anonymous_or_real(request) @@ -688,6 +746,7 @@ def package(request, package_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//compound def package_compounds(request, package_uuid): current_user = _anonymous_or_real(request) @@ -814,6 +873,7 @@ def package_compound_structures(request, package_uuid, compound_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//compound//structure/ def package_compound_structure(request, package_uuid, compound_uuid, structure_uuid): current_user = _anonymous_or_real(request) @@ -893,12 +953,14 @@ def package_rules(request, package_uuid): else: 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) else: return HttpResponseBadRequest() + # https://envipath.org/package//rule/ def package_rule(request, package_uuid, rule_uuid): current_user = _anonymous_or_real(request) @@ -945,6 +1007,7 @@ def package_rule(request, package_uuid, rule_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//reaction def package_reactions(request, package_uuid): current_user = _anonymous_or_real(request) @@ -995,6 +1058,7 @@ def package_reactions(request, package_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//reaction/ def package_reaction(request, package_uuid, reaction_uuid): current_user = _anonymous_or_real(request) @@ -1039,6 +1103,7 @@ def package_reaction(request, package_uuid, reaction_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//pathway def package_pathways(request, package_uuid): current_user = _anonymous_or_real(request) @@ -1115,6 +1180,7 @@ def package_pathways(request, package_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//pathway/ def package_pathway(request, package_uuid, pathway_uuid): current_user: User = _anonymous_or_real(request) @@ -1186,6 +1252,7 @@ def package_pathway(request, package_uuid, pathway_uuid): else: return HttpResponseBadRequest() + # https://envipath.org/package//pathway//node def package_pathway_nodes(request, package_uuid, pathway_uuid): current_user = _anonymous_or_real(request) @@ -1452,49 +1519,6 @@ def users(request): 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): current_user = _anonymous_or_real(request) @@ -1551,14 +1575,6 @@ def user(request, user_uuid): 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_group = request.POST.get('default-group') 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['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) @@ -1682,6 +1699,7 @@ def group(request, group_uuid): return redirect(current_group.url) + def settings(request): current_user = _anonymous_or_real(request) context = get_base_context(request) @@ -1705,8 +1723,10 @@ def settings(request): description = request.POST.get('prediction-setting-description') 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_depth = min(max(int(request.POST.get('prediction-setting-max-depth', 1)), s.DEFAULT_MAX_DEPTH), s.DEFAULT_MAX_DEPTH) + 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_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') diff --git a/templates/framework.html b/templates/framework.html index 4ffe4fa5..880c3a54 100644 --- a/templates/framework.html +++ b/templates/framework.html @@ -152,7 +152,7 @@