~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-23 02:42:54 UTC
  • Revision ID: svn-v3-trunk0:2b9c9e99-6f39-0410-b283-7f802c844ae2:trunk:935
userdb: Large changes:
 - Split out semesters into their own table.
 - Give semesters and enrolments an active flag.
   + Enrolments cannot be active if their semester is not.
 - Introduce the project set concept, to link groups to projects.
   + Groups and projects now reference a project set, not an offering.

Also update the DB access code to respect the group changes.

<FONT COLOR="red"><BLINK>

  ** WARNING: This migration will destroy all groups, enrolments and
              offerings. **

</BLINK></FONT>

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
 
34
38
import md5
35
39
import os
36
40
import stat
38
42
import time
39
43
import uuid
40
44
import warnings
41
 
 
 
45
import filecmp
 
46
import logging
42
47
import conf
43
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
44
61
 
45
62
def make_svn_repo(login, throw_on_error=True):
46
63
    """Create a repository for the given user.
54
71
        print repr(exc)
55
72
        if throw_on_error:
56
73
            raise
57
 
    try:
58
 
        os.system("chown -R www-data:www-data %s" % path)
59
 
    except Exception:
60
 
        pass
 
74
 
 
75
    chown_to_webserver(path)
61
76
 
62
77
def rebuild_svn_config():
63
78
    """Build the complete SVN configuration file.
88
103
        f.write("\n")
89
104
    f.close()
90
105
    os.rename(conf.svn_conf + ".new", conf.svn_conf)
 
106
    chown_to_webserver(conf.svn_conf)
91
107
 
92
108
def make_svn_config(login, throw_on_error=True):
93
109
    """Add an entry to the apache-svn config file for the given user.
101
117
    #f.write("@admin = rw\n")
102
118
    f.write("\n")
103
119
    f.close()
 
120
    chown_to_webserver(conf.svn_conf)
104
121
 
105
122
def make_svn_auth(login, throw_on_error=True):
106
123
    """Setup svn authentication for the given user.
120
137
    if res != 0 and throw_on_error:
121
138
        raise Exception("Unable to create ivle-auth for %s" % login)
122
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
 
123
144
    return passwd
124
145
 
125
 
def make_jail(username, uid, force=True):
 
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):
126
176
    """Creates a new user's jail space, in the jail directory as configured in
127
177
    conf.py.
128
178
 
129
 
    This expects there to be a "template" directory within the jail root which
130
 
    contains all the files for a sample student jail. It creates the student's
131
 
    directory in the jail root, by making a hard-link copy of every file in the
132
 
    template directory, recursively.
 
179
    This only creates things within /home - everything else is expected to be
 
180
    part of another UnionFS branch.
133
181
 
134
182
    Returns the path to the user's home directory.
135
183
 
142
190
 
143
191
    force: If false, exception if jail already exists for this user.
144
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.
145
197
    """
146
198
    # MUST run as root or some of this may fail
147
199
    if os.getuid() != 0:
148
200
        raise Exception("Must run make_jail as root")
149
201
    
150
 
    templatedir = os.path.join(conf.jail_base, 'template')
151
 
    if not os.path.isdir(templatedir):
152
 
        raise Exception("Template jail directory does not exist: " +
153
 
            templatedir)
154
202
    # tempdir is for putting backup homes in
155
 
    tempdir = os.path.join(conf.jail_base, 'temp')
 
203
    tempdir = os.path.join(conf.jail_base, '__temp__')
156
204
    if not os.path.exists(tempdir):
157
205
        os.makedirs(tempdir)
158
206
    elif not os.path.isdir(tempdir):
159
207
        os.unlink(tempdir)
160
208
        os.mkdir(tempdir)
161
 
    userdir = os.path.join(conf.jail_base, username)
 
209
    userdir = os.path.join(conf.jail_src_base, username)
162
210
    homedir = os.path.join(userdir, 'home')
 
211
    userhomedir = os.path.join(homedir, username)   # Return value
163
212
 
