~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
Person and Account
==================

The Person object is responsible for updating the status of its
Account object.


Activating user accounts
------------------------

A user may activate their account that was created by an automated
process. Matsubara's account was created during a code import.

    >>> from zope.component import getUtility
    >>> from lp.services.identity.interfaces.emailaddress import IEmailAddressSet
    >>> from lp.registry.interfaces.person import IPersonSet

    >>> emailset = getUtility(IEmailAddressSet)
    >>> emailaddress = emailset.getByEmail('matsubara@async.com.br')
    >>> matsubara = emailaddress.person
    >>> matsubara.is_valid_person
    False
    >>> matsubara.account_status
    <DBItem AccountStatus.NOACCOUNT, ...>
    >>> print matsubara.password
    None
    >>> print matsubara.preferredemail
    None

The account can only be activated by the user who is claiming
the profile. Sample Person cannot claim it.

    >>> login('test@canonical.com')
    >>> matsubara.account.activate(
    ...     comment="test", password='ok', preferred_email=emailaddress)
    Traceback (most recent call last):
    ...
    Unauthorized: ...'launchpad.Special')

Matsubara can. A password and a preferred email address must be passed
as arguments.

    >>> from zope.security.proxy import removeSecurityProxy
    >>> login('matsubara@async.com.br')
    >>> matsubara.account.activate(
    ...     comment="test", password='ok', preferred_email=emailaddress)
    >>> import transaction
    >>> transaction.commit()
    >>> matsubara.is_valid_person
    True
    >>> matsubara.account.status
    <DBItem AccountStatus.ACTIVE, ...>
    >>> matsubara.account.status_comment
    u'test'
    >>> removeSecurityProxy(matsubara.account.preferredemail).email
    u'matsubara@async.com.br'


Deactivating user accounts
--------------------------

Any user can deactivate his own account, in case they don't want it
anymore or they don't want to be shown as Launchpad users.

As seen below, Foo Bar has a bunch of stuff assigned/owned to/by him in
Launchpad which we'll want to be reassigned/unassigned if his account is
deactivated.  Unfortunately, Foo Bar has no specifications assigned to
him, so we'll assign one just to prove that deactivating his account
will cause this spec to be reassigned.


    >>> personset = getUtility(IPersonSet)
    >>> foobar_preferredemail = emailset.getByEmail('foo.bar@canonical.com')
    >>> foobar = personset.get(foobar_preferredemail.personID)
    >>> foobar.specifications().count() > 0
    True

    >>> from lp.blueprints.model.specification import Specification
    >>> from lp.registry.model.person import Person
    >>> spec = Specification.selectFirst("assignee IS NULL", orderBy='id')
    >>> spec.assignee = foobar

    >>> [membership.team.name for membership in foobar.team_memberships]
    [u'canonical-partner-dev', u'guadamen', u'hwdb-team', u'admins',
     u'launchpad-buildd-admins', u'launchpad', u'testing-spanish-team',
     u'name18', u'ubuntu-team', u'vcs-imports']

    >>> [email.email for email in foobar.validatedemails]
    [u'admin@canonical.com']

    >>> foobar.name
    u'name16'

    >>> foobar.preferredemail.email
    u'foo.bar@canonical.com'

    >>> [coc.active for coc in foobar.signedcocs]
    [True]

    >>> from lp.bugs.interfaces.bugtask import BugTaskSearchParams
    >>> params = BugTaskSearchParams(foobar, assignee=foobar)
    >>> foobar.searchTasks(params).count() > 0
    True

    >>> len(foobar.assigned_specs) > 0
    True

    >>> foobar_pillars = []
    >>> for pillar_name in foobar.getOwnedOrDrivenPillars():
    ...     pillar = pillar_name.pillar
    ...     if pillar.owner == foobar or pillar.driver == foobar:
    ...         foobar_pillars.append(pillar_name)
    >>> len(foobar_pillars) > 0
    True

    >>> foobar_teams = list(Person.selectBy(teamowner=foobar))
    >>> len(foobar_teams) > 0
    True

    >>> foobar.is_valid_person
    True

    >>> comment = ("I'm a person who doesn't want to be listed "
    ...            "as a Launchpad user.")

The deactivateAccount method is restricted to the user himself --not
even launchpad admins can use it.

    >>> login('mark@example.com')
    >>> foobar.deactivateAccount(comment)
    Traceback (most recent call last):
    ...
    Unauthorized: ...'launchpad.Special')

    >>> login('foo.bar@canonical.com')
    >>> foobar.deactivateAccount(comment)
    >>> transaction.commit()

Deactivating an account changes many of the person's attributes.  It
adds a '-deactivatedaccount' suffix to the person's name...

    >>> foobar.name
    u'name16-deactivatedaccount'

...an account status of DEACTIVATED...

    >>> foobar.account_status
    <DBItem AccountStatus.DEACTIVATED...

    >>> foobar.account_status_comment
    u"I'm a person who doesn't want to be listed as a Launchpad user."

...to have no team memberships...

    >>> [membership.team.name for membership in foobar.team_memberships]
    []

...and no validated/preferred email addresses...

    >>> [email.email for email in foobar.validatedemails]
    []

    >>> print getattr(foobar.preferredemail, 'email', None)
    None

...no signed codes of conduct...

    >>> [coc.active for coc in foobar.signedcocs]
    [False]

...no assigned bug tasks...

    >>> foobar.searchTasks(params).count()
    0

...no assigned specs...

    >>> len(foobar.assigned_specs)
    0

...no owned teams...

    >>> Person.selectBy(teamowner=foobar).count()
    0

...no owned or driven pillars...

    >>> foobar.getOwnedOrDrivenPillars().count()
    0

...and, finally, to not be considered a valid person in Launchpad.

    >>> transaction.commit()
    >>> foobar.is_valid_person
    False

It's also important to note that the teams/pillars owned/driven by Foo
Bar are now owned/driven by the registry admins team.

    >>> from lp.app.interfaces.launchpad import ILaunchpadCelebrities
    >>> registry_experts = getUtility(ILaunchpadCelebrities).registry_experts

    >>> registry_pillars = set(registry_experts.getOwnedOrDrivenPillars())
    >>> registry_pillars.issuperset(foobar_pillars)
    True

    >>> registry_teams = set(Person.selectBy(teamowner=registry_experts))
    >>> registry_teams.issuperset(foobar_teams)
    True


Reactivating user accounts
--------------------------

Accounts can be reactivated. A comment and a non-None preferred email address
are required to reactivate() an account, though.

    >>> foobar.account.reactivate(
    ...     'This will raise an error.', password='', preferred_email=None)
    Traceback (most recent call last):
     ...
    AssertionError: Account ... cannot be activated without a preferred
                    email address.

    >>> foobar.reactivate(
    ...     'User reactivated the account using reset password.',
    ...     password="ok",
    ...     preferred_email=foobar_preferredemail)
    >>> transaction.commit()  # To see the changes on other stores.
    >>> foobar.account.status
    <DBItem AccountStatus.ACTIVE...

    >>> foobar.account.status_comment
    u'User reactivated the account using reset password.'

The person name is fixed if it was altered when it was deactivated.

    >>> foobar.name
    u'name16'