Commit 1f27676a authored by Sofia Papagiannaki's avatar Sofia Papagiannaki
Browse files

astakos oa2: Handle unicode query parameters in the redirection endpoint

parent b1126fe8
...@@ -36,10 +36,13 @@ import urlparse ...@@ -36,10 +36,13 @@ import urlparse
import uuid import uuid
import datetime import datetime
import json import json
import urltools
from base64 import b64encode, b64decode from base64 import b64encode, b64decode
from hashlib import sha512 from hashlib import sha512
from synnefo.util.text import uenc
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
...@@ -47,8 +50,12 @@ logger = logging.getLogger(__name__) ...@@ -47,8 +50,12 @@ logger = logging.getLogger(__name__)
def urlencode(params): def urlencode(params):
if hasattr(params, 'urlencode') and callable(getattr(params, 'urlencode')): if hasattr(params, 'urlencode') and callable(getattr(params, 'urlencode')):
return params.urlencode() return params.urlencode()
for k in params:
params[uenc(k)] = uenc(params.pop(k))
return urllib.urlencode(params) return urllib.urlencode(params)
def normalize(url):
return urltools.normalize(uenc(url))
def handles_oa2_requests(func): def handles_oa2_requests(func):
def wrapper(self, *args, **kwargs): def wrapper(self, *args, **kwargs):
...@@ -450,7 +457,7 @@ class SimpleBackend(object): ...@@ -450,7 +457,7 @@ class SimpleBackend(object):
scope=None, token_type="Bearer"): scope=None, token_type="Bearer"):
if scope and code_instance.scope != scope: if scope and code_instance.scope != scope:
raise OA2Error("Invalid scope") raise OA2Error("Invalid scope")
if redirect_uri != code_instance.redirect_uri: if normalize(redirect_uri) != normalize(code_instance.redirect_uri):
raise OA2Error("The redirect uri does not match " raise OA2Error("The redirect uri does not match "
"the one used during authorization") "the one used during authorization")
token = self.add_token_for_client(token_type, code_instance) token = self.add_token_for_client(token_type, code_instance)
...@@ -562,7 +569,8 @@ class SimpleBackend(object): ...@@ -562,7 +569,8 @@ class SimpleBackend(object):
raise OA2Error("Redirect uri length limit exceeded") raise OA2Error("Redirect uri length limit exceeded")
if not client.redirect_uri_is_valid(redirect_uri): if not client.redirect_uri_is_valid(redirect_uri):
raise OA2Error("Mismatching redirect uri") raise OA2Error("Mismatching redirect uri")
if expected_value is not None and redirect_uri != expected_value: if expected_value is not None and \
normalize(redirect_uri) != normalize(expected_value):
raise OA2Error("Invalid redirect uri") raise OA2Error("Invalid redirect uri")
else: else:
try: try:
...@@ -574,7 +582,7 @@ class SimpleBackend(object): ...@@ -574,7 +582,7 @@ class SimpleBackend(object):
def validate_state(self, client, params, headers): def validate_state(self, client, params, headers):
return params.get('state') return params.get('state')
raise OA2Error("Invalid state") #raise OA2Error("Invalid state")
def validate_scope(self, client, params, headers): def validate_scope(self, client, params, headers):
scope = params.get('scope') scope = params.get('scope')
......
...@@ -38,6 +38,7 @@ import base64 ...@@ -38,6 +38,7 @@ import base64
import datetime import datetime
from collections import namedtuple from collections import namedtuple
from urltools import normalize
from django.test import TransactionTestCase as TestCase from django.test import TransactionTestCase as TestCase
from django.test import Client as TestClient from django.test import Client as TestClient
...@@ -49,6 +50,8 @@ from astakos.oa2 import settings ...@@ -49,6 +50,8 @@ from astakos.oa2 import settings
from astakos.oa2.models import Client, AuthorizationCode, Token from astakos.oa2.models import Client, AuthorizationCode, Token
from astakos.im.tests import common from astakos.im.tests import common
from synnefo.util.text import uenc
ParsedURL = namedtuple('ParsedURL', ['host', 'scheme', 'path', 'params', ParsedURL = namedtuple('ParsedURL', ['host', 'scheme', 'path', 'params',
'url']) 'url'])
...@@ -216,8 +219,10 @@ class TestOA2(TestCase, URLAssertionsMixin): ...@@ -216,8 +219,10 @@ class TestOA2(TestCase, URLAssertionsMixin):
self.assertEqual(token.token_type, token_type) self.assertEqual(token.token_type, token_type)
self.assertEqual(token.grant_type, 'authorization_code') self.assertEqual(token.grant_type, 'authorization_code')
#self.assertEqual(token.user, expected.get('user')) #self.assertEqual(token.user, expected.get('user'))
self.assertEqual(token.redirect_uri, expected.get('redirect_uri')) self.assertEqual(normalize(uenc(token.redirect_uri)),
self.assertEqual(token.scope, expected.get('scope')) normalize(uenc(expected.get('redirect_uri'))))
self.assertEqual(normalize(uenc(token.scope)),
normalize(uenc(expected.get('scope'))))
self.assertEqual(token.state, expected.get('state')) self.assertEqual(token.state, expected.get('state'))
except Token.DoesNotExist: except Token.DoesNotExist:
self.fail("Invalid access_token") self.fail("Invalid access_token")
...@@ -271,7 +276,7 @@ class TestOA2(TestCase, URLAssertionsMixin): ...@@ -271,7 +276,7 @@ class TestOA2(TestCase, URLAssertionsMixin):
# mixed up credentials/client_id's # mixed up credentials/client_id's
self.client.set_credentials('client1', 'secret') self.client.set_credentials('client1', 'secret')
r = self.client.authorize_code('client2') r = self.client.authorize_code('client3')
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
self.assertCount(AuthorizationCode, 0) self.assertCount(AuthorizationCode, 0)
...@@ -342,7 +347,7 @@ class TestOA2(TestCase, URLAssertionsMixin): ...@@ -342,7 +347,7 @@ class TestOA2(TestCase, URLAssertionsMixin):
# valid request: trusted client # valid request: trusted client
params = {'redirect_uri': self.client3_redirect_uri, params = {'redirect_uri': self.client3_redirect_uri,
'scope': self.client3_redirect_uri, 'scope': self.client3_redirect_uri,
'extra_param': '123'} 'extra_param': 'γιουνικοντ'}
self.client.set_credentials('client3', 'secret') self.client.set_credentials('client3', 'secret')
r = self.client.authorize_code('client3', urlparams=params) r = self.client.authorize_code('client3', urlparams=params)
self.assertEqual(r.status_code, 302) self.assertEqual(r.status_code, 302)
...@@ -413,6 +418,27 @@ class TestOA2(TestCase, URLAssertionsMixin): ...@@ -413,6 +418,27 @@ class TestOA2(TestCase, URLAssertionsMixin):
r = self.client.authorize_code('client3', urlparams=params) r = self.client.authorize_code('client3', urlparams=params)
self.assertEqual(r.status_code, 400) self.assertEqual(r.status_code, 400)
# redirect uri descendant
redirect_uri = '%s/more?α=γιουνικοντ' % self.client3_redirect_uri
params['redirect_uri'] = redirect_uri
self.client.set_credentials('client3', 'secret')
r = self.client.authorize_code('client3', urlparams=params)
self.assertEqual(r.status_code, 302)
self.assertCount(AuthorizationCode, 6)
# redirect is valid
redirect = self.get_redirect_url(r)
self.assertParam(redirect, "code")
self.assertParamEqual(redirect, "state", 'csrfstate')
self.assertNoParam(redirect, "extra_param")
self.assertHost(redirect, "server3.com")
self.assertPath(redirect, urlparse.urlparse(redirect_uri).path)
code = AuthorizationCode.objects.get(code=redirect.params['code'][0])
self.assertEqual(code.state, 'csrfstate')
self.assertEqual(normalize(uenc(code.redirect_uri)),
normalize(uenc(redirect_uri)))
def test_get_token(self): def test_get_token(self):
# invalid method # invalid method
r = self.client.get(self.client.token_url) r = self.client.get(self.client.token_url)
...@@ -526,3 +552,26 @@ class TestOA2(TestCase, URLAssertionsMixin): ...@@ -526,3 +552,26 @@ class TestOA2(TestCase, URLAssertionsMixin):
'scope': redirect_uri, 'scope': redirect_uri,
'state': None} 'state': None}
self.assert_access_token_response(r, expected) self.assert_access_token_response(r, expected)
redirect_uri = '%s/more?α=γιουνικοντ' % self.client3_redirect_uri
params = {'redirect_uri': redirect_uri}
r = self.client.authorize_code('client3', urlparams=params)
self.assertCount(AuthorizationCode, 1)
redirect = self.get_redirect_url(r)
code_instance = AuthorizationCode.objects.get(
code=redirect.params['code'][0])
# valid request
self.client.set_credentials('client3', 'secret')
r = self.client.access_token(code_instance.code,
redirect_uri='%sa' % redirect_uri)
self.assertEqual(r.status_code, 400)
r = self.client.access_token(code_instance.code,
redirect_uri=redirect_uri)
self.assertCount(AuthorizationCode, 0) # assert code is consumed
self.assertCount(Token, 3)
expected = {'redirect_uri': redirect_uri,
'scope': redirect_uri,
'state': None}
self.assert_access_token_response(r, expected)
...@@ -74,6 +74,7 @@ INSTALL_REQUIRES = [ ...@@ -74,6 +74,7 @@ INSTALL_REQUIRES = [
'django-ratelimit==0.1', 'django-ratelimit==0.1',
'requests', 'requests',
'inflect', 'inflect',
'urltools',
'snf-django-lib', 'snf-django-lib',
'snf-branding', 'snf-branding',
'snf-webproject', 'snf-webproject',
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment