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

« back to all changes in this revision

Viewing changes to lib/common/makeuser.py

  • Committer: mattgiuca
  • Date: 2008-02-05 01:41:15 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:409
Moved www/conf and www/common to a new directory lib. This separates the "web"
part of IVLE from what is becoming less web oriented (at least from Apache's
standpoint).
Modified setup.py to install this lib directory correctly and write conf in
the right place. Also adds the lib directory to ivle.pth.

Show diffs side-by-side

added added

removed removed

Lines of Context:
31
31
 
32
32
# TODO: When creating a new home directory, chown it to its owner
33
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
34
import os
40
 
import stat
41
35
import shutil
42
 
import time
43
 
import uuid
44
36
import warnings
45
 
import filecmp
46
 
import logging
 
37
 
47
38
import conf
48
39
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(path, throw_on_error=True):
63
 
    """Create a Subversion repository at the given path.
64
 
    """
65
 
    try:
66
 
        res = os.system("svnadmin create '%s'" % path)
67
 
        if res != 0 and throw_on_error:
68
 
            raise Exception("Cannot create repository for %s" % login)
69
 
    except Exception, exc:
70
 
        print repr(exc)
71
 
        if throw_on_error:
72
 
            raise
73
 
 
74
 
    chown_to_webserver(path)
75
 
 
76
 
def rebuild_svn_config():
77
 
    """Build the complete SVN configuration file.
78
 
    """
79
 
    conn = db.DB()
80
 
    users = conn.get_users()
81
 
    groups = {}
82
 
    for u in users:
83
 
        role = str(u.role)
84
 
        if role not in groups:
85
 
            groups[role] = []
86
 
        groups[role].append(u.login)
87
 
    f = open(conf.svn_conf + ".new", "w")
88
 
    f.write("# IVLE SVN Repositories Configuration\n")
89
 
    f.write("# Auto-generated on %s\n" % time.asctime())
90
 
    f.write("\n")
91
 
    f.write("[groups]\n")
92
 
    for (g,ls) in groups.iteritems():
93
 
        f.write("%s = %s\n" % (g, ",".join(ls)))
94
 
    f.write("\n")
95
 
    for u in users:
96
 
        f.write("[%s:/]\n" % u.login)
97
 
        f.write("%s = rw\n" % u.login)
98
 
        #f.write("@tutor = r\n")
99
 
        #f.write("@lecturer = rw\n")
100
 
        #f.write("@admin = rw\n")
101
 
        f.write("\n")
102
 
    f.close()
103
 
    os.rename(conf.svn_conf + ".new", conf.svn_conf)
104
 
    chown_to_webserver(conf.svn_conf)
105
 
 
106
 
def make_svn_auth(login, throw_on_error=True):
107
 
    """Setup svn authentication for the given user.
108
 
       FIXME: create local.auth entry
109
 
    """
110
 
    passwd = md5.new(uuid.uuid4().bytes).digest().encode('hex')
111
 
    if os.path.exists(conf.svn_auth_ivle):
112
 
        create = ""
113
 
    else:
114
 
        create = "c"
115
 
 
116
 
    db.DB().update_user(login, svn_pass=passwd)
117
 
 
118
 
    res = os.system("htpasswd -%smb %s %s %s" % (create,
119
 
                                              conf.svn_auth_ivle,
120
 
                                              login, passwd))
121
 
    if res != 0 and throw_on_error:
122
 
        raise Exception("Unable to create ivle-auth for %s" % login)
123
 
 
124
 
    # Make sure the file is owned by the web server
125
 
    if create == "c":
126
 
        chown_to_webserver(conf.svn_auth_ivle)
127
 
 
128
 
    return passwd
129
 
 
130
 
def generate_manifest(basedir, targetdir, parent=''):
131
 
    """ From a basedir and a targetdir work out which files are missing or out 
132
 
    of date and need to be added/updated and which files are redundant and need 
133
 
    to be removed.
134
 
    
135
 
    parent: This is used for the recursive call to track the relative paths 
136
 
    that we have decended.
137
 
    """
138
 
    
139
 
    cmp = filecmp.dircmp(basedir, targetdir)
140
 
 
141
 
    # Add all new files and files that have changed
142
 
    to_add = [os.path.join(parent,x) for x in (cmp.left_only + cmp.diff_files)]
143
 
 
144
 
    # Remove files that are redundant
