1
Copyright 2010 Canonical Ltd. This software is licensed under the
2
GNU Affero General Public License version 3 (see the file LICENSE).
4
= Launchpad Single-Signon Workflow: Registration =
6
If a user wants to use a Launchpad-SSO web site, but does not have a
7
Launchpad account, they can register directly from the login page.
9
First we will set up the helper view that lets us test the final
10
portion of the authentication process:
12
>>> from openid.consumer.consumer import Consumer
13
>>> from openid.fetchers import setDefaultFetcher
14
>>> from openid.store.memstore import MemoryStore
15
>>> from canonical.signon.testing.openidhelpers import (
16
... complete_from_browser, make_identifier_select_endpoint,
18
>>> setDefaultFetcher(PublisherFetcher())
20
The authentication process is started by the relying party issuing a
21
checkid_setup request, sending the user to Launchpad:
23
>>> openid_store = MemoryStore()
24
>>> consumer = Consumer(session={}, store=openid_store)
26
>>> request = consumer.beginWithoutDiscovery(
27
... make_identifier_select_endpoint(PROTOCOL_URI))
28
>>> browser.open(request.redirectURL(
29
... 'http://launchpad.dev/', 'http://launchpad.dev/+openid-consumer'))
31
When a new account is created we'll use the creation rationale specified for
32
the trust_root given by the relying party. We will set up an RP
33
configuration that uses the UBUNTU_SHOP creation rationale:
35
>>> from identityprovider.models import OpenIDRPConfig
36
>>> from identityprovider.models.const import AccountCreationRationale
37
>>> rpconfig = OpenIDRPConfig.objects.create(
38
... trust_root='http://launchpad.dev/',
39
... displayname='The Ubuntu Store from Canonical',
40
... description="For the Ubuntu Store, you need a Launchpad account "
41
... "so we can remember your order details and keep in "
42
... "touch with you about your orders.",
43
... creation_rationale=AccountCreationRationale.OWNER_CREATED_UBUNTU_SHOP)
45
At this point, we are at the login page. Lets try to create a new
46
account for an email address that has already been registered. This
47
shouldn't result in an error, because we don't want to reveal
48
(non-)existing email addresses:
50
>>> from BeautifulSoup import BeautifulSoup
51
>>> browser.getLink('New account').click()
52
>>> browser.getControl(name='email').value = 'test@canonical.com'
53
>>> browser.getControl(name='continue').click()
54
>>> soup = BeautifulSoup(browser.contents)
55
>>> print soup.find('h2').renderContents()
56
Registration mail sent
58
If we instead pick a new email address, we can register an account:
60
>>> browser.open(request.redirectURL(
61
... 'http://launchpad.dev/', 'http://launchpad.dev/+openid-consumer'))
62
>>> browser.getLink('New account').click()
63
>>> browser.getControl(name='email').value = 'new-user@example.com'
64
>>> browser.getControl(name='continue').click()
65
>>> soup = BeautifulSoup(browser.contents)
66
>>> print soup.find('h2').renderContents()
67
Registration mail sent
69
The user would then check their email, and find a message:
71
Let's extract the URL from the email and follow the link:
73
>>> from identityprovider.models.const import LoginTokenType
74
>>> from identityprovider.models import AuthToken
75
>>> token = AuthToken.objects.filter(
76
... email='new-user@example.com',
77
... token_type=LoginTokenType.NEWPERSONLESSACCOUNT,
78
... date_consumed=None).order_by('-date_created')[0].token
79
>>> link = 'http://openid.launchpad.dev/token/%s' % token
80
>>> browser.open(link)
82
http://openid.launchpad.dev/token/.../+newaccount
84
The user can enter their full name and password, to complete the
87
>>> browser.getControl(name='displayname').value = 'New User'
88
>>> browser.getControl(name='password').value = 'testP4ss'
89
>>> browser.getControl(name='passwordconfirm').value = 'testP4ss'
90
>>> browser.getControl(name='continue').click()
92
Now the user is logged in with their new account, and has been
93
directed back to the original site:
95
>>> soup = BeautifulSoup(browser.contents)
96
>>> print soup.find('h2').renderContents()
97
Sign in to The Ubuntu Store from Canonical
98
>>> browser.getControl(name='yes').click()
100
http://launchpad.dev/+openid-consumer?...
102
The creation rationale has been set correctly:
104
>>> from identityprovider.models import Account
105
>>> account = Account.objects.get_by_email('new-user@example.com')
106
>>> expected_claimed_id = (
107
... 'http://openid.launchpad.dev/+id/' + account.openid_identifier)
108
>>> print account.creation_rationale
111
And the response matches the new OpenID:
113
>>> info = complete_from_browser(consumer, browser, expected_claimed_id)
114
>>> print info.status
116
>>> info.endpoint.claimed_id == expected_claimed_id
119
Since this account was created using OpenID, we will not create an entry in
120
the Person table for it -- it will only be created when the user logs into
123
>>> print account.person
129
>>> from identityprovider.models import EmailAddress
130
>>> setDefaultFetcher(None)
131
>>> AuthToken.objects.filter(email='new-user@example.com').delete()
132
>>> account = Account.objects.get_by_email('new-user@example.com')
133
>>> account.emailaddress_set.all()[0].delete()
134
>>> print EmailAddress.objects.filter(email='new-user@example.com')
137
>>> print Account.objects.filter(displayname='New User')
139
>>> rpconfig.delete()