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