diff --git a/keystone/config.py b/keystone/config.py index 72fd0dc..5ad995a 100644 --- a/keystone/config.py +++ b/keystone/config.py @@ -139,6 +139,9 @@ register_str('policy_file', default='policy.json') register_str('policy_default_rule', default=None) #default max request size is 112k register_int('max_request_body_size', default=114688) +register_int('max_param_size', default=64) +# we allow tokens to be a bit larger to accomidate PKI +register_int('max_token_size', default=8192) #ssl options register_bool('enable', group='ssl', default=False) diff --git a/keystone/exception.py b/keystone/exception.py index 2787e06..21aebc8 100644 --- a/keystone/exception.py +++ b/keystone/exception.py @@ -84,6 +84,19 @@ class StringLengthExceeded(ValidationError): %(type)s(CHAR(%(length)d)).""" +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 SecurityError(Error): """Avoids exposing details of security failures, unless in debug mode.""" diff --git a/keystone/token/controllers.py b/keystone/token/controllers.py index 7035986..450fda5 100644 --- a/keystone/token/controllers.py +++ b/keystone/token/controllers.py @@ -5,12 +5,13 @@ from keystone.common import cms from keystone.common import controller from keystone.common import dependency from keystone.common import logging +from keystone.common import utils from keystone import config from keystone import exception from keystone.openstack.common import timeutils from keystone.token import core - +CONF = config.CONF LOG = logging.getLogger(__name__) @@ -22,13 +23,13 @@ class ExternalAuthNotApplicable(Exception): @dependency.requires('catalog_api') class Auth(controller.V2Controller): def ca_cert(self, context, auth=None): - ca_file = open(config.CONF.signing.ca_certs, 'r') + ca_file = open(CONF.signing.ca_certs, 'r') data = ca_file.read() ca_file.close() return data def signing_cert(self, context, auth=None): - cert_file = open(config.CONF.signing.certfile, 'r') + cert_file = open(CONF.signing.certfile, 'r') data = cert_file.read() cert_file.close() return data @@ -110,17 +111,17 @@ class Auth(controller.V2Controller): service_catalog = Auth.format_catalog(catalog_ref) token_data['access']['serviceCatalog'] = service_catalog - if config.CONF.signing.token_format == 'UUID': + if CONF.signing.token_format == 'UUID': token_id = uuid.uuid4().hex - elif config.CONF.signing.token_format == 'PKI': + elif CONF.signing.token_format == 'PKI': token_id = cms.cms_sign_token(json.dumps(token_data), - config.CONF.signing.certfile, - config.CONF.signing.keyfile) + CONF.signing.certfile, + CONF.signing.keyfile) else: raise exception.UnexpectedError( 'Invalid value for token_format: %s.' ' Allowed values are PKI or UUID.' % - config.CONF.signing.token_format) + CONF.signing.token_format) try: self.token_api.create_token( context, token_id, dict(key=token_id, @@ -156,6 +157,9 @@ class Auth(controller.V2Controller): attribute="id", target="token") old_token = auth['token']['id'] + if len(old_token) > CONF.max_token_size: + raise exception.ValidationSizeError(attribute='token', + size=CONF.max_token_size) try: old_token_ref = self.token_api.get_token(context=context, @@ -204,6 +208,10 @@ class Auth(controller.V2Controller): attribute='password', target='passwordCredentials') password = auth['passwordCredentials']['password'] + max_pw_size = utils.MAX_PASSWORD_LENGTH + if password and len(password) > max_pw_size: + raise exception.ValidationSizeError(attribute='password', + size=max_pw_size) if ("userId" not in auth['passwordCredentials'] and "username" not in auth['passwordCredentials']): @@ -212,7 +220,14 @@ class Auth(controller.V2Controller): target='passwordCredentials') user_id = auth['passwordCredentials'].get('userId', None) + if user_id and len(user_id) > CONF.max_param_size: + raise exception.ValidationSizeError(attribute='userId', + size=CONF.max_param_size) + username = auth['passwordCredentials'].get('username', '') + if len(username) > CONF.max_param_size: + raise exception.ValidationSizeError(attribute='username', + size=CONF.max_param_size) if username: try: @@ -299,7 +314,15 @@ class Auth(controller.V2Controller): Returns a valid tenant_id if it exists, or None if not specified. """ tenant_id = auth.get('tenantId', None) + if tenant_id and len(tenant_id) > CONF.max_param_size: + raise exception.ValidationSizeError(attribute='tenantId', + size=CONF.max_param_size) + tenant_name = auth.get('tenantName', None) + if tenant_name and len(tenant_name) > CONF.max_param_size: + raise exception.ValidationSizeError(attribute='tenantName', + size=CONF.max_param_size) + if tenant_name: try: tenant_ref = self.identity_api.get_tenant_by_name( @@ -386,8 +409,8 @@ class Auth(controller.V2Controller): if cms.is_ans1_token(token_id): data = json.loads(cms.cms_verify(cms.token_to_cms(token_id), - config.CONF.signing.certfile, - config.CONF.signing.ca_certs)) + CONF.signing.certfile, + CONF.signing.ca_certs)) data['access']['token']['user'] = data['access']['user'] data['access']['token']['metadata'] = data['access']['metadata'] if belongs_to: @@ -458,8 +481,8 @@ class Auth(controller.V2Controller): data = {'revoked': tokens} json_data = json.dumps(data) signed_text = cms.cms_sign_text(json_data, - config.CONF.signing.certfile, - config.CONF.signing.keyfile) + CONF.signing.certfile, + CONF.signing.keyfile) return {'signed': signed_text} diff --git a/tests/test_auth.py b/tests/test_auth.py index 58a603f..2a1ba26 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -27,8 +27,8 @@ from keystone import token CONF = config.CONF -def _build_user_auth(token=None, username=None, - password=None, tenant_name=None): +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 @@ -41,10 +41,14 @@ def _build_user_auth(token=None, username=None, 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 @@ -121,6 +125,45 @@ class AuthBadRequests(AuthTest): self.assertRaises(exception.ValidationError, self.api.authenticate, {}, {'auth': 'abcd'}) + 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) + class AuthWithToken(AuthTest): def setUp(self):