145
 
    to_remove = [os.path.join(parent,x) for x in cmp.right_only]
146
 
    
147
 
    # Recurse
148
 
    for d in cmp.common_dirs:
149
 
        newbasedir = os.path.join(basedir, d)
150
 
        newtargetdir = os.path.join(targetdir, d)
151
 
        newparent = os.path.join(parent, d)
152
 
        (sadd,sremove) = generate_manifest(newbasedir, newtargetdir, newparent)
153
 
        to_add += sadd
154
 
        to_remove += sremove
155
 
 
156
 
    return (to_add, to_remove)
157
 
 
158
 
 
159
 
def make_jail(username, uid, force=True, svn_pass=None):
 
40
 
 
41
def makeuser(username, password, nick, fullname, rolenm, studentid):
 
42
    """Creates a new user on a pre-installed system.
 
43
    Sets up the following:
 
44
    * User's jail and home directory within the jail.
 
45
    * Subversion repository
 
46
    * Check out Subversion workspace into jail
 
47
    * Database details for user
 
48
    * Unix user account
 
49
    """
 
50
    homedir = make_jail(username)
 
51
    make_user_db(username, password, nick, fullname, rolenm, studentid)
 
52
    # TODO: -p password (need to use crypt)
 
53
    if os.system("useradd -d %s '%s'" % (homedir, username)) != 0:
 
54
        raise Exception("Failed to add Unix user account")
 
55
 
 
56
def make_jail(username, force=True):
160
57
    """Creates a new user's jail space, in the jail directory as configured in
161
58
    conf.py.
162
59
 
163
 
    This only creates things within /home - everything else is expected to be
164
 
    part of another UnionFS branch.
 
60
    This expects there to be a "template" directory within the jail root which
 
61
    contains all the files for a sample student jail. It creates the student's
 
62
    directory in the jail root, by making a hard-link copy of every file in the
 
63
    template directory, recursively.
165
64
 
166
65
    Returns the path to the user's home directory.
167
66
 
168
 
    Chowns the user's directory within the jail to the given UID.
169
 
 
170
 
    Note: This takes separate username and uid arguments. The UID need not
171
 
    *necessarily* correspond to a Unix username at all, if all you are
172
 
    planning to do is setuid to it. This allows the caller the freedom of
173
 
    deciding the binding between username and uid, if any.
174
 
 
175
67
    force: If false, exception if jail already exists for this user.
176
68
    If true (default), overwrites it, but preserves home directory.
177
 
 
178
 
    svn_pass: If provided this will be a string, the randomly-generated
179
 
    Subversion password for this user (if you happen to already have it).
180
 
    If not provided, it will be read from the database.
181
69
    """
182
70
    # MUST run as root or some of this may fail
183
71
    if os.getuid() != 0:
184
72
        raise Exception("Must run make_jail as root")
185
73
    
 
74
    templatedir = os.path.join(conf.jail_base, 'template')
 
75
    if not os.path.isdir(templatedir):
 
76
        raise Exception("Template jail directory does not exist: " +
 
77
            templatedir)
186
78
    # tempdir is for putting backup homes in
187
 
    tempdir = os.path.join(conf.jail_base, '__temp__')
 
79
    tempdir = os.path.join(conf.jail_base, 'temp')
188
80
    if not os.path.exists(tempdir):
189
81
        os.makedirs(tempdir)
190
82
    elif not os.path.isdir(tempdir):
191
83
        os.unlink(tempdir)
192
84
        os.mkdir(tempdir)
193
 
    userdir = os.path.join(conf.jail_src_base, username)
 
85
    userdir = os.path.join(conf.jail_base, username)
194
86
    homedir = os.path.join(userdir, 'home')
195
 
    userhomedir = os.path.join(homedir, username)   # Return value
196
87
 
197
88
    if os.path.exists(userdir):
198
89
        if not force:
199
90
            raise Exception("User's jail already exists")
200
91
        # User jail already exists. Blow it away but preserve their home
201
 
        # directory. It should be all that is there anyway, but you never
202
 
        # know!
 
92
        # directory.
203
93
        # Ignore warnings about the use of tmpnam
204
94
        warnings.simplefilter('ignore')
205
95
        homebackup = os.tempnam(tempdir)
208
98
        # into a directory if it already exists, just fails. Therefore it is
209
99
        # not susceptible to tmpnam symlink attack.
210
100
        shutil.move(homedir, homebackup)
211
 
        shutil.rmtree(userdir)
212
 
        os.makedirs(homedir)
213
 
        shutil.move(homebackup, homedir)
214
 
        # Change the ownership of all the files to the right unixid
215
 
        logging.debug("chown %s's home directory files to uid %d"
216
 
            %(username, uid))
217
 
        os.chown(userhomedir, uid, uid)
218
 
        for root, dirs, files in os.walk(userhomedir):
219
 
            for fsobj in dirs + files:
220
 
                os.chown(os.path.join(root, fsobj), uid, uid)
 
101
        try:
 
102
            # Any errors that occur after making the backup will be caught and
 
103
            # the backup will be un-made.
 
104
            # XXX This will still leave the user's jail in an unusable state,
 
105
            # but at least they won't lose their files.
 
106
            shutil.rmtree(userdir)
 
107
 
 
108
            # Hard-link (copy aliasing) the entire tree over
 
109
            linktree(templatedir, userdir)
 
110
        finally:
 
111
            # Set up the user's home directory (restore backup)
 
112
            # First make sure the directory is empty and its parent exists
 
113
            try:
 
114
                shutil.rmtree(homedir)
 
115
            except:
 
116
                pass
 
117
            # XXX If this fails the user's directory will be lost (in the temp
 
118
            # directory). But it shouldn't fail as homedir should not exist.
 
119
            os.makedirs(homedir)
 
120
            shutil.move(homebackup, homedir)
 
121
        return os.path.join(homedir, username)
221
122
    else:
222
123
        # No user jail exists
 
124
        # Hard-link (copy aliasing) the entire tree over
 
125
        linktree(templatedir, userdir)
 
126
 
223
127
        # Set up the user's home directory
224
 
        os.makedirs(userhomedir)
225
 
        # Chown (and set the GID to the same as the UID).
226
 
        os.chown(userhomedir, uid, uid)
227
 
        # Chmod to rwxr-xr-x (755)
228
 
        os.chmod(userhomedir, 0755)
229
 
 
230
 
    # There are 2 special files which need to be generated specific to this
231
 
    # user: /opt/ivle/lib/conf/conf.py and /etc/passwd.
232
 
    # "__" username "__" users are exempt (special)
233
 
    if not (username.startswith("__") and username.endswith("__")):
234
 
        make_conf_py(username, userdir, conf.jail_system, svn_pass)
235
 
        make_etc_passwd(username, userdir, conf.jail_system, uid)
236
 
 
237
 
    return userhomedir
238
 
 
239
 
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
240
 
    """
241
 
    Creates (overwriting any existing file, and creating directories) a
242
 
    file /opt/ivle/lib/conf/conf.py in a given user's jail.
243
 
    username: Username.
244
 
    user_jail_dir: User's jail dir, ie. conf.jail_base + username
245
 
    staging_dir: The dir with the staging copy of the jail. (With the
246
 
        template conf.py file).
247
 
    svn_pass: As with make_jail. User's SVN password, but if not supplied,
248
 
        will look up in the DB.
249
 
    """
250
 
    template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
251
 
    conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
252
 
    os.makedirs(os.path.dirname(conf_path))
253
 
 
254
 
    # If svn_pass isn't supplied, grab it from the DB
255
 
    if svn_pass is None:
256
 
        dbconn = db.DB()
257
 
        svn_pass = dbconn.get_user(username).svn_pass
258
 
        dbconn.close()
259
 
 
260
 
    # Read the contents of the template conf file
261
 
    try:
262
 
        template_conf_file = open(template_conf_path, "r")
263
 
        template_conf_data = template_conf_file.read()
264
 
        template_conf_file.close()
265
 
    except:
266
 
        # Couldn't open template conf.py for some reason
267
 
        # Just treat it as empty file
268
 
        template_conf_data = ("# Warning: Problem building config script.\n"
269
 
                              "# Could not find template conf.py file.\n")
270
 
 
271
 
    conf_file = open(conf_path, "w")
272
 
    conf_file.write(template_conf_data)
273
 
    conf_file.write("\n# The login name for the owner of the jail\n")
274
 
    conf_file.write("login = %s\n" % repr(username))
275
 
    conf_file.write("\n")
276
 
    conf_file.write("# The subversion-only password for the owner of "
277
 
        "the jail\n")
