fix: remove no longer needed htpasswd infrastructure (#7590)
This commit is contained in:
parent
a1902cfeca
commit
704f9967fd
|
@ -1,30 +0,0 @@
|
||||||
# Copyright The IETF Trust 2016-2020, All Rights Reserved
|
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
|
|
||||||
import io
|
|
||||||
import subprocess, hashlib
|
|
||||||
from django.utils.encoding import force_bytes
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
def update_htpasswd_file(username, password):
|
|
||||||
if getattr(settings, 'USE_PYTHON_HTDIGEST', None):
|
|
||||||
pass_file = settings.HTPASSWD_FILE
|
|
||||||
realm = settings.HTDIGEST_REALM
|
|
||||||
prefix = force_bytes('%s:%s:' % (username, realm))
|
|
||||||
key = force_bytes(hashlib.md5(prefix + force_bytes(password)).hexdigest())
|
|
||||||
f = io.open(pass_file, 'r+b')
|
|
||||||
pos = f.tell()
|
|
||||||
line = f.readline()
|
|
||||||
while line:
|
|
||||||
if line.startswith(prefix):
|
|
||||||
break
|
|
||||||
pos=f.tell()
|
|
||||||
line = f.readline()
|
|
||||||
f.seek(pos)
|
|
||||||
f.write(b'%s%s\n' % (prefix, key))
|
|
||||||
f.close()
|
|
||||||
else:
|
|
||||||
p = subprocess.Popen([settings.HTPASSWD_COMMAND, "-b", settings.HTPASSWD_FILE, username, password], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
||||||
stdout, stderr = p.communicate()
|
|
|
@ -3,13 +3,10 @@
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
import io
|
|
||||||
import logging # pyflakes:ignore
|
import logging # pyflakes:ignore
|
||||||
import os
|
|
||||||
import re
|
import re
|
||||||
import requests
|
import requests
|
||||||
import requests_mock
|
import requests_mock
|
||||||
import shutil
|
|
||||||
import time
|
import time
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
@ -21,7 +18,6 @@ from oic.oic.message import RegistrationResponse, AuthorizationResponse
|
||||||
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
|
from oic.utils.authn.client import CLIENT_AUTHN_METHOD
|
||||||
from oidc_provider.models import RSAKey
|
from oidc_provider.models import RSAKey
|
||||||
from pyquery import PyQuery
|
from pyquery import PyQuery
|
||||||
from unittest import skipIf
|
|
||||||
from urllib.parse import urlsplit
|
from urllib.parse import urlsplit
|
||||||
|
|
||||||
import django.core.signing
|
import django.core.signing
|
||||||
|
@ -35,7 +31,6 @@ import debug # pyflakes:ignore
|
||||||
|
|
||||||
from ietf.group.factories import GroupFactory, RoleFactory
|
from ietf.group.factories import GroupFactory, RoleFactory
|
||||||
from ietf.group.models import Group, Role, RoleName
|
from ietf.group.models import Group, Role, RoleName
|
||||||
from ietf.ietfauth.htpasswd import update_htpasswd_file
|
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role
|
||||||
from ietf.meeting.factories import MeetingFactory
|
from ietf.meeting.factories import MeetingFactory
|
||||||
from ietf.nomcom.factories import NomComFactory
|
from ietf.nomcom.factories import NomComFactory
|
||||||
|
@ -45,41 +40,12 @@ from ietf.person.tasks import send_apikey_usage_emails_task
|
||||||
from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory
|
from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory
|
||||||
from ietf.review.models import ReviewWish, UnavailablePeriod
|
from ietf.review.models import ReviewWish, UnavailablePeriod
|
||||||
from ietf.stats.models import MeetingRegistration
|
from ietf.stats.models import MeetingRegistration
|
||||||
from ietf.utils.decorators import skip_coverage
|
|
||||||
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
|
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
|
||||||
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
from ietf.utils.test_utils import TestCase, login_testing_unauthorized
|
||||||
from ietf.utils.timezone import date_today
|
from ietf.utils.timezone import date_today
|
||||||
|
|
||||||
|
|
||||||
if os.path.exists(settings.HTPASSWD_COMMAND):
|
|
||||||
skip_htpasswd_command = False
|
|
||||||
skip_message = ""
|
|
||||||
else:
|
|
||||||
skip_htpasswd_command = True
|
|
||||||
skip_message = ("Skipping htpasswd test: The binary for htpasswd wasn't found in the\n "
|
|
||||||
"location indicated in settings.py.")
|
|
||||||
print(" "+skip_message)
|
|
||||||
|
|
||||||
class IetfAuthTests(TestCase):
|
class IetfAuthTests(TestCase):
|
||||||
def setUp(self):
|
|
||||||
super().setUp()
|
|
||||||
self.saved_use_python_htdigest = getattr(settings, "USE_PYTHON_HTDIGEST", None)
|
|
||||||
settings.USE_PYTHON_HTDIGEST = True
|
|
||||||
|
|
||||||
self.saved_htpasswd_file = settings.HTPASSWD_FILE
|
|
||||||
self.htpasswd_dir = self.tempdir('htpasswd')
|
|
||||||
settings.HTPASSWD_FILE = os.path.join(self.htpasswd_dir, "htpasswd")
|
|
||||||
io.open(settings.HTPASSWD_FILE, 'a').close() # create empty file
|
|
||||||
|
|
||||||
self.saved_htdigest_realm = getattr(settings, "HTDIGEST_REALM", None)
|
|
||||||
settings.HTDIGEST_REALM = "test-realm"
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
shutil.rmtree(self.htpasswd_dir)
|
|
||||||
settings.USE_PYTHON_HTDIGEST = self.saved_use_python_htdigest
|
|
||||||
settings.HTPASSWD_FILE = self.saved_htpasswd_file
|
|
||||||
settings.HTDIGEST_REALM = self.saved_htdigest_realm
|
|
||||||
super().tearDown()
|
|
||||||
|
|
||||||
def test_index(self):
|
def test_index(self):
|
||||||
self.assertEqual(self.client.get(urlreverse("ietf.ietfauth.views.index")).status_code, 200)
|
self.assertEqual(self.client.get(urlreverse("ietf.ietfauth.views.index")).status_code, 200)
|
||||||
|
@ -162,15 +128,6 @@ class IetfAuthTests(TestCase):
|
||||||
|
|
||||||
return confirm_url
|
return confirm_url
|
||||||
|
|
||||||
def username_in_htpasswd_file(self, username):
|
|
||||||
with io.open(settings.HTPASSWD_FILE) as f:
|
|
||||||
for l in f:
|
|
||||||
if l.startswith(username + ":"):
|
|
||||||
return True
|
|
||||||
with io.open(settings.HTPASSWD_FILE) as f:
|
|
||||||
print(f.read())
|
|
||||||
|
|
||||||
return False
|
|
||||||
|
|
||||||
# For the lowered barrier to account creation period, we are disabling this kind of failure
|
# For the lowered barrier to account creation period, we are disabling this kind of failure
|
||||||
# def test_create_account_failure(self):
|
# def test_create_account_failure(self):
|
||||||
|
@ -223,8 +180,6 @@ class IetfAuthTests(TestCase):
|
||||||
self.assertEqual(Person.objects.filter(user__username=email).count(), 1)
|
self.assertEqual(Person.objects.filter(user__username=email).count(), 1)
|
||||||
self.assertEqual(Email.objects.filter(person__user__username=email).count(), 1)
|
self.assertEqual(Email.objects.filter(person__user__username=email).count(), 1)
|
||||||
|
|
||||||
self.assertTrue(self.username_in_htpasswd_file(email))
|
|
||||||
|
|
||||||
|
|
||||||
# This also tests new account creation.
|
# This also tests new account creation.
|
||||||
def test_create_existing_account(self):
|
def test_create_existing_account(self):
|
||||||
|
@ -490,7 +445,6 @@ class IetfAuthTests(TestCase):
|
||||||
self.assertEqual(r.status_code, 200)
|
self.assertEqual(r.status_code, 200)
|
||||||
q = PyQuery(r.content)
|
q = PyQuery(r.content)
|
||||||
self.assertEqual(len(q("form .is-invalid")), 0)
|
self.assertEqual(len(q("form .is-invalid")), 0)
|
||||||
self.assertTrue(self.username_in_htpasswd_file(user.username))
|
|
||||||
|
|
||||||
# reuse reset url
|
# reuse reset url
|
||||||
r = self.client.get(confirm_url)
|
r = self.client.get(confirm_url)
|
||||||
|
@ -614,23 +568,6 @@ class IetfAuthTests(TestCase):
|
||||||
self.assertEqual(r.status_code, 302)
|
self.assertEqual(r.status_code, 302)
|
||||||
self.assertEqual(ReviewWish.objects.filter(doc=doc, team=review_req.team).count(), 0)
|
self.assertEqual(ReviewWish.objects.filter(doc=doc, team=review_req.team).count(), 0)
|
||||||
|
|
||||||
def test_htpasswd_file_with_python(self):
|
|
||||||
# make sure we test both Python and call-out to binary
|
|
||||||
settings.USE_PYTHON_HTDIGEST = True
|
|
||||||
|
|
||||||
update_htpasswd_file("foo", "passwd")
|
|
||||||
self.assertTrue(self.username_in_htpasswd_file("foo"))
|
|
||||||
|
|
||||||
@skipIf(skip_htpasswd_command, skip_message)
|
|
||||||
@skip_coverage
|
|
||||||
def test_htpasswd_file_with_htpasswd_binary(self):
|
|
||||||
# make sure we test both Python and call-out to binary
|
|
||||||
settings.USE_PYTHON_HTDIGEST = False
|
|
||||||
|
|
||||||
update_htpasswd_file("foo", "passwd")
|
|
||||||
self.assertTrue(self.username_in_htpasswd_file("foo"))
|
|
||||||
|
|
||||||
|
|
||||||
def test_change_password(self):
|
def test_change_password(self):
|
||||||
chpw_url = urlreverse("ietf.ietfauth.views.change_password")
|
chpw_url = urlreverse("ietf.ietfauth.views.change_password")
|
||||||
prof_url = urlreverse("ietf.ietfauth.views.profile")
|
prof_url = urlreverse("ietf.ietfauth.views.profile")
|
||||||
|
|
|
@ -65,7 +65,6 @@ from ietf.group.models import Role, Group
|
||||||
from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm,
|
from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm,
|
||||||
ChangePasswordForm, get_person_form, RoleEmailForm,
|
ChangePasswordForm, get_person_form, RoleEmailForm,
|
||||||
NewEmailForm, ChangeUsernameForm, PersonPasswordForm)
|
NewEmailForm, ChangeUsernameForm, PersonPasswordForm)
|
||||||
from ietf.ietfauth.htpasswd import update_htpasswd_file
|
|
||||||
from ietf.ietfauth.utils import has_role
|
from ietf.ietfauth.utils import has_role
|
||||||
from ietf.name.models import ExtResourceName
|
from ietf.name.models import ExtResourceName
|
||||||
from ietf.nomcom.models import NomCom
|
from ietf.nomcom.models import NomCom
|
||||||
|
@ -222,8 +221,6 @@ def confirm_account(request, auth):
|
||||||
user = User.objects.create(username=email, email=email)
|
user = User.objects.create(username=email, email=email)
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
# password is also stored in htpasswd file
|
|
||||||
update_htpasswd_file(email, password)
|
|
||||||
|
|
||||||
# make sure the rest of the person infrastructure is
|
# make sure the rest of the person infrastructure is
|
||||||
# well-connected
|
# well-connected
|
||||||
|
@ -552,8 +549,6 @@ def confirm_password_reset(request, auth):
|
||||||
|
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save()
|
user.save()
|
||||||
# password is also stored in htpasswd file
|
|
||||||
update_htpasswd_file(user.username, password)
|
|
||||||
|
|
||||||
success = True
|
success = True
|
||||||
else:
|
else:
|
||||||
|
@ -693,8 +688,6 @@ def change_password(request):
|
||||||
|
|
||||||
user.set_password(new_password)
|
user.set_password(new_password)
|
||||||
user.save()
|
user.save()
|
||||||
# password is also stored in htpasswd file
|
|
||||||
update_htpasswd_file(user.username, new_password)
|
|
||||||
# keep the session
|
# keep the session
|
||||||
update_session_auth_hash(request, user)
|
update_session_auth_hash(request, user)
|
||||||
|
|
||||||
|
@ -731,13 +724,10 @@ def change_username(request):
|
||||||
form = ChangeUsernameForm(user, request.POST)
|
form = ChangeUsernameForm(user, request.POST)
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
new_username = form.cleaned_data["username"]
|
new_username = form.cleaned_data["username"]
|
||||||
password = form.cleaned_data["password"]
|
|
||||||
assert new_username in emails
|
assert new_username in emails
|
||||||
|
|
||||||
user.username = new_username.lower()
|
user.username = new_username.lower()
|
||||||
user.save()
|
user.save()
|
||||||
# password is also stored in htpasswd file
|
|
||||||
update_htpasswd_file(user.username, password)
|
|
||||||
# keep the session
|
# keep the session
|
||||||
update_session_auth_hash(request, user)
|
update_session_auth_hash(request, user)
|
||||||
|
|
||||||
|
|
|
@ -979,8 +979,6 @@ DE_GFM_BINARY = '/usr/bin/de-gfm.ruby2.5'
|
||||||
# Account settings
|
# Account settings
|
||||||
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
|
DAYS_TO_EXPIRE_REGISTRATION_LINK = 3
|
||||||
MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK = 60
|
MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK = 60
|
||||||
HTPASSWD_COMMAND = "/usr/bin/htpasswd"
|
|
||||||
HTPASSWD_FILE = "/a/www/htpasswd"
|
|
||||||
|
|
||||||
# Generation of pdf files
|
# Generation of pdf files
|
||||||
GHOSTSCRIPT_COMMAND = "/usr/bin/gs"
|
GHOSTSCRIPT_COMMAND = "/usr/bin/gs"
|
||||||
|
|
|
@ -1,63 +0,0 @@
|
||||||
# Copyright The IETF Trust 2014-2020, All Rights Reserved
|
|
||||||
import io
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from textwrap import dedent
|
|
||||||
|
|
||||||
from django.contrib.auth.models import User
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
|
|
||||||
def import_htpasswd_file(filename, verbosity=1, overwrite=False):
|
|
||||||
with io.open(filename) as file:
|
|
||||||
for line in file:
|
|
||||||
if not ':' in line:
|
|
||||||
raise ValueError('Found a line without colon separator in the htpassword file %s:'
|
|
||||||
' "%s"' % (file.name, line))
|
|
||||||
username, password = line.strip().split(':', 1)
|
|
||||||
try:
|
|
||||||
user = User.objects.get(username__iexact=username)
|
|
||||||
if overwrite == True or not user.password:
|
|
||||||
if password.startswith('{SHA}'):
|
|
||||||
user.password = "sha1$$%s" % password[len('{SHA}'):]
|
|
||||||
elif password.startswith('$apr1$'):
|
|
||||||
user.password = "md5$%s" % password[len('$apr1$'):]
|
|
||||||
else: # Assume crypt
|
|
||||||
user.password = "crypt$$%s" % password
|
|
||||||
user.save()
|
|
||||||
if verbosity > 0:
|
|
||||||
sys.stderr.write('.')
|
|
||||||
if verbosity > 1:
|
|
||||||
sys.stderr.write(' %s\n' % username)
|
|
||||||
except User.DoesNotExist:
|
|
||||||
if verbosity > 1:
|
|
||||||
sys.stderr.write('\nNo such user: %s\n' % username)
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
"""
|
|
||||||
Import passwords from one or more htpasswd files to Django's auth_user table.
|
|
||||||
|
|
||||||
This command only imports passwords; it does not import usernames, as that
|
|
||||||
would leave usernames without associated Person records in the database,
|
|
||||||
something which is undesirable.
|
|
||||||
|
|
||||||
By default the command won't overwrite existing password entries, but
|
|
||||||
given the --force switch, it will overwrite existing entries too. Without
|
|
||||||
the --force switch, the command is safe to run repeatedly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
help = dedent(__doc__).strip()
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument('--force',
|
|
||||||
action='store_true', dest='overwrite', default=False,
|
|
||||||
help='Overwrite existing passwords in the auth_user table.')
|
|
||||||
|
|
||||||
|
|
||||||
args = '[path [path [...]]]'
|
|
||||||
|
|
||||||
def handle(self, *filenames, **options):
|
|
||||||
overwrite = options.get('overwrite', False)
|
|
||||||
verbosity = int(options.get('verbosity'))
|
|
||||||
for fn in filenames:
|
|
||||||
import_htpasswd_file(fn, verbosity=verbosity, overwrite=overwrite)
|
|
||||||
|
|
Loading…
Reference in a new issue