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
if 'unixid' not in props or props['unixid'] is None:
69
# For the time being, just choose a random unixid.
70
# This may result in duplicates, but the unixid is essentially
71
# a monitoring/logging convenience only. Oh, and dups could kill
73
props['unixid'] = random.randrange(5000,10000)
74
# raise NotImplementedError, "No algorithm for creating uids yet!"
76
# props['unixid'] = unixid
78
common.makeuser.make_user_db(**props)
80
return int(props['unixid'])
82
def update_user(props):
83
"""Create the database record for the given user.
85
login - user who is making the change (not necessarily the one
87
update - dict of fields to be updated. The fields are all those
88
in the login table of the db.
90
Omitted fields will not be set.
92
update = props['update']
93
# Note: "login" is special - it is not part of the kwargs to
94
# db.update_user, but the first arg - used to find the user to update.
95
# However it doesn't need any special treatment here.
98
db.update_user(**update)
101
def get_login(login, passwd, realm, existing_login, _may_save):
102
"""Callback function used by pysvn for authentication.
103
login, passwd: Credentials of the user attempting to log in.
104
passwd in clear (this should be the user's svn_pass, a
105
randomly-generated permanent password for this user, not their main
107
realm, existing_login, _may_save: The 3 arguments passed by pysvn to
109
The following has been determined empirically, not from docs:
110
existing_login will be the name of the user who owns the process on
111
the first attempt, "" on subsequent attempts. We use this fact.
113
logging.debug("Getting password for %s (realm %s)" % (login, realm))
114
# Only provide credentials on the _first_ attempt.
115
# If we're being asked again, then it means the credentials failed for
116
# some reason and we should just fail. (This is not desirable, but it's
117
# better than being asked an infinite number of times).
118
if existing_login == "":
119
logging.warning("Could not authenticate SVN for %s" % login)
120
return (False, login, passwd, False)
122
return (True, login, passwd, False)
124
def activate_user(props):
125
"""Create the on-disk stuff for the given user.
126
Sets the state of the user in the db from pending to enabled.
128
login - the user name for the jail
133
login = props['login']
139
# FIXME: check we're pending
141
details = db.get_user(login)
143
# make svn config/auth
145
logging.debug("Creating repo")
146
common.makeuser.make_svn_repo(login, throw_on_error=False)
147
logging.debug("Creating svn config")
148
common.makeuser.make_svn_config(login, throw_on_error=False)
149
logging.debug("Creating svn auth")
150
passwd = common.makeuser.make_svn_auth(login, throw_on_error=False)
151
logging.debug("passwd: %s" % passwd)
154
svn.callback_get_login = partial(get_login, login, passwd)
156
if conf.svn_addr[-1] != '/':
157
conf.svn_addr = conf.svn_addr + "/"
159
# FIXME: This should be a loop over enrolements.
160
# We're not going to fix this now because it requires
161
# a large amount of admin console work to manage subject
163
# Instead, we're just going to use a single offering with
164
# a "short" name of "info1".
165
logging.debug("Creating /info1")
167
svn.mkdir(conf.svn_addr + login + "/info1",
168
"Initial creation of work directory for Informatics 1")
169
except Exception, exc:
170
logging.warning("While mkdiring info1: %s" % str(exc))
172
logging.debug("Creating /stuff")
174
svn.mkdir(conf.svn_addr + login + "/stuff",
175
"Initial creation of directory for miscellania")
176
except Exception, exc:
177
logging.warning("While mkdiring stuff: %s" % str(exc))
180
logging.debug("Creating jail")
181
common.makeuser.make_jail(login, details.unixid, svn_pass=passwd)
183
do_checkout(props, svn_pass=passwd)
185
# FIXME: should this be nicer?
186
os.system("chown -R %d:%d %s" \
187
% (details.unixid, details.unixid,
188
common.studpath.url_to_local(login)[1]))
190
logging.info("Enabling user")
191
db.update_user(login, state='enabled')
193
return {"response": "okay"}
198
def do_checkout(props, svn_pass=None):
199
"""Create the default contents of a user's jail by checking out from the
201
If any directory already exists, just fail silently (so this is not a
202
"wipe-and-checkout", merely a checkout if it failed or something).
204
login - the user name for the jail
208
login = props['login']
210
# If svn_pass isn't supplied, grab it from the DB
213
user = db.get_user(login)
214
svn_pass = user.svn_pass
218
svn.callback_get_login = partial(get_login, login, svn_pass)
220
if conf.svn_addr[-1] != os.sep:
221
conf.svn_addr += os.sep
225
logging.debug("Checking out directories in the jail")
227
svn.checkout(conf.svn_addr + login + "/stuff",
228
common.studpath.url_to_local(login + "/stuff")[1])
229
os.system("chown -R %d:%d %s" \
230
% (user.unixid, user.unixid,
231
common.studpath.url_to_local(login + "/stuff")[1]))
232
except Exception, exc:
233
logging.warning("While mkdiring stuff: %s" % str(exc))
236
svn.checkout(conf.svn_addr + login + "/info1",
237
common.studpath.url_to_local(login + "/info1")[1])
238
os.system("chown -R %d:%d %s" \
239
% (user.unixid, user.unixid,
240
common.studpath.url_to_local(login + "/info1")[1]))
241
except Exception, exc:
242
logging.warning("While mkdiring info1: %s" % str(exc))
247
return {"response": "okay"}
250
'create_user':create_user,
251
'update_user':update_user,
252
'activate_user':activate_user,
253
'do_checkout':do_checkout,
258
pidfile = open('/var/run/usrmgt-server.pid', 'w')
259
pidfile.write('%d\n' % os.getpid())
261
except IOError, (errno, strerror):
262
print "Couldn't write PID file. IO error(%s): %s" % (errno, strerror)
266
logging.debug(repr(props))
267
action = props.keys()[0]
268
return actions[action](props[action])
270
if __name__ == "__main__":
272
print >>sys.stderr, "Usage: usrmgt-server <port> <magic>"
274
port = int(sys.argv[1])
279
logging.basicConfig(filename="/var/log/usrmgt.log", level=logging.INFO)
280
logging.info("Starting usrmgt server on port %d (pid = %d)" % (port, pid))
282
common.chat.start_server(port, magic, True, dispatch, initializer)