~azzar1/unity/add-show-desktop-key

« back to all changes in this revision

Viewing changes to lib/common/makeuser.py

  • Committer: wagrant
  • Date: 2008-07-16 00:24:07 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:879
diffservice, fileservice_lib: Factor out conversion of strings to
      revision specifications, to common.svn.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# IVLE - Informatics Virtual Learning Environment
 
2
# Copyright (C) 2007-2008 The University of Melbourne
 
3
#
 
4
# This program is free software; you can redistribute it and/or modify
 
5
# it under the terms of the GNU General Public License as published by
 
6
# the Free Software Foundation; either version 2 of the License, or
 
7
# (at your option) any later version.
 
8
#
 
9
# This program is distributed in the hope that it will be useful,
 
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
# GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public License
 
15
# along with this program; if not, write to the Free Software
 
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 
17
 
 
18
# Module: MakeUser
 
19
# Author: Matt Giuca
 
20
# Date:   1/2/2008
 
21
 
 
22
# Allows creation of users. This sets up the following:
 
23
# * User's jail and home directory within the jail.
 
24
# * Subversion repository (TODO)
 
25
# * Check out Subversion workspace into jail (TODO)
 
26
# * Database details for user
 
27
# * Unix user account
 
28
 
 
29
# TODO: Sanitize login name and other fields.
 
30
# Users must not be called "temp" or "template".
 
31
 
 
32
# TODO: When creating a new home directory, chown it to its owner
 
33
 
 
34
# TODO: In chown_to_webserver:
 
35
# Do not call os.system("chown www-data") - use Python lib
 
36
# and use the web server uid given in conf. (Several places).
 
37
 
 
38
import md5
 
39
import os
 
40
import stat
 
41
import shutil
 
42
import time
 
43
import uuid
 
44
import warnings
 
45
import filecmp
 
46
import logging
 
47
import conf
 
48
import db
 
49
import pulldown_subj
 
50
 
 
51
def chown_to_webserver(filename):
 
52
    """
 
53
    Chowns a file so the web server user owns it.
 
54
    (This is useful in setting up Subversion conf files).
 
55
    Assumes root.
 
56
    """
 
57
    try:
 
58
        os.system("chown -R www-data:www-data %s" % filename)
 
59
    except:
 
60
        pass
 
61
 
 
62
def make_svn_repo(login, throw_on_error=True):
 
63
    """Create a repository for the given user.
 
64
    """
 
65
    path = os.path.join(conf.svn_repo_path, login)
 
66
    try:
 
67
        res = os.system("svnadmin create '%s'" % path)
 
68
        if res != 0 and throw_on_error:
 
69
            raise Exception("Cannot create repository for %s" % login)
 
70
    except Exception, exc:
 
71
        print repr(exc)
 
72
        if throw_on_error:
 
73
            raise
 
74
 
 
75
    chown_to_webserver(path)
 
76
 
 
77
def rebuild_svn_config():
 
78
    """Build the complete SVN configuration file.
 
79
    """
 
80
    conn = db.DB()
 
81
    res = conn.query("SELECT login, rolenm FROM login;").dictresult()
 
82
    groups = {}
 
83
    for r in res:
 
84
        role = r['rolenm']
 
85
        if role not in groups:
 
86
            groups[role] = []
 
87
        groups[role].append(r['login'])
 
88
    f = open(conf.svn_conf + ".new", "w")
 
89
    f.write("# IVLE SVN Repositories Configuration\n")
 
90
    f.write("# Auto-generated on %s\n" % time.asctime())
 
91
    f.write("\n")
 
92
    f.write("[groups]\n")
 
93
    for (g,ls) in groups.iteritems():
 
94
        f.write("%s = %s\n" % (g, ",".join(ls)))
 
95
    f.write("\n")
 
96
    for r in res:
 
97
        login = r['login']
 
98
        f.write("[%s:/]\n" % login)
 
99
        f.write("%s = rw\n" % login)
 
100
        #f.write("@tutor = r\n")
 
101
        #f.write("@lecturer = rw\n")
 
102
        #f.write("@admin = rw\n")
 
103
        f.write("\n")
 
104
    f.close()
 
105
    os.rename(conf.svn_conf + ".new", conf.svn_conf)
 
106
    chown_to_webserver(conf.svn_conf)
 
107
 
 
108
def make_svn_config(login, throw_on_error=True):
 
109
    """Add an entry to the apache-svn config file for the given user.
 
110
       Assumes the given user is either a guest or a student.
 
111
    """
 
112
    f = open(conf.svn_conf, "a")
 
113
    f.write("[%s:/]\n" % login)
 
114
    f.write("%s = rw\n" % login)
 
115
    #f.write("@tutor = r\n")
 
116
    #f.write("@lecturer = rw\n")
 
117
    #f.write("@admin = rw\n")
 
118
    f.write("\n")
 
119
    f.close()
 
120
    chown_to_webserver(conf.svn_conf)
 
121
 
 
122
def make_svn_auth(login, throw_on_error=True):
 
123
    """Setup svn authentication for the given user.
 
124
       FIXME: create local.auth entry
 
125
    """
 
126
    passwd = md5.new(uuid.uuid4().bytes).digest().encode('hex')
 
127
    if os.path.exists(conf.svn_auth_ivle):
 
128
        create = ""
 
129
    else:
 
130
        create = "c"
 
131
 
 
132
    db.DB().update_user(login, svn_pass=passwd)
 
133
 
 
134
    res = os.system("htpasswd -%smb %s %s %s" % (create,
 
135
                                              conf.svn_auth_ivle,
 
136
                                              login, passwd))
 
137
    if res != 0 and throw_on_error:
 
138
        raise Exception("Unable to create ivle-auth for %s" % login)
 
139
 
 
140
    # Make sure the file is owned by the web server
 
141
    if create == "c":
 
142
        chown_to_webserver(conf.svn_auth_ivle)
 
143
 
 
144
    return passwd
 
145
 
 
146
def generate_manifest(basedir, targetdir, parent=''):
 
147
    """ From a basedir and a targetdir work out which files are missing or out 
 
148
    of date and need to be added/updated and which files are redundant and need 
 
149
    to be removed.
 
150
    
 
151
    parent: This is used for the recursive call to track the relative paths 
 
152
    that we have decended.
 
153
    """
 
154
    
 
155
    cmp = filecmp.dircmp(basedir, targetdir)
 
156
 
 
157
    # Add all new files and files that have changed
 
158
    to_add = [os.path.join(parent,x) for x in (cmp.left_only + cmp.diff_files)]
 
159
 
 
160
    # Remove files that are redundant
 
161
    to_remove = [os.path.join(parent,x) for x in cmp.right_only]
 
162
    
 
163
    # Recurse
 
164
    for d in cmp.common_dirs:
 
165
        newbasedir = os.path.join(basedir, d)
 
166
        newtargetdir = os.path.join(targetdir, d)
 
167
        newparent = os.path.join(parent, d)
 
168
        (sadd,sremove) = generate_manifest(newbasedir, newtargetdir, newparent)
 
169
        to_add += sadd
 
170
        to_remove += sremove
 
171
 
 
172
    return (to_add, to_remove)
 
173
 
 
174
 
 
175
def make_jail(username, uid, force=True, svn_pass=None):
 
176
    """Creates a new user's jail space, in the jail directory as configured in
 
177
    conf.py.
 
178
 
 
179
    This only creates things within /home - everything else is expected to be
 
180
    part of another UnionFS branch.
 
181
 
 
182
    Returns the path to the user's home directory.
 
183
 
 
184
    Chowns the user's directory within the jail to the given UID.
 
185
 
 
186
    Note: This takes separate username and uid arguments. The UID need not
 
187
    *necessarily* correspond to a Unix username at all, if all you are
 
188
    planning to do is setuid to it. This allows the caller the freedom of
 
189
    deciding the binding between username and uid, if any.
 
190
 
 
191
    force: If false, exception if jail already exists for this user.
 
192
    If true (default), overwrites it, but preserves home directory.
 
193
 
 
194
    svn_pass: If provided this will be a string, the randomly-generated
 
195
    Subversion password for this user (if you happen to already have it).
 
196
    If not provided, it will be read from the database.
 
197
    """
 
198
    # MUST run as root or some of this may fail
 
199
    if os.getuid() != 0:
 
200
        raise Exception("Must run make_jail as root")
 
201
    
 
202
    # tempdir is for putting backup homes in
 
203
    tempdir = os.path.join(conf.jail_base, '__temp__')
 
204
    if not os.path.exists(tempdir):
 
205
        os.makedirs(tempdir)
 
206
    elif not os.path.isdir(tempdir):
 
207
        os.unlink(tempdir)
 
208
        os.mkdir(tempdir)
 
