You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

113 lines
3.9 KiB

# -*- coding: utf-8 -*-
A python module for managing htpasswd files.
The code is based on
Original author: Eli Carter
Copyright 2010 Lars Kruse <>
Copyright before 2010 Eli Carter
This module is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This module is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with This module. If not, see <>.
import crypt
import shutil
import tempfile
import random
import os
def _get_random_salt():
"""Returns a string of 2 random letters"""
letters = 'abcdefghijklmnopqrstuvwxyz' \
return random.choice(letters) + random.choice(letters)
class HtpasswdFile:
"""A class for manipulating htpasswd files."""
def __init__(self, filename, create=False):
self.entries = {}
self.filename = filename
if not create:
def load(self):
"""Read the htpasswd file into memory.
This function may raise an IOError exception.
self.entries = {}
for line in open(self.filename, 'r').readlines():
if ":" in line:
username, pwhash = line.split(':', 1)
# remove spaces
username = username.strip()
# remove newline characters and spaces
pwhash = pwhash.strip()
self.entries[username] = pwhash
def save(self):
"""Write the htpasswd file to disk
The file is written in a safe manner: first a temporary file with the
resulting content is created in the directory of the original file.
Afterwards it is moved to the original filename.
This function may raise an IOError exception.
# create a temporary file besides the original file
temp_file, temp_filename = tempfile.mkstemp(
dir=os.path.dirname(self.filename), text=False)
if os.path.isfile(self.filename):
# copy the original file mode (mod, timestamps)
shutil.copystat(self.filename, temp_filename)
sorted_names = sorted(self.entries.keys())
for name in sorted_names:
line = "%s:%s%s" % (name, self.entries[name], os.linesep)
os.write(temp_file, line.encode())
except IOError:
except IOError:
# ignore errors during failure handling
# move the temporary file to the original filename
os.rename(temp_filename, self.filename)
def update(self, username, password):
"""Replace the entry for the given user, or add it if new."""
pwhash = crypt.crypt(password, _get_random_salt())
self.entries[username] = pwhash
def delete(self, username):
"""Remove the entry for the given user."""
if username in self.entries:
def verify(self, username, password):
"""Check if the given password matches the hash."""
if username in self.entries:
crypthash = self.entries[username]
return crypt.crypt(password, crypthash) == crypthash
return False
def get_usernames(self):
names = sorted(self.entries.keys())
return names