Revision e28a4841 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