8
from functools import partial
16
import common.makeuser
17
import common.studpath
20
# usrmgt-server <port> <magic>
22
# User management operations:
24
# - [Re]Create jail for a user
25
# - Create a svn repository for a user
29
# - Checkout repository as home directory
31
# - Disable a user's account
32
# - Enable a user's account
34
# - Rebuild svn config
35
# - Rebuild svn auth file
36
# - Rebuild passwd + push to nodes.
38
def create_user(props):
39
"""Create the database record for the given user.
41
login - used as a unix login name and svn repository name.
43
unixid - the unix uid under which execution will take place
44
on the behalf of the user. Don't use 0! If not specified
45
or None, one will be allocated from the configured
48
password - the clear-text password for the user. If this property is
49
absent or None, this is an indication that external
50
authentication should be used (i.e. LDAP).
52
email - the user's email address.
54
nick - the display name to use.
56
fullname - The name of the user for results and/or other official
59
rolenm - The user's role. Must be one of "anyone", "student",
60
"tutor", "lecturer", "admin".
62
studentid - If supplied and not None, the student id of the user for
63
results and/or other official purposes.
65
Return Value: the uid associated with the user. INT
68
common.makeuser.make_user_db(**props)
69
user = common.db.get_user(props["login"])
72
def update_user(props):
73
"""Create the database record for the given user.
75
login - user who is making the change (not necessarily the one
77
update - dict of fields to be updated. The fields are all those
78
in the login table of the db.
80
Omitted fields will not be set.
82
update = props['update']
83
# Note: "login" is special - it is not part of the kwargs to
84
# db.update_user, but the first arg - used to find the user to update.
85
# However it doesn't need any special treatment here.
88
db.update_user(**update)
91
def get_login(login, passwd, realm, existing_login, _may_save):
92
"""Callback function used by pysvn for authentication.
93
login, passwd: Credentials of the user attempting to log in.
94
passwd in clear (this should be the user's svn_pass, a
95
randomly-generated permanent password for this user, not their main
97
realm, existing_login, _may_save: The 3 arguments passed by pysvn to
99
The following has been determined empirically, not from docs:
100
existing_login will be the name of the user who owns the process on
101
the first attempt, "" on subsequent attempts. We use this fact.
103
logging.debug("Getting password for %s (realm %s)" % (login, realm))
104
# Only provide credentials on the _first_ attempt.
105
# If we're being asked again, then it means the credentials failed for
106
# some reason and we should just fail. (This is not desirable, but it's
107
# better than being asked an infinite number of times).
108
if existing_login == "":
109
logging.warning("Could not authenticate SVN for %s" % login)
110
return (False, login, passwd, False)
112
return (True, login, passwd, False)
114
def activate_user(props):
115
"""Create the on-disk stuff for the given user.
116
Sets the state of the user in the db from pending to enabled.
118
login - the user name for the jail
123
os.umask(0022) # Bad, but start_server sets it worse.
125
login = props['login']
131
# FIXME: check we're pending
133
details = db.get_user(login)
135
# make svn config/auth
137
logging.debug("Creating repo")
138
common.makeuser.make_svn_repo(login, throw_on_error=False)
139
logging.debug("Creating svn config")
140
common.makeuser.make_svn_config(login, throw_on_error=False)
141
logging.debug("Creating svn auth")
142
passwd = common.makeuser.make_svn_auth(login, throw_on_error=False)
143
logging.debug("passwd: %s" % passwd)
146
svn.callback_get_login = partial(get_login, login, passwd)
148
if conf.svn_addr[-1] != '/':
149
conf.svn_addr = conf.svn_addr + "/"
151
# FIXME: This should be a loop over enrolements.
152
# We're not going to fix this now because it requires
153
# a large amount of admin console work to manage subject
155
# Instead, we're just going to use a single offering with
156
# a "short" name of "info1".
157
logging.debug("Creating /info1")
159
svn.mkdir(conf.svn_addr + login + "/info1",
160
"Initial creation of work directory for Informatics 1")
161
except Exception, exc:
162
logging.warning("While mkdiring info1: %s" % str(exc))
164
logging.debug("Creating /stuff")
166
svn.mkdir(conf.svn_addr + login + "/stuff",
167
"Initial creation of directory for miscellania")
168
except Exception, exc:
169
logging.warning("While mkdiring stuff: %s" % str(exc))
172
logging.debug("Creating jail")
173
common.makeuser.make_jail(login, details.unixid, svn_pass=passwd)
175
common.makeuser.mount_jail(login)
177
do_checkout(props, svn_pass=passwd)
179
# FIXME: should this be nicer?
180
os.system("chown -R %d:%d %s" \
181
% (details.unixid, details.unixid,
182
common.studpath.url_to_local(login)[1]))
184
logging.info("Enabling user")
185
db.update_user(login, state='enabled')
187
return {"response": "okay"}
192
def do_checkout(props, svn_pass=None):
193
"""Create the default contents of a user's jail by checking out from the
195
If any directory already exists, just fail silently (so this is not a
196
"wipe-and-checkout", merely a checkout if it failed or something).
198
login - the user name for the jail
202
login = props['login']
205
user = db.get_user(login)
206
# If svn_pass isn't supplied, grab it from the DB
208
svn_pass = user.svn_pass
212
svn.callback_get_login = partial(get_login, login, svn_pass)
214
if conf.svn_addr[-1] != os.sep:
215
conf.svn_addr += os.sep
219
logging.debug("Checking out directories in the jail")
221
svn.checkout(conf.svn_addr + login + "/stuff",
222
common.studpath.url_to_local(login + "/stuff")[1])
223
os.system("chown -R %d:%d %s" \
224
% (user.unixid, user.unixid,
225
common.studpath.url_to_local(login + "/stuff")[1]))
226
except Exception, exc:
227
logging.warning("While mkdiring stuff: %s" % str(exc))
230
svn.checkout(conf.svn_addr + login + "/info1",
231
common.studpath.url_to_local(login + "/info1")[1])
232
os.system("chown -R %d:%d %s" \
233
% (user.unixid, user.unixid,
234
common.studpath.url_to_local(login + "/info1")[1]))
235
except Exception, exc:
236
logging.warning("While mkdiring info1: %s" % str(exc))
241
return {"response": "okay"}
244
'create_user':create_user,
245
'update_user':update_user,
246
'activate_user':activate_user,
247
'do_checkout':do_checkout,
252
pidfile = open('/var/run/usrmgt-server.pid', 'w')
253
pidfile.write('%d\n' % os.getpid())
255
except IOError, (errno, strerror):
256
print "Couldn't write PID file. IO error(%s): %s" % (errno, strerror)
260
logging.debug(repr(props))
261
action = props.keys()[0]
262
return actions[action](props[action])
264
if __name__ == "__main__":
266
print >>sys.stderr, "Usage: usrmgt-server <port> <magic>"
268
port = int(sys.argv[1])
273
logging.basicConfig(filename="/var/log/usrmgt.log", level=logging.INFO)
274
logging.info("Starting usrmgt server on port %d (pid = %d)" % (port, pid))
276
common.chat.start_server(port, magic, True, dispatch, initializer)