Created
April 12, 2015 13:54
-
-
Save xdtianyu/5baeedb70df39e7e3d49 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
"""Replacement for htpasswd""" | |
# Original author: Eli Carter | |
import os | |
import sys | |
import random | |
from optparse import OptionParser | |
# We need a crypt module, but Windows doesn't have one by default. Try to find | |
# one, and tell the user if we can't. | |
try: | |
import crypt | |
except ImportError: | |
try: | |
import fcrypt as crypt | |
except ImportError: | |
sys.stderr.write("Cannot find a crypt module. " | |
"Possibly http://carey.geek.nz/code/python-fcrypt/\n") | |
sys.exit(1) | |
def salt(): | |
"""Returns a string of 2 randome 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: | |
if os.path.exists(self.filename): | |
self.load() | |
else: | |
raise Exception("%s does not exist" % self.filename) | |
def load(self): | |
"""Read the htpasswd file into memory.""" | |
lines = open(self.filename, 'r').readlines() | |
self.entries = [] | |
for line in lines: | |
username, pwhash = line.split(':') | |
entry = [username, pwhash.rstrip()] | |
self.entries.append(entry) | |
def save(self): | |
"""Write the htpasswd file to disk""" | |
open(self.filename, 'w').writelines(["%s:%s\n" % (entry[0], entry[1]) | |
for entry in self.entries]) | |
def update(self, username, password): | |
"""Replace the entry for the given user, or add it if new.""" | |
pwhash = crypt.crypt(password, salt()) | |
matching_entries = [entry for entry in self.entries | |
if entry[0] == username] | |
if matching_entries: | |
matching_entries[0][1] = pwhash | |
else: | |
self.entries.append([username, pwhash]) | |
def delete(self, username): | |
"""Remove the entry for the given user.""" | |
self.entries = [entry for entry in self.entries | |
if entry[0] != username] | |
def main(): | |
"""%prog [-c] -b filename username password | |
Create or update an htpasswd file""" | |
# For now, we only care about the use cases that affect tests/functional.py | |
parser = OptionParser(usage=main.__doc__) | |
parser.add_option('-b', action='store_true', dest='batch', default=False, | |
help='Batch mode; password is passed on the command line IN THE CLEAR.' | |
) | |
parser.add_option('-c', action='store_true', dest='create', default=False, | |
help='Create a new htpasswd file, overwriting any existing file.') | |
parser.add_option('-D', action='store_true', dest='delete_user', | |
default=False, help='Remove the given user from the password file.') | |
options, args = parser.parse_args() | |
def syntax_error(msg): | |
"""Utility function for displaying fatal error messages with usage | |
help. | |
""" | |
sys.stderr.write("Syntax error: " + msg) | |
sys.stderr.write(parser.get_usage()) | |
sys.exit(1) | |
if not options.batch: | |
syntax_error("Only batch mode is supported\n") | |
# Non-option arguments | |
if len(args) < 2: | |
syntax_error("Insufficient number of arguments.\n") | |
filename, username = args[:2] | |
if options.delete_user: | |
if len(args) != 2: | |
syntax_error("Incorrect number of arguments.\n") | |
password = None | |
else: | |
if len(args) != 3: | |
syntax_error("Incorrect number of arguments.\n") | |
password = args[2] | |
passwdfile = HtpasswdFile(filename, create=options.create) | |
if options.delete_user: | |
passwdfile.delete(username) | |
else: | |
passwdfile.update(username, password) | |
passwdfile.save() | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment