renamed "htname" to "zone"

added users to change their passwords (without admin access)
This commit is contained in:
lars 2012-09-20 01:51:08 +00:00
parent 9266f5c789
commit ef4f638797
5 changed files with 131 additions and 25 deletions

View file

@ -30,6 +30,7 @@ sys.path.insert(1, '/usr/share/pyshared')
import htpasswd import htpasswd
import bobo import bobo
import genshi.template import genshi.template
import genshi.filters
import ConfigParser import ConfigParser
import re import re
@ -46,15 +47,57 @@ REGEX = {
if "HTMAN_CONFIG" in os.environ: if "HTMAN_CONFIG" in os.environ:
CONFIG_FILE_LOCATIONS = [os.environ["HTMAN_CONFIG"]] CONFIG_FILE_LOCATIONS = [os.environ["HTMAN_CONFIG"]]
@bobo.query('') @bobo.query('')
def redirect_frontpage(): def redirect_frontpage():
return bobo.redirect(web_defaults["base_url"] + '/') return bobo.redirect(web_defaults["base_url"])
@bobo.query('/') @bobo.query('/')
def show_frontpage(): def show_frontpage():
values = web_defaults.copy() values = web_defaults.copy()
return render("frontpage.html", **values) return render("frontpage.html", **values)
@bobo.query('/password')
def change_password(zone=None, username=None, old_password=None,
new_password=None, new_password2=None):
if zone:
zone = zone.strip()
if username:
username = username.strip()
if old_password:
old_password = old_password.strip()
if new_password:
new_password = new_password.strip()
if new_password2:
new_password2 = new_password2.strip()
values = web_defaults.copy()
values["error"] = None
values["success"] = None
input_data = {"zone": zone, "username": username}
verified = False
if is_zone_valid(zone):
htdb = get_htpasswd(zone)
if htdb and username and (username in htdb.get_usernames()) and \
htdb.verify(username, old_password):
verified = True
if old_password is None:
# first visit of this page: no action, no errors
pass
elif new_password != new_password2:
values["error"] = "New passwords do not match."
elif not new_password:
values["error"] = "No new password given."
elif verified:
htdb.update(username, new_password)
htdb.save()
values["success"] = "Password changed successfully."
else:
values["error"] = "Authentication error: zone, username or password is invalid."
return render("password_change.html", input_data=input_data, **values)
@bobo.query('/admin') @bobo.query('/admin')
def show_files(): def show_files():
values = web_defaults.copy() values = web_defaults.copy()
@ -66,32 +109,44 @@ def show_files():
values["mapping"] = [(zone_name, "%s%s/%s" % (web_defaults["base_url"], "admin/manage", zone_name)) for zone_name in all_zones] values["mapping"] = [(zone_name, "%s%s/%s" % (web_defaults["base_url"], "admin/manage", zone_name)) for zone_name in all_zones]
return render("list_mappings.html", **values) return render("list_mappings.html", **values)
def is_zone_valid(zone):
return zone and (zone in get_mapping()) and re.match(REGEX["mapping"], zone)
@bobo.query('/manage') @bobo.query('/manage')
def show_htpasswd(htname=None): def show_htpasswd(zone=None):
if (htname is None) or (not htname in get_mapping()) or (not re.match(REGEX["mapping"], htname)): if not is_zone_valid(zone):
return bobo.redirect(web_defaults["base_url"]) return bobo.redirect(web_defaults["base_url"])
else: else:
# do a redirect: this allows the webserver to check the permissions for this URL # do a redirect: this allows the webserver to check the permissions for this URL
return bobo.redirect("%smanage/%s" % (web_defaults["base_url"], str(htname))) return bobo.redirect("%smanage/%s" % (web_defaults["base_url"], str(zone)))
def get_htpasswd(zone, auto_create=False):
htpasswd_file = get_htpasswd_filename(zone)
do_create_file = auto_create and not os.path.isfile(htpasswd_file)
try:
return htpasswd.HtpasswdFile(htpasswd_file, create=do_create_file)
except IOError:
return None
# the alternative "/admin" URL allows to define super-user htaccess rules via # the alternative "/admin" URL allows to define super-user htaccess rules via
# Apache's "Location" directive # Apache's "Location" directive
@bobo.query('/admin/manage/:htname') @bobo.query('/admin/manage/:zone')
@bobo.query('/manage/:htname') @bobo.query('/manage/:zone')
def manage_htpasswd(htname=None, action=None, username=None, password=None): def manage_htpasswd(zone=None, action=None, username=None, password=None):
values = web_defaults.copy() values = web_defaults.copy()
values["error"] = None values["error"] = None
values["success"] = None values["success"] = None
if (htname is None) or (not htname in get_mapping()) or (not re.match(REGEX["mapping"], htname)): if not is_zone_valid(zone):
return bobo.redirect(web_defaults["base_url"]) return bobo.redirect(web_defaults["base_url"])
values["htname"] = htname values["zone"] = zone
htpasswd_file = get_htpasswd_filename(htname) htdb = get_htpasswd(zone, auto_create=True)
do_create_file = not os.path.isfile(htpasswd_file) if not htdb:
try:
htdb = htpasswd.HtpasswdFile(htpasswd_file, create=do_create_file)
except IOError:
values["error"] = "Failed to read htpasswd file" values["error"] = "Failed to read htpasswd file"
htdb = None values["usernames"] = []
else: else:
if action is None: if action is None:
# just show the current state # just show the current state
@ -127,13 +182,11 @@ def manage_htpasswd(htname=None, action=None, username=None, password=None):
values["error"] = "The user exists already!" values["error"] = "The user exists already!"
else: else:
values["error"] = "Invalid action" values["error"] = "Invalid action"
if not htdb is None:
values["usernames"] = htdb.get_usernames() values["usernames"] = htdb.get_usernames()
else:
values["usernames"] = []
# show the current htpasswd file # show the current htpasswd file
return render("manage_users.html", **values) return render("manage_users.html", **values)
def get_config(): def get_config():
config = ConfigParser.SafeConfigParser() config = ConfigParser.SafeConfigParser()
for filename in CONFIG_FILE_LOCATIONS: for filename in CONFIG_FILE_LOCATIONS:
@ -148,6 +201,7 @@ def get_config():
print >>sys.stderr, "Failed to load config file from %s" % str(CONFIG_FILE_LOCATIONS) print >>sys.stderr, "Failed to load config file from %s" % str(CONFIG_FILE_LOCATIONS)
sys.exit(1) sys.exit(1)
def get_mapping(): def get_mapping():
try: try:
mapping_file = config.get("Locations", "mapping") mapping_file = config.get("Locations", "mapping")
@ -178,8 +232,9 @@ def get_mapping():
mapping[name] = location mapping[name] = location
return mapping return mapping
def get_htpasswd_filename(htname):
basename = mapping[htname] def get_htpasswd_filename(zone):
basename = mapping[zone]
if basename and (basename != os.path.abspath(basename)): if basename and (basename != os.path.abspath(basename)):
# relative filename in mapping file # relative filename in mapping file
# let's try the htpasswd_directory setting # let's try the htpasswd_directory setting
@ -203,6 +258,7 @@ def get_templates_dir(config):
sys.exit(3) sys.exit(3)
return os.path.abspath(templates_dir) return os.path.abspath(templates_dir)
def render(filename, input_data=None, **values): def render(filename, input_data=None, **values):
stream = loader.load(filename).generate(**values) stream = loader.load(filename).generate(**values)
if not input_data is None: if not input_data is None:
@ -216,6 +272,8 @@ config = get_config()
mapping = get_mapping() mapping = get_mapping()
templates_dir = get_templates_dir(config) templates_dir = get_templates_dir(config)
web_defaults = dict(config.items("Web")) web_defaults = dict(config.items("Web"))
if not web_defaults["base_url"].endswith("/"):
web_defaults["base_url"] += "/"
loader = genshi.template.TemplateLoader([templates_dir], auto_reload=False) loader = genshi.template.TemplateLoader([templates_dir], auto_reload=False)

View file

@ -102,6 +102,14 @@ class HtpasswdFile:
if username in self.entries: if username in self.entries:
self.entries.pop(username) self.entries.pop(username)
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
else:
return False
def get_usernames(self): def get_usernames(self):
names = self.entries.keys() names = self.entries.keys()
names.sort() names.sort()

View file

@ -10,7 +10,7 @@
<body> <body>
<form name="show_htpasswd" action="manage" method="POST"> <form name="show_htpasswd" action="manage" method="POST">
<label for="name">Access zone:</label> <label for="name">Access zone:</label>
<input type="text" size="30" name="htname" id="name" /> <input type="text" size="30" name="zone" id="name" />
<input type="submit" name="submit" value="Show" /> <input type="submit" name="submit" value="Show" />
</form> </form>
</body> </body>

View file

@ -8,9 +8,10 @@
<head/> <head/>
<body> <body>
<h2>Manage user access for <i>${htname}</i></h2> <h2>Manage user access for <i>${zone}</i></h2>
<div py:if="error" class="error">${error}</div> <div py:if="error" class="error">${error}</div>
<div py:if="success" class="success">${success}</div>
<h3>Add user</h3> <h3>Add user</h3>
<form name="add-passwd" action="" method="POST"> <form name="add-passwd" action="" method="POST">
@ -20,7 +21,7 @@
<label for="password_add">Password:</label> <label for="password_add">Password:</label>
<input type="password" size="20" name="password" id="password_add" /> <input type="password" size="20" name="password" id="password_add" />
<input type="hidden" name="action" value="new" /> <input type="hidden" name="action" value="new" />
<input type="hidden" name="htname" value="${htname}"/> <input type="hidden" name="zone" value="${zone}"/>
<input type="submit" name="submit" value="Add user" /> <input type="submit" name="submit" value="Add user" />
</form> </form>
@ -37,7 +38,7 @@
<label for="password_update">New password:</label> <label for="password_update">New password:</label>
<input type="password" size="20" name="password" id="password_update" /> <input type="password" size="20" name="password" id="password_update" />
<input type="hidden" name="action" value="update" /> <input type="hidden" name="action" value="update" />
<input type="hidden" name="htname" value="${htname}"/> <input type="hidden" name="zone" value="${zone}"/>
<input type="submit" name="submit" value="Change password" /> <input type="submit" name="submit" value="Change password" />
</form> </form>
@ -50,7 +51,7 @@
<option py:for="username in usernames">${username}</option> <option py:for="username in usernames">${username}</option>
</select> </select>
<input type="hidden" name="action" value="del" /> <input type="hidden" name="action" value="del" />
<input type="hidden" name="htname" value="${htname}"/> <input type="hidden" name="zone" value="${zone}"/>
<input type="submit" name="submit" value="Delete user" /> <input type="submit" name="submit" value="Delete user" />
</form> </form>
</py:if> </py:if>

View file

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html xml:lang="de" lang="de"
xmlns="http://www.w3.org/1999/xhtml"
xmlns:xi="http://www.w3.org/2001/XInclude"
xmlns:py="http://genshi.edgewall.org/">
<xi:include href="layout.html" />
<head/>
<body>
<h2>Change user password</h2>
<div py:if="error" class="error">${error}</div>
<div py:if="success" class="success">${success}</div>
<form name="update-passwd" action="password" method="POST">
<table border="0">
<tr>
<td><label for="zone">Zone:</label></td>
<td><input type="text" size="20" name="zone" id="zone" /></td>
</tr><tr>
<td><label for="username">User name:</label></td>
<td><input type="text" size="20" name="username" id="username" /></td>
</tr><tr>
<td><label for="old_password">Old password:</label></td>
<td><input type="password" size="20" name="old_password" id="old_password" /></td>
</tr><tr>
<td><label for="new_password">New password:</label></td>
<td><input type="password" size="20" name="new_password" id="new_password" /></td>
</tr><tr>
<td><label for="new_password2">New password (repeat):</label></td>
<td><input type="password" size="20" name="new_password2" id="new_password2" /></td>
</tr>
</table>
<input type="submit" name="submit" value="Change password" />
</form>
</body>
</html>