~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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
ArchiveSubscriber
=================

This content class represents a subscription by a person to an IArchive.
The subscription represents that person's ability to download items from
the archive's repository.  The subscription is granted by a person who
has upload permission to the archive.  Once created the subscription is only
viewable by other uploaders and the person in the subscription.

See also archiveauthtoken.txt.

First, create a person 'joesmith' and a team 'team_cprov':

    >>> login('foo.bar@canonical.com')
    >>> joesmith = factory.makePerson(name="joesmith",
    ...                               displayname="Joe Smith",
    ...                               password="test",
    ...                               email="joe@example.com")
    >>> johnsmith = factory.makePerson(name="johnsmith",
    ...                               displayname="John Smith",
    ...                               password="test",
    ...                               email="john@example.com")
    >>> fredsmith = factory.makePerson(name="fredsmith",
    ...                               displayname="Fred Smith",
    ...                               password="test",
    ...                               email="fred@example.com")
    >>> from lp.registry.interfaces.person import IPersonSet
    >>> cprov = getUtility(IPersonSet).getByName("cprov")
    >>> no_priv = getUtility(IPersonSet).getByName("no-priv")
    >>> team_cprov = factory.makeTeam(cprov, "Team Cprov")
    >>> johnsmith.join(team_cprov)
    >>> from lp.testing.mail_helpers import print_emails


Creating new subscriptions
--------------------------

New subscriptions are created using IArchive.newSubscription()

Operations with subscriptions are security protected, so to start with we'll
log in as an unprivileged user.

    >>> login("no-priv@canonical.com")

We can create a new subscription for joesmith to access cprov's PPA like this:

    >>> new_sub = cprov.archive.newSubscription(joesmith, cprov)
    Traceback (most recent call last):
    ...
    Unauthorized:...

That failed because only people who have launchpad.Append (basically, upload
access) on the context archive are allowed to create subscriptions.

Users cannot create their own subscriptions either.  Log in as joesmith:

    >>> login("joe@example.com")
    >>> new_token = cprov.archive.newSubscription(joesmith, cprov)
    Traceback (most recent call last):
    ...
    Unauthorized:...

If we log in as cprov it will still not work because his archive is
public:

    >>> login("celso.providelo@canonical.com")
    >>> new_sub = cprov.archive.newSubscription(
    ...     joesmith, cprov, description=u"subscription for joesmith")
    Traceback (most recent call last):
    ...
    ArchiveNotPrivate: Only private archives can have subscriptions.

If we create a private ppa for Celso, then he can create a
subscription for joesmith:

    >>> login('foo.bar@canonical.com')
    >>> cprov_private_ppa = factory.makeArchive(
    ...     owner=cprov, distribution=cprov.archive.distribution,
    ...     private=True, name='p3a',
    ...     description="packages to help my friends.")
    >>> login("celso.providelo@canonical.com")
    >>> new_sub = cprov_private_ppa.newSubscription(
    ...     joesmith, cprov, description=u"subscription for joesmith")

The new subscription is returned and reflects the data:

    >>> print new_sub.displayname
    Joe Smith's access to PPA named p3a for Celso Providelo

    >>> print new_sub.registrant.name
    cprov

    >>> print new_sub.description
    subscription for joesmith

    >>> print new_sub.status.name
    CURRENT

Subscriptions also contain some date information:

    >>> new_sub.date_created is not None
    True

    >>> print new_sub.date_expires
    None

An email is sent to the subscribed person when the ArchiveSubscriber
entry is created:

    >>> print_emails(include_reply_to=True) #doctest: -NORMALIZE_WHITESPACE
    From: Celso Providelo <noreply@launchpad.net>
    To: joe@example.com
    Reply-To: Celso Providelo <celso.providelo@canonical.com>
    Subject: PPA access granted for PPA named p3a for Celso Providelo
    Hello Joe Smith,
    <BLANKLINE>
    Launchpad: access to a private archive
    --------------------------------------
    <BLANKLINE>
    Celso Providelo has granted you access to a private software archive
    "PPA named p3a for Celso Providelo" (ppa:cprov/p3a), which is hosted by
    Launchpad and has the following description:
    <BLANKLINE>
    packages to help my friends.
    <BLANKLINE>
    To start downloading and using software from this archive you need to
    view your access details by visiting this link:
    <BLANKLINE>
    <http://launchpad.dev/~/+archivesubscriptions>
    <BLANKLINE>
    You can find out more about Celso Providelo here:
    <BLANKLINE>
    <http://launchpad.dev/~cprov>
    <BLANKLINE>
    If you'd prefer not to use software from this archive, you can safely
    ignore this email. However, if you have any concerns you can contact the
    Launchpad team by emailing feedback@launchpad.net
    <BLANKLINE>
    Regards,
    The Launchpad team
    ----------------------------------------