164
213
    if os.path.exists(userdir):
165
214
        if not force:
166
215
            raise Exception("User's jail already exists")
167
216
        # User jail already exists. Blow it away but preserve their home
168
 
        # directory.
 
217
        # directory. It should be all that is there anyway, but you never
 
218
        # know!
169
219
        # Ignore warnings about the use of tmpnam
170
220
        warnings.simplefilter('ignore')
171
221
        homebackup = os.tempnam(tempdir)
174
224
        # into a directory if it already exists, just fails. Therefore it is
175
225
        # not susceptible to tmpnam symlink attack.
176
226
        shutil.move(homedir, homebackup)
177
 
        try:
178
 
            # Any errors that occur after making the backup will be caught and
179
 
            # the backup will be un-made.
180
 
            # XXX This will still leave the user's jail in an unusable state,
181
 
            # but at least they won't lose their files.
182
 
            shutil.rmtree(userdir)
183
 
 
184
 
            # Hard-link (copy aliasing) the entire tree over
185
 
            linktree(templatedir, userdir)
186
 
        finally:
187
 
            # Set up the user's home directory (restore backup)
188
 
            # First make sure the directory is empty and its parent exists
189
 
            try:
190
 
                shutil.rmtree(homedir)
191
 
            except:
192
 
                pass
193
 
            # XXX If this fails the user's directory will be lost (in the temp
194
 
            # directory). But it shouldn't fail as homedir should not exist.
195
 
            os.makedirs(homedir)
196
 
            shutil.move(homebackup, homedir)
197
 
        return os.path.join(homedir, username)
 
227
        shutil.rmtree(userdir)
 
228
        os.makedirs(homedir)
 
229
        shutil.move(homebackup, homedir)
 
230
        # Change the ownership of all the files to the right unixid
 
231
        logging.debug("chown %s's home directory files to uid %d"
 
232
            %(username, uid))
 
233
        os.chown(userhomedir, uid, uid)
 
234
        for root, dirs, files in os.walk(userhomedir):
 
235
            for fsobj in dirs + files:
 
236
                os.chown(os.path.join(root, fsobj), uid, uid)
198
237
    else:
199
238
        # No user jail exists
200
 
        # Hard-link (copy aliasing) the entire tree over
201
 
        linktree(templatedir, userdir)
202
 
 
203
239
        # Set up the user's home directory
204
 
        userhomedir = os.path.join(homedir, username)
205
 
        os.mkdir(userhomedir)
 
240
        os.makedirs(userhomedir)
206
241
        # Chown (and set the GID to the same as the UID).
207
242
        os.chown(userhomedir, uid, uid)
208
243
        # Chmod to rwxr-xr-x (755)
209
244
        os.chmod(userhomedir, 0755)
210
 
        return userhomedir
 
245
 
 
246
    # There are 2 special files which need to be generated specific to this
 
247
    # user: /opt/ivle/lib/conf/conf.py and /etc/passwd.
 
248
    # "__" username "__" users are exempt (special)
 
249
    if not (username.startswith("__") and username.endswith("__")):
 
250
        make_conf_py(username, userdir, conf.jail_system, svn_pass)
 
251
        make_etc_passwd(username, userdir, conf.jail_system, uid)
 
252
 
 
253
    return userhomedir
 
254
 
 
255
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
 
256
    """
 
257
    Creates (overwriting any existing file, and creating directories) a
 
258
    file /opt/ivle/lib/conf/conf.py in a given user's jail.
 
259
    username: Username.
 
260
    user_jail_dir: User's jail dir, ie. conf.jail_base + username
 
261
    staging_dir: The dir with the staging copy of the jail. (With the
 
262
        template conf.py file).
 
263
    svn_pass: As with make_jail. User's SVN password, but if not supplied,
 
264
        will look up in the DB.
 
265
    """
 
266
    template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
 
267
    conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
 
268
    os.makedirs(os.path.dirname(conf_path))
 
269
 
 
270
    # If svn_pass isn't supplied, grab it from the DB
 
