Revision e28a4841

b/snf-astakos-app/astakos/api/services.py
96 96
            {'versionId': '',
97 97
             'publicURL': None},
98 98
        ],
99
        'resources': {},
99 100
    },
101

  
102
    'astakos_oa2': {
103
        'type': 'astakos_auth',
104
        'component': 'astakos',
105
        'prefix': 'oa2',
106
        'public': True,
107
        'endpoints': [
108
            {'versionId': '',
109
             'publicURL': None},
110
        ],
111
        'resources': {},
112
    }
100 113
}
b/snf-astakos-app/astakos/im/settings.py
55 55
KEYSTONE_PREFIX = get_path(astakos_services, 'astakos_identity.prefix')
56 56
WEBLOGIN_PREFIX = get_path(astakos_services, 'astakos_weblogin.prefix')
57 57
ADMIN_PREFIX = get_path(astakos_services, 'astakos_admin.prefix')
58
OA2_PREFIX = get_path(astakos_services, 'astakos_oa2.prefix')
58 59

  
59 60
# Set the expiration time of newly created auth tokens
60 61
# to be this many hours after their creation time.
b/snf-astakos-app/astakos/oa2/backends/base.py
6 6

  
7 7
from base64 import b64encode, b64decode
8 8
from hashlib import sha512
9
from time import time, mktime
10

  
9 11

  
10 12
import logging
11 13
logger = logging.getLogger(__name__)
......
64 66

  
65 67
class Request(object):
66 68

  
67
    def __init__(self, method, GET=None, POST=None, META=None, secure=False,
68
                 user=None):
69
    def __init__(self, method, path, GET=None, POST=None, META=None,
70
                 secure=False, user=None):
69 71
        self.method = method
72
        self.path = path
70 73

  
71 74
        if not GET:
72 75
            GET = {}
......
177 180

  
178 181
    def __new__(cls, name, bases, attrs):
179 182
        super_new = super(BackendBase, cls).__new__
180
        parents = [b for b in bases if isinstance(b, BackendBase)]
181
        meta = attrs.pop('Meta', None)
183
        #parents = [b for b in bases if isinstance(b, BackendBase)]
184
        #meta = attrs.pop('Meta', None)
182 185
        return super_new(cls, name, bases, attrs)
183 186

  
184 187
    @classmethod
......
200 203

  
201 204
    token_endpoint = 'token/'
202 205
    token_length = 30
203
    token_expires = 3600
206
    token_expires = 300
204 207

  
205 208
    authorization_endpoint = 'auth/'
206 209
    authorization_code_length = 60
207 210
    authorization_response_types = ['code', 'token']
208 211

  
209
    grant_types = ['authorization_code', 'implicit']
212
    grant_types = ['authorization_code']
210 213

  
211 214
    response_cls = Response
212 215
    request_cls = Request
......
231 234
        return self.response_cls(status=status, headers=headers, body=body)
232 235

  
233 236
    # ORM Methods
234
    def create_authorization_code(self, client, code, redirect_uri, scope,
235
                                  state, **kwargs):
237
    def create_authorization_code(self, user, client, code, redirect_uri,
238
                                  scope, state, **kwargs):
236 239
        code_params = {
237 240
            'code': code,
238 241
            'redirect_uri': redirect_uri,
239
            'client_id': client.get_id(),
242
            'client': client,
240 243
            'scope': scope,
241
            'state': state
244
            'state': state,
245
            'user': user
242 246
        }
243 247
        code_params.update(kwargs)
244
        return self.code_model.create(code, **code_params)
248
        code_instance = self.code_model.create(**code_params)
249
        logger.info('%r created' % code_instance)
250
        return code_instance
245 251

  
246
    def _token_params(self, value, token_type, client, scope):
252
    def _token_params(self, value, token_type, authorization, scope):
247 253
        created_at = datetime.datetime.now()
248 254
        expires = self.token_expires
249 255
        expires_at = created_at + datetime.timedelta(seconds=expires)
250 256
        token_params = {
251
            'token': value,
257
            'code': value,
252 258
            'token_type': token_type,
253
            'client': client,
254
            'scope': scope,
255 259
            'created_at': created_at,
256
            'expires': expires,
257
            'expires_at': expires_at
260
            'expires_at': expires_at,
261
            'user': authorization.user,
262
            'redirect_uri': authorization.redirect_uri,
263
            'client': authorization.client,
264
            'scope': authorization.scope,
258 265
        }
259 266
        return token_params
260 267

  
261
    def create_token(self, value, token_type, client, scope, refresh=False):
262
        params = self._token_params(value, token_type, client, scope)
268
    def create_token(self, value, token_type, authorization, scope,
269
                     refresh=False):
270
        params = self._token_params(value, token_type, authorization, scope)
