~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/launchpad/doc/webapp-publication.txt

Merge db-devel.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
= Launchpad Publication =
 
1
Launchpad Publication
 
2
=====================
2
3
 
3
4
Launchpad uses the generic Zope3 publisher. It registers several
4
5
factories that are responsible for instantiating the appropriate
6
7
zope.publisher.IPublication for the request.
7
8
 
8
9
 
9
 
== Virtual host configurations ==
 
10
Virtual host configurations
 
11
---------------------------
10
12
 
11
13
The configuration defines a number of domains, one for the main
12
14
Launchpad site and one for the sites of the various applications.
118
120
    xmlrpc.launchpad.dev
119
121
 
120
122
 
121
 
== VirtualHostRequestPublicationFactory ==
 
123
VirtualHostRequestPublicationFactory
 
124
------------------------------------
122
125
 
123
126
A number of VirtualHostRequestPublicationFactories are registered with
124
127
Zope to handle requests for a particular vhost, port, and set of HTTP
322
325
    <class 'canonical.launchpad.webapp.publication.LaunchpadBrowserPublication'>
323
326
 
324
327
 
325
 
== Zope Publisher integration ==
 
328
Zope Publisher integration
 
329
--------------------------
326
330
 
327
331
A factory is registered for each of our available virtual host. This
328
332
is done by the register_launchpad_request_publication_factories
417
421
      Allow: POST
418
422
 
419
423
    >>> print_request_and_publication(
420
 
    ...     'xmlrpc.launchpad.dev', method='POST', mime_type='application/xml')
 
424
    ...     'xmlrpc.launchpad.dev', method='POST',
 
425
    ...     mime_type='application/xml')
421
426
    ProtocolErrorRequest
422
427
    ProtocolErrorPublication: status=415
423
428
 
483
488
    >>> login(ANONYMOUS)
484
489
 
485
490
 
486
 
== ILaunchpadBrowserApplicationRequest ==
 
491
ILaunchpadBrowserApplicationRequest
 
492
-----------------------------------
487
493
 
488
494
All Launchpad requests provides the ILaunchpadBrowserApplicationRequest
489
495
interface. That interface is an extension of the zope standard
497
503
    True
498
504
 
499
505
 
500
 
== Handling form data using IBrowserFormNG ==
 
506
Handling form data using IBrowserFormNG
 
507
---------------------------------------
501
508
 
502
509
Submitted form data is available in the form_ng request attribute. This
503
510
is an object providing the IBrowserFormNG interface which offers two
582
589
    items_field
583
590
 
584
591
 
585
 
== Page ID ==
 
592
Page ID
 
593
-------
586
594
 
587
595
Our publication implementation sets a WSGI variable 'launchpad.pageid'.
588
596
This is an identifier of the form ContextName:ViewName.
654
662
    ''
655
663
 
656
664
 
657
 
== Tick counts ==
 
665
Tick counts
 
666
-----------
658
667
 
659
668
Similarly to our page IDs, our publication implementation will store the
660
669
tick counts for the traversal and object publication processes in WSGI
832
841
    >>> login(ANONYMOUS)
833
842
 
834
843
 
835
 
== Transaction Logging ==
 
844
Transaction Logging
 
845
-------------------
836
846
 
837
847
The publication implementation is responsible for putting the name
838
848
of the logged in user in the transaction. (The afterCall() hook is
872
882
     / 16
873
883
 
874
884
 
875
 
== Read-Only Requests ==
 
885
Read-Only Requests
 
886
------------------
876
887
 
877
888
Our publication implementation make sure that requests supposed to be
878
889
read-only (GET and HEAD) don't change anything in the database.
943
954
    Darwin
944
955
 
945
956
 
946
 
=== GET requests on api.launchpad.net ===
 
957
GET requests on api.launchpad.net
 
958
.................................
947
959
 
948
960
In the case of WebServicePublication, though, we have to commit the
949
961
transaction after GET requests in order to persist new entries added to
992
1004
    ...     'api.launchpad.dev', 'GET',
993
1005
    ...     extra_environment=dict(QUERY_STRING=urlencode(form)))
994
1006
    >>> test_request.processInputs()
995
 
    >>> print publication.getPrincipal(test_request).title
 
1007
    >>> publication.getPrincipal(test_request)
996
1008
    Traceback (most recent call last):
997
1009
    ...
998
 
    Unauthorized: Invalid nonce/timestamp: This nonce has been used already.
999
 
 
1000
 
 
1001
 
== Doomed transactions are aborted ==
 
1010
    NonceAlreadyUsed: This nonce has been used already.
 
1011
 
 
1012
 
 
1013
Doomed transactions are aborted
 
1014
-------------------------------
1002
1015
 
1003
1016
Doomed transactions are aborted.
1004
1017
 
1032
1045
    >>> del txn.abort # Clean up test fixture.
1033
1046
 
1034
1047
 
1035
 
== Requests on Python C Methods succeed ==
 
1048
Requests on Python C Methods succeed
 
1049
------------------------------------
1036
1050
 
1037
1051
Rarely but occasionally, it is possible to traverse to a Python C method.
1038
1052
For instance, an XMLRPC proxy might allow a traversal to __repr__.
1048
1062
    '{}'
1049
1063
 
