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

« back to all changes in this revision

Viewing changes to lib/common/makeuser.py

  • Committer: William Grant
  • Date: 2012-06-28 01:52:02 UTC
  • Revision ID: me@williamgrant.id.au-20120628015202-f6ru7o367gt6nvgz
Hah

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 conf
47
 
import db
48
 
 
49
 
def chown_to_webserver(filename):
50
 
    """
51
 
    Chowns a file so the web server user owns it.
52
 
    (This is useful in setting up Subversion conf files).
53
 
    Assumes root.
54
 
    """
55
 
    try:
56
 
        os.system("chown -R www-data:www-data %s" % filename)
57
 
    except:
58
 
        pass
59
 
 
60
 
def make_svn_repo(login, throw_on_error=True):
61
 
    """Create a repository for the given user.
62
 
    """
63
 
    path = os.path.join(conf.svn_repo_path, login)
64
 
    try:
65
 
        res = os.system("svnadmin create '%s'" % path)
66
 
        if res != 0 and throw_on_error:
67
 
            raise Exception("Cannot create repository for %s" % login)
68
 
    except Exception, exc:
69
 
        print repr(exc)
70
 
        if throw_on_error:
71
 
            raise
72
 
 
73
 
    chown_to_webserver(path)
74
 
 
75
 
def rebuild_svn_config():
76
 
    """Build the complete SVN configuration file.
77
 
    """
78
 
    conn = db.DB()
79
 
    res = conn.query("SELECT login, rolenm FROM login;").dictresult()
80
 
    groups = {}
81
 
    for r in res:
82
 
        role = r['rolenm']
83
 
        if role not in groups:
84
 
            groups[role] = []
85
 
        groups[role].append(r['login'])
86
 
    f = open(conf.svn_conf + ".new", "w")
87
 
    f.write("# IVLE SVN Repositories Configuration\n")
88
 
    f.write("# Auto-generated on %s\n" % time.asctime())
89
 
    f.write("\n")
90
 
    f.write("[groups]\n")
91
 
    for (g,ls) in groups.iteritems():
92
 
        f.write("%s = %s\n" % (g, ",".join(ls)))
93
 
    f.write("\n")
94
 
    for r in res:
95
 
        login = r['login']
96
 
        f.write("[%s:/]\n" % login)
