Revision efaec842 snf-astakos-app/astakos/oa2/backends/base.py

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")

Also available in: Unified diff