diff --git a/dmoj/settings.py b/dmoj/settings.py index e45ba5681..e6de00e6d 100755 --- a/dmoj/settings.py +++ b/dmoj/settings.py @@ -55,6 +55,7 @@ VNOJ_ORG_PP_SCALE = 1 VNOJ_OFFICIAL_CONTEST_MODE = False +VNOJ_ALLOW_LOGIN_BY_IP_ADDRESS = False # Contribution points function # Both should be int @@ -690,6 +691,7 @@ 'social_core.backends.facebook.FacebookOAuth2', 'judge.social_auth.GitHubSecureEmailOAuth2', 'django.contrib.auth.backends.ModelBackend', + 'judge.ip_based_auth.IPBasedAuthBackend', ) SOCIAL_AUTH_PIPELINE = ( diff --git a/judge/forms.py b/judge/forms.py index d2e5daca0..0545a81e1 100755 --- a/judge/forms.py +++ b/judge/forms.py @@ -6,6 +6,7 @@ import webauthn from django import forms from django.conf import settings +from django.contrib.auth import authenticate from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.models import User from django.core.exceptions import ValidationError @@ -47,6 +48,15 @@ } +def get_client_ip(request): + x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') + if x_forwarded_for: + ip = x_forwarded_for.split(',')[0] + else: + ip = request.META.get('REMOTE_ADDR') + return ip + + class ProfileForm(ModelForm): if newsletter_id is not None: newsletter = forms.BooleanField(label=_('Subscribe to contest updates'), initial=False, required=False) @@ -428,13 +438,43 @@ def _has_social_auth(self, key): getattr(settings, 'SOCIAL_AUTH_%s_SECRET' % key, None)) def clean(self): - username = self.cleaned_data.get('username') + is_ip_login = settings.VNOJ_ALLOW_LOGIN_BY_IP_ADDRESS and self.request.POST.get('ip-login', 'false') == 'true' + try: - user = User.objects.get(username=username) - except User.DoesNotExist: + if is_ip_login: + ip = get_client_ip(self.request) + profile = Profile.objects.filter(ip=ip).order_by('-last_access').select_related('user').first() + + if profile is None: + raise Profile.DoesNotExist + + user = profile.user + else: + username = self.cleaned_data.get('username') + user = User.objects.get(username=username) + except (Profile.DoesNotExist, User.DoesNotExist): user = None + if user is not None: self.confirm_login_allowed(user) + + if is_ip_login: + # a hack to remove the username/password errors, since we don't need them + del self.errors['username'] + del self.errors['password'] + + # set the password to the user's password to pass the form_valid check + if user is not None: + self.cleaned_data['password'] = user.password + + # super.clean() will skip the check, since there is no username/password when logging in by IP address, + # so this is a hack to process that manually + self.user_cache = authenticate(self.request, ip_address=ip) + if self.user_cache is None: + raise self.get_invalid_login_error() + else: + self.confirm_login_allowed(self.user_cache) + return super(CustomAuthenticationForm, self).clean() def confirm_login_allowed(self, user): diff --git a/judge/ip_based_auth.py b/judge/ip_based_auth.py new file mode 100644 index 000000000..be5f51554 --- /dev/null +++ b/judge/ip_based_auth.py @@ -0,0 +1,15 @@ +from django.contrib.auth.backends import ModelBackend + +from judge.models import Profile + + +class IPBasedAuthBackend(ModelBackend): + def authenticate(self, request, ip_address=None): + try: + profile = Profile.objects.filter(ip=ip_address).order_by('-last_access').select_related('user').first() + if profile is None: + return None + return profile.user + except Profile.DoesNotExist: + user = None + return user diff --git a/judge/views/user.py b/judge/views/user.py index 49e3a69ad..876e0e75a 100644 --- a/judge/views/user.py +++ b/judge/views/user.py @@ -153,7 +153,10 @@ def get(self, request, *args, **kwargs): class CustomLoginView(LoginView): template_name = 'registration/login.html' - extra_context = {'title': gettext_lazy('Login')} + extra_context = { + 'title': gettext_lazy('Login'), + 'allow_login_by_ip_address': settings.VNOJ_ALLOW_LOGIN_BY_IP_ADDRESS, + } authentication_form = CustomAuthenticationForm redirect_authenticated_user = True diff --git a/templates/registration/login.html b/templates/registration/login.html index 35df5e8d2..a59af1344 100644 --- a/templates/registration/login.html +++ b/templates/registration/login.html @@ -26,6 +26,18 @@ {% endblock %} +{% block js_media %} + {% if allow_login_by_ip_address %} + + {% endif %} +{% endblock %} + {% block body %}