1050
1064
 
1051
 
== HEAD requests have empty body ==
 
1065
HEAD requests have empty body
 
1066
-----------------------------
1052
1067
 
1053
1068
The publication implementation also makes sure that no body is
1054
1069
returned as part of HEAD requests. (Again this is handled by the
1085
1100
    Some boring content.
1086
1101
 
1087
1102
 
1088
 
== Authentication of requests ==
 
1103
Authentication of requests
 
1104
--------------------------
1089
1105
 
1090
1106
In LaunchpadBrowserPublication, authentication happens in the
1091
1107
beforeTraversal() hook. Our publication will set the principal to
1183
1199
    >>> form2 = form.copy()
1184
1200
    >>> form2['oauth_nonce'] = '1764572616e48616d6d65724c61686'
1185
1201
    >>> test_request = LaunchpadTestRequest(form=form2)
1186
 
    >>> print publication.getPrincipal(test_request).title
 
1202
    >>> publication.getPrincipal(test_request)
1187
1203
    Traceback (most recent call last):
1188
1204
    ...
1189
 
    Unauthorized: Expired token...
 
1205
    TokenException: Expired token...
 
1206
 
1190
1207
    >>> access_token.date_expires = now + timedelta(days=1)
1191
1208
    >>> syncUpdate(access_token)
1192
1209
 
1194
1211
    >>> form2['oauth_token'] += 'z'
1195
1212
    >>> form2['oauth_nonce'] = '4572616e48616d6d65724c61686176'
1196
1213
    >>> test_request = LaunchpadTestRequest(form=form2)
1197
 
    >>> print publication.getPrincipal(test_request).title
 
1214
    >>> publication.getPrincipal(test_request)
1198
1215
    Traceback (most recent call last):
1199
1216
    ...
1200
 
    Unauthorized: Unknown access token...
 
1217
    TokenException: Unknown access token...
1201
1218
 
1202
1219
The consumer must be registered as well, and the signature must be
1203
1220
correct.
1205
1222
    >>> form2 = form.copy()
1206
1223
    >>> form2['oauth_consumer_key'] += 'z'
1207
1224
    >>> test_request = LaunchpadTestRequest(form=form2)
1208
 
    >>> print publication.getPrincipal(test_request).title
 
1225
    >>> publication.getPrincipal(test_request)
1209
1226
    Traceback (most recent call last):
1210
1227
    ...
1211
1228
    Unauthorized: Unknown consumer (foobar123451432z).
1214
1231
    >>> form2['oauth_signature'] += 'z'
1215
1232
    >>> form2['oauth_nonce'] = '2616e48616d6d65724c61686176457'
1216
1233
    >>> test_request = LaunchpadTestRequest(form=form2)
1217
 
    >>> print publication.getPrincipal(test_request).title
 
1234
    >>> publication.getPrincipal(test_request)
1218
1235
    Traceback (most recent call last):
1219
1236
    ...
1220
 
    Unauthorized: Invalid signature.
 
1237
    TokenException: Invalid signature.
1221
1238
 
1222
1239
The nonce specified in the request must not have been used before in a request
1223
1240
with this same token and timestamp.
1229
1246
    Guilherme Salgado
1230
1247
 
1231
1248
    >>> test_request = LaunchpadTestRequest(form=form2)
1232
 
    >>> print publication.getPrincipal(test_request).title
 
1249
    >>> publication.getPrincipal(test_request)
1233
1250
    Traceback (most recent call last):
1234
1251
    ...
1235
 
    Unauthorized: Invalid nonce/timestamp: This nonce has been used already.
 
1252
    NonceAlreadyUsed: This nonce has been used already.
1236
1253
 
1237
1254
The timestamp must not be older than TIMESTAMP_ACCEPTANCE_WINDOW from the most
1238
1255
recent request for this token.
1247
1264
 
1248
1265
    >>> form2['oauth_timestamp'] -= 10
1249
1266
    >>> test_request = LaunchpadTestRequest(form=form2)
1250
 
    >>> print publication.getPrincipal(test_request).title
 
1267
    >>> publication.getPrincipal(test_request)
1251
1268
    Traceback (most recent call last):
1252
1269
    ...
1253
 
    Unauthorized: Invalid nonce/timestamp: Timestamp too old compared ...
 
1270
    TimestampOrderingError: Timestamp too old compared ...
1254
1271
 
1255
1272
Last but not least, the timestamp must not be too far in the future, as
1256
1273
defined by TIMESTAMP_SKEW_WINDOW.
1260
1277
    >>> form2 = form.copy()
1261
1278
    >>> form2['oauth_timestamp'] += (TIMESTAMP_SKEW_WINDOW+10)
1262
1279
    >>> test_request = LaunchpadTestRequest(form=form2)
1263
 
    >>> print publication.getPrincipal(test_request).title
 
1280
    >>> publication.getPrincipal(test_request)
1264
1281
    Traceback (most recent call last):
1265
1282
    ...
1266
 
    Unauthorized: Invalid nonce/timestamp: Timestamp ... from bad system clock
 
1283
    ClockSkew: Timestamp ... from bad system clock
1267
1284
 
1268
1285
Close the bogus request that was started by the call to
1269
1286
beforeTraversal, in order to ensure we leave our state sane.