1
# Copyright 2010 Canonical Ltd. This software is licensed under the
2
# GNU Affero General Public License version 3 (see the file LICENSE).
4
from zope.component import getUtility
5
from zope.interface import implements, classImplements
7
from lazr.restful.interfaces import IServiceRootResource
8
from lazr.restful.simple import (RootResourceAbsoluteURL, RootResource,
10
from lazr.restful.frameworks.django import DjangoWebServiceConfiguration
11
from lazr.restful.wsgi import BaseWSGIWebServiceConfiguration
12
from lazr.restful.utils import get_current_browser_request
14
from identityprovider.models import (Account, EmailAddress,
15
AccountPassword, Token, APIUser, Person)
16
from identityprovider.models.const import (
17
AccountCreationRationale, AccountStatus, EmailStatus, LoginTokenType)
18
from identityprovider.models.oauthtoken import (
19
create_oauth_token_for_account)
20
from identityprovider.models.authtoken import AuthToken, AuthTokenFactory
21
from identityprovider.utils import encrypt_launchpad_password
23
from identityprovider.webservice.interfaces import (IAccount, ICaptcha,
24
IRegistration, IValidation, IAuthentication, IAccountSet,
25
IAuthenticationSet, IRegistrationSet, ICaptchaSet)
26
from identityprovider.webservice.forms import WebserviceCreateAccountForm
27
from identityprovider.models.captcha import Captcha as CaptchaModel
28
from identityprovider.views.server import get_team_memberships
31
class RootAbsoluteURL(RootResourceAbsoluteURL):
33
This class contains no code of its own. It's defined so that grok will pick
38
# Create set objects for top-level collections, which are all
39
# pretty much the same.
40
def make_set(name, url_path, interface, manager, id_field):
41
"""Create a object to publish as a top-level collection."""
45
def get(self, request, unique_id):
46
return manager.objects.get(**{id_field: unique_id})
49
return getUtility(IServiceRootResource)
51
dict = {'getAll': getAll,
53
'__parent__': property(__parent__),
54
'__url_path__': url_path}
56
set_class = type(name, (TraverseWithGet,), dict)
57
classImplements(set_class, interface)
61
ValidationSet = make_set('ValidationSet', 'validation', IAccountSet,
65
class AccountSet(make_set('BaseAccountSet', 'accounts', IAccountSet,
67
""" These methods are protected with OAuth."""
69
request = get_current_browser_request()
70
user = request.environment.get('authenticated_user')
71
emails = EmailAddress.objects.filter(account=user,
72
status=EmailStatus.VALIDATED)
73
preferred_email = user.preferredemail
74
if preferred_email is not None:
75
preferred_email = preferred_email.email
77
people = Person.objects.filter(lp_account=user)
78
if people.count() == 1:
79
username = people[0].name
81
username = user.openid_identifier
83
return {'username': username,
84
'displayname': user.displayname,
85
'openid_identifier': user.openid_identifier,
86
'preferred_email': preferred_email,
87
'verified_emails': [e.email for e in emails],
88
'unverified_emails': [e.email
89
for e in user.unverified_emails()],
92
def team_memberships(self, team_names):
93
request = get_current_browser_request()
94
user = request.environment['authenticated_user']
95
memberships = get_team_memberships(team_names, user, True)
98
def validate_email(self, email_token):
99
request = get_current_browser_request()
100
user = request.environment['authenticated_user']
102
token = user.authtoken_set.get(
103
token=email_token, token_type=LoginTokenType.VALIDATEEMAIL)
105
email = EmailAddress.objects.get(email__iexact=token.email)
106
email.status = EmailStatus.VALIDATED
111
return {'email': email.email}
112
except AuthToken.DoesNotExist:
113
return {'errors': {'email_token': ["Bad email token!"]}}
116
def api_user_required(func):
117
def wrapper(*args, **kwargs):
118
request = get_current_browser_request()
119
user = request.environment.get('authenticated_user')
121
if isinstance(user, APIUser):
122
return func(*args, **kwargs)
123
request.response.setStatus(403)
124
return '403 Forbidden'
128
def plain_user_required(func):
129
def wrapper(*args, **kwargs):
130
request = get_current_browser_request()
131
user = request.environment.get('authenticated_user')
133
if isinstance(user, Account):
134
return func(*args, **kwargs)
135
request.response.setStatus(403)
136
return '403 Forbidden'
140
class AuthenticationSet(make_set('BaseAuthenticationSet', 'authentications',
141
IAuthenticationSet, Account, 'id')):
142
""" All these methods assume that they're run behind Basic Auth """
144
def authenticate(self, token_name):
145
request = get_current_browser_request()
146
account = request.environment['authenticated_user']
147
token = create_oauth_token_for_account(account, token_name)
148
return token.serialize()
151
def list_tokens(self, consumer_key):
152
tokens = Token.objects.filter(
153
consumer__account__openid_identifier=consumer_key)
154
result = [{'token': t.token, 'name': t.name} for t in tokens]
158
def validate_token(self, token, consumer_key):
160
token = Token.objects.get(
161
consumer__account__openid_identifier=consumer_key,
163
return token.serialize()
164
except Token.DoesNotExist:
168
def invalidate_token(self, token, consumer_key):
169
tokens = Token.objects.filter(token=token,
170
consumer__account__openid_identifier=consumer_key)
174
def team_memberships(self, team_names, openid_identifier):
175
accounts = Account.objects.filter(openid_identifier=openid_identifier)
176
accounts = list(accounts)
178
if len(accounts) == 1:
179
account = accounts[0]
180
memberships = get_team_memberships(team_names, account, False)
186
class RegistrationSet(make_set('BaseRegistrationSet', 'registration',
187
IRegistrationSet, Account, 'id')):
188
def register(self, **kwargs):
189
form = WebserviceCreateAccountForm(kwargs)
190
if not form.is_valid():
191
errors = dict((k, map(unicode, v))
192
for (k, v) in form.errors.items())
193
result = {'status': 'error', 'errors': errors}
195
cleaned_data = form.cleaned_data
196
requested_email = cleaned_data['email']
197
emails = EmailAddress.objects.filter(email__iexact=requested_email)
199
return {'status': 'error', 'errors':
200
{'email': 'Email already registered'}}
201
account = Account.objects.create(
202
creation_rationale=AccountCreationRationale.OWNER_CREATED_LAUNCHPAD,
203
status=AccountStatus.ACTIVE,
206
account.emailaddress_set.create(
207
email=cleaned_data['email'],
208
status=EmailStatus.NEW
210
password = encrypt_launchpad_password(
211
cleaned_data['password'])
212
password_obj = AccountPassword.objects.create(
216
account.accountpassword
218
token = AuthTokenFactory().new_api_email_validation_token(
219
account, cleaned_data['email'])
220
token.sendEmailValidationToken()
224
'message': "Email verification required."
228
class Captcha(object):
237
def __unicode__(self):
238
return "%s" % (self.id,)
240
# IDjangoLocation implementation
242
def __parent__(self):
243
from identityprovider.webservice.models import CaptchaSet
247
def __url_path__(self):
251
class CaptchaSet(make_set('BaseCaptchaSet', 'captchas', ICaptchaSet,
254
request = get_current_browser_request()
255
return CaptchaModel.new(request.environment).serialize()
258
class SSOWebServiceRootResource(RootResource):
259
"""The root resource for the web service"""
261
def _build_top_level_objects(self):
262
"""Create data structure of top level objects."""
264
'captchas': (ICaptcha, CaptchaSet()),
265
'registration': (IRegistration, RegistrationSet()),
266
'validation': (IValidation, ValidationSet()),
267
'accounts': (IAccount, AccountSet()),
268
'authentications': (IAuthentication, AuthenticationSet()),
270
return collections, {}
273
class WebServiceConfiguration(DjangoWebServiceConfiguration,
274
BaseWSGIWebServiceConfiguration):
275
"""Configuration information for this web service.
277
This class contains no code of its own. It's defined so that grok
278
will pick it up. The actual configuration is in settings.py.