~ubuntuone-pqm-team/canonical-identity-provider/trunk

« back to all changes in this revision

Viewing changes to identityprovider/webservice/models.py

  • Committer: Danny Tamez
  • Date: 2010-04-21 15:29:24 UTC
  • Revision ID: danny.tamez@canonical.com-20100421152924-lq1m92tstk2iz75a
Canonical SSO Provider (Open Source) - Initial Commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2010 Canonical Ltd.  This software is licensed under the
 
2
# GNU Affero General Public License version 3 (see the file LICENSE).
 
3
 
 
4
from zope.component import getUtility
 
5
from zope.interface import implements, classImplements
 
6
 
 
7
from lazr.restful.interfaces import IServiceRootResource
 
8
from lazr.restful.simple import (RootResourceAbsoluteURL, RootResource,
 
9
                                 TraverseWithGet)
 
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
 
13
 
 
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
 
22
 
 
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
 
29
 
 
30
 
 
31
class RootAbsoluteURL(RootResourceAbsoluteURL):
 
32
    """
 
33
    This class contains no code of its own. It's defined so that grok will pick
 
34
    it up.
 
35
    """
 
36
 
 
37
 
 
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."""
 
42
    def getAll(self):
 
43
        return []
 
44
 
 
45
    def get(self, request, unique_id):
 
46
        return manager.objects.get(**{id_field: unique_id})
 
47
 
 
48
    def __parent__(self):
 
49
        return getUtility(IServiceRootResource)
 
50
 
 
51
    dict = {'getAll': getAll,
 
52
             'get': get,
 
53
             '__parent__': property(__parent__),
 
54
             '__url_path__': url_path}
 
55
 
 
56
    set_class = type(name, (TraverseWithGet,), dict)
 
57
    classImplements(set_class, interface)
 
58
    return set_class
 
59
 
 
60
 
 
61
ValidationSet = make_set('ValidationSet', 'validation', IAccountSet,
 
62
                            Account, 'id')
 
63
 
 
64
 
 
65
class AccountSet(make_set('BaseAccountSet', 'accounts', IAccountSet,
 
66
                            Account, 'id')):
 
67
    """ These methods are protected with OAuth."""
 
68
    def me(self):
 
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
 
76
 
 
77
        people = Person.objects.filter(lp_account=user)
 
78
        if people.count() == 1:
 
79
            username = people[0].name
 
80
        else:
 
81
            username = user.openid_identifier
 
82
 
 
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()],
 
90
               }
 
91
 
 
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)
 
96
        return memberships
 
97
 
 
98
    def validate_email(self, email_token):
 
99
        request = get_current_browser_request()
 
100
        user = request.environment['authenticated_user']
 
101
        try:
 
102
            token = user.authtoken_set.get(
 
103
                token=email_token, token_type=LoginTokenType.VALIDATEEMAIL)
 
104
 
 
105
            email = EmailAddress.objects.get(email__iexact=token.email)
 
106
            email.status = EmailStatus.VALIDATED
 
107
            email.save()
 
108
 
 
109
            token.consume()
 
110
 
 
111
            return {'email': email.email}
 
112
        except AuthToken.DoesNotExist:
 
113
            return {'errors': {'email_token': ["Bad email token!"]}}
 
114
 
 
115
 
 
116
def api_user_required(func):
 
117
    def wrapper(*args, **kwargs):
 
118
        request = get_current_browser_request()
 
119
        user = request.environment.get('authenticated_user')
 
120
        if user:
 
121
            if isinstance(user, APIUser):
 
122
                return func(*args, **kwargs)
 
123
        request.response.setStatus(403)
 
124
        return '403 Forbidden'
 
125
    return wrapper
 
126
 
 
127
 
 
128
def plain_user_required(func):
 
129
    def wrapper(*args, **kwargs):
 
130
        request = get_current_browser_request()
 
131
        user = request.environment.get('authenticated_user')
 
132
        if user:
 
133
            if isinstance(user, Account):
 
134
                return func(*args, **kwargs)
 
135
        request.response.setStatus(403)
 
136
        return '403 Forbidden'
 
137
    return wrapper
 
138
 
 
139
 
 
140
class AuthenticationSet(make_set('BaseAuthenticationSet', 'authentications',
 
141
                                 IAuthenticationSet, Account, 'id')):
 
142
    """ All these methods assume that they're run behind Basic Auth """
 