271
    if svn_pass is None:
 
272
        dbconn = db.DB()
 
273
        svn_pass = dbconn.get_user(username).svn_pass
 
274
        dbconn.close()
 
275
 
 
276
    # Read the contents of the template conf file
 
277
    try:
 
278
        template_conf_file = open(template_conf_path, "r")
 
279
        template_conf_data = template_conf_file.read()
 
280
        template_conf_file.close()
 
281
    except:
 
282
        # Couldn't open template conf.py for some reason
 
283
        # Just treat it as empty file
 
284
        template_conf_data = ("# Warning: Problem building config script.\n"
 
285
                              "# Could not find template conf.py file.\n")
 
286
 
 
287
    conf_file = open(conf_path, "w")
 
288
    conf_file.write(template_conf_data)
 
289
    conf_file.write("\n# The login name for the owner of the jail\n")
 
290
    conf_file.write("login = %s\n" % repr(username))
 
291
    conf_file.write("\n")
 
292
    conf_file.write("# The subversion-only password for the owner of "
 
293
        "the jail\n")
 
294
    conf_file.write("svn_pass = %s\n" % repr(svn_pass))
 
295
    conf_file.close()
 
296
 
 
297
    # Make this file world-readable
 
298
    # (chmod 644 conf_path)
 
299
    os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
 
300
                        | stat.S_IROTH)
 
301
 
 
302
def make_etc_passwd(username, user_jail_dir, template_dir, unixid):
 
303
    """
 
304
    Creates /etc/passwd in the given user's jail. This will be identical to
 
305
    that in the template jail, except for the added entry for this user.
 
306
    """
 
307
    template_passwd_path = os.path.join(template_dir, "etc/passwd")
 
308
    passwd_path = os.path.join(user_jail_dir, "etc/passwd")
 
309
    passwd_dir = os.path.dirname(passwd_path)
 
310
    if not os.path.exists(passwd_dir):
 
311
        os.makedirs(passwd_dir)
 
312
    shutil.copy(template_passwd_path, passwd_path)
 
313
    passwd_file = open(passwd_path, 'a')
 
314
    passwd_file.write('%s:x:%d:%d::/home/%s:/bin/bash'
 
315
                      % (username, unixid, unixid, username))
 
316
    passwd_file.close()
211
317
 
212
318
def linktree(src, dst):
213
319
    """Recursively hard-link a directory tree using os.link().
255
361
    "password" argument, which will be hashed internally.
256
362
    Also do not supply a state. All users are created in the "no_agreement"
257
363
    state.
 
364
    Also pulls the user's subjects using the configured subject pulldown
 
365
    module, and adds enrolments to the DB.
258
366
    Throws an exception if the user already exists.
259
367
    """
260
368
    dbconn = db.DB()
273
381
        if res != 0 and throw_on_error:
274
382
            raise Exception("Unable to create local-auth for %s" % kwargs['login'])
275
383
 
 
384
    # Make sure the file is owned by the web server
 
385
    if create == "c":
 
386
        chown_to_webserver(conf.svn_auth_local)
 
387
 
 
388
    # Pulldown subjects and add enrolments
 
389
    pulldown_subj.enrol_user(kwargs['login'])
 
390
 
 
391
def mount_jail(login):
 
392
    # This is where we'll mount to...
 
393
    destdir = os.path.join(conf.jail_base, login)
 
394
    # ... and this is where we'll get the user bits.
 
395
    srcdir = os.path.join(conf.jail_src_base, login)
 
396
    try:
 
397
        if not os.path.exists(destdir):
 
398
            os.mkdir(destdir)
 
399
        if os.system('/bin/mount -t aufs -o dirs=%s:%s=ro none %s'
 
400
                     % (srcdir, conf.jail_system, destdir)) == 0:
 
401
            logging.info("mounted user %s's jail." % login)
 
402
        else:
 
403
            logging.error("failed to mount user %s's jail!" % login)
 
404
    except Exception, message:
 
405
        logging.warning(str(message))