263 271
        if refresh:
264 272
            refresh_token = self.generate_token()
265 273
            params['refresh_token'] = refresh_token
266 274
            # TODO: refresh token expires ???
267
        token = self.token_model.create(value, **params)
275
        token = self.token_model.create(**params)
276
        logger.info('%r created' % token)
277
        return token
268 278

  
269
    def delete_authorization_code(self, code):
270
        del self.code_model.ENTRIES[code]
279
#    def delete_authorization_code(self, code):
280
#        del self.code_model.ENTRIES[code]
271 281

  
272 282
    def get_client_by_id(self, client_id):
273 283
        return self.client_model.get(client_id)
......
283 293
        if not code_instance:
284 294
            raise OA2Error("Invalid code", code)
285 295

  
286
        if client.id != code_instance.client_id:
296
        if client.get_id() != code_instance.client.get_id():
287 297
            raise OA2Error("Invalid code for client", code, client)
288 298
        return code_instance
289 299

  
......
321 331
                                       state, **kwargs)
322 332
        return code
323 333

  
334
    def add_token_for_client(self, token_type, authorization, refresh=False):
335
        token = self.generate_token()
336
        self.create_token(token, token_type, authorization, refresh)
337
        return token
338

  
324 339
    #
325 340
    # Response helpers
326 341
    #
327 342

  
328
    def grant_accept_response(self, client, redirect_uri, scope, state,
329
                              request):
343
    def grant_accept_response(self, client, redirect_uri, scope, state):
330 344
        context = {'client': client.get_id(), 'redirect_uri': redirect_uri,
331
                   'scope': scope, 'state': state, 'url': url}
345
                   'scope': scope, 'state': state,
346
                   #'url': url,
347
                   }
348
        json_content = json.dumps(context)
349
        return self.response_cls(status=200, body=json_content)
350

  
351
    def grant_token_response(self, token, token_type):
352
        context = {'access_token': token, 'token_type': token_type,
353
                   'expires_in': self.token_expires}
332 354
        json_content = json.dumps(context)
333 355
        return self.response_cls(status=200, body=json_content)
334 356

  
335
    def build_redirect_to_login_response(self, request):
336
        return Response(302, headers={'Location': '/login'})
357
    def redirect_to_login_response(self, request, params):
358
        parts = list(urlparse.urlsplit(request.path))
359
        parts[3] = urllib.urlencode(params)
360
        query = {'next': urlparse.urlunsplit(parts)}
361
        return Response(302,
362
                        headers={'Location': '%s?%s' %
363
                                 (self.get_login_uri(),
364
                                  urllib.urlencode(query))})
365

  
366
    def redirect_to_uri(self, redirect_uri, code, state=None):
367
        parts = list(urlparse.urlsplit(redirect_uri))
368
        params = dict(urlparse.parse_qsl(parts[3], keep_blank_values=True))
369
        params['code'] = code
370
        if state is not None:
371
            params['state'] = state
372
        parts[3] = urllib.urlencode(params)
373
        return Response(302,
374
                        headers={'Location': '%s' %
375
                                 urlparse.urlunsplit(parts)})
337 376

  
338 377
    def build_response_from_error(self, exception):
339 378
        response = Response(400)
......
353 392
    # Processor methods
354 393
    #
355 394

  
356
    def grant_authorization_code(self, user, client, code, redirect_uri,
357
                                 scope):
358
        code = self.get_client_authorization_code(client, code)
359
        if code.scope != scope:
360
            raise OA2Error("Invalid scope")
361
        token = self.add_token_for_client(client, "Bearer", code.scope,
362
                                          refresh=True)
363
        self.delete_authorization_code(code.code)
364
        return token
365

  
395
    def process_code_request(self, user, client, uri, scope, state):
396
        code = self.add_authorization_code(user, client, uri, scope, state)
397
        return self.redirect_to_uri(uri, code, state)
366 398

  
367 399
    #
368 400
    # Helpers
369 401
    #
370 402

  
403
    def grant_authorization_code(self, client, code_instance, redirect_uri,
404
                                 scope=None, token_type="Bearer"):
405
        if scope and code_instance.scope != scope:
406
            raise OA2Error("Invalid scope")
407
        if redirect_uri != code_instance.redirect_uri:
408
            raise OA2Error("The redirect uri does not match "
409
                           "the one used during authorization")
410
        token = self.add_token_for_client(token_type, code_instance)
411
        self.delete_authorization_code(code_instance)  # use only once
412
        return token, token_type
413

  
414
    def consume_token(self, token):
415
        token_instance = self.get_token(token)
416
        expires_at = mktime(token_instance.expires_at.timetuple())
417
        if time() > expires_at:
