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