3944.1.1
by Francis J. Lacoste
Use system version python2.4 for scripts. |
1 |
#!/usr/bin/python2.4
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
2 |
# Copyright 2007 Canonical Ltd. All rights reserved.
|
3 |
||
5516.1.4
by Mark Shuttleworth
Tests for r=gmb |
4 |
"""Remove personal details of a user from the database, leaving a stub."""
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
5 |
|
6 |
__metaclass__ = type |
|
7 |
__all__ = [] |
|
8 |
||
9 |
import _pythonpath |
|
10 |
||
11 |
from optparse import OptionParser |
|
12 |
import sys |
|
13 |
||
14 |
from canonical.database.sqlbase import connect |
|
15 |
from canonical.launchpad.scripts import db_options, logger_options, logger |
|
4611.5.11
by Curtis Hovey
Fixed QuestionStatus reference. |
16 |
from canonical.launchpad.interfaces import ( |
17 |
PersonCreationRationale, QuestionStatus) |
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
18 |
|
19 |
def close_account(con, log, username): |
|
20 |
"""Close a person's account.
|
|
5516.1.4
by Mark Shuttleworth
Tests for r=gmb |
21 |
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
22 |
Return True on success, or log an error message and return False
|
23 |
"""
|
|
24 |
cur = con.cursor() |
|
25 |
cur.execute(""" |
|
4990.3.8
by Matthew Paul Thomas
Removes calendar stuff from account-closing script. |
26 |
SELECT Person.id, name, teamowner
|
3691.361.2
by Stuart Bishop
Allow email address on command line and tidy |
27 |
FROM Person LEFT OUTER JOIN EmailAddress
|
28 |
ON Person.id = EmailAddress.person
|
|
29 |
WHERE name=%(username)s or lower(email)=lower(%(username)s) |
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
30 |
""", vars()) |
31 |
try: |
|
4990.3.8
by Matthew Paul Thomas
Removes calendar stuff from account-closing script. |
32 |
person_id, username, teamowner = cur.fetchone() |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
33 |
except TypeError: |
34 |
log.fatal("User %s does not exist" % username) |
|
35 |
return False |
|
36 |
||
37 |
# We don't do teams
|
|
38 |
if teamowner is not None: |
|
39 |
log.fatal("%s is a team" % username) |
|
40 |
return False |
|
41 |
||
3691.361.2
by Stuart Bishop
Allow email address on command line and tidy |
42 |
log.info("Closing %s's account" % username) |
43 |
||
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
44 |
def table_notification(table): |
45 |
log.debug("Handling the %s table" % table) |
|
46 |
||
47 |
# All names starting with 'removed' are blacklisted, so this will always
|
|
48 |
# succeed.
|
|
49 |
new_name = 'removed%d' % person_id |
|
50 |
||
51 |
# Clean out personal details from the Person table
|
|
52 |
table_notification('Person') |
|
53 |
unknown_rationale = PersonCreationRationale.UNKNOWN.value |
|
54 |
cur.execute(""" |
|
55 |
UPDATE Person
|
|
56 |
SET displayname='Removed by request', password=NULL,
|
|
5516.1.1
by Mark Shuttleworth
Initial cut of db for location |
57 |
name=%(new_name)s, language=NULL, |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
58 |
addressline1=NULL, addressline2=NULL, organization=NULL,
|
59 |
city=NULL, province=NULL, country=NULL, postcode=NULL,
|
|
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
60 |
phone=NULL, homepage_content=NULL, icon=NULL, mugshot=NULL,
|
61 |
hide_email_addresses=TRUE, registrant=NULL, logo=NULL,
|
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
62 |
creation_rationale=%(unknown_rationale)s, creation_comment=NULL |
63 |
WHERE id=%(person_id)s |
|
64 |
""", vars()) |
|
65 |
||
66 |
# Reassign their bugs
|
|
67 |
table_notification('BugTask') |
|
68 |
cur.execute(""" |
|
69 |
UPDATE BugTask SET assignee=NULL WHERE assignee=%(person_id)s |
|
70 |
""", vars()) |
|
71 |
||
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
72 |
# Reassign questions assigned to the user, and close all their questions
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
73 |
# since nobody else can
|
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
74 |
table_notification('Question') |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
75 |
cur.execute(""" |
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
76 |
UPDATE Question SET assignee=NULL WHERE assignee=%(person_id)s |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
77 |
""", vars()) |
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
78 |
closed_question_status = QuestionStatus.SOLVED.value |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
79 |
cur.execute(""" |
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
80 |
UPDATE Question
|
81 |
SET status=%(closed_question_status)s, whiteboard= |
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
82 |
'Closed by Launchpad due to owner requesting account removal'
|
83 |
WHERE owner=%(person_id)s |
|
84 |
""", vars()) |
|
85 |
||
86 |
# Remove rows from tables in simple cases in the given order
|
|
87 |
removals = [ |
|
88 |
# Trash their email addresses. Unsociable privacy nut jobs who request
|
|
89 |
# account removal would be pissed if they reregistered with their old
|
|
90 |
# email address and this resurrected their deleted account, as the
|
|
91 |
# email address is probably the piece of data we store that they where
|
|
92 |
# most concerned with being removed from our systems.
|
|
93 |
('EmailAddress', 'person'), |
|
94 |
||
95 |
# Trash their codes of conduct and GPG keys
|
|
96 |
('SignedCodeOfConduct', 'owner'), |
|
97 |
('GpgKey', 'owner'), |
|
98 |
||
99 |
# Subscriptions
|
|
100 |
('BountySubscription', 'person'), |
|
101 |
('BranchSubscription', 'person'), |
|
102 |
('BugSubscription', 'person'), |
|
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
103 |
('QuestionSubscription', 'person'), |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
104 |
('POSubscription', 'person'), |
105 |
('SpecificationSubscription', 'person'), |
|
106 |
||
107 |
# Personal stuff, freeing up the namespace for others who want to play
|
|
108 |
# or just to remove any fingerprints identifying the user.
|
|
109 |
('IrcId', 'person'), |
|
110 |
('JabberId', 'person'), |
|
111 |
('WikiName', 'person'), |
|
112 |
('PersonLanguage', 'person'), |
|
5516.1.1
by Mark Shuttleworth
Initial cut of db for location |
113 |
('PersonLocation', 'person'), |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
114 |
('SshKey', 'person'), |
115 |
||
116 |
# Karma
|
|
117 |
('Karma', 'person'), |
|
118 |
('KarmaCache', 'person'), |
|
119 |
('KarmaTotalCache', 'person'), |
|
120 |
||
121 |
# Team memberships
|
|
122 |
('TeamMembership', 'person'), |
|
123 |
('TeamParticipation', 'person'), |
|
124 |
||
125 |
# Contacts
|
|
126 |
('PackageBugContact', 'bugcontact'), |
|
4360.1.2
by Stuart Bishop
Fix close-account.py and add a basic test |
127 |
('AnswerContact', 'person'), |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
128 |
|
129 |
# Pending items in queues
|
|
130 |
('POExportRequest', 'person'), |
|
131 |
||
132 |
# Access lists
|
|
133 |
('PushMirrorAccess', 'person'), |
|
134 |
('DistroComponentUploader', 'uploader'), |
|
135 |
]
|
|
136 |
for table, person_id_column in removals: |
|
137 |
table_notification(table) |
|
138 |
cur.execute(""" |
|
139 |
DELETE FROM %(table)s WHERE %(person_id_column)s=%(person_id)d |
|
140 |
""" % vars()) |
|
141 |
||
142 |
# Trash Sprint Attendance records in the future.
|
|
143 |
table_notification('SprintAttendance') |
|
144 |
cur.execute(""" |
|
145 |
DELETE FROM SprintAttendance
|
|
146 |
USING Sprint
|
|
147 |
WHERE Sprint.id = SprintAttendance.sprint
|
|
148 |
AND attendee=%(person_id)s |
|
149 |
AND Sprint.time_starts > CURRENT_TIMESTAMP AT TIME ZONE 'UTC'
|
|
150 |
""", vars()) |
|
151 |
||
152 |
return True |
|
153 |
||
154 |
def main(): |
|
3691.361.2
by Stuart Bishop
Allow email address on command line and tidy |
155 |
parser = OptionParser( |
156 |
'%prog [options] (username|email) [...]'
|
|
157 |
)
|
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
158 |
db_options(parser) |
159 |
logger_options(parser) |
|
160 |
||
161 |
(options, args) = parser.parse_args() |
|
162 |
||
163 |
if len(args) == 0: |
|
164 |
parser.error("Must specify username (Person.name)") |
|
165 |
||
166 |
log = logger(options) |
|
167 |
||
3691.361.2
by Stuart Bishop
Allow email address on command line and tidy |
168 |
con = None |
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
169 |
try: |
170 |
log.debug("Connecting to database") |
|
171 |
con = connect(options.dbuser) |
|
172 |
for username in args: |
|
173 |
if not close_account(con, log, username): |
|
174 |
log.debug("Rolling back") |
|
175 |
con.rollback() |
|
176 |
return 1 |
|
177 |
log.debug("Committing changes") |
|
178 |
con.commit() |
|
179 |
return 0 |
|
180 |
except: |
|
181 |
log.exception("Unhandled exception") |
|
182 |
log.debug("Rolling back") |
|
3691.361.2
by Stuart Bishop
Allow email address on command line and tidy |
183 |
if con is not None: |
184 |
con.rollback() |
|
3691.361.1
by Stuart Bishop
Initial cut at account closure script |
185 |
return 1 |
186 |
||
187 |
if __name__ == '__main__': |
|
188 |
sys.exit(main()) |