diff --git a/htman/htpasswd.py b/htman/htpasswd.py new file mode 100644 index 0000000..f485217 --- /dev/null +++ b/htman/htpasswd.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +""" +$Id$ + +A python module for managing htpasswd files. + +The code is based on http://trac.edgewall.org/export/9825/trunk/contrib/htpasswd.py +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 +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +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' \ + 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + '0123456789/.' + 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: + self.load() + + 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(): + username, pwhash = line.split(':', 1) + 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=True) + try: + if os.path.isfile(self.filename): + # copy the original file mode (mod, timestamps) + shutil.copystat(self.filename, temp_filename) + for name, pwhash in self.entries.items(): + os.write(temp_file, "%s:%s%s" % (name, pwhash, os.linesep)) + os.close(temp_file) + except IOError: + try: + os.remove(temp_filename) + except IOError: + # ignore errors during failure handling + pass + else: + # 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: + self.entries.pop(username) + + def get_usernames(self): + return self.entries.keys() +