bcrypt the passwords
This commit is contained in:
parent
e344821225
commit
4899210334
|
@ -25,9 +25,16 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
|
import bcrypt
|
||||||
|
|
||||||
|
from keystone import config
|
||||||
from keystone.common import logging
|
from keystone.common import logging
|
||||||
|
|
||||||
|
|
||||||
|
CONF = config.CONF
|
||||||
|
config.register_int('bcrypt_strength', default=12)
|
||||||
|
|
||||||
|
|
||||||
def import_class(import_str):
|
def import_class(import_str):
|
||||||
"""Returns a class from a string including module and class."""
|
"""Returns a class from a string including module and class."""
|
||||||
mod_str, _sep, class_str = import_str.rpartition('.')
|
mod_str, _sep, class_str = import_str.rpartition('.')
|
||||||
|
@ -134,6 +141,24 @@ class Ec2Signer(object):
|
||||||
return b64
|
return b64
|
||||||
|
|
||||||
|
|
||||||
|
def hash_password(password):
|
||||||
|
"""Hash a password. Hard."""
|
||||||
|
salt = bcrypt.gensalt(CONF.bcrypt_strength)
|
||||||
|
return bcrypt.hashpw(password, salt)
|
||||||
|
|
||||||
|
|
||||||
|
def check_password(password, hashed):
|
||||||
|
"""Check that a plaintext password matches hashed.
|
||||||
|
|
||||||
|
Due to the way bcrypt works, hashing a password with the hashed
|
||||||
|
version of that password as salt will return the hashed version
|
||||||
|
of that password (mostly). Neat!
|
||||||
|
|
||||||
|
"""
|
||||||
|
check = bcrypt.hashpw(password, hashed[:29])
|
||||||
|
return check == hashed
|
||||||
|
|
||||||
|
|
||||||
# From python 2.7
|
# From python 2.7
|
||||||
def check_output(*popenargs, **kwargs):
|
def check_output(*popenargs, **kwargs):
|
||||||
r"""Run command with arguments and return its output as a byte string.
|
r"""Run command with arguments and return its output as a byte string.
|
||||||
|
|
|
@ -114,6 +114,18 @@ def register_cli_bool(*args, **kw):
|
||||||
return conf.register_cli_opt(cfg.BoolOpt(*args, **kw), group=group)
|
return conf.register_cli_opt(cfg.BoolOpt(*args, **kw), group=group)
|
||||||
|
|
||||||
|
|
||||||
|
def register_int(*args, **kw):
|
||||||
|
conf = kw.pop('conf', CONF)
|
||||||
|
group = _ensure_group(kw, conf)
|
||||||
|
return conf.register_opt(cfg.IntOpt(*args, **kw), group=group)
|
||||||
|
|
||||||
|
|
||||||
|
def register_cli_int(*args, **kw):
|
||||||
|
conf = kw.pop('conf', CONF)
|
||||||
|
group = _ensure_group(kw, conf)
|
||||||
|
return conf.register_cli_opt(cfg.IntOpt(*args, **kw), group=group)
|
||||||
|
|
||||||
|
|
||||||
def _ensure_group(kw, conf):
|
def _ensure_group(kw, conf):
|
||||||
group = kw.pop('group', None)
|
group = kw.pop('group', None)
|
||||||
if group:
|
if group:
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from keystone import identity
|
from keystone import identity
|
||||||
from keystone.common import kvs
|
from keystone.common import kvs
|
||||||
|
from keystone.common import utils
|
||||||
|
|
||||||
|
|
||||||
def _filter_user(user_ref):
|
def _filter_user(user_ref):
|
||||||
|
@ -12,6 +13,13 @@ def _filter_user(user_ref):
|
||||||
return user_ref
|
return user_ref
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_hashed_password(user_ref):
|
||||||
|
pw = user_ref.get('password', None)
|
||||||
|
if pw is not None:
|
||||||
|
user_ref['password'] = utils.hash_password(pw)
|
||||||
|
return user_ref
|
||||||
|
|
||||||
|
|
||||||
class Identity(kvs.Base, identity.Driver):
|
class Identity(kvs.Base, identity.Driver):
|
||||||
# Public interface
|
# Public interface
|
||||||
def authenticate(self, user_id=None, tenant_id=None, password=None):
|
def authenticate(self, user_id=None, tenant_id=None, password=None):
|
||||||
|
@ -24,7 +32,8 @@ class Identity(kvs.Base, identity.Driver):
|
||||||
user_ref = self._get_user(user_id)
|
user_ref = self._get_user(user_id)
|
||||||
tenant_ref = None
|
tenant_ref = None
|
||||||
metadata_ref = None
|
metadata_ref = None
|
||||||
if not user_ref or user_ref.get('password') != password:
|
if (not user_ref
|
||||||
|
or not utils.check_password(password, user_ref.get('password'))):
|
||||||
raise AssertionError('Invalid user / password')
|
raise AssertionError('Invalid user / password')
|
||||||
if tenant_id and tenant_id not in user_ref['tenants']:
|
if tenant_id and tenant_id not in user_ref['tenants']:
|
||||||
raise AssertionError('Invalid tenant')
|
raise AssertionError('Invalid tenant')
|
||||||
|
@ -78,15 +87,13 @@ class Identity(kvs.Base, identity.Driver):
|
||||||
user_ref = self._get_user(user_id)
|
user_ref = self._get_user(user_id)
|
||||||
tenants = set(user_ref.get('tenants', []))
|
tenants = set(user_ref.get('tenants', []))
|
||||||
tenants.add(tenant_id)
|
tenants.add(tenant_id)
|
||||||
user_ref['tenants'] = list(tenants)
|
self.update_user(user_id, {'tenants': list(tenants)})
|
||||||
self.update_user(user_id, user_ref)
|
|
||||||
|
|
||||||
def remove_user_from_tenant(self, tenant_id, user_id):
|
def remove_user_from_tenant(self, tenant_id, user_id):
|
||||||
user_ref = self._get_user(user_id)
|
user_ref = self._get_user(user_id)
|
||||||
tenants = set(user_ref.get('tenants', []))
|
tenants = set(user_ref.get('tenants', []))
|
||||||
tenants.remove(tenant_id)
|
tenants.remove(tenant_id)
|
||||||
user_ref['tenants'] = list(tenants)
|
self.update_user(user_id, {'tenants': list(tenants)})
|
||||||
self.update_user(user_id, user_ref)
|
|
||||||
|
|
||||||
def get_tenants_for_user(self, user_id):
|
def get_tenants_for_user(self, user_id):
|
||||||
user_ref = self._get_user(user_id)
|
user_ref = self._get_user(user_id)
|
||||||
|
@ -118,6 +125,7 @@ class Identity(kvs.Base, identity.Driver):
|
||||||
|
|
||||||
# CRUD
|
# CRUD
|
||||||
def create_user(self, user_id, user):
|
def create_user(self, user_id, user):
|
||||||
|
user = _ensure_hashed_password(user)
|
||||||
self.db.set('user-%s' % user_id, user)
|
self.db.set('user-%s' % user_id, user)
|
||||||
self.db.set('user_name-%s' % user['name'], user)
|
self.db.set('user_name-%s' % user['name'], user)
|
||||||
user_list = set(self.db.get('user_list', []))
|
user_list = set(self.db.get('user_list', []))
|
||||||
|
@ -128,10 +136,13 @@ class Identity(kvs.Base, identity.Driver):
|
||||||
def update_user(self, user_id, user):
|
def update_user(self, user_id, user):
|
||||||
# get the old name and delete it too
|
# get the old name and delete it too
|
||||||
old_user = self.db.get('user-%s' % user_id)
|
old_user = self.db.get('user-%s' % user_id)
|
||||||
|
new_user = old_user.copy()
|
||||||
|
user = _ensure_hashed_password(user)
|
||||||
|
new_user.update(user)
|
||||||
self.db.delete('user_name-%s' % old_user['name'])
|
self.db.delete('user_name-%s' % old_user['name'])
|
||||||
self.db.set('user-%s' % user_id, user)
|
self.db.set('user-%s' % user_id, new_user)
|
||||||
self.db.set('user_name-%s' % user['name'], user)
|
self.db.set('user_name-%s' % new_user['name'], new_user)
|
||||||
return user
|
return new_user
|
||||||
|
|
||||||
def delete_user(self, user_id):
|
def delete_user(self, user_id):
|
||||||
old_user = self.db.get('user-%s' % user_id)
|
old_user = self.db.get('user-%s' % user_id)
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
from keystone import identity
|
from keystone import identity
|
||||||
from keystone.common import sql
|
from keystone.common import sql
|
||||||
|
from keystone.common import utils
|
||||||
from keystone.common.sql import migration
|
from keystone.common.sql import migration
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +12,13 @@ def _filter_user(user_ref):
|
||||||
return user_ref
|
return user_ref
|
||||||
|
|
||||||
|
|
||||||
|
def _ensure_hashed_password(user_ref):
|
||||||
|
pw = user_ref.get('password', None)
|
||||||
|
if pw is not None:
|
||||||
|
user_ref['password'] = utils.hash_password(pw)
|
||||||
|
return user_ref
|
||||||
|
|
||||||
|
|
||||||
class User(sql.ModelBase, sql.DictBase):
|
class User(sql.ModelBase, sql.DictBase):
|
||||||
__tablename__ = 'user'
|
__tablename__ = 'user'
|
||||||
id = sql.Column(sql.String(64), primary_key=True)
|
id = sql.Column(sql.String(64), primary_key=True)
|
||||||
|
@ -106,7 +114,8 @@ class Identity(sql.Base, identity.Driver):
|
||||||
user_ref = self._get_user(user_id)
|
user_ref = self._get_user(user_id)
|
||||||
tenant_ref = None
|
tenant_ref = None
|
||||||
metadata_ref = None
|
metadata_ref = None
|
||||||
if not user_ref or user_ref.get('password') != password:
|
if (not user_ref
|
||||||
|
or not utils.check_password(password, user_ref.get('password'))):
|
||||||
raise AssertionError('Invalid user / password')
|
raise AssertionError('Invalid user / password')
|
||||||
|
|
||||||
tenants = self.get_tenants_for_user(user_id)
|
tenants = self.get_tenants_for_user(user_id)
|
||||||
|
@ -246,6 +255,7 @@ class Identity(sql.Base, identity.Driver):
|
||||||
|
|
||||||
# CRUD
|
# CRUD
|
||||||
def create_user(self, user_id, user):
|
def create_user(self, user_id, user):
|
||||||
|
user = _ensure_hashed_password(user)
|
||||||
session = self.get_session()
|
session = self.get_session()
|
||||||
with session.begin():
|
with session.begin():
|
||||||
user_ref = User.from_dict(user)
|
user_ref = User.from_dict(user)
|
||||||
|
@ -258,6 +268,7 @@ class Identity(sql.Base, identity.Driver):
|
||||||
with session.begin():
|
with session.begin():
|
||||||
user_ref = session.query(User).filter_by(id=user_id).first()
|
user_ref = session.query(User).filter_by(id=user_id).first()
|
||||||
old_user_dict = user_ref.to_dict()
|
old_user_dict = user_ref.to_dict()
|
||||||
|
user = _ensure_hashed_password(user)
|
||||||
for k in user:
|
for k in user:
|
||||||
old_user_dict[k] = user[k]
|
old_user_dict[k] = user[k]
|
||||||
new_user = User.from_dict(old_user_dict)
|
new_user = User.from_dict(old_user_dict)
|
||||||
|
|
|
@ -334,13 +334,10 @@ class UserController(wsgi.Application):
|
||||||
self.identity_api.add_user_to_tenant(tenant_id, user_id)
|
self.identity_api.add_user_to_tenant(tenant_id, user_id)
|
||||||
return {'user': new_user_ref}
|
return {'user': new_user_ref}
|
||||||
|
|
||||||
# NOTE(termie): this is really more of a patch than a put
|
|
||||||
def update_user(self, context, user_id, user):
|
def update_user(self, context, user_id, user):
|
||||||
|
# NOTE(termie): this is really more of a patch than a put
|
||||||
self.assert_admin(context)
|
self.assert_admin(context)
|
||||||
user_ref = self.identity_api._get_user(context, user_id)
|
user_ref = self.identity_api.update_user(context, user_id, user)
|
||||||
del user['id']
|
|
||||||
user_ref.update(user)
|
|
||||||
self.identity_api.update_user(context, user_id, user_ref)
|
|
||||||
return {'user': user_ref}
|
return {'user': user_ref}
|
||||||
|
|
||||||
def delete_user(self, context, user_id):
|
def delete_user(self, context, user_id):
|
||||||
|
|
|
@ -107,6 +107,11 @@ class TestCase(unittest.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestCase, self).setUp()
|
super(TestCase, self).setUp()
|
||||||
|
self.config()
|
||||||
|
|
||||||
|
def config(self):
|
||||||
|
CONF(config_files=[etcdir('keystone.conf'),
|
||||||
|
testsdir('test_overrides.conf')])
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
for path in self._paths:
|
for path in self._paths:
|
||||||
|
@ -137,10 +142,11 @@ class TestCase(unittest.TestCase):
|
||||||
for user in fixtures.USERS:
|
for user in fixtures.USERS:
|
||||||
user_copy = user.copy()
|
user_copy = user.copy()
|
||||||
tenants = user_copy.pop('tenants')
|
tenants = user_copy.pop('tenants')
|
||||||
rv = self.identity_api.create_user(user['id'], user_copy)
|
rv = self.identity_api.create_user(user['id'], user_copy.copy())
|
||||||
for tenant_id in tenants:
|
for tenant_id in tenants:
|
||||||
self.identity_api.add_user_to_tenant(tenant_id, user['id'])
|
self.identity_api.add_user_to_tenant(tenant_id, user['id'])
|
||||||
setattr(self, 'user_%s' % user['id'], rv)
|
setattr(self, 'user_%s' % user['id'], user_copy)
|
||||||
|
print user_copy
|
||||||
|
|
||||||
for role in fixtures.ROLES:
|
for role in fixtures.ROLES:
|
||||||
rv = self.identity_api.create_role(role['id'], role)
|
rv = self.identity_api.create_role(role['id'], role)
|
||||||
|
|
|
@ -45,6 +45,11 @@ class IdentityTests(object):
|
||||||
self.assertDictEquals(tenant_ref, self.tenant_bar)
|
self.assertDictEquals(tenant_ref, self.tenant_bar)
|
||||||
self.assertDictEquals(metadata_ref, self.metadata_foobar)
|
self.assertDictEquals(metadata_ref, self.metadata_foobar)
|
||||||
|
|
||||||
|
def test_password_hashed(self):
|
||||||
|
user_ref = self.identity_api._get_user(self.user_foo['id'])
|
||||||
|
self.assertNotEqual(user_ref['password'], self.user_foo['password'])
|
||||||
|
|
||||||
|
|
||||||
def test_get_tenant_bad_tenant(self):
|
def test_get_tenant_bad_tenant(self):
|
||||||
tenant_ref = self.identity_api.get_tenant(
|
tenant_ref = self.identity_api.get_tenant(
|
||||||
tenant_id=self.tenant_bar['id'] + 'WRONG')
|
tenant_id=self.tenant_bar['id'] + 'WRONG')
|
||||||
|
|
|
@ -45,7 +45,6 @@ class KcMasterTestCase(CompatTestCase):
|
||||||
from keystoneclient.v2_0 import client as ks_client
|
from keystoneclient.v2_0 import client as ks_client
|
||||||
reload(ks_client)
|
reload(ks_client)
|
||||||
|
|
||||||
self._config()
|
|
||||||
self.public_app = self.loadapp('keystone', name='main')
|
self.public_app = self.loadapp('keystone', name='main')
|
||||||
self.admin_app = self.loadapp('keystone', name='admin')
|
self.admin_app = self.loadapp('keystone', name='admin')
|
||||||
|
|
||||||
|
@ -63,10 +62,6 @@ class KcMasterTestCase(CompatTestCase):
|
||||||
self.user_foo['id'], self.tenant_bar['id'],
|
self.user_foo['id'], self.tenant_bar['id'],
|
||||||
dict(roles=['keystone_admin'], is_admin='1'))
|
dict(roles=['keystone_admin'], is_admin='1'))
|
||||||
|
|
||||||
def _config(self):
|
|
||||||
CONF(config_files=[test.etcdir('keystone.conf'),
|
|
||||||
test.testsdir('test_overrides.conf')])
|
|
||||||
|
|
||||||
def get_client(self, user_ref=None, tenant_ref=None):
|
def get_client(self, user_ref=None, tenant_ref=None):
|
||||||
if user_ref is None:
|
if user_ref is None:
|
||||||
user_ref = self.user_foo
|
user_ref = self.user_foo
|
||||||
|
@ -87,10 +82,9 @@ class KcMasterTestCase(CompatTestCase):
|
||||||
self.assertEquals(tenants[0].id, self.tenant_bar['id'])
|
self.assertEquals(tenants[0].id, self.tenant_bar['id'])
|
||||||
|
|
||||||
def test_authenticate_tenant_id_and_tenants(self):
|
def test_authenticate_tenant_id_and_tenants(self):
|
||||||
client = self._client(username='FOO',
|
client = self._client(username=self.user_foo['name'],
|
||||||
password='foo2',
|
password=self.user_foo['password'],
|
||||||
tenant_id='bar')
|
tenant_id='bar')
|
||||||
|
|
||||||
tenants = client.tenants.list()
|
tenants = client.tenants.list()
|
||||||
self.assertEquals(tenants[0].id, self.tenant_bar['id'])
|
self.assertEquals(tenants[0].id, self.tenant_bar['id'])
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ CONF = config.CONF
|
||||||
|
|
||||||
|
|
||||||
class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase):
|
class KcMasterSqlTestCase(test_keystoneclient.KcMasterTestCase):
|
||||||
def _config(self):
|
def config(self):
|
||||||
CONF(config_files=[test.etcdir('keystone.conf'),
|
CONF(config_files=[test.etcdir('keystone.conf'),
|
||||||
test.testsdir('test_overrides.conf'),
|
test.testsdir('test_overrides.conf'),
|
||||||
test.testsdir('backend_sql.conf')])
|
test.testsdir('backend_sql.conf')])
|
||||||
|
|
|
@ -1,2 +1,5 @@
|
||||||
|
[DEFAULT]
|
||||||
|
bcrypt_strength = 2
|
||||||
|
|
||||||
[catalog]
|
[catalog]
|
||||||
template_file = default_catalog.templates
|
template_file = default_catalog.templates
|
||||||
|
|
|
@ -7,5 +7,6 @@ routes
|
||||||
pycli
|
pycli
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
sqlalchemy-migrate
|
sqlalchemy-migrate
|
||||||
|
py-bcrypt
|
||||||
|
|
||||||
-e git+https://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient
|
-e git+https://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient
|
||||||
|
|
Loading…
Reference in New Issue