209
    userdir = os.path.join(conf.jail_src_base, username)
 
210
    homedir = os.path.join(userdir, 'home')
 
211
    userhomedir = os.path.join(homedir, username)   # Return value
 
212
 
 
213
    if os.path.exists(userdir):
 
214
        if not force:
 
215
            raise Exception("User's jail already exists")
 
216
        # User jail already exists. Blow it away but preserve their home
 
217
        # directory. It should be all that is there anyway, but you never
 
218
        # know!
 
219
        # Ignore warnings about the use of tmpnam
 
220
        warnings.simplefilter('ignore')
 
221
        homebackup = os.tempnam(tempdir)
 
222
        warnings.resetwarnings()
 
223
        # Note: shutil.move does not behave like "mv" - it does not put a file
 
224
        # into a directory if it already exists, just fails. Therefore it is
 
225
        # not susceptible to tmpnam symlink attack.
 
226
        shutil.move(homedir, homebackup)
 
227
        shutil.rmtree(userdir)
 
228
        os.makedirs(homedir)
 
229
        shutil.move(homebackup, homedir)
 
230
    else:
 
231
        # No user jail exists
 
232
        # Set up the user's home directory
 
233
        os.makedirs(userhomedir)
 
234
        # Chown (and set the GID to the same as the UID).
 
235
        os.chown(userhomedir, uid, uid)
 
236
        # Chmod to rwxr-xr-x (755)
 
237
        os.chmod(userhomedir, 0755)
 
238
 
 
239
    # There are 2 special files which need to be generated specific to this
 
240
    # user: /opt/ivle/lib/conf/conf.py and /etc/passwd.
 
241
    # "__" username "__" users are exempt (special)
 
242
    if not (username.startswith("__") and username.endswith("__")):
 
243
        make_conf_py(username, userdir, conf.jail_system, svn_pass)
 
244
        make_etc_passwd(username, userdir, conf.jail_system, uid)
 
245
 
 
246
    return userhomedir
 
247
 
 
248
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
 
249
    """
 
250
    Creates (overwriting any existing file, and creating directories) a
 
251
    file /opt/ivle/lib/conf/conf.py in a given user's jail.
 
252
    username: Username.
 
253
    user_jail_dir: User's jail dir, ie. conf.jail_base + username
 
254
    staging_dir: The dir with the staging copy of the jail. (With the
 
255
        template conf.py file).
 
256
    svn_pass: As with make_jail. User's SVN password, but if not supplied,
 
257
        will look up in the DB.
 
258
    """
 
259
    template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
 
260
    conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
 
261
    os.makedirs(os.path.dirname(conf_path))
 
262
 
 
263
    # If svn_pass isn't supplied, grab it from the DB
 
264
    if svn_pass is None:
 
265
        dbconn = db.DB()
 
266
        svn_pass = dbconn.get_user(username).svn_pass
 
267
        dbconn.close()
 
268
 
 
269
    # Read the contents of the template conf file
 
270
    try:
 
271
        template_conf_file = open(template_conf_path, "r")
 
272
        template_conf_data = template_conf_file.read()
 
273
        template_conf_file.close()
 
274
    except:
 
275
        # Couldn't open template conf.py for some reason
 
276
        # Just treat it as empty file
 
277
        template_conf_data = ("# Warning: Problem building config script.\n"
 
278
                              "# Could not find template conf.py file.\n")
 
279
 
 
280
    conf_file = open(conf_path, "w")
 
281
    conf_file.write(template_conf_data)
 
282
    conf_file.write("\n# The login name for the owner of the jail\n")
 
283
    conf_file.write("login = %s\n" % repr(username))
 
284
    conf_file.write("\n")
 
285
    conf_file.write("# The subversion-only password for the owner of "
 
286
        "the jail\n")
 
287
    conf_file.write("svn_pass = %s\n" % repr(svn_pass))
 
288
    conf_file.close()
 
289
 
 
290
    # Make this file world-readable
 
291
    # (chmod 644 conf_path)
 
292
    os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
 
293
                        | stat.S_IROTH)
 
294
 
 
295
def make_etc_passwd(username, user_jail_dir, template_dir, unixid):
 
296
    """
 
297
    Creates /etc/passwd in the given user's jail. This will be identical to
 
298
    that in the template jail, except for the added entry for this user.
 
299
    """
 
300
    template_passwd_path = os.path.join(template_dir, "etc/passwd")
 
