summaryrefslogtreecommitdiff
path: root/admin.py
blob: 98d4376d65816da2f6ff17628d86a91b5222594b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300

import sys, os, urllib, StringIO, traceback, cgi, binascii, getopt, shutil
import zipfile, gzip, tarfile
#sys.path.append('/usr/local/pypi/lib')

import store, config

def set_password(store, name, pw):
    """ Reset the user's password and send an email to the address given.
    """
    user = store.get_user(name.strip())
    if user is None:
        raise ValueError, 'user name unknown to me'
    store.store_user(user['name'], pw.strip(), user['email'], None)
    print 'done'

def remove_spam(store, namepat, confirm=False):
    '''Remove packages that match namepat (SQL wildcards).

    The packages will be removed. Additionally the user that created them will
    have their password set to 'spammer'.

    Pass the additional command-line argument "confirm" to perform the
    deletions and modifications.

    This will additionally display the IP address(es) of the spam submissions.
    '''
    assert confirm in (False, 'confirm')
    cursor = st.get_cursor()
    cursor.execute("""
       select packages.name, submitted_date, submitted_by, submitted_from
        from packages, journals
        where packages.name LIKE %s
          and packages.name = journals.name
          and action = 'create'
    """, (namepat,))

    if not confirm:
        print 'NOT taking any action; add "confirm" to the command line to act'

    users = set()
    ips = set()
    for name, date, by, ip in cursor.fetchall():
        ips.add(ip)
        users.add(by)
        print 'delete', name, 'submitted on', date
        if confirm:
            store.remove_package(name)

    print 'IP addresses of spammers to possibly block:'
    for ip in ips:
        print '  ', ip

    for user in users:
        print 'disable user', user
        if confirm:
            cursor.execute("update users set password='spammer' where name=%s",
                (user,))

def remove_package(store, name):
    ''' Remove a package from the database
    '''
    store.remove_package(name)
    print 'done'

def add_owner(store, package, owner):
    user = store.get_user(owner)
    if user is None:
        raise ValueError, 'user name unknown to me'
    if not store.has_package(package):
        raise ValueError, 'no such package'
    store.add_role(owner, 'Owner', package)

def delete_owner(store, package, owner):
    user = store.get_user(owner)
    if user is None:
        raise ValueError, 'user name unknown to me'
    if not store.has_package(package):
        raise ValueError, 'no such package'
    for role in store.get_package_roles(package):
        if role['role_name']=='Owner' and role['user_name']==owner:
            break
    else:
        raise ValueError, "user is not currently owner"
    store.delete_role(owner, 'Owner', package)

def add_classifier(st, classifier):
    ''' Add a classifier to the trove_classifiers list
    '''
    cursor = st.get_cursor()
    cursor.execute("select max(id) from trove_classifiers")
    id = cursor.fetchone()[0]
    if id:
        id = int(id) + 1
    else:
        id = 1
    fields = [f.strip() for f in classifier.split('::')]
    for f in fields:
        assert ':' not in f
    levels = []
    for l in range(2, len(fields)):
        c2 = ' :: '.join(fields[:l])
        store.safe_execute(cursor, 'select id from trove_classifiers where classifier=%s', (c2,))
        l = cursor.fetchone()
        if not l:
            raise ValueError, c2 + " is not a known classifier"
        levels.append(l[0])
    levels += [id] + [0]*(3-len(levels))
    store.safe_execute(cursor, 'insert into trove_classifiers (id, classifier, l2, l3, l4, l5) '
        'values (%s,%s,%s,%s,%s,%s)', [id, classifier]+levels)

def rename_package(store, old, new):
    ''' Rename a package. '''
    if not store.has_package(old):
        raise ValueError, 'no such package'
    if store.has_package(new):
        raise ValueError, new+' exists'
    store.rename_package(old, new)
    print "Please give www-data permissions to all files of", new

def add_mirror(store, root, user):
    ''' Add a mirror to the mirrors list
    '''
    store.add_mirror(root, user)

    print 'done'

def delete_mirror(store, root):
    ''' Delete a mirror
    '''
    store.delete_mirror(root)
    print 'done'

def delete_old_docs(config, store):
    '''Delete documentation directories for packages that have been deleted'''
    for i in os.listdir(config.database_docs_dir):
        if not store.has_package(i):
           path = os.path.join(config.database_docs_dir, i)
           print "Deleting", path
           shutil.rmtree(path)

def keyrotate(config, store):
    '''Rotate server key'''
    key_dir = config.key_dir
    prefixes = (os.path.join(key_dir, 'privkey'), os.path.join(key_dir,'pubkey'))
    def rename_if_exists(oldsuffix, newsuffix):
        for p in prefixes:
            if os.path.exists(p+oldsuffix):
                os.rename(p+oldsuffix, p+newsuffix)
    # 1. generate new new key
    os.system('openssl dsaparam -out /tmp/param 2048')
    os.system('openssl gendsa -out %s/privkey.newnew /tmp/param' % key_dir)
    os.system('openssl dsa -in %s/privkey.newnew -pubout -out %s/pubkey.newnew' % (key_dir, key_dir))
    os.unlink('/tmp/param')
    # 2. delete old old key
    for p in prefixes:
        if os.path.exists(p+'.old'):
            os.unlink(p+'.old')
    # 3. rotate current key -> old key
    rename_if_exists('', '.old')
    # 4. rotate new key -> current key
    rename_if_exists('.new', '')
    # 5. rotate new new key -> new key
    rename_if_exists('.newnew', '.new')
    # 6. restart web server
    os.system('/usr/sbin/apache2ctl graceful')
    # 7. log rotation
    store.log_keyrotate()

