renamed "htname" to "zone"
added users to change their passwords (without admin access)
This commit is contained in:
parent
4c7ed4b7be
commit
d58d3c35ca
5 changed files with 131 additions and 25 deletions
98
htman.py
98
htman.py
|
@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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>
|
||||||
|
|
39
templates/password_change.html
Normal file
39
templates/password_change.html
Normal 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>
|
||||||
|
|
Loading…
Reference in a new issue