418
            self.delete_token(token_instance)  # delete expired token
419
            raise OA2Error("Token has expired")
420
        # TODO: delete token?
421
        return token_instance
422

  
371 423
    def _get_credentials(self, params, headers):
372 424
        if 'HTTP_AUTHORIZATION' in headers:
373 425
            scheme, b64credentials = headers.get(
374 426
                'HTTP_AUTHORIZATION').split(" ")
427
            if scheme != 'Basic':
428
                # TODO: raise 401 + WWW-Authenticate
429
                raise OA2Error("Unsupported authorization scheme")
375 430
            credentials = b64decode(b64credentials).split(":")
376 431
            return scheme, credentials
377 432
        else:
378 433
            return None, None
379 434
        pass
380 435

  
436
    def _get_authorization(self, params, headers):
437
        scheme, client_credentials = self._get_credentials(params, headers)
438
        no_authorization = scheme is None and client_credentials is None
439
        if no_authorization:
440
            raise OA2Error("Missing authorization header")
441
        return client_credentials
442

  
381 443
    def get_redirect_uri_from_params(self, client, params, default=True):
382 444
        """
383 445
        Accepts a client instance and request parameters.
......
393 455
        return redirect_uri
394 456

  
395 457
    #
396
    # Parameters validation methods
458
    # Request identifiers
397 459
    #
398 460

  
399
    def validate_client(params, meta, requires_auth=False):
400
        raise OA2Error("Invalid client")
461
    def identify_authorize_request(self, params, headers):
462
        return params.get('response_type'), params
401 463

  
402
    def validate_redirect_uri(client, params, headers, allow_default=True):
403
        raise OA2Error("Invalid redirect uri")
464
    def identify_token_request(self, headers, params):
465
        content_type = headers.get('CONTENT_TYPE')
466
        if content_type != 'application/x-www-form-urlencoded':
467
            raise OA2Error("Invalid Content-Type header")
468
        return params.get('grant_type')
404 469

  
405
    def validate_state(params, meta):
406
        raise OA2Error("Invalid state")
470
    #
471
    # Parameters validation methods
472
    #
407 473

  
408
    def validate_scope(client, params, headers):
474
    def validate_client(self, params, meta, requires_auth=True,
475
                        client_id_required=True):
476
        client_id = params.get('client_id')
477
        if client_id is None and client_id_required:
478
            raise OA2Error("Client identification is required")
479

  
480
        client_credentials = None
481
        try:  # check authorization header
482
            client_credentials = self._get_authorization(params, meta)
483
            if client_credentials is not None:
484
                _client_id = client_credentials[0]
485
                if client_id is not None and client_id != _client_id:
486
                    raise OA2Error("Client identification conflicts "
487
                                   "with client authorization")
488
                client_id = _client_id
489
        except:
490
            pass
491

  
492
        if client_id is None:
493
            raise OA2Error("Missing client identification")
494

  
495
        client = self.get_client_by_id(client_id)
496

  
497
        if requires_auth and client.requires_auth:
498
            if client_credentials is None:
499
                raise OA2Error("Client authentication in required")
500

  
501
        if client_credentials is not None:
502
            self.check_credentials(client, *client_credentials)
503
        return client
504

  
505
    def validate_redirect_uri(self, client, params, headers,
506
                              allow_default=True, is_required=False,
507
                              expected_value=None):
508
        redirect_uri = params.get('redirect_uri')
509
        if is_required and redirect_uri is None:
510
            raise OA2Error("Missing redirect uri")
511
        if redirect_uri is not None:
512
            if not bool(urlparse.urlparse(redirect_uri).scheme):
513
                raise OA2Error("Redirect uri should be an absolute URI")
514
            if not client.redirect_uri_is_valid(redirect_uri):
515
                raise OA2Error("Mismatching redirect uri")
516
            if expected_value is not None and redirect_uri != expected_value:
517
                raise OA2Error("Invalid redirect uri")
518
        return redirect_uri
519

  
520
    def validate_state(self, client, params, headers):
521
        return params.get('state')
409 522
        raise OA2Error("Invalid state")
410 523

  
524
    def validate_scope(self, client, params, headers):
525
        scope = params.get('scope')
526
        if scope is not None:
527
            scope = scope.split(' ')[0]  # keep only the first
528
        # TODO: check for invalid characters
529
        return scope
530

  
531
    def validate_code(self, client, params, headers):
532
        code = params.get('code')
533
        if code is None:
534
            raise OA2Error("Missing authorization code")
535
        return self.get_client_authorization_code(client, code)
536

  
411 537
    #
412 538
    # Requests validation methods
413 539
    #
414 540

  
415
    def validate_code_request(params, headers):
416
        client = self.validate_client(params, headers, False)
541
    def validate_code_request(self, params, headers):
542
        client = self.validate_client(params, headers, requires_auth=False)
417 543
        redirect_uri = self.validate_redirect_uri(client, params, headers)
418 544
        scope = self.validate_scope(client, params, headers)
419 545
        state = self.validate_state(client, params, headers)
420 546
        return client, redirect_uri, scope, state
421 547

  
422
    def validate_token_request(params, headers):
423
        client = self.validate_client(params, headers, False)
548
    def validate_token_request(self, params, headers, requires_auth=False):
549
        client = self.validate_client(params, headers)
424 550
        redirect_uri = self.validate_redirect_uri(client, params, headers)
425 551
        scope = self.validate_scope(client, params, headers)
426 552
        state = self.validate_state(client, params, headers)
427 553
        return client, redirect_uri, scope, state
428 554

  
555
    def validate_code_grant(self, params, headers):
556
        client = self.validate_client(params, headers,
557
                                      client_id_required=False)
558
        code_instance = self.validate_code(client, params, headers)
559
        redirect_uri = self.validate_redirect_uri(
560
            client, params, headers,
561
            expected_value=code_instance.redirect_uri)
562
        return client, redirect_uri, code_instance
563

  
429 564
    #
430 565
    # Endpoint methods
431 566
    #
......
448 583

  
449 584
        if auth_type == 'code':
450 585
            client, uri, scope, state = \
451
                    self.validate_code_request(params, request.META)
586
                self.validate_code_request(params, request.META)
452 587
        elif auth_type == 'token':
453
            client, uri, scope, state = \
454
                self.validate_token_request(params, request.META)
588
            raise OA2Error("Unsupported response type")
589
#            client, uri, scope, state = \
590
#                self.validate_token_request(params, request.META)
455 591
        else:
456 592
            #TODO: handle custom type
457 593
            raise OA2Error("Invalid authorization type")
458 594

  
459
        user = self.get_user_from_request(request)
595
        user = getattr(request, 'user', None)
460 596
        if not user:
461
            return self.redirect_to_login_response(request)
597
            return self.redirect_to_login_response(request, params)
462 598

  
463 599
        if request.method == 'POST':
464 600
            if auth_type == 'code':
465 601
                return self.process_code_request(user, client, uri, scope,
466 602
                                                 state)
467 603
            elif auth_type == 'token':
468
                return self.process_code_request(user, client, uri, scope,
469
                                                 state)
604
                raise OA2Error("Unsupported response type")
605
#                return self.process_token_request(user, client, uri, scope,
606
#                                                 state)
470 607
            else:
471 608
                #TODO: handle custom type
472 609
                raise OA2Error("Invalid authorization type")
473 610
        else:
474
            return self.grant_accept_response()
611
            if client.is_trusted:
612
                return self.process_code_request(user, client, uri, scope,
613
                                                 state)
614
            else:
615
                return self.grant_accept_response(client, uri, scope, state)
616

  
617
    @handles_oa2_requests
618
    def grant_token(self, request, **extra):
619
        """
