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