301
    passwd_path = os.path.join(user_jail_dir, "etc/passwd")
 
302
    passwd_dir = os.path.dirname(passwd_path)
 
303
    if not os.path.exists(passwd_dir):
 
304
        os.makedirs(passwd_dir)
 
305
    shutil.copy(template_passwd_path, passwd_path)
 
306
    passwd_file = open(passwd_path, 'a')
 
307
    passwd_file.write('%s:x:%d:%d::/home/%s:/bin/bash'
 
308
                      % (username, unixid, unixid, username))
 
309
    passwd_file.close()
 
310
 
 
311
def linktree(src, dst):
 
312
    """Recursively hard-link a directory tree using os.link().
 
313
 
 
314
    The destination directory must not already exist.
 
315
    If exception(s) occur, an Error is raised with a list of reasons.
 
316
 
 
317
    Symlinks are preserved (in fact, hard links are created which point to the
 
318
    symlinks).
 
319
 
 
320
    Code heavily based upon shutil.copytree from Python 2.5 library.
 
321
    """
 
322
    names = os.listdir(src)
 
323
    os.makedirs(dst)
 
324
    errors = []
 
325
    for name in names:
 
326
        srcname = os.path.join(src, name)
 
327
        dstname = os.path.join(dst, name)
 
328
        try:
 
329
            if os.path.isdir(srcname):
 
330
                linktree(srcname, dstname)
 
331
            else:
 
332
                os.link(srcname, dstname)
 
333
            # XXX What about devices, sockets etc.?
 
334
        except (IOError, os.error), why:
 
335
            errors.append((srcname, dstname, str(why)))
 
336
        # catch the Error from the recursive copytree so that we can
 
337
        # continue with other files
 
338
        except Exception, err:
 
339
            errors.append(err.args[0])
 
340
    try:
 
341
        shutil.copystat(src, dst)
 
342
    except WindowsError:
 
343
        # can't copy file access times on Windows
 
344
        pass
 
345
    except OSError, why:
 
346
        errors.extend((src, dst, str(why)))
 
347
    if errors:
 
348
        raise Exception, errors
 
349
 
 
350
def make_user_db(throw_on_error = True, **kwargs):
 
351
    """Creates a user's entry in the database, filling in all the fields.
 
352
    All arguments must be keyword args. They are the fields in the table.
 
353
    However, instead of supplying a "passhash", you must supply a
 
354
    "password" argument, which will be hashed internally.
 
355
    Also do not supply a state. All users are created in the "no_agreement"
 
356
    state.
 
357
    Also pulls the user's subjects using the configured subject pulldown
 
358
    module, and adds enrolments to the DB.
 
359
    Throws an exception if the user already exists.
 
360
    """
 
361
    dbconn = db.DB()
 
362
    dbconn.create_user(**kwargs)
 
363
    dbconn.close()
 
364
 
 
365
    if kwargs['password']:
 
366
        if os.path.exists(conf.svn_auth_local):
 
367
            create = ""
 
368
        else:
 
369
            create = "c"
 
370
        res = os.system("htpasswd -%smb %s %s %s" % (create,
 
371
                                                     conf.svn_auth_local,
 
372
                                                     kwargs['login'],
 
373
                                                     kwargs['password']))
 
374
        if res != 0 and throw_on_error:
 
375
            raise Exception("Unable to create local-auth for %s" % kwargs['login'])
 
376
 
 
377
    # Make sure the file is owned by the web server
 
378
    if create == "c":
 
379
        chown_to_webserver(conf.svn_auth_local)
 
380
 
 
381
    # Pulldown subjects and add enrolments
 
382
    pulldown_subj.enrol_user(kwargs['login'])
 
383
 
 
384
def mount_jail(login):
 
385
    # This is where we'll mount to...
 
386
    destdir = os.path.join(conf.jail_base, login)
 
387
    # ... and this is where we'll get the user bits.
 
388
    srcdir = os.path.join(conf.jail_src_base, login)
 
389
    try:
 
390
        if not os.path.exists(destdir):
 
391
            os.mkdir(destdir)
 
392
        if os.system('/bin/mount -t aufs -o dirs=%s:%s=ro none %s'
 
393
                     % (srcdir, conf.jail_system, destdir)) == 0:
 
394
            logging.info("mounted user %s's jail." % login)
 
395
        else:
 
396
            logging.error("failed to mount user %s's jail!" % login)
 
397
    except Exception, message:
 
398
        logging.warning(str(message))