~launchpad-pqm/launchpad/devel

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