def merge_user(store, old, new):
    c = store.get_cursor()
    if not store.get_user(old):
        print "Old does not exist"
        raise SystemExit
    if not store.get_user(new):
        print "New does not exist"
        raise SystemExit

    c.execute('update openids set name=%s where name=%s', (new, old))
    c.execute('update sshkeys set name=%s where name=%s', (new, old))
    c.execute('update roles set user_name=%s where user_name=%s', (new, old))
    c.execute('delete from rego_otk where name=%s', (old,))
    c.execute('update journals set submitted_by=%s where submitted_by=%s', (new, old))
    c.execute('update mirrors set user_name=%s where user_name=%s', (new, old))
    c.execute('update comments set user_name=%s where user_name=%s', (new, old))
    c.execute('update ratings set user_name=%s where user_name=%s', (new, old))
    c.execute('update comments_journal set submitted_by=%s where submitted_by=%s', (new, old))
    c.execute('delete from users where name=%s', (old,))

def rename_user(store, old, new):
    c = store.get_cursor()
    old_user = store.get_user(old)
    if not old_user:
        raise SystemExit("Old does not exist")
    if store.get_user(new):
        raise SystemExit("New user already exists!")

    c.execute('insert into users (name, email, password) values (%s, %s, %s)',
        (new, old_user['email'] + '.temp', old_user['password']))
    c.execute('update openids set name=%s where name=%s', (new, old))
    c.execute('update sshkeys set name=%s where name=%s', (new, old))
    c.execute('update roles set user_name=%s where user_name=%s', (new, old))
    c.execute('delete from rego_otk where name=%s', (old,))
    c.execute('update journals set submitted_by=%s where submitted_by=%s', (new, old))
    c.execute('update mirrors set user_name=%s where user_name=%s', (new, old))
    c.execute('update comments set user_name=%s where user_name=%s', (new, old))
    c.execute('update ratings set user_name=%s where user_name=%s', (new, old))
    c.execute('update comments_journal set submitted_by=%s where submitted_by=%s', (new, old))
    c.execute('delete from users where name=%s', (old,))
    c.execute('update users set email=%s where name=%s', (old_user['email'], new))

def show_user(store, name):
    user = store.get_user(name)
    if not user:
        sys.exit('user %r does not exist' % name)
    for key in user.keys():
        print '%s: %s' % (key, user[key])
    for p in store.get_user_packages(name):
        print '%s: %s' % (p['package_name'], p['role_name'])

def nuke_nested_lists(store, confirm=False):
    c = store.get_cursor()
    c.execute("""select name, version, summary from releases
        where summary like '%nested lists%'""")
    hits = {}
    for name, version, summary in c.fetchall():
        if "printer of nested lists" in summary:
            hits[name] = summary
            continue
        for f in store.list_files(name, version):
            path = store.gen_file_path(f['python_version'], name, f['filename'])
            if path.endswith('.zip'):
                z = zipfile.ZipFile(path)
                for i in z.infolist():
                    if not i.filename.endswith('.py'): continue
                    if 'def print_lol' in z.read(i.filename):
                        hits[name] = summary
            elif path.endswith('.tar.gz'):
                z = gzip.GzipFile(path)
                t = tarfile.TarFile(fileobj=z)
                for i in t.getmembers():
                    if not i.name.endswith('.py'): continue
                    f = t.extractfile(i.name)
                    if 'def print_lol' in f.read():
                        hits[name] = summary
    for name in hits:
        if confirm:
            store.remove_package(name)
        print '%s: %s' % (name, hits[name])
    if confirm:
        print 'removed %d packages' % len(hits)
    else:
        print 'WOULD HAVE removed %d packages' % len(hits)

if __name__ == '__main__':
    config = config.Config('/data/pypi/config.ini')
    st = store.Store(config)
    st.open()
    command = sys.argv[1]
    args = (st, ) + tuple(sys.argv[2:])
    try:
        if command == 'password':
            set_password(*args)
        elif command == 'rmpackage':
            remove_package(*args)
        elif command == 'rmspam':
            remove_spam(*args)
        elif command == 'addclass':
            add_classifier(*args)
            print 'done'
        elif command == 'addowner':
            add_owner(*args)
        elif command == 'delowner':
            delete_owner(*args)
        elif command == 'rename':
            rename_package(*args)
        elif command == 'addmirror':
            add_mirror(*args)
        elif command == 'delmirror':
            delete_mirror(*args)
        elif command == 'delolddocs':
            delete_old_docs(config, *args)
        elif command == 'send_comments':
            send_comments(*args)
        elif command == 'mergeuser':
            merge_user(*args)
        elif command == 'renameuser':
            rename_user(*args)
        elif command == 'nuke_nested_lists':
            nuke_nested_lists(*args)
        elif command == 'keyrotate':
            keyrotate(config, *args)
        elif command == 'user':
            show_user(*args)
        else:
            print "unknown command '%s'!"%command
        st.changed()
    finally:
        st.close()