From 704f9967fd1a02a23f1ad5793c263e6400514f50 Mon Sep 17 00:00:00 2001 From: Robert Sparks Date: Wed, 26 Jun 2024 13:33:09 -0500 Subject: [PATCH] fix: remove no longer needed htpasswd infrastructure (#7590) --- ietf/ietfauth/htpasswd.py | 30 --------- ietf/ietfauth/tests.py | 63 ------------------- ietf/ietfauth/views.py | 10 --- ietf/settings.py | 2 - .../management/commands/import_htpasswd.py | 63 ------------------- 5 files changed, 168 deletions(-) delete mode 100644 ietf/ietfauth/htpasswd.py delete mode 100644 ietf/utils/management/commands/import_htpasswd.py diff --git a/ietf/ietfauth/htpasswd.py b/ietf/ietfauth/htpasswd.py deleted file mode 100644 index 3716d98600..0000000000 --- a/ietf/ietfauth/htpasswd.py +++ /dev/null @@ -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() diff --git a/ietf/ietfauth/tests.py b/ietf/ietfauth/tests.py index 503c091a85..6a85c6eb13 100644 --- a/ietf/ietfauth/tests.py +++ b/ietf/ietfauth/tests.py @@ -3,13 +3,10 @@ import datetime -import io import logging # pyflakes:ignore -import os import re import requests import requests_mock -import shutil import time import urllib @@ -21,7 +18,6 @@ from oic.utils.authn.client import CLIENT_AUTHN_METHOD from oidc_provider.models import RSAKey from pyquery import PyQuery -from unittest import skipIf from urllib.parse import urlsplit import django.core.signing @@ -35,7 +31,6 @@ from ietf.group.factories import GroupFactory, RoleFactory 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.meeting.factories import MeetingFactory from ietf.nomcom.factories import NomComFactory @@ -45,41 +40,12 @@ from ietf.review.factories import ReviewRequestFactory, ReviewAssignmentFactory from ietf.review.models import ReviewWish, UnavailablePeriod 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.test_utils import TestCase, login_testing_unauthorized 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): - 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): self.assertEqual(self.client.get(urlreverse("ietf.ietfauth.views.index")).status_code, 200) @@ -162,15 +128,6 @@ def extract_confirm_url(self, confirm_email): 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 # def test_create_account_failure(self): @@ -223,8 +180,6 @@ def register_and_verify(self, email): self.assertEqual(Person.objects.filter(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. def test_create_existing_account(self): @@ -490,7 +445,6 @@ def test_reset_password(self): self.assertEqual(r.status_code, 200) q = PyQuery(r.content) self.assertEqual(len(q("form .is-invalid")), 0) - self.assertTrue(self.username_in_htpasswd_file(user.username)) # reuse reset url r = self.client.get(confirm_url) @@ -614,23 +568,6 @@ def test_review_overview(self): self.assertEqual(r.status_code, 302) 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): chpw_url = urlreverse("ietf.ietfauth.views.change_password") prof_url = urlreverse("ietf.ietfauth.views.profile") diff --git a/ietf/ietfauth/views.py b/ietf/ietfauth/views.py index 7c3f72108e..61c7b929b1 100644 --- a/ietf/ietfauth/views.py +++ b/ietf/ietfauth/views.py @@ -65,7 +65,6 @@ from ietf.ietfauth.forms import ( RegistrationForm, PasswordForm, ResetPasswordForm, TestEmailForm, ChangePasswordForm, get_person_form, RoleEmailForm, NewEmailForm, ChangeUsernameForm, PersonPasswordForm) -from ietf.ietfauth.htpasswd import update_htpasswd_file from ietf.ietfauth.utils import has_role from ietf.name.models import ExtResourceName from ietf.nomcom.models import NomCom @@ -222,8 +221,6 @@ def confirm_account(request, auth): user = User.objects.create(username=email, email=email) user.set_password(password) user.save() - # password is also stored in htpasswd file - update_htpasswd_file(email, password) # make sure the rest of the person infrastructure is # well-connected @@ -552,8 +549,6 @@ def confirm_password_reset(request, auth): user.set_password(password) user.save() - # password is also stored in htpasswd file - update_htpasswd_file(user.username, password) success = True else: @@ -693,8 +688,6 @@ def change_password(request): user.set_password(new_password) user.save() - # password is also stored in htpasswd file - update_htpasswd_file(user.username, new_password) # keep the session update_session_auth_hash(request, user) @@ -731,13 +724,10 @@ def change_username(request): form = ChangeUsernameForm(user, request.POST) if form.is_valid(): new_username = form.cleaned_data["username"] - password = form.cleaned_data["password"] assert new_username in emails user.username = new_username.lower() user.save() - # password is also stored in htpasswd file - update_htpasswd_file(user.username, password) # keep the session update_session_auth_hash(request, user) diff --git a/ietf/settings.py b/ietf/settings.py index 895fa489b3..1ee289a193 100644 --- a/ietf/settings.py +++ b/ietf/settings.py @@ -979,8 +979,6 @@ def skip_unreadable_post(record): # Account settings DAYS_TO_EXPIRE_REGISTRATION_LINK = 3 MINUTES_TO_EXPIRE_RESET_PASSWORD_LINK = 60 -HTPASSWD_COMMAND = "/usr/bin/htpasswd" -HTPASSWD_FILE = "/a/www/htpasswd" # Generation of pdf files GHOSTSCRIPT_COMMAND = "/usr/bin/gs" diff --git a/ietf/utils/management/commands/import_htpasswd.py b/ietf/utils/management/commands/import_htpasswd.py deleted file mode 100644 index c33a46b727..0000000000 --- a/ietf/utils/management/commands/import_htpasswd.py +++ /dev/null @@ -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) -