278
 
    conf_file.write("svn_pass = %s\n" % repr(svn_pass))
279
 
    conf_file.close()
280
 
 
281
 
    # Make this file world-readable
282
 
    # (chmod 644 conf_path)
283
 
    os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
284
 
                        | stat.S_IROTH)
285
 
 
286
 
def make_etc_passwd(username, user_jail_dir, template_dir, unixid):
287
 
    """
288
 
    Creates /etc/passwd in the given user's jail. This will be identical to
289
 
    that in the template jail, except for the added entry for this user.
290
 
    """
291
 
    template_passwd_path = os.path.join(template_dir, "etc/passwd")
292
 
    passwd_path = os.path.join(user_jail_dir, "etc/passwd")
293
 
    passwd_dir = os.path.dirname(passwd_path)
294
 
    if not os.path.exists(passwd_dir):
295
 
        os.makedirs(passwd_dir)
296
 
    shutil.copy(template_passwd_path, passwd_path)
297
 
    passwd_file = open(passwd_path, 'a')
298
 
    passwd_file.write('%s:x:%d:%d::/home/%s:/bin/bash'
299
 
                      % (username, unixid, unixid, username))
300
 
    passwd_file.close()
 
128
        userhomedir = os.path.join(homedir, username)
 
129
        os.mkdir(userhomedir)
 
130
        return userhomedir
301
131
 
302
132
def linktree(src, dst):
303
133
    """Recursively hard-link a directory tree using os.link().
338
168
    if errors:
339
169
        raise Exception, errors
340
170
 
341
 
def make_user_db(throw_on_error = True, **kwargs):
 
171
def make_user_db(login, password, nick, fullname, rolenm, studentid,
 
172
    force=True):
342
173
    """Creates a user's entry in the database, filling in all the fields.
343
 
    All arguments must be keyword args. They are the fields in the table.
344
 
    However, instead of supplying a "passhash", you must supply a
345
 
    "password" argument, which will be hashed internally.
346
 
    Also do not supply a state. All users are created in the "no_agreement"
347
 
    state.
348
 
    Also pulls the user's subjects using the configured subject pulldown
349
 
    module, and adds enrolments to the DB.
350
 
    Throws an exception if the user already exists.
 
174
    If force is False, throws an exception if the user already exists.
 
175
    If True, overwrites the user's entry in the DB.
351
176
    """
352
177
    dbconn = db.DB()
353
 
    dbconn.create_user(**kwargs)
 
178
    if force:
 
179
        # Delete user if it exists
 
180
        try:
 
181
            dbconn.delete_user(login)
 
182
        except:
 
183
            pass
 
184
    dbconn.create_user(login, password, nick, fullname, rolenm, studentid)
354
185
    dbconn.close()
355
 
 
356
 
    if kwargs['password']:
357
 
        if os.path.exists(conf.svn_auth_local):
358
 
            create = ""
359
 
        else:
360
 
            create = "c"
361
 
        res = os.system("htpasswd -%smb %s %s %s" % (create,
362
 
                                                     conf.svn_auth_local,
363
 
                                                     kwargs['login'],
364
 
                                                     kwargs['password']))
365
 
        if res != 0 and throw_on_error:
366
 
            raise Exception("Unable to create local-auth for %s" % kwargs['login'])
367
 
 
368
 
    # Make sure the file is owned by the web server
369
 
    if create == "c":
370
 
        chown_to_webserver(conf.svn_auth_local)
371
 
 
372
 
    # Pulldown subjects and add enrolments
373
 
    pulldown_subj.enrol_user(kwargs['login'])
374
 
 
375
 
def mount_jail(login):
376
 
    # This is where we'll mount to...
377
 
    destdir = os.path.join(conf.jail_base, login)
378
 
    # ... and this is where we'll get the user bits.
379
 
    srcdir = os.path.join(conf.jail_src_base, login)
380
 
    try:
381
 
        if not os.path.exists(destdir):
382
 
            os.mkdir(destdir)
383
 
        if os.system('/bin/mount -t aufs -o dirs=%s:%s=ro none %s'
384
 
                     % (srcdir, conf.jail_system, destdir)) == 0:
385
 
            logging.info("mounted user %s's jail." % login)
386
 
        else:
387
 
            logging.error("failed to mount user %s's jail!" % login)
388
 
    except Exception, message:
389
 
        logging.warning(str(message))