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