143
    @plain_user_required
 
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()
 
149
 
 
150
    @api_user_required
 
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]
 
155
        return result
 
156
 
 
157
    @api_user_required
 
158
    def validate_token(self, token, consumer_key):
 
159
        try:
 
160
            token = Token.objects.get(
 
161
                consumer__account__openid_identifier=consumer_key,
 
162
                token=token)
 
163
            return token.serialize()
 
164
        except Token.DoesNotExist:
 
165
            return False
 
166
 
 
167
    @api_user_required
 
168
    def invalidate_token(self, token, consumer_key):
 
169
        tokens = Token.objects.filter(token=token,
 
170
            consumer__account__openid_identifier=consumer_key)
 
171
        tokens.delete()
 
172
 
 
173
    @api_user_required
 
174
    def team_memberships(self, team_names, openid_identifier):
 
175
        accounts = Account.objects.filter(openid_identifier=openid_identifier)
 
176
        accounts = list(accounts)
 
177
 
 
178
        if len(accounts) == 1:
 
179
            account = accounts[0]
 
180
            memberships = get_team_memberships(team_names, account, False)
 
181
            return memberships
 
182
        else:
 
183
            return []
 
184
 
 
185
 
 
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}
 
194
            return result
 
195
        cleaned_data = form.cleaned_data
 
196
        requested_email = cleaned_data['email']
 
197
        emails = EmailAddress.objects.filter(email__iexact=requested_email)
 
198
        if len(emails) > 0:
 
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,
 
204
            displayname=''
 
205
            )
 
206
        account.emailaddress_set.create(
 
207
            email=cleaned_data['email'],
 
208
            status=EmailStatus.NEW
 
209
            )
 
210
        password = encrypt_launchpad_password(
 
211
            cleaned_data['password'])
 
212
        password_obj = AccountPassword.objects.create(
 
213
            password=password,
 
214
            account=account,
 
215
            )
 
216
        account.accountpassword
 
217
 
 
218
        token = AuthTokenFactory().new_api_email_validation_token(
 
219
            account, cleaned_data['email'])
 
220
        token.sendEmailValidationToken()
 
221
 
 
222
        return {
 
223
            'status': 'ok',
 
224
            'message': "Email verification required."
 
225
        }
 
226
 
 
227
 
 
228
class Captcha(object):
 
229
    implements(ICaptcha)
 
230
    message = ''
 
231
    date_created = ''
 
232
 
 
233
    @property
 
234
    def content(self):
 
235
        return ''
 
236
 
 
237
    def __unicode__(self):
 
238
        return "%s" % (self.id,)
 
239
 
 
240
    # IDjangoLocation implementation
 
241
    @property
 
242
    def __parent__(self):
 
243
        from identityprovider.webservice.models import CaptchaSet
 
244
        return CaptchaSet()
 
245
 
 
246
    @property
 
247
    def __url_path__(self):
 
248
        return str(self.id)
 
249
 
 
250
 
 
251
class CaptchaSet(make_set('BaseCaptchaSet', 'captchas', ICaptchaSet,
 
252
                          Captcha, 'id')):
 
253
    def new(self):
 
254
        request = get_current_browser_request()
 
255
        return CaptchaModel.new(request.environment).serialize()
 
256
 
 
257
 
 
258
class SSOWebServiceRootResource(RootResource):
 
259
    """The root resource for the web service"""
 
260
 
 
261
    def _build_top_level_objects(self):
 
262
        """Create data structure of top level objects."""
 
263
        collections = {
 
264
            'captchas': (ICaptcha, CaptchaSet()),
 
265
            'registration': (IRegistration, RegistrationSet()),
 
266
            'validation': (IValidation, ValidationSet()),
 
267
            'accounts': (IAccount, AccountSet()),
 
268
            'authentications': (IAuthentication, AuthenticationSet()),
 
269
        }
 
270
        return collections, {}
 
271
 
 
272
 
 
273
class WebServiceConfiguration(DjangoWebServiceConfiguration,
 
274
                              BaseWSGIWebServiceConfiguration):
 
275
    """Configuration information for this web service.
 
276
 
 
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.
 
279
    """