If the description of the P3A is changed to None, and a new user subscribed
the e-mail does not contain the description.

    >>> cprov_private_ppa.description = None
    >>> unused = cprov_private_ppa.newSubscription(fredsmith, cprov)
    >>> print_emails(include_reply_to=True) #doctest: -NORMALIZE_WHITESPACE
    From: Celso Providelo <noreply@launchpad.net>
    To: fred@example.com
    Reply-To: Celso Providelo <celso.providelo@canonical.com>
    Subject: PPA access granted for PPA named p3a for Celso Providelo
    Hello Fred Smith,
    <BLANKLINE>
    Launchpad: access to a private archive
    --------------------------------------
    <BLANKLINE>
    Celso Providelo has granted you access to a private software archive
    "PPA named p3a for Celso Providelo" (ppa:cprov/p3a), which is hosted by
    Launchpad.
    <BLANKLINE>
    To start downloading and using software from this archive you need to
    view your access details by visiting this link:
    <BLANKLINE>
    <http://launchpad.dev/~/+archivesubscriptions>
    <BLANKLINE>
    You can find out more about Celso Providelo here:
    <BLANKLINE>
    <http://launchpad.dev/~cprov>
    <BLANKLINE>
    If you'd prefer not to use software from this archive, you can safely
    ignore this email. However, if you have any concerns you can contact the
    Launchpad team by emailing feedback@launchpad.net
    <BLANKLINE>
    Regards,
    The Launchpad team
    ----------------------------------------

A subscription for a subscriber who already has a current subscription
cannot be created:

    >>> new_sub = cprov_private_ppa.newSubscription(
    ...     joesmith, cprov, description=u"subscription for joesmith")
    Traceback (most recent call last):
    ...
    AlreadySubscribed: Joe Smith already has a current subscription for
    'PPA named p3a for Celso Providelo'.


Add another subscription for the test user, this time to mark's ppa:

    >>> login("mark@example.com")
    >>> mark = getUtility(IPersonSet).getByName("mark")
    >>> mark_private_ppa = factory.makeArchive(
    ...     owner=mark, distribution=mark.archive.distribution,
    ...     private=True, name='p3a')
    >>> new_sub_to_mark_ppa = mark_private_ppa.newSubscription(
    ...     joesmith, mark, description=u"subscription for joesmith")

    >>> print_emails()
    From: Mark Shuttleworth <noreply@launchpad.net>
    To: joe@example.com
    ...

And also a subscription for a Team:

    >>> new_team_sub_to_mark_ppa = mark_private_ppa.newSubscription(
    ...     team_cprov, mark, description=u"Access for cprov team")

    >>> print_emails()
    From: Mark Shuttleworth <noreply@launchpad.net>
    To: celso.providelo@canonical.com
    ...


Explicitly set the date_created for testing purposes:

    >>> from datetime import datetime
    >>> import pytz
    >>> from zope.security.proxy import removeSecurityProxy
    >>> removeSecurityProxy(new_sub).date_created = datetime(
    ...     2009, 2, 26, tzinfo=pytz.UTC)
    >>> removeSecurityProxy(new_sub_to_mark_ppa).date_created = datetime(
    ...     2009, 2, 22, tzinfo=pytz.UTC)
    >>> removeSecurityProxy(new_team_sub_to_mark_ppa).date_created = (
    ...     datetime(2009, 2, 24, tzinfo=pytz.UTC))

Commit the new subscriptions to the database.

    >>> from storm.store import Store
    >>> Store.of(new_sub).commit()

Retrieving existing subscriptions
---------------------------------

The ArchiveSubscriberSet utility allows you to retrieve subscriptions by
subscriber and archive.  To access subscriptions you need launchpad.View
privilege which applies to the person in the subscriptions and launchpad
admins.

    >>> from lp.soyuz.enums import ArchiveSubscriberStatus
    >>> from lp.soyuz.interfaces.archivesubscriber import (
    ...     IArchiveSubscriberSet)
    >>> sub_set = getUtility(IArchiveSubscriberSet)

    >>> login("no-priv@canonical.com")

    >>> sub = sub_set.getBySubscriber(new_sub.subscriber)
    Traceback (most recent call last):
    ...
    Unauthorized:...

