From d58d3c35caef5cb2c8ce7d056b537493304be3b0 Mon Sep 17 00:00:00 2001
From: lars
Date: Thu, 20 Sep 2012 01:51:08 +0000
Subject: [PATCH] renamed "htname" to "zone" added users to change their
passwords (without admin access)
---
htman.py | 98 +++++++++++++++++++++++++++-------
htpasswd.py | 8 +++
templates/frontpage.html | 2 +-
templates/manage_users.html | 9 ++--
templates/password_change.html | 39 ++++++++++++++
5 files changed, 131 insertions(+), 25 deletions(-)
create mode 100644 templates/password_change.html
diff --git a/htman.py b/htman.py
index f825977..881309f 100644
--- a/htman.py
+++ b/htman.py
@@ -30,6 +30,7 @@ sys.path.insert(1, '/usr/share/pyshared')
import htpasswd
import bobo
import genshi.template
+import genshi.filters
import ConfigParser
import re
@@ -46,15 +47,57 @@ REGEX = {
if "HTMAN_CONFIG" in os.environ:
CONFIG_FILE_LOCATIONS = [os.environ["HTMAN_CONFIG"]]
+
@bobo.query('')
def redirect_frontpage():
- return bobo.redirect(web_defaults["base_url"] + '/')
+ return bobo.redirect(web_defaults["base_url"])
+
@bobo.query('/')
def show_frontpage():
values = web_defaults.copy()
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')
def show_files():
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]
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')
-def show_htpasswd(htname=None):
- if (htname is None) or (not htname in get_mapping()) or (not re.match(REGEX["mapping"], htname)):
+def show_htpasswd(zone=None):
+ if not is_zone_valid(zone):
return bobo.redirect(web_defaults["base_url"])
else:
# 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
# Apache's "Location" directive
-@bobo.query('/admin/manage/:htname')
-@bobo.query('/manage/:htname')
-def manage_htpasswd(htname=None, action=None, username=None, password=None):
+@bobo.query('/admin/manage/:zone')
+@bobo.query('/manage/:zone')
+def manage_htpasswd(zone=None, action=None, username=None, password=None):
values = web_defaults.copy()
values["error"] = 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"])
- values["htname"] = htname
- htpasswd_file = get_htpasswd_filename(htname)
- do_create_file = not os.path.isfile(htpasswd_file)
- try:
- htdb = htpasswd.HtpasswdFile(htpasswd_file, create=do_create_file)
- except IOError:
+ values["zone"] = zone
+ htdb = get_htpasswd(zone, auto_create=True)
+ if not htdb:
values["error"] = "Failed to read htpasswd file"
- htdb = None
+ values["usernames"] = []
else:
if action is None:
# 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!"
else:
values["error"] = "Invalid action"
- if not htdb is None:
values["usernames"] = htdb.get_usernames()
- else:
- values["usernames"] = []
# show the current htpasswd file
return render("manage_users.html", **values)
+
def get_config():
config = ConfigParser.SafeConfigParser()
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)
sys.exit(1)
+
def get_mapping():
try:
mapping_file = config.get("Locations", "mapping")
@@ -178,8 +232,9 @@ def get_mapping():
mapping[name] = location
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)):
# relative filename in mapping file
# let's try the htpasswd_directory setting
@@ -203,6 +258,7 @@ def get_templates_dir(config):
sys.exit(3)
return os.path.abspath(templates_dir)
+
def render(filename, input_data=None, **values):
stream = loader.load(filename).generate(**values)
if not input_data is None:
@@ -216,6 +272,8 @@ config = get_config()
mapping = get_mapping()
templates_dir = get_templates_dir(config)
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)
diff --git a/htpasswd.py b/htpasswd.py
index 6ffd7ab..454302b 100644
--- a/htpasswd.py
+++ b/htpasswd.py
@@ -102,6 +102,14 @@ class HtpasswdFile:
if username in self.entries:
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):
names = self.entries.keys()
names.sort()
diff --git a/templates/frontpage.html b/templates/frontpage.html
index e319e06..f3db4fb 100644
--- a/templates/frontpage.html
+++ b/templates/frontpage.html
@@ -10,7 +10,7 @@
diff --git a/templates/manage_users.html b/templates/manage_users.html
index 71f37cb..8583708 100644
--- a/templates/manage_users.html
+++ b/templates/manage_users.html
@@ -8,9 +8,10 @@
- Manage user access for ${htname}
+ Manage user access for ${zone}
${error}
+ ${success}
Add user
@@ -37,7 +38,7 @@
-
+
@@ -50,7 +51,7 @@
-
+
diff --git a/templates/password_change.html b/templates/password_change.html
new file mode 100644
index 0000000..ca27327
--- /dev/null
+++ b/templates/password_change.html
@@ -0,0 +1,39 @@
+
+
+
+
+
+ Change user password
+
+ ${error}
+ ${success}
+
+
+
+
+