~launchpad-pqm/launchpad/devel

10637.3.1 by Guilherme Salgado
Use the default python version instead of a hard-coded version
1
#!/usr/bin/python -S
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("""
7675.395.80 by Stuart Bishop
Fix scripts/close-account.py
29
        SELECT Person.id, Person.account, 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:
7675.395.80 by Stuart Bishop
Fix scripts/close-account.py
35
        person_id, account_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
7675.395.80 by Stuart Bishop
Fix scripts/close-account.py
54
    # Remove the EmailAddress. This is the most important step, as
55
    # people requesting account removal seem to primarily be interested
56
    # in ensuring we no longer store this information.
57
    table_notification('EmailAddress')
6198.1.15 by Stuart Bishop
Fix close-account.py and tests
58
    cur.execute("""
7675.395.80 by Stuart Bishop
Fix scripts/close-account.py
59
        DELETE FROM EmailAddress WHERE person = %s
6198.1.62 by Stuart Bishop
More review feedback
60
        """ % sqlvalues(person_id))
61
3691.361.1 by Stuart Bishop
Initial cut at account closure script
62
    # Clean out personal details from the Person table
63
    table_notification('Person')
64
    unknown_rationale = PersonCreationRationale.UNKNOWN.value
65
    cur.execute("""
66
        UPDATE Person
6198.1.15 by Stuart Bishop
Fix close-account.py and tests
67
        SET displayname='Removed by request',
6198.1.62 by Stuart Bishop
More review feedback
68
            name=%(new_name)s, language=NULL, account=NULL,
3691.361.1 by Stuart Bishop
Initial cut at account closure script
69
            addressline1=NULL, addressline2=NULL, organization=NULL,
70
            city=NULL, province=NULL, country=NULL, postcode=NULL,
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
71
            phone=NULL, homepage_content=NULL, icon=NULL, mugshot=NULL,
72
            hide_email_addresses=TRUE, registrant=NULL, logo=NULL,
3691.361.1 by Stuart Bishop
Initial cut at account closure script
73
            creation_rationale=%(unknown_rationale)s, creation_comment=NULL
74
        WHERE id=%(person_id)s
75
        """, vars())
76
7675.395.80 by Stuart Bishop
Fix scripts/close-account.py
77
    # Remove the Account. We don't set the status to deactivated,
78
    # as this script is used to satisfy people who insist on us removing
79
    # all their personal details from our systems. This includes any
80
    # identification tokens like email addresses or openid identifiers.
81
    # So the Account record would be unusable, and contain no useful
82
    # information.
83
    table_notification('Account')
84
    if account_id is not None:
85
        cur.execute("""
86
            DELETE FROM Account WHERE id = %s
87
            """ % sqlvalues(account_id))
88
3691.361.1 by Stuart Bishop
Initial cut at account closure script
89
    # Reassign their bugs
90
    table_notification('BugTask')
91
    cur.execute("""
92
        UPDATE BugTask SET assignee=NULL WHERE assignee=%(person_id)s
93
        """, vars())
94
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
95
    # Reassign questions assigned to the user, and close all their questions
3691.361.1 by Stuart Bishop
Initial cut at account closure script
96
    # since nobody else can
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
97
    table_notification('Question')
3691.361.1 by Stuart Bishop
Initial cut at account closure script
98
    cur.execute("""
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
99
        UPDATE Question SET assignee=NULL WHERE assignee=%(person_id)s
3691.361.1 by Stuart Bishop
Initial cut at account closure script
100
        """, vars())
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
101
    closed_question_status = QuestionStatus.SOLVED.value
3691.361.1 by Stuart Bishop
Initial cut at account closure script
102
    cur.execute("""
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
103
        UPDATE Question
104
        SET status=%(closed_question_status)s, whiteboard=
3691.361.1 by Stuart Bishop
Initial cut at account closure script
105
            'Closed by Launchpad due to owner requesting account removal'
106
        WHERE owner=%(person_id)s
107
        """, vars())
108
109
    # Remove rows from tables in simple cases in the given order
110
    removals = [
111
        # Trash their email addresses. Unsociable privacy nut jobs who request
112
        # account removal would be pissed if they reregistered with their old
113
        # email address and this resurrected their deleted account, as the
114
        # email address is probably the piece of data we store that they where
115
        # most concerned with being removed from our systems.
116
        ('EmailAddress', 'person'),
117
118
        # Trash their codes of conduct and GPG keys
119
        ('SignedCodeOfConduct', 'owner'),
120
        ('GpgKey', 'owner'),
121
122
        # Subscriptions
123
        ('BranchSubscription', 'person'),
124
        ('BugSubscription', 'person'),
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
125
        ('QuestionSubscription', 'person'),
3691.361.1 by Stuart Bishop
Initial cut at account closure script
126
        ('POSubscription', 'person'),
127
        ('SpecificationSubscription', 'person'),
128
129
        # Personal stuff, freeing up the namespace for others who want to play
130
        # or just to remove any fingerprints identifying the user.
131
        ('IrcId', 'person'),
132
        ('JabberId', 'person'),
133
        ('WikiName', 'person'),
134
        ('PersonLanguage', 'person'),
5516.1.1 by Mark Shuttleworth
Initial cut of db for location
135
        ('PersonLocation', 'person'),
3691.361.1 by Stuart Bishop
Initial cut at account closure script
136
        ('SshKey', 'person'),
10303.1.1 by Gary Poster
use newest version of zc.buildout
137
3691.361.1 by Stuart Bishop
Initial cut at account closure script
138
        # Karma
139
        ('Karma', 'person'),
140
        ('KarmaCache', 'person'),
141
        ('KarmaTotalCache', 'person'),
142
143
        # Team memberships
144
        ('TeamMembership', 'person'),
145
        ('TeamParticipation', 'person'),
146
147
        # Contacts
6025.3.27 by Tom Berger
delete from PackageBugSupervisor when deleting an account
148
        ('PackageBugSupervisor', 'bug_supervisor'),
4360.1.2 by Stuart Bishop
Fix close-account.py and add a basic test
149
        ('AnswerContact', 'person'),
3691.361.1 by Stuart Bishop
Initial cut at account closure script
150
151
        # Pending items in queues
152
        ('POExportRequest', 'person'),
153
154
        # Access lists
155
        ('PushMirrorAccess', 'person'),
156
        ('DistroComponentUploader', 'uploader'),
157
        ]
158
    for table, person_id_column in removals:
159
        table_notification(table)
160
        cur.execute("""
161
                DELETE FROM %(table)s WHERE %(person_id_column)s=%(person_id)d
162
                """ % vars())
163
164
    # Trash Sprint Attendance records in the future.
165
    table_notification('SprintAttendance')
166
    cur.execute("""
167
        DELETE FROM SprintAttendance
168
        USING Sprint
169
        WHERE Sprint.id = SprintAttendance.sprint
170
            AND attendee=%(person_id)s
171
            AND Sprint.time_starts > CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
172
        """, vars())
173
174
    return True
175
176
def main():
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
177
    parser = OptionParser(
178
            '%prog [options] (username|email) [...]'
179
            )
3691.361.1 by Stuart Bishop
Initial cut at account closure script
180
    db_options(parser)
181
    logger_options(parser)
182
183
    (options, args) = parser.parse_args()
184
185
    if len(args) == 0:
186
        parser.error("Must specify username (Person.name)")
187
188
    log = logger(options)
189
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
190
    con = None
3691.361.1 by Stuart Bishop
Initial cut at account closure script
191
    try:
192
        log.debug("Connecting to database")
193
        con = connect(options.dbuser)
194
        for username in args:
195
            if not close_account(con, log, username):
196
                log.debug("Rolling back")
197
                con.rollback()
198
                return 1
199
        log.debug("Committing changes")
200
        con.commit()
201
        return 0
202
    except:
203
        log.exception("Unhandled exception")
204
        log.debug("Rolling back")
3691.361.2 by Stuart Bishop
Allow email address on command line and tidy
205
        if con is not None:
206
            con.rollback()
3691.361.1 by Stuart Bishop
Initial cut at account closure script
207
        return 1
208
209
if __name__ == '__main__':
210
    sys.exit(main())