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

@@ -20,7 +21,7 @@ - +
@@ -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}
+ +
+ + + + + + + + + + + + + + + + + +
+ +
+ + +