File: htpasswd.py

package info (click to toggle)
trac 1.2%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 17,452 kB
  • ctags: 8,602
  • sloc: python: 71,206; makefile: 358; sh: 79; xml: 10
file content (136 lines) | stat: -rwxr-xr-x 4,545 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2008-2013 Edgewall Software
# Copyright (C) 2008 Eli Carter
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. The terms
# are also available at http://trac.edgewall.com/license.html.
#
# This software consists of voluntary contributions made by many
# individuals. For the exact contribution history, see the revision
# history and logs, available at http://trac.edgewall.org/.

import getpass
import optparse
import sys

from trac.util import salt
from trac.util.compat import crypt, wait_for_file_mtime_change
from trac.util.text import printerr, printout

if crypt is None:
    printerr("The crypt module is not found. Install the passlib package "
             "from PyPI.", newline=True)
    sys.exit(1)


class HtpasswdFile(object):
    """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."""
        self.entries = []
        with open(self.filename, 'r') as f:
            for line in f:
                username, pwhash = line.split(':')
                entry = [username, pwhash.rstrip()]
                self.entries.append(entry)

    def save(self):
        """Write the htpasswd file to disk"""
        wait_for_file_mtime_change(self.filename)
        with open(self.filename, 'w') as f:
            f.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(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] filename username
        %prog -b[c] filename username password
        %prog -D filename username
    """
    # For now, we only care about the use cases that affect tests/functional.py
    parser = optparse.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.')

    if len(sys.argv) <= 1:
        parser.print_help()
        sys.exit(1)

    options, args = parser.parse_args()

    def syntax_error(msg):
        """Utility function for displaying fatal error messages with usage
        help.
        """
        printerr("Syntax error: " + msg, newline=True)
        printerr(parser.format_help(), newline=True)
        sys.exit(1)

    # Non-option arguments
    if len(args) < 2:
        syntax_error("Insufficient number of arguments.\n")
    filename, username = args[:2]
    password = None
    if options.delete_user:
        if len(args) != 2:
            syntax_error("Incorrect number of arguments.\n")
    else:
        if len(args) == 3 and options.batch:
            password = args[2]
        elif len(args) == 2 and not options.batch:
            first = getpass.getpass("New password:")
            second = getpass.getpass("Re-type new password:")
            if first == second:
                password = first
            else:
                printout("htpasswd: password verification error")
                return
        else:
            syntax_error("Incorrect number of arguments.\n")

    try:
        passwdfile = HtpasswdFile(filename, create=options.create)
    except IOError:
        syntax_error("File not found.\n")
    else:
        if options.delete_user:
            passwdfile.delete(username)
        else:
            passwdfile.update(username, password)
        passwdfile.save()


if __name__ == '__main__':
    main()