620
        Used in the following cases
621
        """
622
        if not request.secure:
623
            raise OA2Error("Secure request required")
624

  
625
        grant_type = self.identify_token_request(request.META, request.POST)
626

  
627
        if grant_type == 'authorization_code':
628
            client, redirect_uri, code = \
629
                self.validate_code_grant(request.POST, request.META)
630
            token, token_type = \
631
                self.grant_authorization_code(client, code, redirect_uri)
632
            return self.grant_token_response(token, token_type)
633
        elif (grant_type in ['client_credentials', 'token'] or
634
              self.is_uri(grant_type)):
635
            raise OA2Error("Unsupported grant type")
636
        else:
637
            #TODO: handle custom type
638
            raise OA2Error("Invalid grant type")
b/snf-astakos-app/astakos/oa2/backends/djangobackend.py
1
import astakos.oa2.models as oa2_models
2

  
1 3
from astakos.oa2.backends import base as oa2base
2 4
from astakos.oa2.backends import base as errors
3
from astakos.oa2.models import *
4 5

  
5
from django.conf.urls.defaults import patterns, url
6 6
from django import http
7
from django.conf import settings
8
from django.core.exceptions import ValidationError
9
from django.core.validators import URLValidator
10
from django.core.urlresolvers import reverse
11
from django.conf.urls.defaults import patterns, url
12
from django.views.decorators.csrf import csrf_exempt
13

  
14
import logging
15
logger = logging.getLogger(__name__)
7 16

  
8 17

  
9 18
class DjangoViewsMixin(object):
10 19

  
11 20
    def auth_view(self, request):
12 21
        oa2request = self.build_request(request)
13
        response = self.authorize(oa2request, accept=False)
14
        return self._build_response(response)
22
        oa2response = self.authorize(oa2request, accept=False)
23
        return self._build_response(oa2response)
15 24

  
25
    @csrf_exempt
16 26
    def token_view(self, request):
17
        return http.HttpResponse("token view")
27
        oa2request = self.build_request(request)
28
        oa2response = self.grant_token(oa2request)
29
        return self._build_response(oa2response)
18 30

  
19 31

  
20 32
class DjangoBackendORMMixin(object):
21 33

  
22 34
    def get_client_by_credentials(self, username, password):
23 35
        try:
24
            return Client.objects.get(identifier=username, secret=password)
25
        except Client.DoesNotExist:
36
            return oa2_models.Client.objects.get(identifier=username,
37
                                                 secret=password)
38
        except oa2_models.Client.DoesNotExist:
26 39
            raise errors.InvalidClientID("No such client found")
27 40

  
28 41
    def get_client_by_id(self, clientid):
29 42
        try:
30
            return Client.objects.get(identifier=clientid)
31
        except Client.DoesNotExist:
43
            return oa2_models.Client.objects.get(identifier=clientid)
44
        except oa2_models.Client.DoesNotExist:
32 45
            raise errors.InvalidClientID("No such client found")
33 46

  
34
    def create_authorization_code(self, client, code, redirect_uri, scope,
35
                                  state, **kwargs):
36
        return AuthorizationCode.objects.create(**{
37
            'code': code,
38
            'client_id': client.get_id(),
39
            'redirect_uri': redirect_uri,
40
            'scope': scope,
41
            'state': state
42
        })
43

  
44
    def create_token(self, value, token_type, client, scope, refresh=False):
45
        params = self._token_params(value, token_type, client, scope)
46
        if refresh:
47
            refresh_token = self.generate_token()
48
            params['refresh_token'] = refresh_token
49
            # TODO: refresh token expires ???
50
        token = self.token_model.create(value, **params)
47
    def get_authorization_code(self, code):
48
        try:
49
            return oa2_models.AuthorizationCode.objects.get(code=code)
50
        except oa2_models.AuthorizationCode.DoesNotExist:
51
            raise errors.OA2Error("No such authorization code")
52

  
53
    def get_token(self, token):
54
        try:
55
            return oa2_models.Token.objects.get(code=token)
56
        except oa2_models.Token.DoesNotExist:
57
            raise errors.OA2Error("No such token")
51 58

  
52 59
    def delete_authorization_code(self, code):
53
        del self.code_model.ENTRIES[code]
60
        code.delete()
61
        logger.info('%r deleted' % code)
62

  
63
    def delete_token(self, token):
64
        token.delete()
65
        logger.info('%r deleted' % token)
66

  
67
    def check_credentials(self, client, username, secret):
68
        if not (username == client.get_id() and secret == client.secret):
69
            raise errors.InvalidAuthorizationRequest("Invalid credentials")
54 70

  
55 71

  
56 72
class DjangoBackend(DjangoBackendORMMixin, oa2base.SimpleBackend,
57 73
                    DjangoViewsMixin):
58 74

  
59
    code_model = AuthorizationCode
75
    code_model = oa2_models.AuthorizationCode.objects
76
    token_model = oa2_models.Token.objects
77
    client_model = oa2_models.Client.objects
60 78

  
61 79
    def _build_response(self, oa2response):
62 80
        response = http.HttpResponse()
......
69 87
    def build_request(self, django_request):
70 88
        params = {
71 89
            'method': django_request.method,
90
            'path': django_request.path,
72 91
            'GET': django_request.GET,
73 92
            'POST': django_request.POST,
74 93
            'META': django_request.META,
75
            'secure': django_request.is_secure(),
94
            'secure': settings.DEBUG or django_request.is_secure(),
95
            #'secure': django_request.is_secure(),
76 96
        }
97
        # TODO: check for valid astakos user
77 98
        if django_request.user.is_authenticated():
78 99
            params['user'] = django_request.user
79 100
        return oa2base.Request(**params)
80 101

  
81 102
    def get_url_patterns(self):
103
        sep = '/' if self.endpoints_prefix else ''
82 104
        _patterns = patterns(
83 105
            '',
84
            url(r'^%s/auth/?$' % self.endpoints_prefix, self.auth_view,
106
            url(r'^%s%sauth/?$' % (self.endpoints_prefix, sep),
107
                self.auth_view,
85 108
                name='%s_authenticate' % self.id),
86
            url(r'^%s/token/?$' % self.endpoints_prefix, self.token_view,
109
            url(r'^%s%stoken/?$' % (self.endpoints_prefix, sep),
110
                self.token_view,
87 111
                name='%s_token' % self.id),
88 112
        )
89 113
        return _patterns
90 114

  
115
    def is_uri(self, string):
116
        validator = URLValidator()
117
        try:
118
            validator(string)
119
        except ValidationError:
120
            return False
121
        else:
122
            return True
123

  
124
    def get_login_uri(self):
125
        return reverse('login')
126

  
91 127

  
92 128
class AstakosBackend(DjangoBackend):
93 129
    pass
b/snf-astakos-app/astakos/oa2/models.py
1
import datetime
2
import urlparse
3

  
1 4
from django.db import models
2 5
from django.utils.translation import ugettext_lazy as _
3

  
4
from astakos.oa2 import settings
6
from django.core.exceptions import ValidationError
5 7

  
6 8
CLIENT_TYPES = (
7 9
    ('confidential', _('Confidential')),
......
10 12

  
11 13
CONFIDENTIAL_TYPES = ['confidential']
12 14

  
15
TOKEN_TYPES = (('Basic', _('Basic')),
16
               ('Bearer', _('Bearer')))
17

  
18
GRANT_TYPES = (('authorization_code', _('Authorization code')),
19
               ('password', _('Password')),
20
               ('client_credentials', _('Client Credentials')))
21

  
13 22

  
14 23
class RedirectUrl(models.Model):
15
    client = models.ForeignKey('oa2.Client')
16
    default = models.BooleanField(default=True)
24
    client = models.ForeignKey('oa2.Client', on_delete=models.PROTECT)
25
    is_default = models.BooleanField(default=True)
17 26
    url = models.URLField(unique=True)
18 27

  
19 28
    class Meta:
20
        ordering = ('default', )
29
        ordering = ('is_default', )
30
        unique_together = ('client', 'url',)
21 31

  
22 32

  
23 33
class Client(models.Model):
24 34
    name = models.CharField(max_length=100)
25 35
    identifier = models.CharField(max_length=255, unique=True)
26
    secret = models.CharField(max_length=255)
36
    secret = models.CharField(max_length=255, null=True, default=None)
27 37
    url = models.CharField(max_length=255)
28 38
    type = models.CharField(max_length=100, choices=CLIENT_TYPES,
29 39
                            default='confidential')
40
    is_trusted = models.BooleanField(default=False)
41

  
42
    def save(self, **kwargs):
43
        if self.secret is None and self.type == 'confidential':
44
            raise ValidationError("Confidential clients require a secret")
45
        super(Client, self).save(**kwargs)
30 46

  
31 47
    def requires_auth(self):
32 48
        return self.type in CONFIDENTIAL_TYPES
......
35 51
        return self.redirecturl_set.get().url
36 52

  
37 53
    def redirect_uri_is_valid(self, uri):
54
        # ignore user specific uri part
55
        parts = list(urlparse.urlsplit(uri))
56
        path = parts[2]
57
        pieces = path.rsplit('/', 3)
58
        parts[2] = '/'.join(pieces[:-3]) if len(pieces) > 3 else path
59
        uri = urlparse.urlunsplit(parts)
60

  
61
        # TODO: handle trailing slashes
38 62
        return self.redirecturl_set.filter(url=uri).count() > 0
39 63

  
40 64
    def get_id(self):
......
42 66

  
43 67

  
44 68
class AuthorizationCode(models.Model):
69
    user = models.ForeignKey('im.AstakosUser', on_delete=models.PROTECT)
45 70
    code = models.TextField()
46 71
    redirect_uri = models.CharField(max_length=255)
47
    client_id = models.CharField(max_length=255, db_index=True)
48
    scope = models.TextField()
72
    client = models.ForeignKey('oa2.Client', on_delete=models.PROTECT)
73
    scope = models.TextField(null=True, default=None)
74
    created_at = models.DateTimeField(default=datetime.datetime.now())
49 75

  
50 76
    # not really useful
51
    state = models.TextField()
77
    state = models.TextField(null=True, default=None)
78

  
79
    def client_id_is_valid(self, client_id):
80
        return self.client_id == client_id
81

  
82
    def redirect_uri_is_valid(self, redirect_uri, client):
83
        return (self.redirect_uri == redirect_uri and
84
                client.redirect_uri_is_valid(redirect_uri))
85

  
86
    def __repr__(self):
87
        return ("Authorization code: %s "
88
                "(user: %s, client: %s, redirect_uri: %s, scope: %s)" % (
89
                    self.code,
90
                    self.user.log_display,
91
                    self.client.get_id(),
92
                    self.redirect_uri, self.scope))
52 93

  
53 94

  
54 95
class Token(models.Model):
55 96
    code = models.TextField()
97
    created_at = models.DateTimeField(default=datetime.datetime.now())
56 98
    expires_at = models.DateTimeField()
99
    token_type = models.CharField(max_length=100, choices=TOKEN_TYPES,
100
                                  default='Bearer')
101
    grant_type = models.CharField(max_length=100, choices=GRANT_TYPES,
102
                                  default='authorization_code')
103

  
104
    # authorization fields
105
    user = models.ForeignKey('im.AstakosUser', on_delete=models.PROTECT)
106
    redirect_uri = models.CharField(max_length=255)
107
    client = models.ForeignKey('oa2.Client', on_delete=models.PROTECT)
108
    scope = models.TextField(null=True, default=None)
109

  
110
    # not really useful
111
    state = models.TextField(null=True, default=None)
112

  
113
    def __repr__(self):
114
        return ("Token: %s (token_type: %s, grant_type: %s, "
115
                "user: %s, client: %s, scope: %s)" % (
116
                    self.code, self.token_type, self.grant_type,
117
                    self.user.log_display, self.client.get_id(), self.scope))
b/snf-astakos-app/astakos/oa2/tests/djangobackend.py
10 10
from django.core.urlresolvers import reverse
11 11
from django.contrib.auth.models import User
12 12

  
13
from astakos.oa2.models import Client, RedirectUrl, AuthorizationCode
13
from astakos.oa2.models import Client, AuthorizationCode
14 14

  
15 15

  
16 16
ParsedURL = namedtuple('ParsedURL', ['host', 'scheme', 'path', 'params',
......
120 120
        params.update(urlparams)
121 121
        self.set_auth_headers(kwargs)
122 122
        if 'reject' in params:
123
            self.post(self.get_url(self.auth_url), data=params)
123
            return self.post(self.get_url(self.auth_url), data=params,
124
                             **kwargs)
124 125
        return self.get(self.get_url(self.auth_url, **params), *args, **kwargs)
125 126

  
126 127
    def set_auth_headers(self, params):
128
        print 'self.credentials:', self.credentials
127 129
        if not self.credentials:
128 130
            return
129 131
        credentials = base64.encodestring('%s:%s' % self.credentials).strip()
......
145 147
        baseurl = reverse('oa2_authenticate').replace('/auth', '/')
146 148
        self.client = OA2Client(baseurl)
147 149
        client1 = Client.objects.create(identifier="client1", secret="secret")
150
        self.client1_redirect_uri = "https://server.com/handle_code"
151
        client1.redirecturl_set.create(url=self.client1_redirect_uri)
152

  
148 153
        client2 = Client.objects.create(identifier="client2", type='public')
149 154
        self.client2_redirect_uri = "https://server2.com/handle_code"
150 155
        client2.redirecturl_set.create(url=self.client2_redirect_uri)
151
        self.client1_redirect_uri = "https://server.com/handle_code"
152
        client1.redirecturl_set.create(url=self.client1_redirect_uri)
156

  
157
        client3 = Client.objects.create(identifier="client3", secret='secret',
158
                                        is_trusted=True)
159
        self.client3_redirect_uri = "https://server3.com/handle_code"
160
        client3.redirecturl_set.create(url=self.client3_redirect_uri)
153 161

  
154 162
        u = User.objects.create(username="user@synnefo.org")
155 163
        u.set_password("password")
......
160 168
        self.assertEqual(r.status_code, 400)
161 169
        self.assertCount(AuthorizationCode, 0)
162 170

  
163
        # no auth header, client is confidential
164
        r = self.client.authorize_code('client1')
165
        self.assertEqual(r.status_code, 400)
166
        self.assertCount(AuthorizationCode, 0)
167

  
168
        # no redirect_uri
169
        #self.client.credentials = ('client1', 'secret')
170
        #r = self.client.authorize_code('client1')
171
        #self.assertEqual(r.status_code, 400)
172
        #self.assertCount(AuthorizationCode, 0)
171
#        # no auth header, client is confidential
172
#        r = self.client.authorize_code('client1')
173
#        self.assertEqual(r.status_code, 400)
174
#        self.assertCount(AuthorizationCode, 0)
173 175

  
174 176
        # mixed up credentials/client_id's
175 177
        self.client.set_credentials('client1', 'secret')
......
182 184
        self.assertEqual(r.status_code, 400)
183 185
        self.assertCount(AuthorizationCode, 0)
184 186

  
185
        self.client.set_credentials()
186
        r = self.client.authorize_code('client1')
187
        self.assertEqual(r.status_code, 400)
188
        self.assertCount(AuthorizationCode, 0)
187
#        self.client.set_credentials()
188
#        r = self.client.authorize_code('client1')
189
#        self.assertEqual(r.status_code, 400)
190
#        self.assertCount(AuthorizationCode, 0)
189 191

  
190 192
        # valid request
191 193
        params = {'redirect_uri': self.client1_redirect_uri,
......
193 195
        self.client.set_credentials('client1', 'secret')
194 196
        r = self.client.authorize_code('client1', urlparams=params)
195 197
        self.assertEqual(r.status_code, 302)
198
        self.assertTrue('Location' in r)
199
        p = urlparse.urlparse(r['Location'])
200
        self.assertEqual(p.netloc, 'testserver:80')
201
        self.assertEqual(p.path, reverse('login'))
196 202

  
197
        self.client.set_credentials()
203
        self.client.set_credentials('client1', 'secret')
198 204
        self.client.login(username="user@synnefo.org", password="password")
199 205
        r = self.client.authorize_code('client1', urlparams=params)
200
        print r
201 206
        self.assertEqual(r.status_code, 200)
202 207

  
203 208
        r = self.client.authorize_code('client1', urlparams=params,
......
219 224
        self.assertCount(AuthorizationCode, 2)
220 225

  
221 226
        code1 = AuthorizationCode.objects.get(code=redirect1.params['code'][0])
222
        self.assertEqual(code1.state, '')
227
        #self.assertEqual(code1.state, '')
228
        self.assertEqual(code1.state, None)
223 229
        self.assertEqual(code1.redirect_uri, self.client1_redirect_uri)
224 230

  
225 231
        code2 = AuthorizationCode.objects.get(code=redirect2.params['code'][0])
b/snf-astakos-app/astakos/oa2/urls.py
1
# Copyright 2013 GRNET S.A. All rights reserved.
2
#
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
#
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
#
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
#
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
#
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

  
34
from astakos.oa2.backends import DjangoBackend
35

  
36
oa2_backend = DjangoBackend(endpoints_prefix='')
37
urlpatterns = oa2_backend.get_url_patterns()
b/snf-astakos-app/astakos/scripts/snf_service_export.py
69 69
            {'versionId': '',
70 70
             'publicURL': None},
71 71
        ],
72
        'resources': {},
72 73
    },
74

  
75
    'astakos_oa2': {
76
        'type': 'astakos_auth',
77
        'component': 'astakos',
78
        'prefix': 'oa2',
79
        'public': True,
80
        'endpoints': [
81
            {'versionId': '',
82
             'publicURL': None},
83
        ],
84
        'resources': {},
85
    }
73 86
}
74 87

  
75 88
cyclades_services = {
b/snf-astakos-app/astakos/urls.py
33 33

  
34 34
from django.conf.urls import include, patterns
35 35

  
36
from astakos.im.settings import BASE_URL, BASE_PATH, ACCOUNTS_PREFIX, \
37
    VIEWS_PREFIX, KEYSTONE_PREFIX, WEBLOGIN_PREFIX, ADMIN_PREFIX
36
from astakos.im.settings import BASE_PATH, ACCOUNTS_PREFIX, \
37
    VIEWS_PREFIX, KEYSTONE_PREFIX, WEBLOGIN_PREFIX, ADMIN_PREFIX, OA2_PREFIX
38 38
from snf_django.lib.api.utils import prefix_pattern
39 39
from snf_django.utils.urls import \
40 40
    extend_with_root_redirects, extend_endpoint_with_slash
41 41
from astakos.im.settings import astakos_services
42
from astakos.oa2.backends import DjangoBackend, AstakosBackend
43 42

  
44 43
urlpatterns = []
45 44

  
......
47 46
extend_endpoint_with_slash(urlpatterns, astakos_services, 'astakos_ui')
48 47
extend_endpoint_with_slash(urlpatterns, astakos_services, 'astakos_weblogin')
49 48

  
50
oa2_backend = DjangoBackend(BASE_URL, 'oa2')
51
astakos_oa2_backend = AstakosBackend(BASE_URL, 'oa2/services', 'services')
52

  
53 49
astakos_patterns = patterns(
54 50
    '',
55 51
    (prefix_pattern(VIEWS_PREFIX), include('astakos.im.urls')),
......
57 53
    (prefix_pattern(KEYSTONE_PREFIX), include('astakos.api.keystone_urls')),
58 54
    (prefix_pattern(WEBLOGIN_PREFIX), include('astakos.im.weblogin_urls')),
59 55
    (prefix_pattern(ADMIN_PREFIX), include('astakos.admin.admin_urls')),
56
    (prefix_pattern(OA2_PREFIX), include('astakos.oa2.urls')),
60 57
)
61 58

  
62 59

  
......
68 65
# set utility redirects
69 66
extend_with_root_redirects(urlpatterns, astakos_services,
70 67
                           'astakos_ui', BASE_PATH)
71

  
72
urlpatterns += oa2_backend.get_url_patterns()
73
#urlpatterns += astakos_oa2_backend.get_url_patterns()

Also available in: Unified diff