Command line script to manage the cpi lab journal users.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

310 lines
11 KiB

#/usr/bin/python
# imports of modules
import ConfigParser
import optparse
import os
import re
import random
import string
import subprocess
import sys
# defining some constants
AUTHZ_PATH = "authz"
HTPWD_PATH = "htpasswd"
SVN_DIR_CREATOR = "svn-dir-creator"
SVN_BASE = "cpi:/"
ADMINS = "administrators"
REGULAR = "users"
RESTRICTED = "restricted"
ALUMNI = "alumni"
READ_ACL = "r"
WRITE_ACL = "rw"
re_separators = re.compile("[\t ,;]+")
# helper functions
def group_users(users):
""" uses the list of users to group them by their group name """
groups = dict()
for user in users.values():
if user.group not in groups:
groups[user.group] = []
groups[user.group].append(user.name)
return groups
def set_new_password(name, length=10):
""" sets a new password for a username """
characters = string.ascii_letters + string.digits
password = "".join(random.choice(characters) for i in range(length))
subprocess.check_call(["htpasswd", "-b", HTPWD_PATH, name, password])
return password
def delete_password(name, length=10):
""" deletes a password for a username """
# if the user was not added to the password db, the removal will show
# an error message that is confusing to the user - at least it confused me
# so redirect this to /dev/null
with open(os.devnull, 'wb') as devnull:
subprocess.check_call(["htpasswd", "-D", HTPWD_PATH, name], stderr=devnull)
# class definitions
class User(object):
""" Collect the username, group and access control lists """
def __init__(self, name, group):
""" initialization of the class """
self.name = name
self.group = group
self.write_acl = []
self.read_acl = []
def __str__(self):
""" return a string representation """
return self.name
def __repr__(self):
""" return a string representation of the object """
return "<User '%s@%s'>" % (self.name, self.group)
class AuthzConfigParser(ConfigParser.ConfigParser, object):
""" custom functions for parsing the "authz" file as used at cpi """
def __init__(self):
""" initialization of the class """
self.users = None
self._acl_defaults = { WRITE_ACL: [], READ_ACL: [] }
super(AuthzConfigParser, self).__init__()
def optionxform(self, value):
""" reset the method to use cases ensitive names """
return str(value)
def read(self, path):
""" set up the acl defaults after reading the file """
super(AuthzConfigParser, self).read(path)
self._acl_defaults = self.get_folder_info("")
def extract_users(self):
""" extract user information from config """
users = dict()
# first we go through the groups, as found in the groups section of the
# authz file
for group, userlist in self.items("groups"):
for username in re_separators.split(userlist):
if username in users:
raise Exception("Found duplicate entry for user " + username)
user = User(username, group)
users[username] = user
# second we scan each section that is related to an svn folder (it
# starts with the svn base) for read and write access user entries
for section in self.sections():
if section.startswith(SVN_BASE):
belongs_to = section.lstrip(SVN_BASE)
for (option, value) in self.items(section):
if option in users:
if value.lower() == WRITE_ACL:
users[option].write_acl.append(belongs_to)
elif value.lower() == READ_ACL:
users[option].read_acl.append(belongs_to)
# return the userlist
return users
def get_folder_info(self, name):
""" returns read and write access info of an svn folder """
if not name.startswith(SVN_BASE):
name = SVN_BASE + name
if not self.has_section(name):
return None
info = self._acl_defaults.copy()
for (option, value) in self.items(name):
if value in (WRITE_ACL, READ_ACL):
info[value].append(option)
# remove explicit dismissed acls
if not value:
for acltype in (WRITE_ACL, READ_ACL):
if option in info[acltype]:
info[acltype].remove(option)
return info
def move_user_to_alumni(self, user):
""" moves a user to the alumni group and removes every access rights """
for access_to in user.write_acl:
folder = SVN_BASE + access_to
self.remove_option(folder, user.name)
for access_to in user.read_acl:
folder = SVN_BASE + access_to
self.remove_option(folder, user.name)
user.write_acl = []
user.read_acl = []
user.group = ALUMNI
delete_password(user.name)
def update_user_groups(self, users):
""" updates the config settings of the groups section """
groups = group_users(users)
for group, userlist in groups.items():
self.set("groups", group, ", ".join(sorted(userlist)))
def write_to_file(self):
with open(AUTHZ_PATH, "w") as filehandle:
self.write(filehandle)
def write(self, fp):
"""Write an .ini-format representation of the configuration state.
this is adapted from the original library file. changes:
- no default section
- group-section at top
- rest of section sorted by name
"""
sorted_keys = sorted(self._sections.keys())
sorting = ["groups"]
sorting.extend([k for k in sorted_keys if k <> "groups"])
for section in sorting:
fp.write("[%s]\n" % section)
for (key, value) in self._sections[section].items():
if key == "__name__":
continue
if (value is not None) or (self._optcre == self.OPTCRE):
key = " = ".join((key, str(value).replace('\n', '\n\t')))
fp.write("%s\n" % (key))
fp.write("\n")
if __name__ == "__main__":
# create configparser instance
config = AuthzConfigParser()
# change option name transformation to case sensitive
config.optionxform = str
# read config file
config.read(AUTHZ_PATH)
users = config.extract_users()
# command line interface:
# no option: display info
# -g display users in a group
# -a add regular user
# -r add restricted user
# -m move to alumni
# -p reset user password
parser = optparse.OptionParser(
usage="usage: %prog [option] name",
description="shows and manipulates svn access rights",
epilog="to grant a restricted user access to another folder, you have to carefully edit the authz file")
parser.add_option("-g", "--groupinfo", action="store_const", dest="what",
const="g", help="display users in a group")
parser.add_option("-a", "--add", action="store_const", dest="what",
const="a", help="add a regular user")
parser.add_option("-r", "--restricted", action="store_const", dest="what",
const="r", help="add a restricted user")
parser.add_option("-m", "--move", action="store_const", dest="what",
const="m", help="move a user to alumni")
parser.add_option("-p", "--password", action="store_const", dest="what",
const="p", help="reset a user password")
options, args = parser.parse_args()
if len(args)==0:
# no arguments? then display all the users!
groups = group_users(users)
for name, usernames in groups.items():
print "Users in group '%s':" % name
for name in sorted(usernames):
print " " + name
sys.exit()
if len(args)>1:
# more than one usename? not here, john boy
sys.exit("please provide only one name")
name = args[0]
if options.what == "g":
# show group information
groups = group_users(users)
if name not in groups:
sys.exit("Group not found")
print "Users in group '%s':" % name
for usernamename in sorted(groups[name]):
print " " + usernamename
sys.exit()
if options.what in ("a", "r"):
# add a user, restricted or regular
if name in users:
sys.exit("Username '%s' already in use" % name)
group = RESTRICTED if options.what == "r" else REGULAR
users[name] = User(name, group)
config.update_user_groups(users)
folder = SVN_BASE + name
config.add_section(folder)
config.set(folder, "@"+RESTRICTED, "")
config.set(folder, name, WRITE_ACL)
#subprocess.check_call(SVN_DIR_CREATOR + " " + name, shell=True)
password = set_new_password(name)
print "New password for user '%s': '%s'" % (name, password)
config.write_to_file()
sys.exit()
# from here downwards we need already existent usernames
if name not in users:
sys.exit("User '%s' not found, use this without a name to get a list of users." % name)
user = users[name]
if options.what == "m":
# move user to alumni
groups = group_users(users)
if user.group == ALUMNI:
sys.exit("User '%s' is already in group '%s'" % (name, ALUMNI))
if user.group == ADMINS:
sys.exit("User '%s' is in group '%s', will not moved to '%s'" % (name, ADMINS, ALUMNI))
config.move_user_to_alumni(user)
config.update_user_groups(users)
config.write_to_file()
sys.exit()
if options.what == "p":
# reset a password
password = set_new_password(name)
print "New password for user '%s': '%s'" % (name, password)
sys.exit()
# no option, just a name:
# print the write acls for a user
print "User %s is in group '%s':" % (name, user.group)
if user.group == ADMINS:
print " Write access is granted to all folders."
elif user.write_acl:
write_acl = [ SVN_BASE + username for username in user.write_acl ]
print " Write access is granted to folders '%s'. " % "', '".join(write_acl)
else:
print " Write access is NOT granted to any folder"
# print the read acls for a user
if user.group == ADMINS:
print " Read access is granted to all folders."
elif user.group == REGULAR:
print " Read access is granted to (nearly) all folders."
elif user.read_acl:
read_acl = [ SVN_BASE + username for username in user.read_acl ]
print " Read access is granted to folders '%s'. " % "', '".join(read_acl)
else:
print " Read access is NOT granted to any folder"
# print the write acls for a journal
info = config.get_folder_info(name)
print "Labjornal %s%s:" % (SVN_BASE, name)
if info[WRITE_ACL]:
print " Write and read access granted to: " + ", ".join(info[WRITE_ACL])
else:
print " No write access granted to anybody"
# print the read acls for a journal
if info[READ_ACL]:
print " Read access granted to: " + ", ".join(info[READ_ACL])
else:
print " No read access granted to anybody"