97
 
        f.write("%s = rw\n" % 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_config(login, throw_on_error=True):
107
 
    """Add an entry to the apache-svn config file for the given user.
108
 
       Assumes the given user is either a guest or a student.
109
 
    """
110
 
    f = open(conf.svn_conf, "a")
111
 
    f.write("[%s:/]\n" % login)
112
 
    f.write("%s = rw\n" % login)
113
 
    #f.write("@tutor = r\n")
114
 
    #f.write("@lecturer = rw\n")
115
 
    #f.write("@admin = rw\n")
116
 
    f.write("\n")
117
 
    f.close()
118
 
    chown_to_webserver(conf.svn_conf)
119
 
 
120
 
def make_svn_auth(login, throw_on_error=True):
121
 
    """Setup svn authentication for the given user.
122
 
       FIXME: create local.auth entry
123
 
    """
124
 
    passwd = md5.new(uuid.uuid4().bytes).digest().encode('hex')
125
 
    if os.path.exists(conf.svn_auth_ivle):
126
 
        create = ""
127
 
    else:
128
 
        create = "c"
129
 
 
130
 
    db.DB().update_user(login, svn_pass=passwd)
131
 
 
132
 
    res = os.system("htpasswd -%smb %s %s %s" % (create,
133
 
                                              conf.svn_auth_ivle,
134
 
                                              login, passwd))
135
 
    if res != 0 and throw_on_error:
136
 
        raise Exception("Unable to create ivle-auth for %s" % login)
137
 
 
138
 
    # Make sure the file is owned by the web server
139
 
    if create == "c":
140
 
        chown_to_webserver(conf.svn_auth_ivle)
141
 
 
142
 
    return passwd
143
 
 
144
 
def generate_manifest(basedir, targetdir, parent=''):
145
 
    """ From a basedir and a targetdir work out which files are missing or out 
146
 
    of date and need to be added/updated and which files are redundant and need 
147
 
    to be removed.
148
 
    
149
 
    parent: This is used for the recursive call to track the relative paths 
150
 
    that we have decended.
151
 
    """
152
 
    
153
 
    cmp = filecmp.dircmp(basedir, targetdir)
154
 
 
155
 
    # Add all new files and files that have changed
156
 
    to_add = [os.path.join(parent,x) for x in (cmp.left_only + cmp.diff_files)]
157
 
 
158
 
    # Remove files that are redundant
159
 
    to_remove = [os.path.join(parent,x) for x in cmp.right_only]
160
 
    
161
 
    # Recurse
162
 
    for d in cmp.common_dirs:
163
 
        newbasedir = os.path.join(basedir, d)
164
 
        newtargetdir = os.path.join(targetdir, d)
165
 
        newparent = os.path.join(parent, d)
166
 
        (sadd,sremove) = generate_manifest(newbasedir, newtargetdir, newparent)
167
 
        to_add += sadd
168
 
        to_remove += sremove
169
 
 
170
 
    return (to_add, to_remove)
171
 
 
172
 
 
173
 
def make_jail(username, uid, force=True, manifest=None, svn_pass=None):
174
 
    """Creates a new user's jail space, in the jail directory as configured in
175
 
    conf.py.
176
 
 
177
 
    This expects there to be a "staging" directory within the jail root which
178
 
    contains all the files for a sample student jail. It creates the student's
179
 
    directory in the jail root, by making a hard-link copy of every file in the
180
 
    staging directory, recursively.
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
 
    manifest: If provided this will be a pair (to_add, to_remove) of files or
195
 
    directories to add or remove from the jail.
196
 
 
197
 
    svn_pass: If provided this will be a string, the randomly-generated
198
 
    Subversion password for this user (if you happen to already have it).
199
 
    If not provided, it will be read from the database.
200
 
    """
201
 
    # MUST run as root or some of this may fail
202
 
    if os.getuid() != 0:
203
 
        raise Exception("Must run make_jail as root")
204
 
    
205
 
    stagingdir = os.path.join(conf.jail_base, '__staging__')
206
 
    if not os.path.isdir(stagingdir):
207
 
        raise Exception("Staging jail directory does not exist: " +
208
 
            stagingdir)
209
 
    # tempdir is for putting backup homes in
210
 
    tempdir = os.path.join(conf.jail_base, '__temp__')
211
 
    if not os.path.exists(tempdir):
212
 
        os.makedirs(tempdir)
213
 
    elif not os.path.isdir(tempdir):
214
 
        os.unlink(tempdir)
215
 
        os.mkdir(tempdir)
216
 
    userdir = os.path.join(conf.jail_base, username)
217
 
    homedir = os.path.join(userdir, 'home')
218
 
 
219
 
    if os.path.exists(userdir):
220
 
        if not force:
221
 
            raise Exception("User's jail already exists")
222
 
        # User jail already exists. Blow it away but preserve their home
223
 
        # directory.
224
 
        # Ignore warnings about the use of tmpnam
225
 
        warnings.simplefilter('ignore')
226
 
        homebackup = os.tempnam(tempdir)
227
 
        warnings.resetwarnings()
228
 
        # Note: shutil.move does not behave like "mv" - it does not put a file
229
 
        # into a directory if it already exists, just fails. Therefore it is
230
 
        # not susceptible to tmpnam symlink attack.
231
 
        shutil.move(homedir, homebackup)
232
 
        try:
233
 
            # Any errors that occur after making the backup will be caught and
234
 
            # the backup will be un-made.
235
 
            # XXX This will still leave the user's jail in an unusable state,
236
 
            # but at least they won't lose their files.
237
 
            if manifest:
238
 
                (to_add, to_remove) = manifest
239
 
                # Remove redundant files and directories
240
 
                for d in to_remove:
241
 
                    dst = os.path.join(userdir, d)
242
 
                    src = os.path.join(stagingdir, d)
243
 
                    if os.path.isdir(dst):
244
 
                        shutil.rmtree(dst)
245
 
                    elif os.path.isfile(dst):
246
 
                        os.remove(dst)
247
 
                # Add new files
248
 
                for d in to_add:
249
 
                    dst = os.path.join(userdir, d)
250
 
                    src = os.path.join(stagingdir, d)
251
 
                    # Clear the previous file/dir
252
 
                    if os.path.isdir(dst):
253
 
                        shutil.rmtree(dst)
254
 
                    elif os.path.isfile(dst):
255
 
                        os.remove(dst)
256
 
                    # Link the file/dirs
257
 
                    if os.path.isdir(src):
258
 
                        linktree(src, dst)
259
 
                    elif os.path.isfile(src):
260
 
                        os.link(src, dst)
261
 
                    
262
 
            else:
263
 
                # No manifest, do a full rebuild
264
 
                shutil.rmtree(userdir)
265
 
                # Hard-link (copy aliasing) the entire tree over
266
 
                linktree(stagingdir, userdir)
267
 
        finally:
268
 
            # Set up the user's home directory (restore backup)
269
 
            # First make sure the directory is empty and its parent exists
270
 
            try:
271
 
                shutil.rmtree(homedir)
272
 
            except:
273
 
                pass
274
 
            # XXX If this fails the user's directory will be lost (in the temp
275
 
            # directory). But it shouldn't fail as homedir should not exist.
276
 
            os.makedirs(homedir)
277
 
            shutil.move(homebackup, homedir)
278
 
        userhomedir = os.path.join(homedir, username)   # Return value
279
 
    else:
280
 
        # No user jail exists
281
 
        # Hard-link (copy aliasing) the entire tree over
282
 
        linktree(stagingdir, userdir)
283
 
 
284
 
        # Set up the user's home directory
285
 
        userhomedir = os.path.join(homedir, username)
286
 
        os.mkdir(userhomedir)
287
 
        # Chown (and set the GID to the same as the UID).
288
 
        os.chown(userhomedir, uid, uid)
289
 
        # Chmod to rwxr-xr-x (755)
290
 
        os.chmod(userhomedir, 0755)
291
 
 
292
 
    # There is 1 special file which should not be hard-linked, but instead
293
 
    # generated specific to this user: /opt/ivle/lib/conf/conf.py.
294
 
    # "__" username "__" users are exempt (special)
295
 
    if not (username.startswith("__") and username.endswith("__")):
296
 
        make_conf_py(username, userdir, stagingdir, svn_pass)
297
 
 
298
 
    return userhomedir
299
 
 
300
 
def make_conf_py(username, user_jail_dir, staging_dir, svn_pass=None):
301
 
    """
302
 
    Creates (overwriting any existing file) a file /opt/ivle/lib/conf/conf.py
303
 
    in a given user's jail.
304
 
    username: Username.
305
 
    user_jail_dir: User's jail dir, ie. conf.jail_base + username
306
 
    staging_dir: The dir with the staging copy of the jail. (With the
307
 
        template conf.py file).
308
 
    svn_pass: As with make_jail. User's SVN password, but if not supplied,
309
 
        will look up in the DB.
310
 
    """
311
 
    # Note: It is important to delete this file and recreate it (somewhat
312
 
    # ironically beginning by pasting the same contents in again), rather than
313
 
    # simply appending.
314
 
    # Note that all files initially are aliased, so appending would result
315
 
    # in a massive aliasing problem. Deleting and recreating ensures that
316
 
    # the conf.py files are unique to each jail.
317
 
    template_conf_path = os.path.join(staging_dir,"opt/ivle/lib/conf/conf.py")
318
 
    conf_path = os.path.join(user_jail_dir, "opt/ivle/lib/conf/conf.py")
319
 
 
320
 
    # If svn_pass isn't supplied, grab it from the DB
321
 
    if svn_pass is None:
322
 
        dbconn = db.DB()
323
 
        svn_pass = dbconn.get_user(username).svn_pass
324
 
        dbconn.close()
325
 
 
326
 
    # Read the contents of the template conf file
327
 
    try:
328
 
        template_conf_file = open(template_conf_path, "r")
329
 
        template_conf_data = template_conf_file.read()
330
 
        template_conf_file.close()
331
 
    except:
332
 
        # Couldn't open template conf.py for some reason
333
 
        # Just treat it as empty file
334
 
        template_conf_data = ""
335
 
 
336
 
    # Remove the target conf file if it exists
337
 
    try:
338
 
        os.remove(conf_path)
339
 
    except OSError:
340
 
        pass
341
 
    conf_file = open(conf_path, "w")
342
 
    conf_file.write(template_conf_data)
343
 
    conf_file.write("\n# The login name for the owner of the jail\n")
344
 
    conf_file.write("login = %s\n" % repr(username))
345
 
    conf_file.write("\n")
346
 
    conf_file.write("# The subversion-only password for the owner of "
347
 
        "the jail\n")
348
 
    conf_file.write("svn_pass = %s\n" % repr(svn_pass))
349
 
    conf_file.close()
350
 
 
351
 
    # Make this file world-readable
352
 
    # (chmod 644 conf_path)
353
 
    os.chmod(conf_path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP
354
 
                        | stat.S_IROTH)
355
 
 
356
 
def linktree(src, dst):
357
 
    """Recursively hard-link a directory tree using os.link().
358
 
 
359
 
    The destination directory must not already exist.
360
 
    If exception(s) occur, an Error is raised with a list of reasons.
361
 
 
362
 
    Symlinks are preserved (in fact, hard links are created which point to the
363
 
    symlinks).
364
 
 
365
 
    Code heavily based upon shutil.copytree from Python 2.5 library.
366
 
    """
367
 
    names = os.listdir(src)
368
 
    os.makedirs(dst)
369
 
    errors = []
370
 
    for name in names:
371
 
        srcname = os.path.join(src, name)
372
 
        dstname = os.path.join(dst, name)
373
 
        try:
374
 
            if os.path.isdir(srcname):
375
 
                linktree(srcname, dstname)
376
 
            else:
377
 
                os.link(srcname, dstname)
378
 
            # XXX What about devices, sockets etc.?
379
 
        except (IOError, os.error), why:
380
 
            errors.append((srcname, dstname, str(why)))
381
 
        # catch the Error from the recursive copytree so that we can
382
 
        # continue with other files
383
 
        except Exception, err:
384
 
            errors.append(err.args[0])
385
 
    try:
386
 
        shutil.copystat(src, dst)
387
 
    except WindowsError:
388
 
        # can't copy file access times on Windows
389
 
        pass
390
 
    except OSError, why:
391
 
        errors.extend((src, dst, str(why)))
392
 
    if errors:
393
 
        raise Exception, errors
394
 
 
395
 
def make_user_db(throw_on_error = True, **kwargs):
396
 
    """Creates a user's entry in the database, filling in all the fields.
397
 
    All arguments must be keyword args. They are the fields in the table.
398
 
    However, instead of supplying a "passhash", you must supply a
399
 
    "password" argument, which will be hashed internally.
400
 
    Also do not supply a state. All users are created in the "no_agreement"
401
 
    state.
402
 
    Throws an exception if the user already exists.
403
 
    """
404
 
    dbconn = db.DB()
405
 
    dbconn.create_user(**kwargs)
406
 
    dbconn.close()
407
 
 
408
 
    if kwargs['password']:
409
 
        if os.path.exists(conf.svn_auth_local):
410
 
            create = ""
411
 
        else:
412
 
            create = "c"
413
 
        res = os.system("htpasswd -%smb %s %s %s" % (create,
414
 
                                                     conf.svn_auth_local,
415
 
                                                     kwargs['login'],
416
 
                                                     kwargs['password']))
417
 
        if res != 0 and throw_on_error:
418
 
            raise Exception("Unable to create local-auth for %s" % kwargs['login'])
419
 
 
420
 
    # Make sure the file is owned by the web server
421
 
    if create == "c":
422
 
        chown_to_webserver(conf.svn_auth_local)