~launchpad-pqm/launchpad/devel

3944.1.1 by Francis J. Lacoste
Use system version python2.4 for scripts.
1
#!/usr/bin/python2.4
3691.361.1 by Stuart Bishop
Initial cut at account closure script
2
# Copyright 2007 Canonical Ltd.  All rights reserved.
3
5516.1.4 by Mark Shuttleworth
Tests for r=gmb
4
"""Remove personal details of a user from the database, leaving a stub."""
3691.361.1 by Stuart Bishop
Initial cut at account closure script
5
6
__metaclass__ = type
7
__all__ = []
8
9
import _pythonpath
10
11
from optparse import OptionParser
12
import sys
13
14
from canonical.database.sqlbase import connect
15
from canonical.launchpad.scripts import db_options, logger_options, logger
4611.5.11 by Curtis Hovey
Fixed QuestionStatus reference.
16
from canonical.launchpad.interfaces import (
17
    PersonCreationRationale, QuestionStatus)
3691.361.1 by Stuart Bishop
Initial cut at account closure script
18
19
def close_account(con, log, username):
20
    """Close a person's account.
5516.1.4 by Mark Shuttleworth
Tests for r=gmb
21
3691.361.1 by Stuart Bishop
Initial cut at account closure script
22
    Return True on success, or log an error message and return False
23
    """
24
    cur = con.cursor()
25
    cur.execute("""
4990.3.8 by Matthew Paul Thomas
Removes calendar stuff from account-closing script.
26
        SELECT Person.id, name, teamowner
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
27
        FROM Person LEFT OUTER JOIN EmailAddress
28
            ON Person.id = EmailAddress.person
29
        WHERE name=%(username)s or lower(email)=lower(%(username)s)
3691.361.1 by Stuart Bishop
Initial cut at account closure script
30
        """, vars())
31
    try:
4990.3.8 by Matthew Paul Thomas
Removes calendar stuff from account-closing script.
32
        person_id, username, teamowner = cur.fetchone()
3691.361.1 by Stuart Bishop
Initial cut at account closure script
33
    except TypeError:
34
        log.fatal("User %s does not exist" % username)
35
        return False
36
37
    # We don't do teams
38
    if teamowner is not None:
39
        log.fatal("%s is a team" % username)
40
        return False
41
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
42
    log.info("Closing %s's account" % username)
43
3691.361.1 by Stuart Bishop
Initial cut at account closure script
44
    def table_notification(table):
45
        log.debug("Handling the %s table" % table)
46
47
    # All names starting with 'removed' are blacklisted, so this will always
48
    # succeed.
49
    new_name = 'removed%d' % person_id
50
51
    # Clean out personal details from the Person table
52
    table_notification('Person')
53
    unknown_rationale = PersonCreationRationale.UNKNOWN.value
54
    cur.execute("""
55
        UPDATE Person
56
        SET displayname='Removed by request', password=NULL,
5516.1.1 by Mark Shuttleworth
Initial cut of db for location
57
            name=%(new_name)s, language=NULL, 
3691.361.1 by Stuart Bishop
Initial cut at account closure script
58
            addressline1=NULL, addressline2=NULL, organization=NULL,
59
            city=NULL, province=NULL, country=NULL, postcode=NULL,
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
60
            phone=NULL, homepage_content=NULL, icon=NULL, mugshot=NULL,
61
            hide_email_addresses=TRUE, registrant=NULL, logo=NULL,
3691.361.1 by Stuart Bishop
Initial cut at account closure script
62
            creation_rationale=%(unknown_rationale)s, creation_comment=NULL
63
        WHERE id=%(person_id)s
64
        """, vars())
65
66
    # Reassign their bugs
67
    table_notification('BugTask')
68
    cur.execute("""
69
        UPDATE BugTask SET assignee=NULL WHERE assignee=%(person_id)s
70
        """, vars())
71
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
72
    # Reassign questions assigned to the user, and close all their questions
3691.361.1 by Stuart Bishop
Initial cut at account closure script
73
    # since nobody else can
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
74
    table_notification('Question')
3691.361.1 by Stuart Bishop
Initial cut at account closure script
75
    cur.execute("""
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
76
        UPDATE Question SET assignee=NULL WHERE assignee=%(person_id)s
3691.361.1 by Stuart Bishop
Initial cut at account closure script
77
        """, vars())
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
78
    closed_question_status = QuestionStatus.SOLVED.value
