Merge lp:~canonical-isd-hackers/canonical-identity-provider/compatible-piston into lp:canonical-identity-provider/release
- compatible-piston
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Ricardo Kirkner |
Approved revision: | no longer in the source branch. |
Merged at revision: | 151 |
Proposed branch: | lp:~canonical-isd-hackers/canonical-identity-provider/compatible-piston |
Merge into: | lp:canonical-identity-provider/release |
Diff against target: |
3535 lines (+2090/-1018) 30 files modified
debian/control (+1/-10) django_project/config.example/settings.py (+1/-7) doctests/stories/api-authentications.txt (+4/-4) doctests/stories/api-workflows.txt (+5/-2) identityprovider/api10/decorators.py (+36/-0) identityprovider/api10/forms.py (+55/-0) identityprovider/api10/handlers.py (+410/-0) identityprovider/api10/urls.py (+41/-0) identityprovider/auth.py (+26/-0) identityprovider/models/account.py (+0/-14) identityprovider/preflight.py (+2/-2) identityprovider/store.py (+35/-0) identityprovider/templates/wadl1.0.xml (+1378/-0) identityprovider/tests/test_forms.py (+4/-10) identityprovider/tests/test_handlers.py (+79/-45) identityprovider/tests/test_models_account.py (+0/-11) identityprovider/tests/test_models_api.py (+0/-21) identityprovider/tests/test_wsgi.py (+0/-71) identityprovider/urls.py (+5/-0) identityprovider/webservice/__init__.py (+0/-2) identityprovider/webservice/forms.py (+0/-65) identityprovider/webservice/interfaces.py (+0/-191) identityprovider/webservice/models.py (+0/-389) identityprovider/webservice/site.zcml (+0/-15) identityprovider/wsgi.py (+1/-84) mockservice/sso_mockserver/mockserver.py (+5/-5) payload/__init__.py (+0/-8) requirements.txt (+0/-53) scripts/create_env (+1/-1) setup.py (+1/-8) |
To merge this branch: | bzr merge lp:~canonical-isd-hackers/canonical-identity-provider/compatible-piston |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Łukasz Czyżykowski (community) | Needs Information | ||
Canonical ISD hackers | Pending | ||
Review via email: mp+50668@code.launchpad.net |
Commit message
Implement a lazr.restfulcli
Description of the change
Overview
========
This branch implements a backwards-
Details
=======
The lazr.restful dependency is removed, and zope.interface is now only needed for running the tests, as zope.testbrowser is still used.
SSO's test suite was changed as little as possible for this branch, to ensure backwards-
This branch has been tested interactively using lazr.restfulclient. Ideally we should also test our main higher-level clients (like the desktop ussoc, or mumble's sso client) during qa.
Anthony Lenton (elachuni) wrote : | # |
Łukasz Czyżykowski (lukasz-czyzykowski) wrote : | # |
How do you imagine fixing API in the future taking into the account that from now on the WADL file will have to be modified by hand? Or is this API set in stone and no signature will ever change (of course, besides moving to new version)?
Anthony Lenton (elachuni) wrote : | # |
> How do you imagine fixing API in the future taking into the account that from
> now on the WADL file will have to be modified by hand? Or is this API set in
> stone and no signature will ever change (of course, besides moving to new
> version)?
Yup, next branch should add a 2.0 api (in real Piston style) that can be more easily extended in the future.
Anthony Lenton (elachuni) wrote : | # |
Merged in latest changes for trunk. Requesting a new review.
ISD Branch Mangler (isd-branches-mangler) wrote : | # |
There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.
Preview Diff
1 | === modified file 'debian/control' |
2 | --- debian/control 2011-03-07 22:20:21 +0000 |
3 | +++ debian/control 2011-05-24 16:31:52 +0000 |
4 | @@ -20,15 +20,6 @@ |
5 | python-django-oauth-backend (= 0.1.dev1), |
6 | python-django-preflight (>= 0.1), |
7 | python-django-preflight (<< 0.2), |
8 | - python-django-settings (>= 0.2), |
9 | python-memcache, |
10 | - python-lazr.restful (>= 0.9.18), |
11 | - python-lazr.authentication, |
12 | - python-docutils, |
13 | - python-epydoc, |
14 | - python-restrictedpython, |
15 | - python-schemaconfig (>= 0.1.3.dev1), |
16 | - python-simplejson, |
17 | - python-lazr.uri |
18 | -Conflicts: python-lazr.restful (>= 0.9.22) |
19 | + python-simplejson |
20 | Description: Canonical OpenID provider |
21 | |
22 | === modified file 'django_project/config.example/settings.py' |
23 | --- django_project/config.example/settings.py 2010-08-04 22:13:07 +0000 |
24 | +++ django_project/config.example/settings.py 2011-05-24 16:31:52 +0000 |
25 | @@ -41,17 +41,11 @@ |
26 | # api settings |
27 | API_ENABLED = False |
28 | API_HOST = 'openid.localdomain' |
29 | +OAUTH_DATA_STORE = 'identityprovider.store.SSODataStore' |
30 | APP_SERVERS = [{'SERVER_ID': 'localhost', |
31 | 'HOST': 'localhost', |
32 | 'PORT': 8000, |
33 | }] |
34 | -LAZR_RESTFUL_CODE_REVISION = '1' |
35 | -LAZR_RESTFUL_HOSTNAME = 'sso' |
36 | -LAZR_RESTFUL_PATH_OVERRIDE = None |
37 | -LAZR_RESTFUL_SERVICE_ROOT_URI_PREFIX = 'api/' |
38 | -LAZR_RESTFUL_SERVICE_VERSION_URI_PREFIX = '1.0' |
39 | -LAZR_RESTFUL_USE_HTTPS = False |
40 | -LAZR_RESTFUL_VIEW_PERMISSION = 'zope.Public' |
41 | |
42 | # branding settings |
43 | BRANDED_TEMPLATE_DIR = 'ubuntu' |
44 | |
45 | === modified file 'doctests/stories/api-authentications.txt' |
46 | --- doctests/stories/api-authentications.txt 2011-01-24 18:28:24 +0000 |
47 | +++ doctests/stories/api-authentications.txt 2011-05-24 16:31:52 +0000 |
48 | @@ -120,19 +120,19 @@ |
49 | >>> api.authentications.list_tokens(consumer_key='myopenid') |
50 | Traceback (most recent call last): |
51 | ... |
52 | - HTTPError: HTTP Error 403: Forbidden |
53 | + HTTPError: HTTP Error 403: FORBIDDEN |
54 | ... |
55 | >>> api.authentications.invalidate_token(consumer_key='myopenid', |
56 | ... token='mytoken') |
57 | Traceback (most recent call last): |
58 | ... |
59 | - HTTPError: HTTP Error 403: Forbidden |
60 | + HTTPError: HTTP Error 403: FORBIDDEN |
61 | ... |
62 | >>> api.authentications.validate_token(consumer_key='myopenid', |
63 | ... token='mytoken') |
64 | Traceback (most recent call last): |
65 | ... |
66 | - HTTPError: HTTP Error 403: Forbidden |
67 | + HTTPError: HTTP Error 403: FORBIDDEN |
68 | ... |
69 | |
70 | Test When OAuth Auth Credentials For Normal Users Are Supplied |
71 | @@ -218,5 +218,5 @@ |
72 | >>> api.authentications.authenticate(token_name='this-machine') |
73 | Traceback (most recent call last): |
74 | ... |
75 | - HTTPError: HTTP Error 403: Forbidden |
76 | + HTTPError: HTTP Error 403: FORBIDDEN |
77 | ... |
78 | |
79 | === modified file 'doctests/stories/api-workflows.txt' |
80 | --- doctests/stories/api-workflows.txt 2011-03-14 15:56:30 +0000 |
81 | +++ doctests/stories/api-workflows.txt 2011-05-24 16:31:52 +0000 |
82 | @@ -46,8 +46,11 @@ |
83 | the human being: |
84 | |
85 | >>> captcha = api.captchas.new() |
86 | - >>> captcha |
87 | - {u'captcha_id': u'...', u'image_url': u'https://api-secure.recaptcha.net/image?c=...'} |
88 | + >>> sorted(captcha.keys()) |
89 | + [u'captcha_id', u'image_url'] |
90 | + >>> prefix = u'https://api-secure.recaptcha.net/image?c=' |
91 | + >>> captcha['image_url'].startswith(prefix) |
92 | + True |
93 | |
94 | As you can see captcha is dictionary with two fields: ``captcha_id`` |
95 | which you need to pass to registration function and ``image_url`` which |
96 | |
97 | === added directory 'identityprovider/api10' |
98 | === added file 'identityprovider/api10/__init__.py' |
99 | === added file 'identityprovider/api10/decorators.py' |
100 | --- identityprovider/api10/decorators.py 1970-01-01 00:00:00 +0000 |
101 | +++ identityprovider/api10/decorators.py 2011-05-24 16:31:52 +0000 |
102 | @@ -0,0 +1,36 @@ |
103 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
104 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
105 | + |
106 | +from functools import wraps |
107 | + |
108 | +from django.http import HttpResponseForbidden |
109 | + |
110 | +from identityprovider.models import ( |
111 | + Account, |
112 | + APIUser, |
113 | +) |
114 | + |
115 | +def api_user_required(func): |
116 | + @wraps(func) |
117 | + def wrapper(self, request, *args, **kwargs): |
118 | + user = request.user |
119 | + if user: |
120 | + if isinstance(user, APIUser): |
121 | + return func(self, request, *args, **kwargs) |
122 | + return HttpResponseForbidden('403 Forbidden') |
123 | + return wrapper |
124 | + |
125 | + |
126 | +def plain_user_required(func): |
127 | + @wraps(func) |
128 | + def wrapper(self, request, *args, **kwargs): |
129 | + user = request.user |
130 | + if user: |
131 | + if isinstance(user, Account): |
132 | + return func(self, request, *args, **kwargs) |
133 | + return HttpResponseForbidden('403 Forbidden') |
134 | + return wrapper |
135 | + |
136 | +def named_operation(func): |
137 | + func.is_named_operation = True |
138 | + return func |
139 | |
140 | === added file 'identityprovider/api10/forms.py' |
141 | --- identityprovider/api10/forms.py 1970-01-01 00:00:00 +0000 |
142 | +++ identityprovider/api10/forms.py 2011-05-24 16:31:52 +0000 |
143 | @@ -0,0 +1,55 @@ |
144 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
145 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
146 | + |
147 | +# We use Django forms for webservice input validation |
148 | + |
149 | +from django import forms |
150 | +from django.forms import fields |
151 | +from django.utils.translation import ugettext as _ |
152 | + |
153 | +from identityprovider.utils import password_policy_compliant |
154 | +from identityprovider.models.captcha import Captcha, VerifyCaptchaError |
155 | + |
156 | + |
157 | +PASSWORD_POLICY_ERROR = _("Password must be at least " |
158 | + "8 characters long, and must contain at least one " |
159 | + "number and an upper case letter.") |
160 | + |
161 | + |
162 | +class WebserviceCreateAccountForm(forms.Form): |
163 | + email = fields.EmailField() |
164 | + password = fields.CharField(max_length=256) |
165 | + captcha_id = fields.CharField(max_length=1024) |
166 | + captcha_solution = fields.CharField(max_length=256) |
167 | + remote_ip = fields.CharField(max_length=256) |
168 | + displayname = fields.CharField(max_length=256, required=False) |
169 | + |
170 | + def clean_password(self): |
171 | + if 'password' in self.cleaned_data: |
172 | + password = self.cleaned_data['password'] |
173 | + try: |
174 | + str(password) |
175 | + except UnicodeEncodeError: |
176 | + raise forms.ValidationError( |
177 | + _("Invalid characters in password")) |
178 | + if not password_policy_compliant(password): |
179 | + raise forms.ValidationError(PASSWORD_POLICY_ERROR) |
180 | + return password |
181 | + |
182 | + def clean(self): |
183 | + cleaned_data = self.cleaned_data |
184 | + captcha_id = cleaned_data.get('captcha_id') |
185 | + captcha_solution = cleaned_data.get('captcha_solution') |
186 | + |
187 | + # The remote IP address is absolutely required, and comes from |
188 | + # SSO itself, not from the client. If it's missing, it's a |
189 | + # programming error, and should not be returned to the client |
190 | + # as a validation error. So, we use a normal key lookup here. |
191 | + remote_ip = cleaned_data['remote_ip'] |
192 | + |
193 | + captcha = Captcha(captcha_id) |
194 | + |
195 | + if captcha.verify(captcha_solution, remote_ip): |
196 | + return cleaned_data |
197 | + # not verified |
198 | + raise forms.ValidationError(_("Wrong captcha solution.")) |
199 | |
200 | === added file 'identityprovider/api10/handlers.py' |
201 | --- identityprovider/api10/handlers.py 1970-01-01 00:00:00 +0000 |
202 | +++ identityprovider/api10/handlers.py 2011-05-24 16:31:52 +0000 |
203 | @@ -0,0 +1,410 @@ |
204 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
205 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
206 | + |
207 | +from django.conf import settings |
208 | +from django.core.serializers.json import DateTimeAwareJSONEncoder |
209 | +from django.http import HttpResponseBadRequest |
210 | +from django.shortcuts import render_to_response |
211 | +from django.utils import simplejson |
212 | +from piston.emitters import Emitter |
213 | +from piston.handler import BaseHandler |
214 | + |
215 | +from identityprovider.api10.decorators import ( |
216 | + api_user_required, |
217 | + plain_user_required, |
218 | + named_operation, |
219 | +) |
220 | +from identityprovider.api10.forms import ( |
221 | + PASSWORD_POLICY_ERROR, |
222 | + WebserviceCreateAccountForm, |
223 | +) |
224 | +from identityprovider.models import ( |
225 | + EmailAddress, |
226 | + Account, |
227 | + AccountPassword, |
228 | + AuthToken, |
229 | + AuthTokenFactory, |
230 | +) |
231 | +from identityprovider.models.const import ( |
232 | + AccountCreationRationale, |
233 | + AccountStatus, |
234 | + EmailStatus, |
235 | + LoginTokenType, |
236 | +) |
237 | +from identityprovider.models.captcha import ( |
238 | + Captcha, |
239 | + NewCaptchaError, |
240 | +) |
241 | +from identityprovider.views.server import get_team_memberships |
242 | +from identityprovider.signals import ( |
243 | + account_created, |
244 | + account_email_validated, |
245 | + application_token_created, |
246 | + application_token_invalidated, |
247 | +) |
248 | +from identityprovider.utils import ( |
249 | + CannotResetPasswordException, |
250 | + encrypt_launchpad_password, |
251 | + get_person_and_account_by_email, |
252 | + password_policy_compliant, |
253 | + PersonAndAccountNotFoundException, |
254 | +) |
255 | +from oauth_backend.models import Token |
256 | + |
257 | + |
258 | +class CanNotResetPasswordError(Exception): |
259 | + pass |
260 | + |
261 | + |
262 | +class LazrRestfulEmitter(Emitter): |
263 | + """JSON emitter, lazr.restful flavoured. |
264 | + |
265 | + Reports content-type 'application/json', without specifying a charset |
266 | + as that seems to confuse lazr.restfulclient. |
267 | + """ |
268 | + def render(self, request): |
269 | + seria = simplejson.dumps(self.construct(), |
270 | + cls=DateTimeAwareJSONEncoder, ensure_ascii=False, indent=4) |
271 | + |
272 | + return seria |
273 | +Emitter.register('lazr.restful', LazrRestfulEmitter, 'application/json') |
274 | + |
275 | +class LazrRestfulHandler(BaseHandler): |
276 | + allowed_methods = ('GET', 'POST') |
277 | + |
278 | + response = {} |
279 | + |
280 | + baseurl = settings.SSO_ROOT_URL.strip('/') |
281 | + |
282 | + def __init__(self): |
283 | + self.response = self.response.copy() |
284 | + for key, value in self.response.items(): |
285 | + if key.endswith('_link'): |
286 | + self.response[key] = value % self.baseurl |
287 | + |
288 | + def read(self, request): |
289 | + if not 'ws.op' in request.GET: |
290 | + return self.response |
291 | + return self.named_operation(request, request.GET) |
292 | + |
293 | + def create(self, request): |
294 | + if not 'ws.op' in request.POST: |
295 | + return HttpResponseBadRequest('No operation name given.') |
296 | + return self.named_operation(request, request.POST) |
297 | + |
298 | + def named_operation(self, request, serialized): |
299 | + method_name = serialized['ws.op'] |
300 | + method = getattr(self, method_name, None) |
301 | + is_named_operation = getattr(method, 'is_named_operation', False) |
302 | + if not is_named_operation: |
303 | + return HttpResponseBadRequest('No such operation: %s' % |
304 | + method_name) |
305 | + request.data = self.lazr_restful_deserialize(serialized) |
306 | + return method(request) |
307 | + |
308 | + def lazr_restful_deserialize(self, serialized): |
309 | + data = {} |
310 | + for key in serialized: |
311 | + if key == 'ws.op': |
312 | + continue |
313 | + data[key] = simplejson.loads(serialized[key]) |
314 | + return data |
315 | + |
316 | + |
317 | +class RootHandler(LazrRestfulHandler): |
318 | + allowed_methods = ('GET',) |
319 | + |
320 | + response = { |
321 | + "registrations_collection_link": "%s/api/1.0/registration", |
322 | + "captchas_collection_link": "%s/api/1.0/captchas", |
323 | + "validations_collection_link": "%s/api/1.0/validation", |
324 | + "authentications_collection_link": "%s/api/1.0/authentications", |
325 | + "resource_type_link": "%s/api/1.0/#service-root", |
326 | + "accounts_collection_link": "%s/api/1.0/accounts", |
327 | + } |
328 | + |
329 | + def read(self, request): |
330 | + if ('application/vd.sun.wadl+xml' in request.META['HTTP_ACCEPT'] or |
331 | + 'application/vnd.sun.wadl+xml' in request.META['HTTP_ACCEPT']): |
332 | + context = {'baseurl': self.baseurl} |
333 | + return render_to_response('wadl1.0.xml', context) |
334 | + else: |
335 | + return self.response |
336 | + |
337 | +class CaptchaHandler(LazrRestfulHandler): |
338 | + response = { |
339 | + "total_size": 0, |
340 | + "start": None, |
341 | + "resource_type_link": "%s/api/1.0/#captchas", |
342 | + "entries": [] |
343 | + } |
344 | + |
345 | + @named_operation |
346 | + def new(self, request): |
347 | + try: |
348 | + return Captcha.new().serialize() |
349 | + except NewCaptchaError, e: |
350 | + request.environ['oops-dump'] = True |
351 | + logging.warning(e.traceback) |
352 | + logging.warning("Failed to connect to reCaptcha server") |
353 | + # TODO: Some better return here? |
354 | + return e.dummy |
355 | + |
356 | + |
357 | +class RegistrationHandler(LazrRestfulHandler): |
358 | + response = { |
359 | + "total_size": 0, |
360 | + "start": None, |
361 | + "resource_type_link": "%s/api/1.0/#registrations", |
362 | + "entries": [] |
363 | + } |
364 | + |
365 | + @named_operation |
366 | + def register(self, request): |
367 | + data = request.data |
368 | + data['remote_ip'] = request.environ['REMOTE_ADDR'] |
369 | + form = WebserviceCreateAccountForm(data) |
370 | + try: |
371 | + if not form.is_valid(): |
372 | + errors = dict((k, map(unicode, v)) |
373 | + for (k, v) in form.errors.items()) |
374 | + result = {'status': 'error', 'errors': errors} |
375 | + return result |
376 | + except VerifyCaptchaError, e: |
377 | + request.environment['oops-dump'] = True |
378 | + logging.warning(e.traceback) |
379 | + logging.warning("reCaptcha connection error") |
380 | + return {'status': 'error', 'errors': |
381 | + {'captcha_solution': [ |
382 | + 'Unable to verify captcha. Please try again shortly.']}} |
383 | + |
384 | + cleaned_data = form.cleaned_data |
385 | + requested_email = cleaned_data['email'] |
386 | + emails = EmailAddress.objects.filter(email__iexact=requested_email) |
387 | + if len(emails) > 0: |
388 | + return {'status': 'error', 'errors': |
389 | + {'email': ['Email already registered']}} |
390 | + |
391 | + account = Account.objects.create( |
392 | + creation_rationale=AccountCreationRationale.OWNER_CREATED_LAUNCHPAD, |
393 | + status=AccountStatus.ACTIVE, |
394 | + displayname=cleaned_data['displayname']) |
395 | + |
396 | + account.emailaddress_set.create( |
397 | + email=cleaned_data['email'], |
398 | + status=EmailStatus.NEW) |
399 | + |
400 | + AccountPassword.objects.create( |
401 | + password=encrypt_launchpad_password(cleaned_data['password']), |
402 | + account=account) |
403 | + |
404 | + token = AuthTokenFactory().new_api_email_validation_token( |
405 | + account, cleaned_data['email']) |
406 | + |
407 | + token.sendNewUserEmail('api-newuser.txt') |
408 | + |
409 | + account_created.send(sender=self, |
410 | + openid_identifier=account.openid_identifier) |
411 | + |
412 | + return { |
413 | + 'status': 'ok', |
414 | + 'message': "Email verification required." |
415 | + } |
416 | + |
417 | + @named_operation |
418 | + def request_password_reset_token(self, request): |
419 | + data = request.data |
420 | + email = data['email'] |
421 | + error = False |
422 | + try: |
423 | + person, account = get_person_and_account_by_email(email) |
424 | + except (CannotResetPasswordException, |
425 | + PersonAndAccountNotFoundException): |
426 | + error = True |
427 | + |
428 | + if error or (account is not None and not account.can_reset_password): |
429 | + raise CanNotResetPasswordError( |
430 | + "Can't reset password for this account") |
431 | + |
432 | + token = AuthTokenFactory().new(account, email, email, |
433 | + LoginTokenType.PASSWORDRECOVERY, None) |
434 | + |
435 | + token.sendPasswordResetEmail() |
436 | + |
437 | + return { |
438 | + 'status': 'ok', |
439 | + 'message': "Password reset token sent." |
440 | + } |
441 | + |
442 | + @named_operation |
443 | + def set_new_password(self, request): |
444 | + data = request.data |
445 | + new_password = data['new_password'] |
446 | + token = AuthToken.objects.get( |
447 | + email=data['email'], token=data['token'], |
448 | + token_type=LoginTokenType.PASSWORDRECOVERY) |
449 | + if not token.requester: |
450 | + token.delete() |
451 | + return { |
452 | + 'status': 'error', |
453 | + 'message': "Wrong token, request new one." |
454 | + } |
455 | + if not password_policy_compliant(new_password): |
456 | + return { |
457 | + 'status': 'error', |
458 | + 'errors': [PASSWORD_POLICY_ERROR] |
459 | + } |
460 | + password_obj = token.requester.accountpassword |
461 | + password_obj.password = encrypt_launchpad_password(new_password) |
462 | + password_obj.save() |
463 | + |
464 | + token.consume() |
465 | + |
466 | + return { |
467 | + 'status': 'ok', |
468 | + 'message': "Password changed" |
469 | + } |
470 | + |
471 | + |
472 | +def _serialize_account(user): |
473 | + emails = EmailAddress.objects.filter(account=user, |
474 | + status=EmailStatus.VALIDATED) |
475 | + preferred_email = user.preferredemail |
476 | + if preferred_email is not None: |
477 | + preferred_email = preferred_email.email |
478 | + |
479 | + if user.person: |
480 | + username = user.person.name |
481 | + else: |
482 | + username = user.openid_identifier |
483 | + |
484 | + return { |
485 | + 'username': username, |
486 | + 'displayname': user.displayname, |
487 | + 'openid_identifier': user.openid_identifier, |
488 | + 'preferred_email': preferred_email, |
489 | + 'verified_emails': [e.email for e in emails], |
490 | + 'unverified_emails': [e.email for e in user.unverified_emails()], |
491 | + } |
492 | + |
493 | + |
494 | +class AuthenticationHandler(LazrRestfulHandler): |
495 | + """ All these methods assume that they're run behind Basic Auth """ |
496 | + response = { |
497 | + "total_size": 0, |
498 | + "start": None, |
499 | + "resource_type_link": "%s/api/1.0/#authentications", |
500 | + "entries": [] |
501 | + } |
502 | + |
503 | + @plain_user_required |
504 | + @named_operation |
505 | + def authenticate(self, request): |
506 | + data = request.data |
507 | + account = request.user |
508 | + token = account.create_oauth_token(data['token_name']) |
509 | + application_token_created.send( |
510 | + sender=self, openid_identifier=account.openid_identifier) |
511 | + return token.serialize() |
512 | + |
513 | + @api_user_required |
514 | + @named_operation |
515 | + def list_tokens(self, request): |
516 | + data = request.data |
517 | + tokens = Token.objects.filter( |
518 | + consumer__user__username=data['consumer_key']) |
519 | + result = [{'token': t.token, 'name': t.name} for t in tokens] |
520 | + return result |
521 | + |
522 | + @api_user_required |
523 | + @named_operation |
524 | + def validate_token(self, request): |
525 | + data = request.data |
526 | + try: |
527 | + token = Token.objects.get( |
528 | + consumer__user__username=data['consumer_key'], |
529 | + token=data['token']) |
530 | + return token.serialize() |
531 | + except Token.DoesNotExist: |
532 | + return False |
533 | + |
534 | + @api_user_required |
535 | + @named_operation |
536 | + def invalidate_token(self, request): |
537 | + data = request.data |
538 | + tokens = Token.objects.filter(token=data['token'], |
539 | + consumer__user__username=data['consumer_key']) |
540 | + tokens.delete() |
541 | + application_token_invalidated.send( |
542 | + sender=self, openid_identifier=data['consumer_key']) |
543 | + |
544 | + |
545 | + @api_user_required |
546 | + @named_operation |
547 | + def team_memberships(self, request): |
548 | + data = request.data |
549 | + accounts = Account.objects.filter( |
550 | + openid_identifier=data['openid_identifier']) |
551 | + accounts = list(accounts) |
552 | + |
553 | + if len(accounts) == 1: |
554 | + account = accounts[0] |
555 | + memberships = get_team_memberships(data['team_names'], |
556 | + account, False) |
557 | + return memberships |
558 | + else: |
559 | + return [] |
560 | + |
561 | + @api_user_required |
562 | + @named_operation |
563 | + def account_by_email(self, request): |
564 | + data = request.data |
565 | + account = Account.objects.get_by_email(data['email']) |
566 | + if account: |
567 | + return _serialize_account(account) |
568 | + else: |
569 | + return None |
570 | + |
571 | + @api_user_required |
572 | + @named_operation |
573 | + def account_by_openid(self, request): |
574 | + data = request.data |
575 | + try: |
576 | + account = Account.objects.get(openid_identifier=data['openid']) |
577 | + except Account.DoesNotExist: |
578 | + return None |
579 | + else: |
580 | + return _serialize_account(account) |
581 | + |
582 | + |
583 | +class AccountsHandler(LazrRestfulHandler): |
584 | + @named_operation |
585 | + def me(self, request): |
586 | + account = request.user |
587 | + return _serialize_account(account) |
588 | + |
589 | + @named_operation |
590 | + def team_memberships(self, request): |
591 | + team_names = request.data['team_names'] |
592 | + memberships = get_team_memberships(team_names, request.user, True) |
593 | + return memberships |
594 | + |
595 | + @named_operation |
596 | + def validate_email(self, request): |
597 | + email_token = request.data['email_token'] |
598 | + try: |
599 | + token = request.user.authtoken_set.get( |
600 | + token=email_token, token_type=LoginTokenType.VALIDATEEMAIL) |
601 | + |
602 | + email = EmailAddress.objects.get(email__iexact=token.email) |
603 | + email.status = EmailStatus.VALIDATED |
604 | + email.save() |
605 | + |
606 | + token.consume() |
607 | + |
608 | + account_email_validated.send( |
609 | + openid_identifier=request.user.openid_identifier, |
610 | + sender=self) |
611 | + return {'email': email.email} |
612 | + except AuthToken.DoesNotExist: |
613 | + return {'errors': {'email_token': ["Bad email token!"]}} |
614 | |
615 | === added file 'identityprovider/api10/urls.py' |
616 | --- identityprovider/api10/urls.py 1970-01-01 00:00:00 +0000 |
617 | +++ identityprovider/api10/urls.py 2011-05-24 16:31:52 +0000 |
618 | @@ -0,0 +1,41 @@ |
619 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
620 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
621 | + |
622 | +from django.conf.urls.defaults import patterns, url |
623 | + |
624 | +from piston.authentication import HttpBasicAuthentication |
625 | +from piston.resource import Resource |
626 | + |
627 | +from identityprovider.api10.handlers import ( |
628 | + AccountsHandler, |
629 | + AuthenticationHandler, |
630 | + CaptchaHandler, |
631 | + RegistrationHandler, |
632 | + RootHandler, |
633 | +) |
634 | +from identityprovider.auth import ( |
635 | + basic_authenticate, |
636 | + SSOOAuthAuthentication, |
637 | +) |
638 | + |
639 | +root_resource = Resource(handler=RootHandler) |
640 | +captcha_resource = Resource(handler=CaptchaHandler) |
641 | +registration_resource = Resource(handler=RegistrationHandler) |
642 | +authentication_resource = Resource(handler=AuthenticationHandler, |
643 | + authentication=HttpBasicAuthentication(auth_func=basic_authenticate)) |
644 | +accounts_resource = Resource(handler=AccountsHandler, |
645 | + authentication=SSOOAuthAuthentication()) |
646 | + |
647 | +urlpatterns = patterns('', |
648 | + url(r'^$', root_resource, |
649 | + kwargs={'emitter_format': 'lazr.restful'}), |
650 | + url(r'^captchas$', captcha_resource, |
651 | + kwargs={'emitter_format': 'lazr.restful'}), |
652 | + url(r'^registration$', registration_resource, |
653 | + kwargs={'emitter_format': 'lazr.restful'}), |
654 | + url(r'^authentications$', authentication_resource, |
655 | + kwargs={'emitter_format': 'lazr.restful'}), |
656 | + url(r'^accounts$', accounts_resource, |
657 | + kwargs={'emitter_format': 'lazr.restful'}), |
658 | +) |
659 | + |
660 | |
661 | === modified file 'identityprovider/auth.py' |
662 | --- identityprovider/auth.py 2011-03-04 17:09:45 +0000 |
663 | +++ identityprovider/auth.py 2011-05-24 16:31:52 +0000 |
664 | @@ -2,9 +2,11 @@ |
665 | # GNU Affero General Public License version 3 (see the file LICENSE). |
666 | |
667 | from datetime import datetime |
668 | +from django.http import HttpResponse |
669 | |
670 | from django.conf import settings |
671 | from oauth_backend.models import Consumer, Token |
672 | +from piston.authentication import OAuthAuthentication |
673 | |
674 | from identityprovider.models import Account, AccountPassword, EmailAddress |
675 | from identityprovider.models.api import APIUser |
676 | @@ -66,6 +68,30 @@ |
677 | return user |
678 | |
679 | |
680 | +class SSOOAuthAuthentication(OAuthAuthentication): |
681 | + def is_authenticated(self, request): |
682 | + if not self.is_valid_request(request): |
683 | + return False |
684 | + try: |
685 | + consumer, token, parameters = self.validate_token(request) |
686 | + except oauth.OAuthError, err: |
687 | + return False |
688 | + if not (consumer and token): |
689 | + return False |
690 | + # only allow authentication if account is active |
691 | + account = Account.objects.get(openid_identifier=consumer.key) |
692 | + if not account.is_active: |
693 | + return False |
694 | + request.user = account |
695 | + request.throttle_extra = token.consumer.id |
696 | + return True |
697 | + |
698 | + def challenge(self): |
699 | + resp = HttpResponse("Authorization Required") |
700 | + resp['WWW-Authenticate'] = 'OAuth realm="%s"' % self.realm |
701 | + resp.status_code = 401 |
702 | + return resp |
703 | + |
704 | def oauth_authenticate(oauth_consumer, oauth_token, parameters): |
705 | """Currently only checks that given consumer and token are in database""" |
706 | try: |
707 | |
708 | === modified file 'identityprovider/models/account.py' |
709 | --- identityprovider/models/account.py 2011-05-11 15:29:56 +0000 |
710 | +++ identityprovider/models/account.py 2011-05-24 16:31:52 +0000 |
711 | @@ -8,7 +8,6 @@ |
712 | from django.contrib.auth.models import User |
713 | from django.db import models |
714 | from django.utils.translation import ugettext_lazy as _ |
715 | -from zope.interface import classImplements |
716 | |
717 | from oauth_backend.models import Consumer |
718 | |
719 | @@ -18,7 +17,6 @@ |
720 | encrypt_launchpad_password, |
721 | generate_openid_identifier, |
722 | is_django_13) |
723 | -from identityprovider.webservice.interfaces import IAccount |
724 | |
725 | __all__ = ( |
726 | 'Account', |
727 | @@ -293,18 +291,6 @@ |
728 | # now go and effectively save it |
729 | super(Account, self).save(force_insert, force_update, **kwargs) |
730 | |
731 | - # IDjangoLocation implementation |
732 | - @property |
733 | - def __parent__(self): |
734 | - from identityprovider.webservice.models import AccountSet |
735 | - return AccountSet() |
736 | - |
737 | - @property |
738 | - def __url_path__(self): |
739 | - return str(self.id) |
740 | - |
741 | -classImplements(Account, IAccount) |
742 | - |
743 | |
744 | class AccountPassword(models.Model): |
745 | account = models.OneToOneField(Account, db_column='account') |
746 | |
747 | === modified file 'identityprovider/preflight.py' |
748 | --- identityprovider/preflight.py 2011-03-07 23:22:00 +0000 |
749 | +++ identityprovider/preflight.py 2011-05-24 16:31:52 +0000 |
750 | @@ -21,13 +21,13 @@ |
751 | return internal_authorize(request.user) |
752 | |
753 | def versions(self): |
754 | - import lazr |
755 | import openid |
756 | import identityprovider |
757 | + import piston.utils |
758 | return [ |
759 | {'name': 'SSO', 'version': identityprovider.__version__}, |
760 | - {'name': 'lazr.restful', 'version': lazr.restful.__version__}, |
761 | {'name': 'openid', 'version': openid.__version__}, |
762 | + {'name': 'piston', 'version': piston.utils.get_version()}, |
763 | ] |
764 | |
765 | |
766 | |
767 | === added file 'identityprovider/store.py' |
768 | --- identityprovider/store.py 1970-01-01 00:00:00 +0000 |
769 | +++ identityprovider/store.py 2011-05-24 16:31:52 +0000 |
770 | @@ -0,0 +1,35 @@ |
771 | +from oauth.oauth import OAuthToken |
772 | +from oauth_backend.models import Token, DataStore |
773 | + |
774 | + |
775 | +class SSODataStore(DataStore): |
776 | + def __init__(self, oauth_request=None): |
777 | + """To serve as a Piston datastore we'll need provide this signature. |
778 | + |
779 | + We later won't use the oauth_request, so we can ignore it here. |
780 | + """ |
781 | + super(SSODataStore, self).__init__() |
782 | + |
783 | + def lookup_token(self, token_type, token_field): |
784 | + """ |
785 | + :param token_type: type of token to lookup |
786 | + :param token_field: token to look up |
787 | + |
788 | + :note: token_type should always be 'access' as only such tokens are |
789 | + stored in database |
790 | + |
791 | + :returns: OAuthToken object |
792 | + """ |
793 | + assert token_type == 'access' |
794 | + |
795 | + try: |
796 | + token = Token.objects.get(token=token_field) |
797 | + # Piston expects OAuth tokens to have 'consumer' and 'user' atts. |
798 | + # (see piston.authentication.OAuthAuthentication.is_authenticated) |
799 | + oauthtoken = OAuthToken(token.token, token.token_secret) |
800 | + oauthtoken.consumer = token.consumer |
801 | + oauthtoken.user = token.consumer.user |
802 | + return oauthtoken |
803 | + except Token.DoesNotExist: |
804 | + return None |
805 | + |
806 | |
807 | === added file 'identityprovider/templates/wadl1.0.xml' |
808 | --- identityprovider/templates/wadl1.0.xml 1970-01-01 00:00:00 +0000 |
809 | +++ identityprovider/templates/wadl1.0.xml 2011-05-24 16:31:52 +0000 |
810 | @@ -0,0 +1,1378 @@ |
811 | +<?xml version="1.0"?> |
812 | +<!DOCTYPE application [ |
813 | + <!ENTITY nbsp "\ "> |
814 | +]> |
815 | +<wadl:application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
816 | + xmlns="http://research.sun.com/wadl/2006/10" |
817 | + xmlns:xsd="http://www.w3.org/2001/XMLSchema" |
818 | + xmlns:wadl="http://research.sun.com/wadl/2006/10" |
819 | + xsi:schemaLocation="http://research.sun.com/wadl/2006/10/wadl.xsd"> |
820 | + |
821 | + <!--There is one "service root" resource, located (as you'd expect) |
822 | + at the service root. This very document is the WADL |
823 | + representation of the "service root" resource.--> |
824 | + <wadl:resources base="{{ baseurl }}/api/1.0/"> |
825 | + <wadl:resource path="" type="#service-root"/> |
826 | + </wadl:resources> |
827 | + |
828 | + <!--A "service root" resource responds to GET.--> |
829 | + <wadl:resource_type id="service-root"> |
830 | + <wadl:doc>The root of the web service.</wadl:doc> |
831 | + <wadl:method name="GET" id="service-root-get"> |
832 | + <wadl:response> |
833 | + <wadl:representation href="#service-root-json"/> |
834 | + <wadl:representation mediaType="application/vnd.sun.wadl+xml" id="service-root-wadl"/> |
835 | + </wadl:response> |
836 | + </wadl:method> |
837 | + </wadl:resource_type> |
838 | + |
839 | + <!--The JSON representation of a "service root" resource contains a |
840 | + number of links to collection-type resources.--> |
841 | + <wadl:representation mediaType="application/json" id="service-root-json"> |
842 | + |
843 | + <wadl:param style="plain" |
844 | + path="$['registrations_collection_link']" |
845 | + name="registrations_collection_link"> |
846 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#registrations"/> |
847 | + </wadl:param> |
848 | + |
849 | + |
850 | + <wadl:param style="plain" |
851 | + path="$['authentications_collection_link']" |
852 | + name="authentications_collection_link"> |
853 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#authentications"/> |
854 | + </wadl:param> |
855 | + |
856 | + |
857 | + <wadl:param style="plain" |
858 | + path="$['captchas_collection_link']" |
859 | + name="captchas_collection_link"> |
860 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#captchas"/> |
861 | + </wadl:param> |
862 | + |
863 | + |
864 | + <wadl:param style="plain" |
865 | + path="$['accounts_collection_link']" |
866 | + name="accounts_collection_link"> |
867 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#accounts"/> |
868 | + </wadl:param> |
869 | + |
870 | + |
871 | + <wadl:param style="plain" |
872 | + path="$['validations_collection_link']" |
873 | + name="validations_collection_link"> |
874 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#accounts"/> |
875 | + </wadl:param> |
876 | + |
877 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
878 | + <wadl:doc>The link to the WADL description of this resource.</wadl:doc> |
879 | + <wadl:link/> |
880 | + </wadl:param> |
881 | + </wadl:representation> |
882 | + |
883 | + <!--In addition to the service root, this document describes all the |
884 | + types of resources you might encounter as you browse this web |
885 | + service.--> |
886 | + |
887 | + <!--Begin resource_type definitions for collection resources.--> |
888 | + |
889 | + <wadl:resource_type id="accounts"> |
890 | + |
891 | + <wadl:method name="GET" id="accounts-get"> |
892 | + <wadl:response> |
893 | + <wadl:representation |
894 | + href="{{ baseurl }}/api/1.0/#account-page"/> |
895 | + <wadl:representation |
896 | + mediaType="application/vnd.sun.wadl+xml" |
897 | + id="accounts-wadl"/> |
898 | + </wadl:response> |
899 | + </wadl:method> |
900 | + |
901 | + <wadl:method id="accounts-me" name="GET"> |
902 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
903 | +Get details for the currently authenticated user. |
904 | +</wadl:doc> |
905 | + <wadl:request> |
906 | + |
907 | + <wadl:param style="query" name="ws.op" |
908 | + required="true" fixed="me"> |
909 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
910 | + </wadl:param> |
911 | + |
912 | + </wadl:request> |
913 | + |
914 | + </wadl:method> |
915 | + <wadl:method id="accounts-team_memberships" name="GET"> |
916 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
917 | +Query account for team memberships |
918 | +</wadl:doc> |
919 | + <wadl:request> |
920 | + |
921 | + <wadl:param style="query" name="ws.op" |
922 | + required="true" |
923 | + fixed="team_memberships"> |
924 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
925 | + </wadl:param> |
926 | + <wadl:param style="query" required="true" |
927 | + name="team_names"> |
928 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
929 | +List of team names to check |
930 | +</wadl:doc> |
931 | + |
932 | + </wadl:param> |
933 | + |
934 | + </wadl:request> |
935 | + |
936 | + </wadl:method> |
937 | + <wadl:method id="accounts-validate_email" name="GET"> |
938 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
939 | +Validate email by sending token user received in email |
940 | +</wadl:doc> |
941 | + <wadl:request> |
942 | + |
943 | + <wadl:param style="query" name="ws.op" |
944 | + required="true" |
945 | + fixed="validate_email"> |
946 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
947 | + </wadl:param> |
948 | + <wadl:param style="query" required="true" |
949 | + name="email_token"> |
950 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
951 | +Email validation token. |
952 | +</wadl:doc> |
953 | + |
954 | + </wadl:param> |
955 | + |
956 | + </wadl:request> |
957 | + |
958 | + </wadl:method> |
959 | + </wadl:resource_type> |
960 | + |
961 | + |
962 | + |
963 | + <wadl:resource_type id="authentications"> |
964 | + |
965 | + <wadl:method name="GET" id="authentications-get"> |
966 | + <wadl:response> |
967 | + <wadl:representation |
968 | + href="{{ baseurl }}/api/1.0/#authentication-page"/> |
969 | + <wadl:representation |
970 | + mediaType="application/vnd.sun.wadl+xml" |
971 | + id="authentications-wadl"/> |
972 | + </wadl:response> |
973 | + </wadl:method> |
974 | + |
975 | + <wadl:method id="authentications-me" name="GET"> |
976 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
977 | +Get details for the currently authenticated user. |
978 | +</wadl:doc> |
979 | + <wadl:request> |
980 | + |
981 | + <wadl:param style="query" name="ws.op" |
982 | + required="true" fixed="me"> |
983 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
984 | + </wadl:param> |
985 | + |
986 | + </wadl:request> |
987 | + |
988 | + </wadl:method> |
989 | + <wadl:method id="authentications-authenticate" |
990 | + name="GET"> |
991 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
992 | +Obtain OAuth token for logged in user |
993 | +</wadl:doc> |
994 | + <wadl:request> |
995 | + |
996 | + <wadl:param style="query" name="ws.op" |
997 | + required="true" fixed="authenticate"> |
998 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
999 | + </wadl:param> |
1000 | + <wadl:param style="query" required="true" |
1001 | + name="token_name"> |
1002 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1003 | +Token name. |
1004 | +</wadl:doc> |
1005 | + |
1006 | + </wadl:param> |
1007 | + |
1008 | + </wadl:request> |
1009 | + |
1010 | + </wadl:method> |
1011 | + <wadl:method id="authentications-team_memberships" |
1012 | + name="GET"> |
1013 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1014 | +Query user for team memberships |
1015 | +</wadl:doc> |
1016 | + <wadl:request> |
1017 | + |
1018 | + <wadl:param style="query" name="ws.op" |
1019 | + required="true" |
1020 | + fixed="team_memberships"> |
1021 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1022 | + </wadl:param> |
1023 | + <wadl:param style="query" required="true" |
1024 | + name="team_names"> |
1025 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1026 | +List of team names to check |
1027 | +</wadl:doc> |
1028 | + |
1029 | + </wadl:param> |
1030 | + <wadl:param style="query" required="true" |
1031 | + name="openid_identifier"> |
1032 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1033 | +OpenID Identifier used for asking forteam memberships on behalf of other user. |
1034 | +</wadl:doc> |
1035 | + |
1036 | + </wadl:param> |
1037 | + |
1038 | + </wadl:request> |
1039 | + |
1040 | + </wadl:method> |
1041 | + <wadl:method id="authentications-validate_token" |
1042 | + name="GET"> |
1043 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1044 | +<p>Check that a token is valid.</p> |
1045 | +<p>If valid, this method return the token and consumer secrets</p> |
1046 | + |
1047 | +</wadl:doc> |
1048 | + <wadl:request> |
1049 | + |
1050 | + <wadl:param style="query" name="ws.op" |
1051 | + required="true" |
1052 | + fixed="validate_token"> |
1053 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1054 | + </wadl:param> |
1055 | + <wadl:param style="query" required="true" |
1056 | + name="token"> |
1057 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1058 | +The token you want to validate |
1059 | +</wadl:doc> |
1060 | + |
1061 | + </wadl:param> |
1062 | + <wadl:param style="query" required="true" |
1063 | + name="consumer_key"> |
1064 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1065 | +The consumer key (openid identifier) |
1066 | +</wadl:doc> |
1067 | + |
1068 | + </wadl:param> |
1069 | + |
1070 | + </wadl:request> |
1071 | + |
1072 | + </wadl:method> |
1073 | + <wadl:method id="authentications-account_by_openid" |
1074 | + name="GET"> |
1075 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1076 | +Retrieve account information based on OpenID identifier |
1077 | +</wadl:doc> |
1078 | + <wadl:request> |
1079 | + |
1080 | + <wadl:param style="query" name="ws.op" |
1081 | + required="true" |
1082 | + fixed="account_by_openid"> |
1083 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1084 | + </wadl:param> |
1085 | + <wadl:param style="query" required="true" |
1086 | + name="openid"> |
1087 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1088 | +OpenID Identifier of the account |
1089 | +</wadl:doc> |
1090 | + |
1091 | + </wadl:param> |
1092 | + |
1093 | + </wadl:request> |
1094 | + |
1095 | + </wadl:method> |
1096 | + <wadl:method id="authentications-validate_email" |
1097 | + name="GET"> |
1098 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1099 | +Validate email by sending token user received in email |
1100 | +</wadl:doc> |
1101 | + <wadl:request> |
1102 | + |
1103 | + <wadl:param style="query" name="ws.op" |
1104 | + required="true" |
1105 | + fixed="validate_email"> |
1106 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1107 | + </wadl:param> |
1108 | + <wadl:param style="query" required="true" |
1109 | + name="email_token"> |
1110 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1111 | +Email validation token. |
1112 | +</wadl:doc> |
1113 | + |
1114 | + </wadl:param> |
1115 | + |
1116 | + </wadl:request> |
1117 | + |
1118 | + </wadl:method> |
1119 | + <wadl:method id="authentications-account_by_email" |
1120 | + name="GET"> |
1121 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1122 | +Retrieve account information based on email address |
1123 | +</wadl:doc> |
1124 | + <wadl:request> |
1125 | + |
1126 | + <wadl:param style="query" name="ws.op" |
1127 | + required="true" |
1128 | + fixed="account_by_email"> |
1129 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1130 | + </wadl:param> |
1131 | + <wadl:param style="query" required="true" |
1132 | + name="email"> |
1133 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1134 | +Email address of the account |
1135 | +</wadl:doc> |
1136 | + |
1137 | + </wadl:param> |
1138 | + |
1139 | + </wadl:request> |
1140 | + |
1141 | + </wadl:method> |
1142 | + <wadl:method id="authentications-list_tokens" |
1143 | + name="GET"> |
1144 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1145 | +Get the currently valid tokens for a given user |
1146 | +</wadl:doc> |
1147 | + <wadl:request> |
1148 | + |
1149 | + <wadl:param style="query" name="ws.op" |
1150 | + required="true" fixed="list_tokens"> |
1151 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1152 | + </wadl:param> |
1153 | + <wadl:param style="query" required="true" |
1154 | + name="consumer_key"> |
1155 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1156 | +User's OpenID identifier |
1157 | +</wadl:doc> |
1158 | + |
1159 | + </wadl:param> |
1160 | + |
1161 | + </wadl:request> |
1162 | + |
1163 | + </wadl:method> |
1164 | + <wadl:method id="authentications-invalidate_token" |
1165 | + name="POST"> |
1166 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1167 | +Make the given token invalid |
1168 | +</wadl:doc> |
1169 | + <wadl:request> |
1170 | + <wadl:representation |
1171 | + mediaType="application/x-www-form-urlencoded"> |
1172 | + <wadl:param style="query" name="ws.op" |
1173 | + required="true" |
1174 | + fixed="invalidate_token"> |
1175 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1176 | + </wadl:param> |
1177 | + <wadl:param style="query" required="true" |
1178 | + name="token"> |
1179 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1180 | +The token you want to invalidate |
1181 | +</wadl:doc> |
1182 | + |
1183 | + </wadl:param> |
1184 | + <wadl:param style="query" required="true" |
1185 | + name="consumer_key"> |
1186 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1187 | +The consumer key (openid identifier) |
1188 | +</wadl:doc> |
1189 | + |
1190 | + </wadl:param> |
1191 | + </wadl:representation> |
1192 | + </wadl:request> |
1193 | + |
1194 | + </wadl:method> |
1195 | + </wadl:resource_type> |
1196 | + |
1197 | + |
1198 | + |
1199 | + <wadl:resource_type id="captchas"> |
1200 | + |
1201 | + <wadl:method name="GET" id="captchas-get"> |
1202 | + <wadl:response> |
1203 | + <wadl:representation |
1204 | + href="{{ baseurl }}/api/1.0/#captcha-page"/> |
1205 | + <wadl:representation |
1206 | + mediaType="application/vnd.sun.wadl+xml" |
1207 | + id="captchas-wadl"/> |
1208 | + </wadl:response> |
1209 | + </wadl:method> |
1210 | + |
1211 | + <wadl:method id="captchas-new" name="POST"> |
1212 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1213 | +Generate a new captcha |
1214 | +</wadl:doc> |
1215 | + <wadl:request> |
1216 | + <wadl:representation |
1217 | + mediaType="application/x-www-form-urlencoded"> |
1218 | + <wadl:param style="query" name="ws.op" |
1219 | + required="true" fixed="new"> |
1220 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1221 | + </wadl:param> |
1222 | + </wadl:representation> |
1223 | + </wadl:request> |
1224 | + |
1225 | + </wadl:method> |
1226 | + </wadl:resource_type> |
1227 | + |
1228 | + |
1229 | + |
1230 | + <wadl:resource_type id="registrations"> |
1231 | + |
1232 | + <wadl:method name="GET" id="registrations-get"> |
1233 | + <wadl:response> |
1234 | + <wadl:representation |
1235 | + href="{{ baseurl }}/api/1.0/#registration-page"/> |
1236 | + <wadl:representation |
1237 | + mediaType="application/vnd.sun.wadl+xml" |
1238 | + id="registrations-wadl"/> |
1239 | + </wadl:response> |
1240 | + </wadl:method> |
1241 | + |
1242 | + <wadl:method id="registrations-me" name="GET"> |
1243 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1244 | +Get details for the currently authenticated user. |
1245 | +</wadl:doc> |
1246 | + <wadl:request> |
1247 | + |
1248 | + <wadl:param style="query" name="ws.op" |
1249 | + required="true" fixed="me"> |
1250 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1251 | + </wadl:param> |
1252 | + |
1253 | + </wadl:request> |
1254 | + |
1255 | + </wadl:method> |
1256 | + <wadl:method id="registrations-team_memberships" |
1257 | + name="GET"> |
1258 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1259 | +Query account for team memberships |
1260 | +</wadl:doc> |
1261 | + <wadl:request> |
1262 | + |
1263 | + <wadl:param style="query" name="ws.op" |
1264 | + required="true" |
1265 | + fixed="team_memberships"> |
1266 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1267 | + </wadl:param> |
1268 | + <wadl:param style="query" required="true" |
1269 | + name="team_names"> |
1270 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1271 | +List of team names to check |
1272 | +</wadl:doc> |
1273 | + |
1274 | + </wadl:param> |
1275 | + |
1276 | + </wadl:request> |
1277 | + |
1278 | + </wadl:method> |
1279 | + <wadl:method id="registrations-validate_email" |
1280 | + name="GET"> |
1281 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1282 | +Validate email by sending token user received in email |
1283 | +</wadl:doc> |
1284 | + <wadl:request> |
1285 | + |
1286 | + <wadl:param style="query" name="ws.op" |
1287 | + required="true" |
1288 | + fixed="validate_email"> |
1289 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1290 | + </wadl:param> |
1291 | + <wadl:param style="query" required="true" |
1292 | + name="email_token"> |
1293 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1294 | +Email validation token. |
1295 | +</wadl:doc> |
1296 | + |
1297 | + </wadl:param> |
1298 | + |
1299 | + </wadl:request> |
1300 | + |
1301 | + </wadl:method> |
1302 | + <wadl:method id="registrations-request_password_reset_token" |
1303 | + name="POST"> |
1304 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1305 | +Request password reset code to be sent to the email |
1306 | +</wadl:doc> |
1307 | + <wadl:request> |
1308 | + <wadl:representation |
1309 | + mediaType="application/x-www-form-urlencoded"> |
1310 | + <wadl:param style="query" name="ws.op" |
1311 | + required="true" |
1312 | + fixed="request_password_reset_token"> |
1313 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1314 | + </wadl:param> |
1315 | + <wadl:param style="query" required="true" |
1316 | + name="email"> |
1317 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1318 | +Email address. |
1319 | +</wadl:doc> |
1320 | + |
1321 | + </wadl:param> |
1322 | + </wadl:representation> |
1323 | + </wadl:request> |
1324 | + |
1325 | + </wadl:method> |
1326 | + <wadl:method id="registrations-set_new_password" |
1327 | + name="POST"> |
1328 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1329 | +Set new password for given user |
1330 | +</wadl:doc> |
1331 | + <wadl:request> |
1332 | + <wadl:representation |
1333 | + mediaType="application/x-www-form-urlencoded"> |
1334 | + <wadl:param style="query" name="ws.op" |
1335 | + required="true" |
1336 | + fixed="set_new_password"> |
1337 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1338 | + </wadl:param> |
1339 | + <wadl:param style="query" required="true" |
1340 | + name="new_password"> |
1341 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1342 | +New password |
1343 | +</wadl:doc> |
1344 | + |
1345 | + </wadl:param> |
1346 | + <wadl:param style="query" required="true" |
1347 | + name="token"> |
1348 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1349 | +Password reset token. |
1350 | +</wadl:doc> |
1351 | + |
1352 | + </wadl:param> |
1353 | + <wadl:param style="query" required="true" |
1354 | + name="email"> |
1355 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1356 | +Email address. |
1357 | +</wadl:doc> |
1358 | + |
1359 | + </wadl:param> |
1360 | + </wadl:representation> |
1361 | + </wadl:request> |
1362 | + |
1363 | + </wadl:method> |
1364 | + <wadl:method id="registrations-register" name="POST"> |
1365 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1366 | +Generate a new captcha |
1367 | +</wadl:doc> |
1368 | + <wadl:request> |
1369 | + <wadl:representation |
1370 | + mediaType="application/x-www-form-urlencoded"> |
1371 | + <wadl:param style="query" name="ws.op" |
1372 | + required="true" fixed="register"> |
1373 | + <wadl:doc>The name of the operation being invoked.</wadl:doc> |
1374 | + </wadl:param> |
1375 | + <wadl:param style="query" required="true" |
1376 | + name="captcha_solution"> |
1377 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1378 | +Solution for the generated captcha. |
1379 | +</wadl:doc> |
1380 | + |
1381 | + </wadl:param> |
1382 | + <wadl:param style="query" required="true" |
1383 | + name="captcha_id"> |
1384 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1385 | +ID for the generated captcha |
1386 | +</wadl:doc> |
1387 | + |
1388 | + </wadl:param> |
1389 | + <wadl:param style="query" required="true" |
1390 | + name="password"> |
1391 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1392 | +Password should be at least 8 characters long and contain at least one uppercase letter and a number. |
1393 | +</wadl:doc> |
1394 | + |
1395 | + </wadl:param> |
1396 | + <wadl:param style="query" required="false" |
1397 | + name="displayname"> |
1398 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1399 | +Full name |
1400 | +</wadl:doc> |
1401 | + |
1402 | + </wadl:param> |
1403 | + <wadl:param style="query" required="true" |
1404 | + name="email"> |
1405 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1406 | +Email address. |
1407 | +</wadl:doc> |
1408 | + |
1409 | + </wadl:param> |
1410 | + </wadl:representation> |
1411 | + </wadl:request> |
1412 | + |
1413 | + </wadl:method> |
1414 | + </wadl:resource_type> |
1415 | + |
1416 | + |
1417 | + <!--End resource_type definitions for collection resources.--> |
1418 | + |
1419 | + <!--Begin representation and resource_type definitions for entry |
1420 | + resources and the collections that contain them. --> |
1421 | + |
1422 | + <wadl:resource_type id="account"> |
1423 | + |
1424 | + <wadl:method name="GET" id="account-get"> |
1425 | + <wadl:response> |
1426 | + <wadl:representation |
1427 | + href="{{ baseurl }}/api/1.0/#account-full"/> |
1428 | + <wadl:representation |
1429 | + mediaType="application/xhtml+xml" id="account-xhtml"/> |
1430 | + <wadl:representation |
1431 | + mediaType="application/vnd.sun.wadl+xml" |
1432 | + id="account-wadl"/> |
1433 | + </wadl:response> |
1434 | + </wadl:method> |
1435 | + |
1436 | + <wadl:method name="PUT" id="account-put"> |
1437 | + <wadl:request> |
1438 | + <wadl:representation |
1439 | + href="{{ baseurl }}/api/1.0/#account-full"/> |
1440 | + </wadl:request> |
1441 | + </wadl:method> |
1442 | + |
1443 | + <wadl:method name="PATCH" id="account-patch"> |
1444 | + <wadl:request> |
1445 | + <wadl:representation |
1446 | + href="{{ baseurl }}/api/1.0/#account-diff"/> |
1447 | + </wadl:request> |
1448 | + </wadl:method> |
1449 | + |
1450 | + |
1451 | + |
1452 | + </wadl:resource_type> |
1453 | + |
1454 | + |
1455 | + <wadl:representation mediaType="application/json" |
1456 | + id="account-full"> |
1457 | + <wadl:param style="plain" name="self_link" path="$['self_link']"> |
1458 | + <wadl:doc>The canonical link to this resource.</wadl:doc> |
1459 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#account"/> |
1460 | + </wadl:param> |
1461 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1462 | + <wadl:doc> |
1463 | + The link to the WADL description of this resource. |
1464 | + </wadl:doc> |
1465 | + <wadl:link/> |
1466 | + </wadl:param> |
1467 | + <wadl:param style="plain" name="http_etag" path="$['http_etag']"> |
1468 | + <wadl:doc> |
1469 | + The value of the HTTP ETag for this resource. |
1470 | + </wadl:doc> |
1471 | + </wadl:param> |
1472 | + <wadl:param style="plain" required="true" |
1473 | + path="$['preferred_email']" |
1474 | + name="preferred_email"> |
1475 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1476 | +Primary email address |
1477 | +</wadl:doc> |
1478 | + |
1479 | + </wadl:param> |
1480 | + <wadl:param style="plain" required="true" |
1481 | + path="$['unverified_emails']" |
1482 | + name="unverified_emails"> |
1483 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1484 | +List of unverified emails |
1485 | +</wadl:doc> |
1486 | + |
1487 | + </wadl:param> |
1488 | + <wadl:param style="plain" required="true" |
1489 | + path="$['id']" name="id"> |
1490 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1491 | +Account ID |
1492 | +</wadl:doc> |
1493 | + |
1494 | + </wadl:param> |
1495 | + <wadl:param style="plain" required="true" |
1496 | + path="$['verified_emails']" |
1497 | + name="verified_emails"> |
1498 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1499 | +List of verified emails |
1500 | +</wadl:doc> |
1501 | + |
1502 | + </wadl:param> |
1503 | + </wadl:representation> |
1504 | + |
1505 | + <wadl:representation mediaType="application/json" |
1506 | + id="account-diff"> |
1507 | + <wadl:param style="plain" required="false" |
1508 | + path="$['preferred_email']" |
1509 | + name="preferred_email"> |
1510 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1511 | +Primary email address |
1512 | +</wadl:doc> |
1513 | + |
1514 | + </wadl:param> |
1515 | + <wadl:param style="plain" required="false" |
1516 | + path="$['unverified_emails']" |
1517 | + name="unverified_emails"> |
1518 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1519 | +List of unverified emails |
1520 | +</wadl:doc> |
1521 | + |
1522 | + </wadl:param> |
1523 | + <wadl:param style="plain" required="false" |
1524 | + path="$['id']" name="id"> |
1525 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1526 | +Account ID |
1527 | +</wadl:doc> |
1528 | + |
1529 | + </wadl:param> |
1530 | + <wadl:param style="plain" required="false" |
1531 | + path="$['verified_emails']" |
1532 | + name="verified_emails"> |
1533 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1534 | +List of verified emails |
1535 | +</wadl:doc> |
1536 | + |
1537 | + </wadl:param> |
1538 | + </wadl:representation> |
1539 | + |
1540 | + <!--Collection page for this type of entry--> |
1541 | + <wadl:resource_type id="account-page-resource"> |
1542 | + <wadl:method name="GET" id="account-page-resource-get"> |
1543 | + <wadl:response> |
1544 | + <wadl:representation href="#account-page"/> |
1545 | + </wadl:response> |
1546 | + </wadl:method> |
1547 | + </wadl:resource_type> |
1548 | + |
1549 | + <wadl:representation mediaType="application/json" |
1550 | + id="account-page"> |
1551 | + |
1552 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1553 | + <wadl:link/> |
1554 | + </wadl:param> |
1555 | + |
1556 | + <wadl:param style="plain" name="total_size" path="$['total_size']" required="true"/> |
1557 | + |
1558 | + <wadl:param style="plain" name="start" path="$['start']" required="true"/> |
1559 | + |
1560 | + <wadl:param style="plain" name="next_collection_link" path="$['next_collection_link']"> |
1561 | + <wadl:link resource_type="#account-page-resource"/> |
1562 | + </wadl:param> |
1563 | + |
1564 | + <wadl:param style="plain" name="prev_collection_link" path="$['prev_collection_link']"> |
1565 | + <wadl:link resource_type="#account-page-resource"/> |
1566 | + </wadl:param> |
1567 | + |
1568 | + <wadl:param style="plain" name="entries" path="$['entries']" required="true"/> |
1569 | + |
1570 | + <wadl:param style="plain" name="entry_links" path="$['entries'][*]['self_link']"> |
1571 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#account"/> |
1572 | + </wadl:param> |
1573 | + </wadl:representation> |
1574 | + |
1575 | + |
1576 | + |
1577 | + <wadl:resource_type id="authentication"> |
1578 | + |
1579 | + <wadl:method name="GET" id="authentication-get"> |
1580 | + <wadl:response> |
1581 | + <wadl:representation |
1582 | + href="{{ baseurl }}/api/1.0/#authentication-full"/> |
1583 | + <wadl:representation |
1584 | + mediaType="application/xhtml+xml" |
1585 | + id="authentication-xhtml"/> |
1586 | + <wadl:representation |
1587 | + mediaType="application/vnd.sun.wadl+xml" |
1588 | + id="authentication-wadl"/> |
1589 | + </wadl:response> |
1590 | + </wadl:method> |
1591 | + |
1592 | + <wadl:method name="PUT" id="authentication-put"> |
1593 | + <wadl:request> |
1594 | + <wadl:representation |
1595 | + href="{{ baseurl }}/api/1.0/#authentication-full"/> |
1596 | + </wadl:request> |
1597 | + </wadl:method> |
1598 | + |
1599 | + <wadl:method name="PATCH" id="authentication-patch"> |
1600 | + <wadl:request> |
1601 | + <wadl:representation |
1602 | + href="{{ baseurl }}/api/1.0/#authentication-diff"/> |
1603 | + </wadl:request> |
1604 | + </wadl:method> |
1605 | + |
1606 | + |
1607 | + |
1608 | + </wadl:resource_type> |
1609 | + |
1610 | + |
1611 | + <wadl:representation mediaType="application/json" |
1612 | + id="authentication-full"> |
1613 | + <wadl:param style="plain" name="self_link" path="$['self_link']"> |
1614 | + <wadl:doc>The canonical link to this resource.</wadl:doc> |
1615 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#authentication"/> |
1616 | + </wadl:param> |
1617 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1618 | + <wadl:doc> |
1619 | + The link to the WADL description of this resource. |
1620 | + </wadl:doc> |
1621 | + <wadl:link/> |
1622 | + </wadl:param> |
1623 | + <wadl:param style="plain" name="http_etag" path="$['http_etag']"> |
1624 | + <wadl:doc> |
1625 | + The value of the HTTP ETag for this resource. |
1626 | + </wadl:doc> |
1627 | + </wadl:param> |
1628 | + <wadl:param style="plain" required="true" |
1629 | + path="$['preferred_email']" |
1630 | + name="preferred_email"> |
1631 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1632 | +Primary email address |
1633 | +</wadl:doc> |
1634 | + |
1635 | + </wadl:param> |
1636 | + <wadl:param style="plain" required="true" |
1637 | + path="$['unverified_emails']" |
1638 | + name="unverified_emails"> |
1639 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1640 | +List of unverified emails |
1641 | +</wadl:doc> |
1642 | + |
1643 | + </wadl:param> |
1644 | + <wadl:param style="plain" required="true" |
1645 | + path="$['id']" name="id"> |
1646 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1647 | +Account ID |
1648 | +</wadl:doc> |
1649 | + |
1650 | + </wadl:param> |
1651 | + <wadl:param style="plain" required="true" |
1652 | + path="$['verified_emails']" |
1653 | + name="verified_emails"> |
1654 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1655 | +List of verified emails |
1656 | +</wadl:doc> |
1657 | + |
1658 | + </wadl:param> |
1659 | + </wadl:representation> |
1660 | + |
1661 | + <wadl:representation mediaType="application/json" |
1662 | + id="authentication-diff"> |
1663 | + <wadl:param style="plain" required="false" |
1664 | + path="$['preferred_email']" |
1665 | + name="preferred_email"> |
1666 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1667 | +Primary email address |
1668 | +</wadl:doc> |
1669 | + |
1670 | + </wadl:param> |
1671 | + <wadl:param style="plain" required="false" |
1672 | + path="$['unverified_emails']" |
1673 | + name="unverified_emails"> |
1674 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1675 | +List of unverified emails |
1676 | +</wadl:doc> |
1677 | + |
1678 | + </wadl:param> |
1679 | + <wadl:param style="plain" required="false" |
1680 | + path="$['id']" name="id"> |
1681 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1682 | +Account ID |
1683 | +</wadl:doc> |
1684 | + |
1685 | + </wadl:param> |
1686 | + <wadl:param style="plain" required="false" |
1687 | + path="$['verified_emails']" |
1688 | + name="verified_emails"> |
1689 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1690 | +List of verified emails |
1691 | +</wadl:doc> |
1692 | + |
1693 | + </wadl:param> |
1694 | + </wadl:representation> |
1695 | + |
1696 | + <!--Collection page for this type of entry--> |
1697 | + <wadl:resource_type id="authentication-page-resource"> |
1698 | + <wadl:method name="GET" |
1699 | + id="authentication-page-resource-get"> |
1700 | + <wadl:response> |
1701 | + <wadl:representation href="#authentication-page"/> |
1702 | + </wadl:response> |
1703 | + </wadl:method> |
1704 | + </wadl:resource_type> |
1705 | + |
1706 | + <wadl:representation mediaType="application/json" |
1707 | + id="authentication-page"> |
1708 | + |
1709 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1710 | + <wadl:link/> |
1711 | + </wadl:param> |
1712 | + |
1713 | + <wadl:param style="plain" name="total_size" path="$['total_size']" required="true"/> |
1714 | + |
1715 | + <wadl:param style="plain" name="start" path="$['start']" required="true"/> |
1716 | + |
1717 | + <wadl:param style="plain" name="next_collection_link" path="$['next_collection_link']"> |
1718 | + <wadl:link resource_type="#authentication-page-resource"/> |
1719 | + </wadl:param> |
1720 | + |
1721 | + <wadl:param style="plain" name="prev_collection_link" path="$['prev_collection_link']"> |
1722 | + <wadl:link resource_type="#authentication-page-resource"/> |
1723 | + </wadl:param> |
1724 | + |
1725 | + <wadl:param style="plain" name="entries" path="$['entries']" required="true"/> |
1726 | + |
1727 | + <wadl:param style="plain" name="entry_links" path="$['entries'][*]['self_link']"> |
1728 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#authentication"/> |
1729 | + </wadl:param> |
1730 | + </wadl:representation> |
1731 | + |
1732 | + |
1733 | + |
1734 | + <wadl:resource_type id="captcha"> |
1735 | + |
1736 | + <wadl:method name="GET" id="captcha-get"> |
1737 | + <wadl:response> |
1738 | + <wadl:representation |
1739 | + href="{{ baseurl }}/api/1.0/#captcha-full"/> |
1740 | + <wadl:representation |
1741 | + mediaType="application/xhtml+xml" id="captcha-xhtml"/> |
1742 | + <wadl:representation |
1743 | + mediaType="application/vnd.sun.wadl+xml" |
1744 | + id="captcha-wadl"/> |
1745 | + </wadl:response> |
1746 | + </wadl:method> |
1747 | + |
1748 | + <wadl:method name="PUT" id="captcha-put"> |
1749 | + <wadl:request> |
1750 | + <wadl:representation |
1751 | + href="{{ baseurl }}/api/1.0/#captcha-full"/> |
1752 | + </wadl:request> |
1753 | + </wadl:method> |
1754 | + |
1755 | + <wadl:method name="PATCH" id="captcha-patch"> |
1756 | + <wadl:request> |
1757 | + <wadl:representation |
1758 | + href="{{ baseurl }}/api/1.0/#captcha-diff"/> |
1759 | + </wadl:request> |
1760 | + </wadl:method> |
1761 | + |
1762 | + |
1763 | + |
1764 | + </wadl:resource_type> |
1765 | + |
1766 | + |
1767 | + <wadl:representation mediaType="application/json" |
1768 | + id="captcha-full"> |
1769 | + <wadl:param style="plain" name="self_link" path="$['self_link']"> |
1770 | + <wadl:doc>The canonical link to this resource.</wadl:doc> |
1771 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#captcha"/> |
1772 | + </wadl:param> |
1773 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1774 | + <wadl:doc> |
1775 | + The link to the WADL description of this resource. |
1776 | + </wadl:doc> |
1777 | + <wadl:link/> |
1778 | + </wadl:param> |
1779 | + <wadl:param style="plain" name="http_etag" path="$['http_etag']"> |
1780 | + <wadl:doc> |
1781 | + The value of the HTTP ETag for this resource. |
1782 | + </wadl:doc> |
1783 | + </wadl:param> |
1784 | + <wadl:param style="plain" required="true" |
1785 | + path="$['content']" name="content"> |
1786 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1787 | +Captcha content |
1788 | +</wadl:doc> |
1789 | + |
1790 | + </wadl:param> |
1791 | + <wadl:param style="plain" required="true" |
1792 | + path="$['id']" name="id"> |
1793 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1794 | +Captcha ID |
1795 | +</wadl:doc> |
1796 | + |
1797 | + </wadl:param> |
1798 | + </wadl:representation> |
1799 | + |
1800 | + <wadl:representation mediaType="application/json" |
1801 | + id="captcha-diff"> |
1802 | + <wadl:param style="plain" required="false" |
1803 | + path="$['content']" name="content"> |
1804 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1805 | +Captcha content |
1806 | +</wadl:doc> |
1807 | + |
1808 | + </wadl:param> |
1809 | + <wadl:param style="plain" required="false" |
1810 | + path="$['id']" name="id"> |
1811 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1812 | +Captcha ID |
1813 | +</wadl:doc> |
1814 | + |
1815 | + </wadl:param> |
1816 | + </wadl:representation> |
1817 | + |
1818 | + <!--Collection page for this type of entry--> |
1819 | + <wadl:resource_type id="captcha-page-resource"> |
1820 | + <wadl:method name="GET" id="captcha-page-resource-get"> |
1821 | + <wadl:response> |
1822 | + <wadl:representation href="#captcha-page"/> |
1823 | + </wadl:response> |
1824 | + </wadl:method> |
1825 | + </wadl:resource_type> |
1826 | + |
1827 | + <wadl:representation mediaType="application/json" |
1828 | + id="captcha-page"> |
1829 | + |
1830 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1831 | + <wadl:link/> |
1832 | + </wadl:param> |
1833 | + |
1834 | + <wadl:param style="plain" name="total_size" path="$['total_size']" required="true"/> |
1835 | + |
1836 | + <wadl:param style="plain" name="start" path="$['start']" required="true"/> |
1837 | + |
1838 | + <wadl:param style="plain" name="next_collection_link" path="$['next_collection_link']"> |
1839 | + <wadl:link resource_type="#captcha-page-resource"/> |
1840 | + </wadl:param> |
1841 | + |
1842 | + <wadl:param style="plain" name="prev_collection_link" path="$['prev_collection_link']"> |
1843 | + <wadl:link resource_type="#captcha-page-resource"/> |
1844 | + </wadl:param> |
1845 | + |
1846 | + <wadl:param style="plain" name="entries" path="$['entries']" required="true"/> |
1847 | + |
1848 | + <wadl:param style="plain" name="entry_links" path="$['entries'][*]['self_link']"> |
1849 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#captcha"/> |
1850 | + </wadl:param> |
1851 | + </wadl:representation> |
1852 | + |
1853 | + |
1854 | + |
1855 | + <wadl:resource_type id="registration"> |
1856 | + |
1857 | + <wadl:method name="GET" id="registration-get"> |
1858 | + <wadl:response> |
1859 | + <wadl:representation |
1860 | + href="{{ baseurl }}/api/1.0/#registration-full"/> |
1861 | + <wadl:representation |
1862 | + mediaType="application/xhtml+xml" |
1863 | + id="registration-xhtml"/> |
1864 | + <wadl:representation |
1865 | + mediaType="application/vnd.sun.wadl+xml" |
1866 | + id="registration-wadl"/> |
1867 | + </wadl:response> |
1868 | + </wadl:method> |
1869 | + |
1870 | + <wadl:method name="PUT" id="registration-put"> |
1871 | + <wadl:request> |
1872 | + <wadl:representation |
1873 | + href="{{ baseurl }}/api/1.0/#registration-full"/> |
1874 | + </wadl:request> |
1875 | + </wadl:method> |
1876 | + |
1877 | + <wadl:method name="PATCH" id="registration-patch"> |
1878 | + <wadl:request> |
1879 | + <wadl:representation |
1880 | + href="{{ baseurl }}/api/1.0/#registration-diff"/> |
1881 | + </wadl:request> |
1882 | + </wadl:method> |
1883 | + |
1884 | + |
1885 | + |
1886 | + </wadl:resource_type> |
1887 | + |
1888 | + |
1889 | + <wadl:representation mediaType="application/json" |
1890 | + id="registration-full"> |
1891 | + <wadl:param style="plain" name="self_link" path="$['self_link']"> |
1892 | + <wadl:doc>The canonical link to this resource.</wadl:doc> |
1893 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#registration"/> |
1894 | + </wadl:param> |
1895 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1896 | + <wadl:doc> |
1897 | + The link to the WADL description of this resource. |
1898 | + </wadl:doc> |
1899 | + <wadl:link/> |
1900 | + </wadl:param> |
1901 | + <wadl:param style="plain" name="http_etag" path="$['http_etag']"> |
1902 | + <wadl:doc> |
1903 | + The value of the HTTP ETag for this resource. |
1904 | + </wadl:doc> |
1905 | + </wadl:param> |
1906 | + <wadl:param style="plain" required="true" |
1907 | + path="$['preferred_email']" |
1908 | + name="preferred_email"> |
1909 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1910 | +Primary email address |
1911 | +</wadl:doc> |
1912 | + |
1913 | + </wadl:param> |
1914 | + <wadl:param style="plain" required="true" |
1915 | + path="$['unverified_emails']" |
1916 | + name="unverified_emails"> |
1917 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1918 | +List of unverified emails |
1919 | +</wadl:doc> |
1920 | + |
1921 | + </wadl:param> |
1922 | + <wadl:param style="plain" required="true" |
1923 | + path="$['id']" name="id"> |
1924 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1925 | +Account ID |
1926 | +</wadl:doc> |
1927 | + |
1928 | + </wadl:param> |
1929 | + <wadl:param style="plain" required="true" |
1930 | + path="$['verified_emails']" |
1931 | + name="verified_emails"> |
1932 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1933 | +List of verified emails |
1934 | +</wadl:doc> |
1935 | + |
1936 | + </wadl:param> |
1937 | + </wadl:representation> |
1938 | + |
1939 | + <wadl:representation mediaType="application/json" |
1940 | + id="registration-diff"> |
1941 | + <wadl:param style="plain" required="false" |
1942 | + path="$['preferred_email']" |
1943 | + name="preferred_email"> |
1944 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1945 | +Primary email address |
1946 | +</wadl:doc> |
1947 | + |
1948 | + </wadl:param> |
1949 | + <wadl:param style="plain" required="false" |
1950 | + path="$['unverified_emails']" |
1951 | + name="unverified_emails"> |
1952 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1953 | +List of unverified emails |
1954 | +</wadl:doc> |
1955 | + |
1956 | + </wadl:param> |
1957 | + <wadl:param style="plain" required="false" |
1958 | + path="$['id']" name="id"> |
1959 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1960 | +Account ID |
1961 | +</wadl:doc> |
1962 | + |
1963 | + </wadl:param> |
1964 | + <wadl:param style="plain" required="false" |
1965 | + path="$['verified_emails']" |
1966 | + name="verified_emails"> |
1967 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
1968 | +List of verified emails |
1969 | +</wadl:doc> |
1970 | + |
1971 | + </wadl:param> |
1972 | + </wadl:representation> |
1973 | + |
1974 | + <!--Collection page for this type of entry--> |
1975 | + <wadl:resource_type id="registration-page-resource"> |
1976 | + <wadl:method name="GET" |
1977 | + id="registration-page-resource-get"> |
1978 | + <wadl:response> |
1979 | + <wadl:representation href="#registration-page"/> |
1980 | + </wadl:response> |
1981 | + </wadl:method> |
1982 | + </wadl:resource_type> |
1983 | + |
1984 | + <wadl:representation mediaType="application/json" |
1985 | + id="registration-page"> |
1986 | + |
1987 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
1988 | + <wadl:link/> |
1989 | + </wadl:param> |
1990 | + |
1991 | + <wadl:param style="plain" name="total_size" path="$['total_size']" required="true"/> |
1992 | + |
1993 | + <wadl:param style="plain" name="start" path="$['start']" required="true"/> |
1994 | + |
1995 | + <wadl:param style="plain" name="next_collection_link" path="$['next_collection_link']"> |
1996 | + <wadl:link resource_type="#registration-page-resource"/> |
1997 | + </wadl:param> |
1998 | + |
1999 | + <wadl:param style="plain" name="prev_collection_link" path="$['prev_collection_link']"> |
2000 | + <wadl:link resource_type="#registration-page-resource"/> |
2001 | + </wadl:param> |
2002 | + |
2003 | + <wadl:param style="plain" name="entries" path="$['entries']" required="true"/> |
2004 | + |
2005 | + <wadl:param style="plain" name="entry_links" path="$['entries'][*]['self_link']"> |
2006 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#registration"/> |
2007 | + </wadl:param> |
2008 | + </wadl:representation> |
2009 | + |
2010 | + |
2011 | + |
2012 | + <wadl:resource_type id="validation"> |
2013 | + |
2014 | + <wadl:method name="GET" id="validation-get"> |
2015 | + <wadl:response> |
2016 | + <wadl:representation |
2017 | + href="{{ baseurl }}/api/1.0/#validation-full"/> |
2018 | + <wadl:representation |
2019 | + mediaType="application/xhtml+xml" id="validation-xhtml"/> |
2020 | + <wadl:representation |
2021 | + mediaType="application/vnd.sun.wadl+xml" |
2022 | + id="validation-wadl"/> |
2023 | + </wadl:response> |
2024 | + </wadl:method> |
2025 | + |
2026 | + <wadl:method name="PUT" id="validation-put"> |
2027 | + <wadl:request> |
2028 | + <wadl:representation |
2029 | + href="{{ baseurl }}/api/1.0/#validation-full"/> |
2030 | + </wadl:request> |
2031 | + </wadl:method> |
2032 | + |
2033 | + <wadl:method name="PATCH" id="validation-patch"> |
2034 | + <wadl:request> |
2035 | + <wadl:representation |
2036 | + href="{{ baseurl }}/api/1.0/#validation-diff"/> |
2037 | + </wadl:request> |
2038 | + </wadl:method> |
2039 | + |
2040 | + |
2041 | + |
2042 | + </wadl:resource_type> |
2043 | + |
2044 | + |
2045 | + <wadl:representation mediaType="application/json" |
2046 | + id="validation-full"> |
2047 | + <wadl:param style="plain" name="self_link" path="$['self_link']"> |
2048 | + <wadl:doc>The canonical link to this resource.</wadl:doc> |
2049 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#validation"/> |
2050 | + </wadl:param> |
2051 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
2052 | + <wadl:doc> |
2053 | + The link to the WADL description of this resource. |
2054 | + </wadl:doc> |
2055 | + <wadl:link/> |
2056 | + </wadl:param> |
2057 | + <wadl:param style="plain" name="http_etag" path="$['http_etag']"> |
2058 | + <wadl:doc> |
2059 | + The value of the HTTP ETag for this resource. |
2060 | + </wadl:doc> |
2061 | + </wadl:param> |
2062 | + <wadl:param style="plain" required="true" |
2063 | + path="$['preferred_email']" |
2064 | + name="preferred_email"> |
2065 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2066 | +Primary email address |
2067 | +</wadl:doc> |
2068 | + |
2069 | + </wadl:param> |
2070 | + <wadl:param style="plain" required="true" |
2071 | + path="$['unverified_emails']" |
2072 | + name="unverified_emails"> |
2073 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2074 | +List of unverified emails |
2075 | +</wadl:doc> |
2076 | + |
2077 | + </wadl:param> |
2078 | + <wadl:param style="plain" required="true" |
2079 | + path="$['id']" name="id"> |
2080 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2081 | +Account ID |
2082 | +</wadl:doc> |
2083 | + |
2084 | + </wadl:param> |
2085 | + <wadl:param style="plain" required="true" |
2086 | + path="$['verified_emails']" |
2087 | + name="verified_emails"> |
2088 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2089 | +List of verified emails |
2090 | +</wadl:doc> |
2091 | + |
2092 | + </wadl:param> |
2093 | + </wadl:representation> |
2094 | + |
2095 | + <wadl:representation mediaType="application/json" |
2096 | + id="validation-diff"> |
2097 | + <wadl:param style="plain" required="false" |
2098 | + path="$['preferred_email']" |
2099 | + name="preferred_email"> |
2100 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2101 | +Primary email address |
2102 | +</wadl:doc> |
2103 | + |
2104 | + </wadl:param> |
2105 | + <wadl:param style="plain" required="false" |
2106 | + path="$['unverified_emails']" |
2107 | + name="unverified_emails"> |
2108 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2109 | +List of unverified emails |
2110 | +</wadl:doc> |
2111 | + |
2112 | + </wadl:param> |
2113 | + <wadl:param style="plain" required="false" |
2114 | + path="$['id']" name="id"> |
2115 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2116 | +Account ID |
2117 | +</wadl:doc> |
2118 | + |
2119 | + </wadl:param> |
2120 | + <wadl:param style="plain" required="false" |
2121 | + path="$['verified_emails']" |
2122 | + name="verified_emails"> |
2123 | + <wadl:doc xmlns="http://www.w3.org/1999/xhtml"> |
2124 | +List of verified emails |
2125 | +</wadl:doc> |
2126 | + |
2127 | + </wadl:param> |
2128 | + </wadl:representation> |
2129 | + |
2130 | + <!--Collection page for this type of entry--> |
2131 | + <wadl:resource_type id="validation-page-resource"> |
2132 | + <wadl:method name="GET" |
2133 | + id="validation-page-resource-get"> |
2134 | + <wadl:response> |
2135 | + <wadl:representation href="#validation-page"/> |
2136 | + </wadl:response> |
2137 | + </wadl:method> |
2138 | + </wadl:resource_type> |
2139 | + |
2140 | + <wadl:representation mediaType="application/json" |
2141 | + id="validation-page"> |
2142 | + |
2143 | + <wadl:param style="plain" name="resource_type_link" path="$['resource_type_link']"> |
2144 | + <wadl:link/> |
2145 | + </wadl:param> |
2146 | + |
2147 | + <wadl:param style="plain" name="total_size" path="$['total_size']" required="true"/> |
2148 | + |
2149 | + <wadl:param style="plain" name="start" path="$['start']" required="true"/> |
2150 | + |
2151 | + <wadl:param style="plain" name="next_collection_link" path="$['next_collection_link']"> |
2152 | + <wadl:link resource_type="#validation-page-resource"/> |
2153 | + </wadl:param> |
2154 | + |
2155 | + <wadl:param style="plain" name="prev_collection_link" path="$['prev_collection_link']"> |
2156 | + <wadl:link resource_type="#validation-page-resource"/> |
2157 | + </wadl:param> |
2158 | + |
2159 | + <wadl:param style="plain" name="entries" path="$['entries']" required="true"/> |
2160 | + |
2161 | + <wadl:param style="plain" name="entry_links" path="$['entries'][*]['self_link']"> |
2162 | + <wadl:link resource_type="{{ baseurl }}/api/1.0/#validation"/> |
2163 | + </wadl:param> |
2164 | + </wadl:representation> |
2165 | + |
2166 | + |
2167 | + <!--End representation and resource_type definitions for entry |
2168 | + resources. --> |
2169 | + |
2170 | + <!--Finally, describe the 'hosted binary file' type.--> |
2171 | + <wadl:resource_type id="HostedFile"> |
2172 | + <wadl:method name="GET" id="HostedFile-get"> |
2173 | + <wadl:response> |
2174 | + <wadl:representation status="303"> |
2175 | + <wadl:param style="header" name="Location"/> |
2176 | + </wadl:representation> |
2177 | + </wadl:response> |
2178 | + </wadl:method> |
2179 | + <wadl:method name="PUT" id="HostedFile-put"/> |
2180 | + <wadl:method name="DELETE" id="HostedFile-delete"/> |
2181 | + </wadl:resource_type> |
2182 | + |
2183 | + <!--Define a data type for binary data.--> |
2184 | + <xsd:simpleType name="binary"> |
2185 | + <xsd:list itemType="byte"/> |
2186 | + </xsd:simpleType> |
2187 | + |
2188 | +</wadl:application> |
2189 | |
2190 | === modified file 'identityprovider/tests/test_forms.py' |
2191 | --- identityprovider/tests/test_forms.py 2011-01-19 20:57:48 +0000 |
2192 | +++ identityprovider/tests/test_forms.py 2011-05-24 16:31:52 +0000 |
2193 | @@ -6,8 +6,8 @@ |
2194 | from django.conf import settings |
2195 | from identityprovider.models.account import Account |
2196 | from identityprovider.forms import EditAccountForm, ResetPasswordForm |
2197 | -from identityprovider.webservice import forms |
2198 | -from identityprovider.webservice.forms import WebserviceCreateAccountForm |
2199 | +from identityprovider.api10 import forms |
2200 | +from identityprovider.api10.forms import WebserviceCreateAccountForm |
2201 | from utils import BasicAccountTestCase, SQLCachedTestCase |
2202 | |
2203 | |
2204 | @@ -71,17 +71,11 @@ |
2205 | class WebServiceCreateAccountFormTest(SQLCachedTestCase): |
2206 | |
2207 | def setUp(self): |
2208 | + self.old_dcv = settings.DISABLE_CAPTCHA_VERIFICATION |
2209 | settings.DISABLE_CAPTCHA_VERIFICATION = True |
2210 | |
2211 | - class Request(object): |
2212 | - def __init__(self): |
2213 | - self.environment = {} |
2214 | - self.form = {'captcha_id': None, 'captcha_solution': None} |
2215 | - self.old_get_request = forms.get_current_browser_request |
2216 | - forms.get_current_browser_request = lambda: Request() |
2217 | - |
2218 | def tearDown(self): |
2219 | - forms.get_current_browser_request = self.old_get_request |
2220 | + settings.DISABLE_CAPTCHA_VERIFICATION = self.old_dcv |
2221 | |
2222 | def test_nonascii_password(self): |
2223 | data = {'password': 'Curuzú Cuatiá', |
2224 | |
2225 | === renamed file 'identityprovider/tests/test_webservice.py' => 'identityprovider/tests/test_handlers.py' |
2226 | --- identityprovider/tests/test_webservice.py 2011-03-14 12:51:02 +0000 |
2227 | +++ identityprovider/tests/test_handlers.py 2011-05-24 16:31:52 +0000 |
2228 | @@ -1,72 +1,112 @@ |
2229 | from mock import patch |
2230 | |
2231 | from identityprovider.tests.utils import SQLCachedTestCase |
2232 | -from identityprovider.models.authtoken import (AuthTokenFactory, |
2233 | - LoginTokenType, |
2234 | - AuthToken) |
2235 | -from identityprovider.models.api import APIUser |
2236 | -from identityprovider.models.account import Account |
2237 | -from identityprovider.models.const import AccountStatus |
2238 | -from identityprovider.webservice.models import ( |
2239 | - RegistrationSet, CanNotResetPasswordError, AuthenticationSet) |
2240 | - |
2241 | - |
2242 | -class RegistrationsTestCase(SQLCachedTestCase): |
2243 | +from identityprovider.models import ( |
2244 | + Account, |
2245 | + APIUser, |
2246 | + AuthToken, |
2247 | + AuthTokenFactory, |
2248 | + EmailAddress, |
2249 | +) |
2250 | +from identityprovider.models.const import ( |
2251 | + AccountStatus, |
2252 | + EmailStatus, |
2253 | + LoginTokenType, |
2254 | +) |
2255 | +from identityprovider.api10.handlers import ( |
2256 | + AuthenticationHandler, |
2257 | + CanNotResetPasswordError, |
2258 | + RegistrationHandler, |
2259 | +) |
2260 | + |
2261 | +class MockRequest(object): |
2262 | + def __init__(self, data=None, user=None): |
2263 | + self.user = user |
2264 | + if data is None: |
2265 | + data = {} |
2266 | + self.data = data |
2267 | + self.environ = {'REMOTE_ADDR': '127.0.0.1'} |
2268 | + |
2269 | +class RegistrationHandlerTestCase(SQLCachedTestCase): |
2270 | |
2271 | fixtures = ["test"] |
2272 | pgsql_functions = ["generate_openid_identifier"] |
2273 | |
2274 | def setUp(self): |
2275 | - self.registration = RegistrationSet() |
2276 | + self.registration = RegistrationHandler() |
2277 | |
2278 | def test_authtoken_without_requester_is_handled_properly(self): |
2279 | authtoken = AuthTokenFactory().new(None, None, "test@example.com", |
2280 | LoginTokenType.PASSWORDRECOVERY, |
2281 | None) |
2282 | + request = MockRequest(data={'email': "test@example.com", |
2283 | + 'token': authtoken.token, 'new_password': "pass"}) |
2284 | |
2285 | - response = self.registration.set_new_password("test@example.com", |
2286 | - authtoken.token, "pass") |
2287 | + response = self.registration.set_new_password(request) |
2288 | |
2289 | self.assertEquals(response['status'], "error") |
2290 | |
2291 | def test_reset_token_when_email_is_invalid(self): |
2292 | + request = MockRequest(data={'email': "non-existing@example.com"}) |
2293 | + |
2294 | self.assertRaises(CanNotResetPasswordError, |
2295 | self.registration.request_password_reset_token, |
2296 | - "non-existing@example.com") |
2297 | + request) |
2298 | |
2299 | def test_reset_token_when_account_is_disabled(self): |
2300 | account = Account.objects.get_by_email("mark@example.com") |
2301 | account.status = AccountStatus.SUSPENDED |
2302 | account.save() |
2303 | + request = MockRequest(data={'email': "mark@example.com"}) |
2304 | |
2305 | self.assertRaises(CanNotResetPasswordError, |
2306 | self.registration.request_password_reset_token, |
2307 | - "mark@example.com") |
2308 | + request) |
2309 | |
2310 | def test_reset_password_when_not_existing_token_is_passed(self): |
2311 | + request = MockRequest(data={'email': "mark@example.com", |
2312 | + 'token': "not-existing-token", 'new_password': "password"}) |
2313 | + |
2314 | self.assertRaises(AuthToken.DoesNotExist, |
2315 | - self.registration.set_new_password, |
2316 | - "mark@example.com", "not-existing-token", |
2317 | - "password") |
2318 | + self.registration.set_new_password, request) |
2319 | + |
2320 | + def test_register_email_error_is_in_list(self): |
2321 | + request = MockRequest(data={ |
2322 | + 'email': "register@example.com", |
2323 | + 'password': "blogdf3Daa", |
2324 | + 'captcha_solution': "solution", |
2325 | + 'captcha_id': "id", |
2326 | + }) |
2327 | + EmailAddress.objects.create(email="register@example.com", |
2328 | + status=EmailStatus.NEW) |
2329 | + |
2330 | + registration = RegistrationHandler() |
2331 | + r = registration.register(request) |
2332 | + |
2333 | + self.assertEquals(r['errors'], {'email': ["Email already registered"]}) |
2334 | |
2335 | def test_register_without_displayname(self): |
2336 | - response = self.registration.register( |
2337 | - email='test@example.com', |
2338 | - password='MySecretPassword1', |
2339 | - captcha_id=1, |
2340 | - captcha_solution='foobar') |
2341 | + request = MockRequest(data={ |
2342 | + 'email': "test@example.com", |
2343 | + 'password': "MySecretPassword1", |
2344 | + 'captcha_solution': "foobar", |
2345 | + 'captcha_id': "id", |
2346 | + }) |
2347 | + response = self.registration.register(request) |
2348 | self.assertEqual(response['status'], 'ok') |
2349 | |
2350 | account = Account.objects.get_by_email('test@example.com') |
2351 | self.assertEqual(account.displayname, '') |
2352 | |
2353 | def test_register_with_displayname(self): |
2354 | - response = self.registration.register( |
2355 | - email='test@example.com', |
2356 | - password='MySecretPassword1', |
2357 | - captcha_id=1, |
2358 | - captcha_solution='foobar', |
2359 | - displayname='Test User') |
2360 | + request = MockRequest(data={ |
2361 | + 'email': "test@example.com", |
2362 | + 'password': "MySecretPassword1", |
2363 | + 'captcha_solution': "foobar", |
2364 | + 'captcha_id': "id", |
2365 | + 'displayname': 'Test User', |
2366 | + }) |
2367 | + response = self.registration.register(request) |
2368 | self.assertEqual(response['status'], 'ok') |
2369 | |
2370 | account = Account.objects.get_by_email('test@example.com') |
2371 | @@ -78,24 +118,18 @@ |
2372 | fixtures = ["test"] |
2373 | |
2374 | def setUp(self): |
2375 | - self.authentication = AuthenticationSet() |
2376 | - |
2377 | - @patch('identityprovider.webservice.models.get_current_browser_request') |
2378 | - def test_account_by_openid_with_valid_openid(self, mock_get_request): |
2379 | - mock_get_request.return_value.environment = { |
2380 | - 'authenticated_user': APIUser() |
2381 | - } |
2382 | - |
2383 | - account = self.authentication.account_by_openid(openid="mark_oid") |
2384 | + self.authentication = AuthenticationHandler() |
2385 | + |
2386 | + def test_account_by_openid_with_valid_openid(self): |
2387 | + request = MockRequest(user=APIUser(), data={'openid': "mark_oid"}) |
2388 | + |
2389 | + account = self.authentication.account_by_openid(request) |
2390 | |
2391 | self.assertEquals(account['openid_identifier'], "mark_oid") |
2392 | |
2393 | - @patch('identityprovider.webservice.models.get_current_browser_request') |
2394 | - def test_account_by_openid_with_invalid_openid(self, mock_get_request): |
2395 | - mock_get_request.return_value.environment = { |
2396 | - 'authenticated_user': APIUser() |
2397 | - } |
2398 | + def test_account_by_openid_with_invalid_openid(self): |
2399 | + request = MockRequest(user=APIUser(), data={'openid': "bad-openid"}) |
2400 | |
2401 | - account = self.authentication.account_by_openid(openid="bad-openid") |
2402 | + account = self.authentication.account_by_openid(request) |
2403 | |
2404 | self.assertTrue(account is None) |
2405 | |
2406 | === modified file 'identityprovider/tests/test_models_account.py' |
2407 | --- identityprovider/tests/test_models_account.py 2011-01-11 15:50:14 +0000 |
2408 | +++ identityprovider/tests/test_models_account.py 2011-05-24 16:31:52 +0000 |
2409 | @@ -15,7 +15,6 @@ |
2410 | from identityprovider.models.const import AccountCreationRationale, EmailStatus |
2411 | from identityprovider.readonly import ReadOnlyManager |
2412 | from identityprovider.utils import encrypt_launchpad_password, generate_salt |
2413 | -from identityprovider.webservice.models import AccountSet |
2414 | |
2415 | |
2416 | class MockBackend(object): |
2417 | @@ -242,16 +241,6 @@ |
2418 | password_count = AccountPassword.objects.filter(account=account).count() |
2419 | self.assertEqual(password_count, 0) |
2420 | |
2421 | - def test_parent(self): |
2422 | - account = self.get_existing_account() |
2423 | - parent = getattr(account, '__parent__') |
2424 | - self.assertTrue(isinstance(parent, AccountSet)) |
2425 | - |
2426 | - def test_url_path(self): |
2427 | - account = self.get_existing_account() |
2428 | - url_path = getattr(account, '__url_path__') |
2429 | - self.assertEqual(url_path, str(account.id)) |
2430 | - |
2431 | def test_create_account_with_rationale(self): |
2432 | salt = generate_salt() |
2433 | account = Account.objects.create_account('displayname', 'username', |
2434 | |
2435 | === modified file 'identityprovider/tests/test_models_api.py' |
2436 | --- identityprovider/tests/test_models_api.py 2010-09-22 14:36:08 +0000 |
2437 | +++ identityprovider/tests/test_models_api.py 2011-05-24 16:31:52 +0000 |
2438 | @@ -5,8 +5,6 @@ |
2439 | from identityprovider.models.emailaddress import EmailAddress |
2440 | from identityprovider.models.const import EmailStatus |
2441 | from identityprovider.models.api import APIUser |
2442 | -from identityprovider.webservice.models import RegistrationSet |
2443 | -from identityprovider.webservice import forms |
2444 | from identityprovider.tests.utils import SQLCachedTestCase |
2445 | from identityprovider.utils import encrypt_launchpad_password, generate_salt |
2446 | |
2447 | @@ -45,22 +43,3 @@ |
2448 | def test_unicode(self): |
2449 | self.assertEqual(unicode(self.user), u'username') |
2450 | |
2451 | - |
2452 | -class RegistrationSetTestCase(TestCase): |
2453 | - |
2454 | - def test_register_email_error_is_in_list(self): |
2455 | - old_gcbr = forms.get_current_browser_request |
2456 | - forms.get_current_browser_request = lambda: self |
2457 | - self.environment = {} |
2458 | - EmailAddress.objects.create(email="register@example.com", |
2459 | - status=EmailStatus.NEW) |
2460 | - |
2461 | - registration = RegistrationSet() |
2462 | - r = registration.register(email="register@example.com", |
2463 | - password="blogdf3Daa", |
2464 | - captcha_solution="solution", |
2465 | - captcha_id="id") |
2466 | - |
2467 | - self.assertEquals(r['errors'], {'email': ["Email already registered"]}) |
2468 | - |
2469 | - forms.get_current_browser_request = old_gcbr |
2470 | |
2471 | === removed file 'identityprovider/tests/test_wsgi.py' |
2472 | --- identityprovider/tests/test_wsgi.py 2010-11-15 16:24:53 +0000 |
2473 | +++ identityprovider/tests/test_wsgi.py 1970-01-01 00:00:00 +0000 |
2474 | @@ -1,71 +0,0 @@ |
2475 | -from mock import Mock |
2476 | - |
2477 | -from unittest import TestCase |
2478 | -from identityprovider.wsgi import WSGIDispatch, make_app |
2479 | - |
2480 | - |
2481 | - |
2482 | -class StubWSGIApp(object): |
2483 | - |
2484 | - def __init__(self): |
2485 | - self.called = False |
2486 | - |
2487 | - def __call__(self, environ, start_response): |
2488 | - self.called = True |
2489 | - return [] |
2490 | - |
2491 | - |
2492 | - |
2493 | -class WSGIDispatchTestCase(TestCase): |
2494 | - |
2495 | - |
2496 | - def setUp(self): |
2497 | - self.app_1 = StubWSGIApp() |
2498 | - self.app_2 = StubWSGIApp() |
2499 | - self.default_app = StubWSGIApp() |
2500 | - self.mapping = (('/a1', self.app_1), ('/a2', self.app_2)) |
2501 | - self.dispatch = WSGIDispatch(self.mapping, self.default_app) |
2502 | - |
2503 | - |
2504 | - def call_dispatch_with_path(self, path): |
2505 | - self.dispatch({'PATH_INFO': path}, lambda x: x) |
2506 | - |
2507 | - |
2508 | - def test_call_is_routed_to_the_right_application(self): |
2509 | - self.call_dispatch_with_path('/a1') |
2510 | - |
2511 | - self.assertTrue(self.app_1.called) |
2512 | - |
2513 | - |
2514 | - def test_call_is_routed_to_default_if_path_is_not_matching(self): |
2515 | - self.call_dispatch_with_path('/other') |
2516 | - |
2517 | - self.assertTrue(self.default_app.called) |
2518 | - |
2519 | - |
2520 | - |
2521 | -class MakeAppTestCase(TestCase): |
2522 | - |
2523 | - |
2524 | - def test_make_sure_that_callable_is_returned(self): |
2525 | - app = make_app() |
2526 | - |
2527 | - self.assertTrue(callable(app)) |
2528 | - |
2529 | - |
2530 | - def test_oops_from_api_contain_traceback(self): |
2531 | - environ = { |
2532 | - 'REQUEST_METHOD': 'get', |
2533 | - 'SERVER_NAME': 'test', |
2534 | - 'SERVER_PORT': 80, |
2535 | - 'PATH_INFO': '/api/1.0/', |
2536 | - } |
2537 | - |
2538 | - mock_app = Mock() |
2539 | - mock_app.side_effect = Exception("App error") |
2540 | - |
2541 | - app = make_app() |
2542 | - app.mapping[0][1].application = mock_app |
2543 | - app(environ, Mock()) |
2544 | - |
2545 | - self.assertTrue('oops-dump' in environ) |
2546 | |
2547 | === modified file 'identityprovider/urls.py' |
2548 | --- identityprovider/urls.py 2011-03-04 17:09:45 +0000 |
2549 | +++ identityprovider/urls.py 2011-05-24 16:31:52 +0000 |
2550 | @@ -126,3 +126,8 @@ |
2551 | urlpatterns += patterns('identityprovider.views.ui', |
2552 | (r'^\+(?P<page_name>description|faq)$', 'static_page'), |
2553 | ) |
2554 | + |
2555 | +# Lazr.restful backwards compatible api |
2556 | +urlpatterns += patterns('', |
2557 | + (r'api/1.0/', include('identityprovider.api10.urls')), |
2558 | +) |
2559 | |
2560 | === removed directory 'identityprovider/webservice' |
2561 | === removed file 'identityprovider/webservice/__init__.py' |
2562 | --- identityprovider/webservice/__init__.py 2010-04-21 15:29:24 +0000 |
2563 | +++ identityprovider/webservice/__init__.py 1970-01-01 00:00:00 +0000 |
2564 | @@ -1,2 +0,0 @@ |
2565 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
2566 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2567 | |
2568 | === removed file 'identityprovider/webservice/forms.py' |
2569 | --- identityprovider/webservice/forms.py 2011-03-14 12:51:02 +0000 |
2570 | +++ identityprovider/webservice/forms.py 1970-01-01 00:00:00 +0000 |
2571 | @@ -1,65 +0,0 @@ |
2572 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
2573 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2574 | - |
2575 | -# We use Django forms for webservice input validation |
2576 | - |
2577 | -from django import forms |
2578 | -from django.forms import fields |
2579 | -from django.utils.translation import ugettext as _ |
2580 | -from lazr.restful.utils import get_current_browser_request |
2581 | - |
2582 | -from identityprovider.utils import password_policy_compliant |
2583 | -from identityprovider.models.captcha import Captcha, VerifyCaptchaError |
2584 | - |
2585 | - |
2586 | -PASSWORD_POLICY_ERROR = _("Password must be at least " |
2587 | - "8 characters long, and must contain at least one " |
2588 | - "number and an upper case letter.") |
2589 | - |
2590 | - |
2591 | -class WebserviceCreateAccountForm(forms.Form): |
2592 | - email = fields.EmailField() |
2593 | - password = fields.CharField(max_length=256) |
2594 | - captcha_id = fields.CharField(max_length=1024) |
2595 | - captcha_solution = fields.CharField(max_length=256) |
2596 | - displayname = fields.CharField(required=False) |
2597 | - remote_ip = fields.CharField(max_length=256) |
2598 | - |
2599 | - def clean_password(self): |
2600 | - if 'password' in self.cleaned_data: |
2601 | - password = self.cleaned_data['password'] |
2602 | - try: |
2603 | - str(password) |
2604 | - except UnicodeEncodeError: |
2605 | - raise forms.ValidationError( |
2606 | - _("Invalid characters in password")) |
2607 | - if not password_policy_compliant(password): |
2608 | - raise forms.ValidationError(PASSWORD_POLICY_ERROR) |
2609 | - return password |
2610 | - |
2611 | - def clean(self): |
2612 | - cleaned_data = self.cleaned_data |
2613 | - captcha_id = cleaned_data.get('captcha_id') |
2614 | - captcha_solution = cleaned_data.get('captcha_solution') |
2615 | - |
2616 | - # The remote IP address is absolutely required, and comes from |
2617 | - # SSO itself, not from the client. If it's missing, it's a |
2618 | - # programming error, and should not be returned to the client |
2619 | - # as a validation error. So, we use a normal key lookup here. |
2620 | - remote_ip = cleaned_data['remote_ip'] |
2621 | - |
2622 | - request = get_current_browser_request() |
2623 | - |
2624 | - captcha = Captcha(captcha_id) |
2625 | - |
2626 | - try: |
2627 | - if captcha.verify(captcha_solution, remote_ip): |
2628 | - return cleaned_data |
2629 | - except VerifyCaptchaError, e: |
2630 | - if getattr(request.environment, '__setitem__', False): |
2631 | - request.environment['oops-dump'] = True |
2632 | - logging.warning(e.traceback) |
2633 | - logging.warning("reCaptcha connection error") |
2634 | - |
2635 | - # not verified |
2636 | - raise forms.ValidationError(_("Wrong captcha solution.")) |
2637 | |
2638 | === removed file 'identityprovider/webservice/interfaces.py' |
2639 | --- identityprovider/webservice/interfaces.py 2011-03-14 12:51:02 +0000 |
2640 | +++ identityprovider/webservice/interfaces.py 1970-01-01 00:00:00 +0000 |
2641 | @@ -1,191 +0,0 @@ |
2642 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
2643 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2644 | - |
2645 | -from lazr.restful.frameworks.django import IDjangoLocation |
2646 | -from lazr.restful.declarations import (export_as_webservice_entry, |
2647 | - collection_default_content, exported, export_write_operation, |
2648 | - export_as_webservice_collection, operation_parameters, |
2649 | - export_read_operation) |
2650 | -from zope.schema import Text, TextLine, List |
2651 | - |
2652 | - |
2653 | -__all__ = [ |
2654 | - 'IAccount', |
2655 | - 'ICaptcha', |
2656 | - 'IAccountSet', |
2657 | - 'ICaptchaSet', |
2658 | - 'IRegistrationSet', |
2659 | - 'IValidationSet', |
2660 | - 'IAuthenticationSet' |
2661 | -] |
2662 | - |
2663 | - |
2664 | -class IAccount(IDjangoLocation): |
2665 | - export_as_webservice_entry() |
2666 | - id = exported(Text(title=u"Account ID")) |
2667 | - preferred_email = exported(Text(title=u"Primary email address")) |
2668 | - verified_emails = exported(List(title=u"List of verified emails", |
2669 | - value_type=Text())) |
2670 | - unverified_emails = exported(List(value_type=Text(), |
2671 | - title=u"List of unverified emails")) |
2672 | - |
2673 | - |
2674 | -class IRegistration(IAccount): |
2675 | - export_as_webservice_entry() |
2676 | - |
2677 | - |
2678 | -class IValidation(IAccount): |
2679 | - export_as_webservice_entry() |
2680 | - |
2681 | - |
2682 | -class IAuthentication(IAccount): |
2683 | - export_as_webservice_entry() |
2684 | - |
2685 | - |
2686 | -class ICaptcha(IDjangoLocation): |
2687 | - export_as_webservice_entry() |
2688 | - id = exported(Text(title=u"Captcha ID")) |
2689 | - content = exported(Text(title=u"Captcha content")) |
2690 | - |
2691 | - |
2692 | -class IAccountSet(IDjangoLocation): |
2693 | - export_as_webservice_collection(IAccount) |
2694 | - |
2695 | - @collection_default_content() |
2696 | - def getAll(): |
2697 | - """Fail, as no default content should be provided.""" |
2698 | - |
2699 | - def get(request, unique_id): |
2700 | - """Retrieve a account by its ID.""" |
2701 | - |
2702 | - @export_read_operation() |
2703 | - def me(): |
2704 | - """ Get details for the currently authenticated user. """ |
2705 | - |
2706 | - @operation_parameters( |
2707 | - team_names=List(title=u"List of team names to check", |
2708 | - value_type=Text()) |
2709 | - ) |
2710 | - @export_read_operation() |
2711 | - def team_memberships(team_names): |
2712 | - """Query account for team memberships""" |
2713 | - |
2714 | - @operation_parameters( |
2715 | - email_token=TextLine(title=u"Email validation token.")) |
2716 | - @export_read_operation() |
2717 | - def validate_email(email_token): |
2718 | - """Validate email by sending token user received in email""" |
2719 | - |
2720 | - |
2721 | -class IAuthenticationSet(IAccountSet): |
2722 | - export_as_webservice_collection(IAuthentication) |
2723 | - |
2724 | - @collection_default_content() |
2725 | - def getAll(): |
2726 | - """Fail, as no default content should be provided.""" |
2727 | - |
2728 | - @operation_parameters( |
2729 | - token_name=TextLine(title=u"Token name.")) |
2730 | - @export_read_operation() |
2731 | - def authenticate(token_name): |
2732 | - """Obtain OAuth token for logged in user""" |
2733 | - |
2734 | - @operation_parameters( |
2735 | - consumer_key=TextLine(title=u"User's OpenID identifier")) |
2736 | - @export_read_operation() |
2737 | - def list_tokens(consumer_key): |
2738 | - """ Get the currently valid tokens for a given user """ |
2739 | - |
2740 | - @operation_parameters( |
2741 | - token=TextLine(title=u"The token you want to validate"), |
2742 | - consumer_key=TextLine(title=u"The consumer key (openid identifier)") |
2743 | - ) |
2744 | - @export_read_operation() |
2745 | - def validate_token(token, consumer_key): |
2746 | - """Check that a token is valid. |
2747 | - |
2748 | - If valid, this method return the token and consumer secrets |
2749 | - """ |
2750 | - |
2751 | - @operation_parameters( |
2752 | - token=TextLine(title=u"The token you want to invalidate"), |
2753 | - consumer_key=TextLine(title=u"The consumer key (openid identifier)") |
2754 | - ) |
2755 | - @export_write_operation() |
2756 | - def invalidate_token(token, consumer_key): |
2757 | - """ Make the given token invalid """ |
2758 | - |
2759 | - @operation_parameters( |
2760 | - team_names=List(title=u"List of team names to check", |
2761 | - value_type=Text()), |
2762 | - openid_identifier=Text(title=u"OpenID Identifier used for asking for" |
2763 | - u"team memberships on behalf of other user.") |
2764 | - ) |
2765 | - @export_read_operation() |
2766 | - def team_memberships(team_names, openid_identifier): |
2767 | - """Query user for team memberships""" |
2768 | - |
2769 | - @operation_parameters( |
2770 | - email=Text(title=u"Email address of the account")) |
2771 | - @export_read_operation() |
2772 | - def account_by_email(email): |
2773 | - """Retrieve account information based on email address""" |
2774 | - |
2775 | - @operation_parameters( |
2776 | - openid=Text(title=u"OpenID Identifier of the account")) |
2777 | - @export_read_operation() |
2778 | - def account_by_openid(openid): |
2779 | - """Retrieve account information based on OpenID identifier""" |
2780 | - |
2781 | - |
2782 | -class IValidationSet(IAccountSet): |
2783 | - pass |
2784 | - |
2785 | - |
2786 | -class IRegistrationSet(IAccountSet): |
2787 | - export_as_webservice_collection(IRegistration) |
2788 | - |
2789 | - @collection_default_content() |
2790 | - def getAll(): |
2791 | - """Fail, as no default content should be provided.""" |
2792 | - |
2793 | - @operation_parameters( |
2794 | - email=TextLine(title=u"Email address."), |
2795 | - password=TextLine(title=u"Password should be at least 8 " |
2796 | - "characters long and contain at least one uppercase letter " |
2797 | - "and a number."), |
2798 | - captcha_id=TextLine(title=u"ID for the generated captcha"), |
2799 | - captcha_solution=TextLine(title=u"Solution for the generated " |
2800 | - "captcha."), |
2801 | - displayname=TextLine(title=u"Full name", required=False)) |
2802 | - @export_write_operation() |
2803 | - def register(email, password, captcha_id, captcha_solution, displayname=''): |
2804 | - """ Generate a new captcha """ |
2805 | - |
2806 | - @operation_parameters(email=TextLine(title=u"Email address.")) |
2807 | - @export_write_operation() |
2808 | - def request_password_reset_token(email): |
2809 | - """Request password reset code to be sent to the email""" |
2810 | - |
2811 | - @operation_parameters( |
2812 | - email=TextLine(title=u"Email address."), |
2813 | - token=TextLine(title=u"Password reset token."), |
2814 | - new_password=TextLine(title=u"New password")) |
2815 | - @export_write_operation() |
2816 | - def set_new_password(email, token, new_password): |
2817 | - """Set new password for given user""" |
2818 | - |
2819 | - |
2820 | -class ICaptchaSet(IDjangoLocation): |
2821 | - export_as_webservice_collection(ICaptcha) |
2822 | - |
2823 | - @collection_default_content() |
2824 | - def getAll(): |
2825 | - """Fail, as no default content should be provided.""" |
2826 | - |
2827 | - def get(request, unique_id): |
2828 | - """Retrieve a captcha by its ID.""" |
2829 | - |
2830 | - @export_write_operation() |
2831 | - def new(): |
2832 | - """ Generate a new captcha """ |
2833 | |
2834 | === removed file 'identityprovider/webservice/models.py' |
2835 | --- identityprovider/webservice/models.py 2011-03-14 12:51:02 +0000 |
2836 | +++ identityprovider/webservice/models.py 1970-01-01 00:00:00 +0000 |
2837 | @@ -1,389 +0,0 @@ |
2838 | -# Copyright 2010 Canonical Ltd. This software is licensed under the |
2839 | -# GNU Affero General Public License version 3 (see the file LICENSE). |
2840 | - |
2841 | -from zope.component import getUtility |
2842 | -from zope.interface import implements, classImplements |
2843 | - |
2844 | -from lazr.restful.interfaces import IServiceRootResource |
2845 | -from lazr.restful.simple import (RootResourceAbsoluteURL, RootResource, |
2846 | - TraverseWithGet) |
2847 | -from lazr.restful.frameworks.django import DjangoWebServiceConfiguration |
2848 | -from lazr.restful.wsgi import BaseWSGIWebServiceConfiguration |
2849 | -from lazr.restful.utils import get_current_browser_request |
2850 | -from lazr.restful.declarations import webservice_error |
2851 | - |
2852 | -from oauth_backend.models import Token |
2853 | -from identityprovider.models import (Account, EmailAddress, |
2854 | - AccountPassword, APIUser, Person) |
2855 | -from identityprovider.models.const import ( |
2856 | - AccountCreationRationale, AccountStatus, EmailStatus, LoginTokenType) |
2857 | -from identityprovider.models.authtoken import AuthToken, AuthTokenFactory |
2858 | -from identityprovider.utils import encrypt_launchpad_password |
2859 | -from identityprovider.utils import password_policy_compliant |
2860 | - |
2861 | -from identityprovider.webservice.interfaces import (IAccount, ICaptcha, |
2862 | - IRegistration, IValidation, IAuthentication, IAccountSet, |
2863 | - IAuthenticationSet, IRegistrationSet, ICaptchaSet) |
2864 | -from identityprovider.webservice.forms import ( |
2865 | - WebserviceCreateAccountForm, PASSWORD_POLICY_ERROR) |
2866 | -from identityprovider.models.captcha import ( |
2867 | - Captcha as CaptchaModel, |
2868 | - NewCaptchaError, |
2869 | -) |
2870 | -from identityprovider.views.server import get_team_memberships |
2871 | -from identityprovider.utils import (CannotResetPasswordException, |
2872 | - PersonAndAccountNotFoundException, get_person_and_account_by_email) |
2873 | -from identityprovider.signals import (application_token_invalidated, |
2874 | - application_token_created, |
2875 | - account_email_validated, |
2876 | - account_created) |
2877 | - |
2878 | - |
2879 | -def api_user_required(func): |
2880 | - def wrapper(*args, **kwargs): |
2881 | - request = get_current_browser_request() |
2882 | - user = request.environment.get('authenticated_user') |
2883 | - if user: |
2884 | - if isinstance(user, APIUser): |
2885 | - return func(*args, **kwargs) |
2886 | - request.response.setStatus(403) |
2887 | - return '403 Forbidden' |
2888 | - return wrapper |
2889 | - |
2890 | - |
2891 | -class RootAbsoluteURL(RootResourceAbsoluteURL): |
2892 | - """ |
2893 | - This class contains no code of its own. It's defined so that grok will pick |
2894 | - it up. |
2895 | - """ |
2896 | - |
2897 | - |
2898 | -# Create set objects for top-level collections, which are all |
2899 | -# pretty much the same. |
2900 | -def make_set(name, url_path, interface, manager, id_field): |
2901 | - """Create a object to publish as a top-level collection.""" |
2902 | - def getAll(self): |
2903 | - return [] |
2904 | - |
2905 | - def get(self, request, unique_id): |
2906 | - return manager.objects.get(**{id_field: unique_id}) |
2907 | - |
2908 | - def __parent__(self): |
2909 | - return getUtility(IServiceRootResource) |
2910 | - |
2911 | - dict = {'getAll': getAll, |
2912 | - 'get': get, |
2913 | - '__parent__': property(__parent__), |
2914 | - '__url_path__': url_path} |
2915 | - |
2916 | - set_class = type(name, (TraverseWithGet,), dict) |
2917 | - classImplements(set_class, interface) |
2918 | - return set_class |
2919 | - |
2920 | -def _serialize_account(user): |
2921 | - emails = EmailAddress.objects.filter(account=user, |
2922 | - status=EmailStatus.VALIDATED) |
2923 | - preferred_email = user.preferredemail |
2924 | - if preferred_email is not None: |
2925 | - preferred_email = preferred_email.email |
2926 | - |
2927 | - if user.person: |
2928 | - username = user.person.name |
2929 | - else: |
2930 | - username = user.openid_identifier |
2931 | - |
2932 | - return { |
2933 | - 'username': username, |
2934 | - 'displayname': user.displayname, |
2935 | - 'openid_identifier': user.openid_identifier, |
2936 | - 'preferred_email': preferred_email, |
2937 | - 'verified_emails': [e.email for e in emails], |
2938 | - 'unverified_emails': [e.email for e in user.unverified_emails()], |
2939 | - } |
2940 | - |
2941 | - |
2942 | - |
2943 | -ValidationSet = make_set('ValidationSet', 'validation', IAccountSet, |
2944 | - Account, 'id') |
2945 | - |
2946 | - |
2947 | -class AccountSet(make_set('BaseAccountSet', 'accounts', IAccountSet, |
2948 | - Account, 'id')): |
2949 | - """ These methods are protected with OAuth.""" |
2950 | - def me(self): |
2951 | - request = get_current_browser_request() |
2952 | - user = request.environment.get('authenticated_user') |
2953 | - return _serialize_account(user) |
2954 | - |
2955 | - def team_memberships(self, team_names): |
2956 | - request = get_current_browser_request() |
2957 | - user = request.environment['authenticated_user'] |
2958 | - memberships = get_team_memberships(team_names, user, True) |
2959 | - return memberships |
2960 | - |
2961 | - def validate_email(self, email_token): |
2962 | - request = get_current_browser_request() |
2963 | - user = request.environment['authenticated_user'] |
2964 | - try: |
2965 | - token = user.authtoken_set.get( |
2966 | - token=email_token, token_type=LoginTokenType.VALIDATEEMAIL) |
2967 | - |
2968 | - email = EmailAddress.objects.get(email__iexact=token.email) |
2969 | - email.status = EmailStatus.VALIDATED |
2970 | - email.save() |
2971 | - |
2972 | - token.consume() |
2973 | - |
2974 | - account_email_validated.send( |
2975 | - openid_identifier=user.openid_identifier, |
2976 | - sender=self) |
2977 | - return {'email': email.email} |
2978 | - except AuthToken.DoesNotExist: |
2979 | - return {'errors': {'email_token': ["Bad email token!"]}} |
2980 | - |
2981 | - |
2982 | -def plain_user_required(func): |
2983 | - def wrapper(*args, **kwargs): |
2984 | - request = get_current_browser_request() |
2985 | - user = request.environment.get('authenticated_user') |
2986 | - if user: |
2987 | - if isinstance(user, Account): |
2988 | - return func(*args, **kwargs) |
2989 | - request.response.setStatus(403) |
2990 | - return '403 Forbidden' |
2991 | - return wrapper |
2992 | - |
2993 | - |
2994 | -class AuthenticationSet(make_set('BaseAuthenticationSet', 'authentications', |
2995 | - IAuthenticationSet, Account, 'id')): |
2996 | - """ All these methods assume that they're run behind Basic Auth """ |
2997 | - @plain_user_required |
2998 | - def authenticate(self, token_name): |
2999 | - request = get_current_browser_request() |
3000 | - account = request.environment['authenticated_user'] |
3001 | - token = account.create_oauth_token(token_name) |
3002 | - application_token_created.send( |
3003 | - sender=self, openid_identifier=account.openid_identifier) |
3004 | - return token.serialize() |
3005 | - |
3006 | - @api_user_required |
3007 | - def list_tokens(self, consumer_key): |
3008 | - tokens = Token.objects.filter( |
3009 | - consumer__user__username=consumer_key) |
3010 | - result = [{'token': t.token, 'name': t.name} for t in tokens] |
3011 | - return result |
3012 | - |
3013 | - @api_user_required |
3014 | - def validate_token(self, token, consumer_key): |
3015 | - try: |
3016 | - token = Token.objects.get( |
3017 | - consumer__user__username=consumer_key, |
3018 | - token=token) |
3019 | - return token.serialize() |
3020 | - except Token.DoesNotExist: |
3021 | - return False |
3022 | - |
3023 | - @api_user_required |
3024 | - def invalidate_token(self, token, consumer_key): |
3025 | - tokens = Token.objects.filter(token=token, |
3026 | - consumer__user__username=consumer_key) |
3027 | - tokens.delete() |
3028 | - application_token_invalidated.send( |
3029 | - sender=self, openid_identifier=consumer_key) |
3030 | - |
3031 | - |
3032 | - @api_user_required |
3033 | - def team_memberships(self, team_names, openid_identifier): |
3034 | - accounts = Account.objects.filter(openid_identifier=openid_identifier) |
3035 | - accounts = list(accounts) |
3036 | - |
3037 | - if len(accounts) == 1: |
3038 | - account = accounts[0] |
3039 | - memberships = get_team_memberships(team_names, account, False) |
3040 | - return memberships |
3041 | - else: |
3042 | - return [] |
3043 | - |
3044 | - @api_user_required |
3045 | - def account_by_email(self, email): |
3046 | - account = Account.objects.get_by_email(email) |
3047 | - if account: |
3048 | - return _serialize_account(account) |
3049 | - else: |
3050 | - return None |
3051 | - |
3052 | - @api_user_required |
3053 | - def account_by_openid(self, openid): |
3054 | - try: |
3055 | - account = Account.objects.get(openid_identifier=openid) |
3056 | - except Account.DoesNotExist: |
3057 | - return None |
3058 | - else: |
3059 | - return _serialize_account(account) |
3060 | - |
3061 | -class CanNotResetPasswordError(Exception): |
3062 | - webservice_error(403) |
3063 | - |
3064 | - |
3065 | -class RegistrationSet(make_set('BaseRegistrationSet', 'registration', |
3066 | - IRegistrationSet, Account, 'id')): |
3067 | - def register(self, **kwargs): |
3068 | - request = get_current_browser_request() |
3069 | - # TODO: request doesn't exist in a test, unless we go through |
3070 | - # the test client browser. |
3071 | - if request is None: |
3072 | - kwargs['remote_ip'] = '127.0.0.1' |
3073 | - else: |
3074 | - if hasattr(request, 'environment'): |
3075 | - kwargs['remote_ip'] = request.environment['REMOTE_ADDR'] |
3076 | - else: |
3077 | - kwargs['remote_ip'] = request.environ['REMOTE_ADDR'] |
3078 | - form = WebserviceCreateAccountForm(kwargs) |
3079 | - if not form.is_valid(): |
3080 | - errors = dict((k, map(unicode, v)) |
3081 | - for (k, v) in form.errors.items()) |
3082 | - result = {'status': 'error', 'errors': errors} |
3083 | - return result |
3084 | - |
3085 | - cleaned_data = form.cleaned_data |
3086 | - requested_email = cleaned_data['email'] |
3087 | - emails = EmailAddress.objects.filter(email__iexact=requested_email) |
3088 | - if len(emails) > 0: |
3089 | - return {'status': 'error', 'errors': |
3090 | - {'email': ['Email already registered']}} |
3091 | - |
3092 | - account = Account.objects.create( |
3093 | - creation_rationale=AccountCreationRationale.OWNER_CREATED_LAUNCHPAD, |
3094 | - status=AccountStatus.ACTIVE, |
3095 | - displayname=cleaned_data.get('displayname', '')) |
3096 | - |
3097 | - account.emailaddress_set.create( |
3098 | - email=cleaned_data['email'], |
3099 | - status=EmailStatus.NEW) |
3100 | - |
3101 | - AccountPassword.objects.create( |
3102 | - password=encrypt_launchpad_password(cleaned_data['password']), |
3103 | - account=account) |
3104 | - |
3105 | - token = AuthTokenFactory().new_api_email_validation_token( |
3106 | - account, cleaned_data['email']) |
3107 | - |
3108 | - token.sendNewUserEmail('api-newuser.txt') |
3109 | - |
3110 | - account_created.send(sender=self, |
3111 | - openid_identifier=account.openid_identifier) |
3112 | - |
3113 | - return { |
3114 | - 'status': 'ok', |
3115 | - 'message': "Email verification required." |
3116 | - } |
3117 | - |
3118 | - def request_password_reset_token(self, email): |
3119 | - error = False |
3120 | - try: |
3121 | - person, account = get_person_and_account_by_email(email) |
3122 | - except (CannotResetPasswordException, |
3123 | - PersonAndAccountNotFoundException): |
3124 | - error = True |
3125 | - |
3126 | - if error or (account is not None and not account.can_reset_password): |
3127 | - raise CanNotResetPasswordError( |
3128 | - "Can't reset password for this account") |
3129 | - |
3130 | - token = AuthTokenFactory().new(account, email, email, |
3131 | - LoginTokenType.PASSWORDRECOVERY, None) |
3132 | - |
3133 | - token.sendPasswordResetEmail() |
3134 | - |
3135 | - return { |
3136 | - 'status': 'ok', |
3137 | - 'message': "Password reset token sent." |
3138 | - } |
3139 | - |
3140 | - def set_new_password(self, email, token, new_password): |
3141 | - token = AuthToken.objects.get( |
3142 | - email=email, token=token, |
3143 | - token_type=LoginTokenType.PASSWORDRECOVERY) |
3144 | - if not token.requester: |
3145 | - token.delete() |
3146 | - return { |
3147 | - 'status': 'error', |
3148 | - 'message': "Wrong token, request new one." |
3149 | - } |
3150 | - if not password_policy_compliant(new_password): |
3151 | - return { |
3152 | - 'status': 'error', |
3153 | - 'errors': [PASSWORD_POLICY_ERROR] |
3154 | - } |
3155 | - password_obj = token.requester.accountpassword |
3156 | - password_obj.password = encrypt_launchpad_password(new_password) |
3157 | - password_obj.save() |
3158 | - |
3159 | - token.consume() |
3160 | - |
3161 | - return { |
3162 | - 'status': 'ok', |
3163 | - 'message': "Password changed" |
3164 | - } |
3165 | - |
3166 | - |
3167 | -class Captcha(object): |
3168 | - implements(ICaptcha) |
3169 | - message = '' |
3170 | - date_created = '' |
3171 | - |
3172 | - @property |
3173 | - def content(self): |
3174 | - return '' |
3175 | - |
3176 | - def __unicode__(self): |
3177 | - return "%s" % (self.id,) |
3178 | - |
3179 | - # IDjangoLocation implementation |
3180 | - @property |
3181 | - def __parent__(self): |
3182 | - from identityprovider.webservice.models import CaptchaSet |
3183 | - return CaptchaSet() |
3184 | - |
3185 | - @property |
3186 | - def __url_path__(self): |
3187 | - return str(self.id) |
3188 | - |
3189 | - |
3190 | -class CaptchaSet(make_set('BaseCaptchaSet', 'captchas', ICaptchaSet, |
3191 | - Captcha, 'id')): |
3192 | - def new(self): |
3193 | - request = get_current_browser_request() |
3194 | - try: |
3195 | - return CaptchaModel.new().serialize() |
3196 | - except NewCaptchaError, e: |
3197 | - request.environment['oops-dump'] = True |
3198 | - logging.warning(e.traceback) |
3199 | - logging.warning("Failed to connect to reCaptcha server") |
3200 | - # TODO: Some better return here? |
3201 | - return e.dummy |
3202 | - |
3203 | - |
3204 | - |
3205 | -class SSOWebServiceRootResource(RootResource): |
3206 | - """The root resource for the web service""" |
3207 | - |
3208 | - def _build_top_level_objects(self): |
3209 | - """Create data structure of top level objects.""" |
3210 | - collections = { |
3211 | - 'captchas': (ICaptcha, CaptchaSet()), |
3212 | - 'registration': (IRegistration, RegistrationSet()), |
3213 | - 'validation': (IValidation, ValidationSet()), |
3214 | - 'accounts': (IAccount, AccountSet()), |
3215 | - 'authentications': (IAuthentication, AuthenticationSet()), |
3216 | - } |
3217 | - return collections, {} |
3218 | - |
3219 | - |
3220 | -class WebServiceConfiguration(DjangoWebServiceConfiguration, |
3221 | - BaseWSGIWebServiceConfiguration): |
3222 | - """Configuration information for this web service. |
3223 | - |
3224 | - This class contains no code of its own. It's defined so that grok |
3225 | - will pick it up. The actual configuration is in settings.py. |
3226 | - """ |
3227 | |
3228 | === removed file 'identityprovider/webservice/site.zcml' |
3229 | --- identityprovider/webservice/site.zcml 2010-04-21 15:29:24 +0000 |
3230 | +++ identityprovider/webservice/site.zcml 1970-01-01 00:00:00 +0000 |
3231 | @@ -1,15 +0,0 @@ |
3232 | -<!-- -*- xml -*- --> |
3233 | -<configure xmlns="http://namespaces.zope.org/zope" |
3234 | - xmlns:webservice="http://namespaces.canonical.com/webservice" |
3235 | - xmlns:grok="http://namespaces.zope.org/grok"> |
3236 | - <!-- Copyright 2010 Canonical Ltd. This software is licensed under the |
3237 | - GNU Affero General Public License version 3 (see the file LICENSE). --> |
3238 | - |
3239 | - <include package="lazr.restful" file="basic-site.zcml" /> |
3240 | - <include package="lazr.restful.frameworks" file="django.zcml" /> |
3241 | - <webservice:register module="identityprovider.webservice.interfaces" /> |
3242 | - <grok:grok package="identityprovider.webservice" /> |
3243 | - |
3244 | - <securityPolicy |
3245 | - component="zope.security.simplepolicies.PermissiveSecurityPolicy" /> |
3246 | -</configure> |
3247 | |
3248 | === modified file 'identityprovider/wsgi.py' |
3249 | --- identityprovider/wsgi.py 2010-12-22 15:23:55 +0000 |
3250 | +++ identityprovider/wsgi.py 2011-05-24 16:31:52 +0000 |
3251 | @@ -1,90 +1,7 @@ |
3252 | # Copyright 2010 Canonical Ltd. This software is licensed under the |
3253 | # GNU Affero General Public License version 3 (see the file LICENSE). |
3254 | -import logging |
3255 | -import traceback |
3256 | - |
3257 | from django.core.handlers.wsgi import WSGIHandler |
3258 | -from django.conf import settings |
3259 | - |
3260 | -from lazr.restful.wsgi import WSGIApplication |
3261 | -from lazr.authentication.wsgi import BasicAuthMiddleware, OAuthMiddleware |
3262 | -from identityprovider.auth import basic_authenticate, oauth_authenticate |
3263 | -from oauth_backend.models import DataStore |
3264 | - |
3265 | - |
3266 | - |
3267 | -class WSGIDispatch(object): |
3268 | - """ |
3269 | - Dispatch request to various WSGI applications based on beginning of the |
3270 | - PATH_INFO. |
3271 | - |
3272 | - """ |
3273 | - def __init__(self, mapping, default_application): |
3274 | - """ |
3275 | - :param mapping: iterable with each item as (url, wsgi application) |
3276 | - :param default_application: application to use if none of urls are |
3277 | - matching |
3278 | - """ |
3279 | - self.mapping = mapping |
3280 | - self.default_application = default_application |
3281 | - |
3282 | - |
3283 | - def __call__(self, environ, start_response): |
3284 | - path_info = environ.get('PATH_INFO', '/') |
3285 | - for app_url, app in self.mapping: |
3286 | - if path_info.startswith(app_url): |
3287 | - return app(environ, start_response) |
3288 | - return self.default_application(environ, start_response) |
3289 | - |
3290 | - |
3291 | - |
3292 | -class OopsTracebackReporter(object): |
3293 | - |
3294 | - |
3295 | - def __init__(self, application): |
3296 | - self.application = application |
3297 | - |
3298 | - |
3299 | - def __call__(self, environ, start_response): |
3300 | - try: |
3301 | - return self.application(environ, start_response) |
3302 | - except Exception: |
3303 | - environ['oops-dump'] = True |
3304 | - logging.warning(traceback.format_exc()) |
3305 | - start_response("500 Internal Server Error", |
3306 | - [("Content-type", "text/plain")]) |
3307 | - return ["An unexpected error occurred while " |
3308 | - "processing an API request"] |
3309 | - |
3310 | - |
3311 | |
3312 | def make_app(): |
3313 | - api_re_prefix = r"/api/[\d.]+/" |
3314 | - |
3315 | django = WSGIHandler() |
3316 | - |
3317 | - if not getattr(WSGIApplication, 'configured', False): |
3318 | - WSGIApplication.configure_server(settings.API_HOST, None, |
3319 | - 'identityprovider.webservice') |
3320 | - WSGIApplication.configured = True |
3321 | - |
3322 | - lazr_restful = WSGIApplication |
3323 | - |
3324 | - basic = BasicAuthMiddleware( |
3325 | - lazr_restful, |
3326 | - authenticate_with=basic_authenticate, |
3327 | - protect_path_pattern=api_re_prefix + "authentications" |
3328 | - ) |
3329 | - |
3330 | - oauth = OAuthMiddleware( |
3331 | - basic, |
3332 | - authenticate_with=oauth_authenticate, |
3333 | - data_store=DataStore(), |
3334 | - protect_path_pattern=api_re_prefix + "accounts" |
3335 | - ) |
3336 | - |
3337 | - api = OopsTracebackReporter(oauth) |
3338 | - |
3339 | - dispatch = WSGIDispatch([('/api/1.0', api)], django) |
3340 | - |
3341 | - return dispatch |
3342 | + return django |
3343 | |
3344 | === modified file 'mockservice/sso_mockserver/mockserver.py' |
3345 | --- mockservice/sso_mockserver/mockserver.py 2011-03-14 15:55:46 +0000 |
3346 | +++ mockservice/sso_mockserver/mockserver.py 2011-05-24 16:31:52 +0000 |
3347 | @@ -148,7 +148,7 @@ |
3348 | return |
3349 | |
3350 | def fail403(self): |
3351 | - status = '403 Forbidden' |
3352 | + status = '403 FORBIDDEN' |
3353 | headers = [('Content-type', 'text/plain')] |
3354 | self.start_response(status, headers) |
3355 | return |
3356 | @@ -236,17 +236,17 @@ |
3357 | op = form['ws.op'] |
3358 | if op == 'authenticate': |
3359 | if not self.check_plain_user(): |
3360 | - return ['403 Forbidden'] |
3361 | + return ['403 FORBIDDEN'] |
3362 | token = new_token(form['token_name']) |
3363 | return self.jsons(json=token) |
3364 | elif op == 'validate_token': |
3365 | if not self.check_server_user(): |
3366 | - return ['403 Forbidden'] |
3367 | + return ['403 FORBIDDEN'] |
3368 | token = tokens[form['token']] |
3369 | return self.jsons(json=simplejson.dumps(token)) |
3370 | elif op == 'list_tokens': |
3371 | if not self.check_server_user(): |
3372 | - return ['403 Forbidden'] |
3373 | + return ['403 FORBIDDEN'] |
3374 | result = [{'token': t['token'], 'name': t['name']} |
3375 | for t in tokens.values() |
3376 | if t['consumer_key'] == form['consumer_key'] |
3377 | @@ -276,7 +276,7 @@ |
3378 | op = form['ws.op'] |
3379 | if op == 'invalidate_token': |
3380 | if not self.check_server_user(): |
3381 | - return ['403 Forbidden'] |
3382 | + return ['403 FORBIDDEN'] |
3383 | if form['token'] in tokens: |
3384 | del tokens[form['token']] |
3385 | return self.jsons(json='null') |
3386 | |
3387 | === modified file 'payload/__init__.py' |
3388 | --- payload/__init__.py 2011-05-11 16:21:15 +0000 |
3389 | +++ payload/__init__.py 2011-05-24 16:31:52 +0000 |
3390 | @@ -47,7 +47,6 @@ |
3391 | _check_psycopg2_conflicts() |
3392 | # bootstrap |
3393 | setup_virtualenv() |
3394 | - _bootstrap_dependencies() |
3395 | install_dependencies() |
3396 | _setup_configuration() |
3397 | |
3398 | @@ -157,13 +156,6 @@ |
3399 | sys.exit(1) |
3400 | |
3401 | |
3402 | -def _bootstrap_dependencies(): |
3403 | - virtualenv('pip install bzr==2.1.1 ' |
3404 | - '-f http://launchpad.net/' |
3405 | - 'bzr/2.1/2.1.1/+download/bzr-2.1.1.tar.gz#egg=bzr-2.1.1') |
3406 | - virtualenv('pip install lazr.batchnavigator==1.0') |
3407 | - |
3408 | - |
3409 | def _setup_configuration(): |
3410 | """Setup the configuration branch.""" |
3411 | if os.path.exists('django_project/.config'): |
3412 | |
3413 | === modified file 'requirements.txt' |
3414 | --- requirements.txt 2011-03-04 16:49:25 +0000 |
3415 | +++ requirements.txt 2011-05-24 16:31:52 +0000 |
3416 | @@ -1,56 +1,5 @@ |
3417 | -# extra (implicit) dependencies. |
3418 | -# Made explicit in order to pin them down to versions available on Lucid. |
3419 | -RestrictedPython==3.5.1 |
3420 | -ZConfig==2.7.1 |
3421 | -ZODB3==3.9.4 |
3422 | -docutils==0.6 |
3423 | -epydoc==3.0.1 |
3424 | -grokcore.component==2.0 |
3425 | -httplib2==0.6.0 |
3426 | -lazr.delegates==1.1.0 |
3427 | -lazr.enum==1.1.2 |
3428 | -lazr.lifecycle==1.0 |
3429 | -lazr.uri==1.0.2 |
3430 | -martian==0.12 |
3431 | -pytz==2010b |
3432 | -simplejson==2.0.9 |
3433 | -storm==0.15 |
3434 | -transaction==1.0.0 |
3435 | -van.testing==3.0.0 |
3436 | -zc.lockfile==1.0.0 |
3437 | -zdaemon==2.0.4 |
3438 | -zope.app.pagetemplate==3.10.1 |
3439 | -zope.authentication==3.7.0 |
3440 | -zope.browser==1.2 |
3441 | -zope.browserpage==3.11.0 |
3442 | -zope.cachedescriptors==3.5.0 |
3443 | -zope.configuration==3.7.1 |
3444 | -zope.contenttype==3.5.0 |
3445 | -zope.copy==3.5.0 |
3446 | -zope.datetime>=3.4.0,<=3.4.1dev |
3447 | -zope.dublincore==3.6.0 |
3448 | -zope.event==3.4.1 |
3449 | -zope.exceptions==3.5.2 |
3450 | -zope.i18n==3.7.0 |
3451 | -zope.i18nmessageid==3.5.0 |
3452 | -zope.lifecycleevent==3.6.0 |
3453 | -zope.location==3.8.2 |
3454 | -zope.pagetemplate==3.5.0 |
3455 | -zope.proxy==3.5.0 |
3456 | -zope.publisher==3.10.1 |
3457 | -zope.security==3.7.2 |
3458 | -zope.size==3.4.1 |
3459 | -zope.tal==3.5.2 |
3460 | -zope.tales==3.5.0 |
3461 | -zope.testing==3.8.3 |
3462 | -zope.traversing==3.12.0 |
3463 | - |
3464 | # project dependencies |
3465 | --f http://launchpad.net/lazr.restfulclient/trunk/0.9.11/+download/lazr.restfulclient-0.9.11.tar.gz#egg=lazr.restfulclient-0.9.11 |
3466 | -e bzr+ssh://bazaar.launchpad.net/~canonical-isd-hackers/django-oauth-backend/trunk/@5#egg=django-oauth-backend |
3467 | -# make sure lazr.restful is pinned |
3468 | --f http://launchpad.net/lazr.restful/trunk/0.9.19/+download/lazr.restful-0.9.19.tar.gz#egg=lazr.restful-0.9.19 |
3469 | -lazr.restful==0.9.19 |
3470 | |
3471 | # testing dependencies |
3472 | BeautifulSoup==3.1.0.1 |
3473 | @@ -59,10 +8,8 @@ |
3474 | coverage<3.4 |
3475 | mock==0.6.0 |
3476 | wsgiref==0.1.2 |
3477 | -lazr.restfulclient==0.9.11 |
3478 | wsgi-intercept==0.4 |
3479 | zope.testbrowser==3.5.1 |
3480 | |
3481 | # development dependencies |
3482 | werkzeug |
3483 | - |
3484 | |
3485 | === modified file 'scripts/create_env' |
3486 | --- scripts/create_env 2011-01-03 20:42:20 +0000 |
3487 | +++ scripts/create_env 2011-05-24 16:31:52 +0000 |
3488 | @@ -24,7 +24,7 @@ |
3489 | url='http://10.55.56.106:8000' |
3490 | pypi="-i $url" |
3491 | else |
3492 | - pypi="-f https://launchpad.net/lazr.restful/+download?start=21 -f https://launchpad.net/lazr.restfulclient/+download?start=9" |
3493 | + pypi="-f https://launchpad.net/lazr.restfulclient/+download?start=9" |
3494 | fi |
3495 | |
3496 | virtualenv --distribute --no-site-packages $ENV |
3497 | |
3498 | === modified file 'setup.py' |
3499 | --- setup.py 2011-05-12 09:18:32 +0000 |
3500 | +++ setup.py 2011-05-24 16:31:52 +0000 |
3501 | @@ -40,7 +40,6 @@ |
3502 | 'coverage==2.85', |
3503 | 'mock==0.6.0', |
3504 | 'wsgiref==0.1.2', |
3505 | - 'lazr.restfulclient==0.9.11', |
3506 | 'wsgi-intercept==0.4', |
3507 | 'zope.testbrowser==3.5.1', |
3508 | ] |
3509 | @@ -76,8 +75,6 @@ |
3510 | #'django-oauth-backend', |
3511 | 'django-openid-auth==0.2', |
3512 | 'django-preflight==0.1', |
3513 | - 'lazr.authentication==0.1.2', |
3514 | - 'lazr.restful==0.9.19', |
3515 | # PyPi calls upstream's 1.0a "1.0.1" maybe because it doesn't |
3516 | # understand letters in version strings. Lucid's package uses "1.0a" |
3517 | # as upstream does. |
3518 | @@ -85,16 +82,12 @@ |
3519 | 'psycopg2==2.0.13', |
3520 | 'python-memcached==1.44', |
3521 | 'python-openid==2.2.4', |
3522 | - 'zope.component==3.8.0', |
3523 | - 'zope.interface==3.5.3', |
3524 | - 'zope.schema==3.6.1', |
3525 | + 'django-piston', |
3526 | 'gargoyle', |
3527 | ], |
3528 | tests_require=tests_require, |
3529 | extras_require={'test': tests_require}, |
3530 | dependency_links=[ |
3531 | 'http://initd.org/pub/software/psycopg/PSYCOPG-2-0/', |
3532 | - 'http://launchpad.net/lazr.restful/trunk/0.9.19/+download/lazr.restful-0.9.19.tar.gz#egg=lazr.restful-0.9.19', |
3533 | - 'http://launchpad.net/lazr.restfulclient/trunk/0.9.11/+download/lazr.restfulclient-0.9.11.tar.gz#egg=lazr.restfulclient-0.9.11', |
3534 | ], |
3535 | ) |
Youch, apologies for the huge diff. Fwiw, the wadl template was taken verbatim from what lazr.restful generated previous to being removed, so that's ~1.5k lines that shouldn't need much review