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()) |