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 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)
|
||||
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<body>
|
||||
<form name="show_htpasswd" action="manage" method="POST">
|
||||
<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" />
|
||||
</form>
|
||||
</body>
|
||||
|
|
|
@ -8,9 +8,10 @@
|
|||
<head/>
|
||||
|
||||
<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="success" class="success">${success}</div>
|
||||
|
||||
<h3>Add user</h3>
|
||||
<form name="add-passwd" action="" method="POST">
|
||||
|
@ -20,7 +21,7 @@
|
|||
<label for="password_add">Password:</label>
|
||||
<input type="password" size="20" name="password" id="password_add" />
|
||||
<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" />
|
||||
</form>
|
||||
|
||||
|
@ -37,7 +38,7 @@
|
|||
<label for="password_update">New password:</label>
|
||||
<input type="password" size="20" name="password" id="password_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" />
|
||||
</form>
|
||||
|
||||
|
@ -50,7 +51,7 @@
|
|||
<option py:for="username in usernames">${username}</option>
|
||||
</select>
|
||||
<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" />
|
||||
</form>
|
||||
</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