Skip to content

Commit e8736b2

Browse files
committed
Allow logging in by IP address
There are many situations that this commit is untested against. Not ready for merging yet.
1 parent 63f0773 commit e8736b2

File tree

4 files changed

+63
-3
lines changed

4 files changed

+63
-3
lines changed

dmoj/settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -687,6 +687,7 @@
687687
'social_core.backends.facebook.FacebookOAuth2',
688688
'judge.social_auth.GitHubSecureEmailOAuth2',
689689
'django.contrib.auth.backends.ModelBackend',
690+
'judge.ip_based_auth.IPBasedAuthBackend',
690691
)
691692

692693
SOCIAL_AUTH_PIPELINE = (

judge/forms.py

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import webauthn
77
from django import forms
88
from django.conf import settings
9+
from django.contrib.auth import authenticate
910
from django.contrib.auth.forms import AuthenticationForm
1011
from django.contrib.auth.models import User
1112
from django.core.exceptions import ValidationError
@@ -47,6 +48,15 @@
4748
}
4849

4950

51+
def get_client_ip(request):
52+
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
53+
if x_forwarded_for:
54+
ip = x_forwarded_for.split(',')[0]
55+
else:
56+
ip = request.META.get('REMOTE_ADDR')
57+
return ip
58+
59+
5060
class ProfileForm(ModelForm):
5161
if newsletter_id is not None:
5262
newsletter = forms.BooleanField(label=_('Subscribe to contest updates'), initial=False, required=False)
@@ -428,13 +438,37 @@ def _has_social_auth(self, key):
428438
getattr(settings, 'SOCIAL_AUTH_%s_SECRET' % key, None))
429439

430440
def clean(self):
431-
username = self.cleaned_data.get('username')
441+
is_ip_login = self.request.POST.get('ip-login', 'false') == 'true'
442+
432443
try:
433-
user = User.objects.get(username=username)
434-
except User.DoesNotExist:
444+
if is_ip_login:
445+
ip = get_client_ip(self.request)
446+
user = Profile.objects.filter(ip=ip).select_related('user').first().user
447+
else:
448+
username = self.cleaned_data.get('username')
449+
user = User.objects.get(username=username)
450+
except (Profile.DoesNotExist, User.DoesNotExist):
435451
user = None
452+
436453
if user is not None:
437454
self.confirm_login_allowed(user)
455+
456+
if is_ip_login:
457+
# a hack to remove the username/password errors, since we don't need them
458+
del self.errors['username']
459+
del self.errors['password']
460+
461+
# set the password to the user's password to pass the form_valid check
462+
self.cleaned_data['password'] = user.password
463+
464+
# super.clean() will skip the check, since there is no username/password when logging in by IP address,
465+
# so this is a hack to process that manually
466+
self.user_cache = authenticate(self.request, ip_address=ip)
467+
if self.user_cache is None:
468+
raise self.get_invalid_login_error()
469+
else:
470+
self.confirm_login_allowed(self.user_cache)
471+
438472
return super(CustomAuthenticationForm, self).clean()
439473

440474
def confirm_login_allowed(self, user):

judge/ip_based_auth.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
from django.contrib.auth.backends import ModelBackend
2+
3+
from judge.models import Profile
4+
5+
6+
class IPBasedAuthBackend(ModelBackend):
7+
def authenticate(self, request, ip_address=None):
8+
try:
9+
user = Profile.objects.filter(ip=ip_address).select_related('user').first().user
10+
except Profile.DoesNotExist:
11+
user = None
12+
return user

templates/registration/login.html

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,16 @@
2626
</style>
2727
{% endblock %}
2828

29+
{% block js_media %}
30+
<script>
31+
$(document).ready(function () {
32+
$("#ip-login-button").click(function () {
33+
$("input[name='ip-login']").val("true");
34+
});
35+
});
36+
</script>
37+
{% endblock %}
38+
2939
{% block body %}
3040
<div class="auth-flow-form">
3141
<form action="" method="post" class="form-area">
@@ -57,7 +67,10 @@
5767
</table>
5868
<hr>
5969
<button style="float:right;" type="submit">{{ _('Login!') }}</button>
70+
<!-- TODO: enabled if allowing login by ip address -->
71+
<button style="float:right; margin-right: 0.5em" type="submit" id="ip-login-button" formnovalidate>{{_('Login by IP Address!')}}</button>
6072
<input type="hidden" name="next" value="{{ next }}">
73+
<input type="hidden" name="ip-login" value="false">
6174
</form>
6275
<a href="{{ url('password_reset') }}">{{ _('Forgot your password?') }}</a>
6376

0 commit comments

Comments
 (0)