diff --git a/keystone/exception.py b/keystone/exception.py index c3b3ec8..bb4da37 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -51,6 +51,19 @@ class ValidationError(Error): title = 'Bad Request' +class ValidationSizeError(Error): + """Request attribute %(attribute)s must be less than or equal to %(size)i. + + The server could not comply with the request because the attribute + size is invalid (too large). + + The client is assumed to be in error. + + """ + code = 400 + title = 'Bad Request' + + class Unauthorized(Error): """The request you have made requires authentication.""" code = 401 diff --git a/keystone/service.py b/keystone/service.py index d54c073..89037b5 100644 --- a/keystone/service.py +++ b/keystone/service.py @@ -33,6 +33,12 @@ from keystone import token LOG = logging.getLogger(__name__) +# we allow tokens to be a bit larger to accomidate PKI +_MAX_TOKEN_SIZE = 8192 +_MAX_PARAM_SIZE = 64 +_MAX_PASSWORD_SIZE = 4096 + + class AdminRouter(wsgi.ComposingRouter): def __init__(self): mapper = routes.Mapper() @@ -288,9 +294,22 @@ class TokenController(wsgi.Application): if 'passwordCredentials' in auth: user_id = auth['passwordCredentials'].get('userId', None) + if user_id and len(user_id) > _MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='userId', + size=_MAX_PARAM_SIZE) username = auth['passwordCredentials'].get('username', '') + if len(username) > _MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='username', + size=_MAX_PARAM_SIZE) password = auth['passwordCredentials'].get('password', '') + if len(password) > _MAX_PASSWORD_SIZE: + raise exception.ValidationSizeError(attribute='password', + size=_MAX_PASSWORD_SIZE) + tenant_name = auth.get('tenantName', None) + if tenant_name and len(tenant_name) > _MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='tenantName', + size=_MAX_PARAM_SIZE) if username: try: @@ -302,6 +321,9 @@ class TokenController(wsgi.Application): # more compat tenant_id = auth.get('tenantId', None) + if tenant_id and len(tenant_id) > _MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='tenantId', + size=_MAX_PARAM_SIZE) if tenant_name: try: tenant_ref = self.identity_api.get_tenant_by_name( @@ -342,7 +364,14 @@ class TokenController(wsgi.Application): catalog_ref = {} elif 'token' in auth: old_token = auth['token'].get('id', None) + + if len(old_token) > _MAX_TOKEN_SIZE: + raise exception.ValidationSizeError(attribute='token', + size=_MAX_TOKEN_SIZE) tenant_name = auth.get('tenantName') + if tenant_name and len(tenant_name) > _MAX_PARAM_SIZE: + raise exception.ValidationSizeError(attribute='tenantName', + size=_MAX_PARAM_SIZE) try: old_token_ref = self.token_api.get_token(context=context, diff --git a/tests/test_service.py b/tests/test_service.py index 6fb98c6..f48bd9a 100644 --- a/tests/test_service.py +++ b/tests/test_service.py @@ -17,6 +17,7 @@ import time import default_fixtures from keystone import config +from keystone import exception from keystone import service from keystone import test from keystone.identity.backends import kvs as kvs_identity @@ -25,6 +26,31 @@ from keystone.identity.backends import kvs as kvs_identity CONF = config.CONF +def _build_user_auth(token=None, user_id=None, username=None, + password=None, tenant_id=None, tenant_name=None): + """Build auth dictionary. + + It will create an auth dictionary based on all the arguments + that it receives. + """ + auth_json = {} + if token is not None: + auth_json['token'] = token + if username or password: + auth_json['passwordCredentials'] = {} + if username is not None: + auth_json['passwordCredentials']['username'] = username + if user_id is not None: + auth_json['passwordCredentials']['userId'] = user_id + if password is not None: + auth_json['passwordCredentials']['password'] = password + if tenant_name is not None: + auth_json['tenantName'] = tenant_name + if tenant_id is not None: + auth_json['tenantId'] = tenant_id + return auth_json + + class TokenExpirationTest(test.TestCase): def setUp(self): super(TokenExpirationTest, self).setUp() @@ -75,3 +101,52 @@ class TokenExpirationTest(test.TestCase): def test_maintain_uuid_token_expiration(self): self.opt_in_group('signing', token_format='UUID') self._maintain_token_expiration() + + +class AuthTest(test.TestCase): + def setUp(self): + super(AuthTest, self).setUp() + + CONF.identity.driver = 'keystone.identity.backends.kvs.Identity' + self.load_backends() + self.load_fixtures(default_fixtures) + self.api = service.TokenController() + + def test_authenticate_user_id_too_large(self): + """Verify sending large 'userId' raises the right exception.""" + body_dict = _build_user_auth(user_id='0' * 65, username='FOO', + password='foo2') + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_username_too_large(self): + """Verify sending large 'username' raises the right exception.""" + body_dict = _build_user_auth(username='0' * 65, password='foo2') + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_tenant_id_too_large(self): + """Verify sending large 'tenantId' raises the right exception.""" + body_dict = _build_user_auth(username='FOO', password='foo2', + tenant_id='0' * 65) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_tenant_name_too_large(self): + """Verify sending large 'tenantName' raises the right exception.""" + body_dict = _build_user_auth(username='FOO', password='foo2', + tenant_name='0' * 65) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_token_too_large(self): + """Verify sending large 'token' raises the right exception.""" + body_dict = _build_user_auth(token={'id': '0' * 8193}) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict) + + def test_authenticate_password_too_large(self): + """Verify sending large 'password' raises the right exception.""" + body_dict = _build_user_auth(username='FOO', password='0' * 8193) + self.assertRaises(exception.ValidationSizeError, self.api.authenticate, + {}, body_dict)