Log in as joesmith, who is the person in the subscription.

    >>> login("joe@example.com")

And retrieve the subscription by subscriber and archive:

    >>> print sub_set.getBySubscriber(
    ...     new_sub.subscriber)[0].archive.displayname
    PPA named p3a for Celso Providelo

    >>> print sub_set.getByArchive(new_sub.archive)[1].subscriber.name
    joesmith

The getBySubscriber() method takes an optional archive parameter for
finding a subscription for a particular user in a particular archive:

    >>> print sub_set.getBySubscriber(
    ...     new_sub.subscriber, new_sub.archive)[0].archive.displayname
    PPA named p3a for Celso Providelo

By default the getBySubscriber() and getByArchive() methods return
all current subscriptions, most recently created first:

    >>> login('mark@example.com')
    >>> for subscription in sub_set.getBySubscriber(new_sub.subscriber):
    ...     print subscription.archive.displayname
    ...     print subscription.date_created.date()
    PPA named p3a for Celso Providelo      2009-02-26
    PPA named p3a for Mark Shuttleworth    2009-02-22

    >>> for subscription in sub_set.getByArchive(mark_private_ppa):
    ...     print subscription.subscriber.displayname
    ...     print subscription.date_created.date()
    Team Cprov      2009-02-24
    Joe Smith       2009-02-22

If we cancel one of the subscriptions:

    >>> login("mark@example.com")
    >>> new_sub_to_mark_ppa.status = ArchiveSubscriberStatus.CANCELLED
    >>> login("joe@example.com")

then the cancelled subscription no longer appears in the results
of getBySubscriber() and getByArchive():

    >>> sub_set.getBySubscriber(new_sub.subscriber).count()
    1
    >>> sub_set.getByArchive(mark_private_ppa).count()
    1

Unless we explicitly ask for all subscriptions - not just the current ones:

    >>> sub_set.getBySubscriber(
    ...     new_sub.subscriber, current_only=False).count()
    2
    >>> sub_set.getByArchive(mark_private_ppa, current_only=False).count()
    2

The getBySubscriber() method includes by default subscriptions for teams
to which the provided subscriber belongs:

    >>> joesmith.join(team_cprov)
    >>> for subscription in sub_set.getBySubscriber(joesmith):
    ...     print subscription.archive.displayname
    ...     print subscription.description
    PPA named p3a for Celso Providelo        subscription for joesmith
    PPA named p3a for Mark Shuttleworth      Access for cprov team

Finally, many callsites of getBySubscriber() will be interested not only
in each subscription of the subscriber, but also the generated
ArchiveAuthToken for each subscription of the subscriber. These can
be returned as well using the getBySubscriberWithActiveToken():

First create a token for joesmith's subscription for cprov's archive:

    >>> joesmith_token = cprov_private_ppa.newAuthToken(
    ...     joesmith, u"test_token")

Now print out all subscriptions with their tokens for joesmith:

    >>> def print_subscriptions_with_tokens(subs_with_tokens):
    ...     for subscription, token in subs_with_tokens:
    ...         if token:
    ...             token_text = token.token
    ...         else:
    ...             token_text = "None"
    ...         print subscription.archive.displayname
    ...         print token_text
    >>> print_subscriptions_with_tokens(
    ...     sub_set.getBySubscriberWithActiveToken(joesmith))
    PPA named p3a for Celso Providelo            test_token
    PPA named p3a for Mark Shuttleworth          None

There's a also related method on IPerson that will return the archive URLs
for the activated tokens.

    >>> for url in joesmith.getArchiveSubscriptionURLs(joesmith):
    ...     print url
    http://joesmith:test_token@private-ppa.launchpad.dev/cprov/p3a/ubuntu

This method can only be used by someone with launchpad.Edit on the context
IPerson:

    >>> login("no-priv@canonical.com")
    >>> urls = joesmith.getArchiveSubscriptionURLs(no_priv)
    Traceback (most recent call last):
    ...
    Unauthorized

Deactivated tokens are not included with the returned token for a
subscription:

    >>> login("celso.providelo@canonical.com")
    >>> joesmith_token.deactivate()
    >>> login("joe@example.com")

    >>> print_subscriptions_with_tokens(
    ... sub_set.getBySubscriberWithActiveToken(joesmith))
    PPA named p3a for Celso Providelo            None
    PPA named p3a for Mark Shuttleworth          None


Amending Subscriptions
----------------------

Some of the properties of subscriptions can change after they are created.
To do this, the changer needs to have launchpad.Edit on the subscription,
or be an admin.

Trying to set the properties as the subscribed person will fail:

    >>> from lp.services.database.constants import UTC_NOW
    >>> new_sub.date_expires = UTC_NOW
    Traceback (most recent call last):
    ...
    Unauthorized:...

Log in as someone with launchpad.Edit and it will work:

    >>> login("celso.providelo@canonical.com")
    >>> new_sub.date_expires = UTC_NOW

Other properties that might get modified later are status and description.
We can also do this as an admin.

    >>> login("admin@canonical.com")
    >>> new_sub.description = u"changed by admin"
    >>> new_sub.status = ArchiveSubscriberStatus.EXPIRED

The subscriber and registrant properties are not editable.

    >>> new_sub.subscriber = cprov
    Traceback (most recent call last):
    ...
    ForbiddenAttribute:...

    >>> new_sub.registrant = joesmith
    Traceback (most recent call last):
    ...
    ForbiddenAttribute:...


Cancelling subscriptions
------------------------

Subscriptions can only be cancelled after they are created.  The calling user
also needs launchpad.Edit on the subscription, which means either someone with
IArchive launchpad.Append (as for creating new tokens) or an admin.

    >>> login("no-priv@canonical.com")
    >>> new_sub.cancel()
    Traceback (most recent call last):
    ...
    Unauthorized:...

    >>> login("celso.providelo@canonical.com")
    >>> new_sub.cancel(cprov)

Cancelling sets the date_cancelled value to the current date/time
and cancelled_by to the supplied person.  The status also changes to
CANCELLED.

    >>> new_sub.date_cancelled is not None
    True

    >>> print new_sub.cancelled_by.name
    cprov

    >>> print new_sub.status.name
    CANCELLED

We can do this as an admin too:

    >>> login("admin@canonical.com")
    >>> new_sub.cancel(cprov)


Finding all non-active subscribers
----------------------------------

The method getNonActiveSubscribers() facilitates contacting all the people
included in this subscription who do not yet have an active token for the
corresponding archive.

For example, Joe already has an (unactivated) subscription to Mark's PPA
via the cprov_team:

    >>> for subscription in sub_set.getBySubscriber(joesmith):
    ...     print subscription.archive.displayname
    ...     print subscription.description
    PPA named p3a for Mark Shuttleworth      Access for cprov team

    >>> subscription = sub_set.getBySubscriber(joesmith).first()

So the getNonActiveSubscribers() method for this team subscription will
currently include Joe:

    >>> for person, email in subscription.getNonActiveSubscribers():
    ...     print person.displayname, email.email
    Celso Providelo   celso.providelo@canonical.com
    Joe Smith         joe@example.com
    John Smith        john@example.com

But if we create an auth token for joe to the archive (this could be via
a separate subscription), then he will no longer be listed as a non-active
subscriber for this subscription:

    >>> joesmith_token = mark_private_ppa.newAuthToken(joesmith)
    >>> for person, email in subscription.getNonActiveSubscribers():
    ...     print person.displayname
    Celso Providelo
    John Smith

If the subscription is just for an individual, getNonActiveSubscribers()
will return a list with the single subscriber as expected:

    >>> login("mark@example.com")
    >>> harrysmith = factory.makePerson(name="harrysmith",
    ...                                 displayname="Harry Smith",
    ...                                 password="test",
    ...                                 email="harry@example.com")
    >>> subscription = mark_private_ppa.newSubscription(
    ...     harrysmith, mark, description=u"subscription for joesmith")
    >>> for person, email in subscription.getNonActiveSubscribers():
    ...     print person.displayname
    Harry Smith

If Harry activates a token for his new subscription then
getNonActiveSubscribers will return an empty result set as he is now
"active".

    >>> harry_token = mark_private_ppa.newAuthToken(harrysmith)
    >>> print subscription.getNonActiveSubscribers().count()
    0

If the subscription is for a group which itself contains a group, all
indirect members that are not themselves groups are included:

    >>> launchpad_devs = getUtility(IPersonSet).getByName('launchpad')
    >>> ignored = launchpad_devs.addMember(
    ...     team_cprov, mark, force_team_add=True)
    >>> subscription = mark_private_ppa.newSubscription(
    ...     launchpad_devs, mark, description=u"LP team too")
    >>> for person, email in subscription.getNonActiveSubscribers():
    ...     print person.displayname
    Celso Providelo
    John Smith
    Foo Bar