3691.361.1 by Stuart Bishop
Initial cut at account closure script
79
    cur.execute("""
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
80
        UPDATE Question
81
        SET status=%(closed_question_status)s, whiteboard=
3691.361.1 by Stuart Bishop
Initial cut at account closure script
82
            'Closed by Launchpad due to owner requesting account removal'
83
        WHERE owner=%(person_id)s
84
        """, vars())
85
86
    # Remove rows from tables in simple cases in the given order
87
    removals = [
88
        # Trash their email addresses. Unsociable privacy nut jobs who request
89
        # account removal would be pissed if they reregistered with their old
90
        # email address and this resurrected their deleted account, as the
91
        # email address is probably the piece of data we store that they where
92
        # most concerned with being removed from our systems.
93
        ('EmailAddress', 'person'),
94
95
        # Trash their codes of conduct and GPG keys
96
        ('SignedCodeOfConduct', 'owner'),
97
        ('GpgKey', 'owner'),
98
99
        # Subscriptions
100
        ('BountySubscription', 'person'),
101
        ('BranchSubscription', 'person'),
102
        ('BugSubscription', 'person'),
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
103
        ('QuestionSubscription', 'person'),
3691.361.1 by Stuart Bishop
Initial cut at account closure script
104
        ('POSubscription', 'person'),
105
        ('SpecificationSubscription', 'person'),
106
107
        # Personal stuff, freeing up the namespace for others who want to play
108
        # or just to remove any fingerprints identifying the user.
109
        ('IrcId', 'person'),
110
        ('JabberId', 'person'),
111
        ('WikiName', 'person'),
112
        ('PersonLanguage', 'person'),
5516.1.1 by Mark Shuttleworth
Initial cut of db for location
113
        ('PersonLocation', 'person'),
3691.361.1 by Stuart Bishop
Initial cut at account closure script
114
        ('SshKey', 'person'),
115
        
116
        # Karma
117
        ('Karma', 'person'),
118
        ('KarmaCache', 'person'),
119
        ('KarmaTotalCache', 'person'),
120
121
        # Team memberships
122
        ('TeamMembership', 'person'),
123
        ('TeamParticipation', 'person'),
124
125
        # Contacts
126
        ('PackageBugContact', 'bugcontact'),
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
127
        ('AnswerContact', 'person'),
3691.361.1 by Stuart Bishop
Initial cut at account closure script
128
129
        # Pending items in queues
130
        ('POExportRequest', 'person'),
131
132
        # Access lists
133
        ('PushMirrorAccess', 'person'),
134
        ('DistroComponentUploader', 'uploader'),
135
        ]
136
    for table, person_id_column in removals:
137
        table_notification(table)
138
        cur.execute("""
139
                DELETE FROM %(table)s WHERE %(person_id_column)s=%(person_id)d
140
                """ % vars())
141
142
    # Trash Sprint Attendance records in the future.
143
    table_notification('SprintAttendance')
144
    cur.execute("""
145
        DELETE FROM SprintAttendance
146
        USING Sprint
147
        WHERE Sprint.id = SprintAttendance.sprint
148
            AND attendee=%(person_id)s
149
            AND Sprint.time_starts > CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
150
        """, vars())
151
152
    return True
153
154
def main():
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
155
    parser = OptionParser(
156
            '%prog [options] (username|email) [...]'
157
            )
3691.361.1 by Stuart Bishop
Initial cut at account closure script
158
    db_options(parser)
159
    logger_options(parser)
160
161
    (options, args) = parser.parse_args()
162
163
    if len(args) == 0:
164
        parser.error("Must specify username (Person.name)")
165
166
    log = logger(options)
167
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
168
    con = None
3691.361.1 by Stuart Bishop
Initial cut at account closure script
169
    try:
170
        log.debug("Connecting to database")
171
        con = connect(options.dbuser)
172
        for username in args:
173
            if not close_account(con, log, username):
174
                log.debug("Rolling back")
175
                con.rollback()
176
                return 1
177
        log.debug("Committing changes")
178
        con.commit()
179
        return 0
180
    except:
181
        log.exception("Unhandled exception")
182
        log.debug("Rolling back")
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
183
        if con is not None:
184
            con.rollback()
3691.361.1 by Stuart Bishop
Initial cut at account closure script
185
        return 1
186
187
if __name__ == '__main__':
188
    sys.exit(main())