diff --git a/etc/keystone.conf b/etc/keystone.conf index 4fa144df03..18ab699ed6 100755 --- a/etc/keystone.conf +++ b/etc/keystone.conf @@ -44,6 +44,9 @@ keystone-admin-role = Admin #Role that allows to perform service admin operations. keystone-service-admin-role = KeystoneServiceAdmin +#Tells whether password user need to be hashed in the backend +hash-password = True + [keystone.backends.sqlalchemy] # SQLAlchemy connection string for the reference implementation registry # server. Any valid SQLAlchemy connection string is fine. diff --git a/keystone/backends/__init__.py b/keystone/backends/__init__.py index 4475b429e2..4bd9a4003d 100755 --- a/keystone/backends/__init__.py +++ b/keystone/backends/__init__.py @@ -28,6 +28,7 @@ ADMIN_ROLE_ID = None ADMIN_ROLE_NAME = None SERVICE_ADMIN_ROLE_ID = None SERVICE_ADMIN_ROLE_NAME = None +SHOULD_HASH_PASSWORD = None def configure_backends(options): @@ -43,3 +44,8 @@ def configure_backends(options): global SERVICE_ADMIN_ROLE_NAME SERVICE_ADMIN_ROLE_NAME = options["keystone-service-admin-role"] + + global SHOULD_HASH_PASSWORD + if "hash-password" in options\ + and ast.literal_eval(options["hash-password"]) == True: + SHOULD_HASH_PASSWORD = options["hash-password"] diff --git a/keystone/backends/backendutils.py b/keystone/backends/backendutils.py new file mode 100644 index 0000000000..02970b35f1 --- /dev/null +++ b/keystone/backends/backendutils.py @@ -0,0 +1,49 @@ +from keystone.backends import models +import keystone.backends as backends +from passlib.hash import sha512_crypt as sc + + +def __get_hashed_password(password): + if password != None and len(password) > 0: + return __make_password(password) + else: + return None + + +def set_hashed_password(values): + """ + Sets hashed password for password. + """ + if backends.SHOULD_HASH_PASSWORD: + if type(values) is dict and 'password' in values.keys(): + values['password'] = __get_hashed_password(values['password']) + elif type(values) is models.User: + values.password = __get_hashed_password(values.password) + + +def check_password(raw_password, enc_password): + """ + Compares raw password and encoded password. + """ + if not raw_password: + return False + if backends.SHOULD_HASH_PASSWORD: + return sc.verify(raw_password, enc_password) + else: + return enc_password == raw_password + + +def __make_password(raw_password): + """ + Produce a new encoded password. + """ + if raw_password is None: + return None + hsh = __get_hexdigest(raw_password) + return '%s' % (hsh) + + +#Refer http://packages.python.org/passlib/lib/passlib.hash.sha512_crypt.html +#Using the default properties as of now.Salt gets generated automatically. +def __get_hexdigest(raw_password): + return sc.encrypt(raw_password) diff --git a/keystone/backends/ldap/api/user.py b/keystone/backends/ldap/api/user.py index 0409590e06..f13bc0a674 100644 --- a/keystone/backends/ldap/api/user.py +++ b/keystone/backends/ldap/api/user.py @@ -1,7 +1,7 @@ import ldap import ldap.filter -from keystone import utils +import keystone.backends.backendutils as utils from keystone.backends.api import BaseUserAPI from keystone.backends.sqlalchemy.api.user import UserAPI as SQLUserAPI @@ -37,7 +37,7 @@ class UserAPI(BaseLdapAPI, BaseUserAPI): # Persist the 'name' as the UID values['id'] = values['name'] delattr(values, 'name') - + utils.set_hashed_password(values) values = super(UserAPI, self).create(values) if values['tenant_id'] is not None: self.api.tenant.add_user(values['tenant_id'], values['id']) @@ -55,6 +55,7 @@ class UserAPI(BaseLdapAPI, BaseUserAPI): self.api.tenant.remove_user(old_obj.tenant_id, id) if new_tenant: self.api.tenant.add_user(new_tenant, id) + utils.set_hashed_password(values) super(UserAPI, self).update(id, values, old_obj) def delete(self, id): @@ -113,13 +114,7 @@ class UserAPI(BaseLdapAPI, BaseUserAPI): self.api.tenant.get_users(tenant_id)) def check_password(self, user, password): - try: - self.api.get_connection(self._id_to_dn(user.id), password) - except (ldap.NO_SUCH_OBJECT, ldap.INAPPROPRIATE_AUTH, - ldap.INVALID_CREDENTIALS): - return False - else: - return True + return utils.check_password(password, user.password) add_redirects(locals(), SQLUserAPI, ['get_by_group', 'tenant_group', 'tenant_group_delete', 'user_groups_get_all', diff --git a/keystone/backends/sqlalchemy/api/user.py b/keystone/backends/sqlalchemy/api/user.py index 8c92365aaa..56b0dfe7dd 100755 --- a/keystone/backends/sqlalchemy/api/user.py +++ b/keystone/backends/sqlalchemy/api/user.py @@ -15,7 +15,7 @@ # License for the specific language governing permissions and limitations # under the License. -import keystone.utils as utils +import keystone.backends.backendutils as utils from keystone.backends.sqlalchemy import get_session, models, aliased, \ joinedload from keystone.backends.api import BaseUserAPI @@ -29,17 +29,11 @@ class UserAPI(BaseUserAPI): def create(self, values): user_ref = models.User() - self.__check_and_use_hashed_password(values) + utils.set_hashed_password(values) user_ref.update(values) user_ref.save() return user_ref - def __check_and_use_hashed_password(self, values): - if type(values) is dict and 'password' in values.keys(): - values['password'] = utils.get_hashed_password(values['password']) - elif type(values) is models.User: - values.password = utils.get_hashed_password(values.password) - def get(self, id, session=None): if not session: session = get_session() @@ -119,7 +113,7 @@ class UserAPI(BaseUserAPI): session = get_session() with session.begin(): user_ref = self.get(id, session) - self.__check_and_use_hashed_password(values) + utils.set_hashed_password(values) user_ref.update(values) user_ref.save(session=session) @@ -315,7 +309,7 @@ class UserAPI(BaseUserAPI): return (prev_page, next_page) def check_password(self, user, password): - return user.password == utils.get_hashed_password(password) + return utils.check_password(password, user.password) def get(): diff --git a/keystone/test/etc/ldap.conf.template b/keystone/test/etc/ldap.conf.template index 4785def848..46ed8369fa 100755 --- a/keystone/test/etc/ldap.conf.template +++ b/keystone/test/etc/ldap.conf.template @@ -14,6 +14,7 @@ admin_host = 0.0.0.0 admin_port = 5001 keystone-admin-role = Admin keystone-service-admin-role = KeystoneServiceAdmin +hash-password = True [keystone.backends.sqlalchemy] sql_connection = for_testing_only diff --git a/keystone/test/etc/sql.conf.template b/keystone/test/etc/sql.conf.template index 7657fe018e..e67b37740e 100644 --- a/keystone/test/etc/sql.conf.template +++ b/keystone/test/etc/sql.conf.template @@ -14,6 +14,7 @@ admin_host = 0.0.0.0 admin_port = 5001 keystone-admin-role = Admin keystone-service-admin-role = KeystoneServiceAdmin +hash-password = True [keystone.backends.sqlalchemy] sql_connection = for_testing_only @@ -45,4 +46,4 @@ paste.filter_factory = keystone.middleware.url:filter_factory paste.filter_factory = keystone.frontends.legacy_token_auth:filter_factory [filter:RAX-KEY-extension] -paste.filter_factory = keystone.contrib.extensions.service.raxkey.frontend:filter_factory +paste.filter_factory = keystone.contrib.extensions.service.raxkey.frontend:filter_factory \ No newline at end of file diff --git a/keystone/test/unit/base.py b/keystone/test/unit/base.py index 85cf1bfa94..b6866566c1 100644 --- a/keystone/test/unit/base.py +++ b/keystone/test/unit/base.py @@ -101,6 +101,7 @@ class ServiceAPITest(unittest.TestCase): }, 'keystone-admin-role': 'Admin', 'keystone-service-admin-role': 'KeystoneServiceAdmin', + 'hash-password': 'True', } def setUp(self): diff --git a/keystone/utils.py b/keystone/utils.py index b9e95cd280..444c5e21f1 100755 --- a/keystone/utils.py +++ b/keystone/utils.py @@ -19,9 +19,7 @@ import os import sys import logging import functools - from webob import Response - import keystone.logic.types.fault as fault @@ -144,17 +142,6 @@ def send_legacy_result(code, headers): return resp -# Currently using sha1 to hash, without a salt value. -# Need to research relevant openstack standards. -def get_hashed_password(password): - if password != None and len(password) > 0: - return password - # why is this disabled? - #return hashlib.sha1(password).hexdigest() - else: - return None - - def import_module(module_name, class_name=None): '''Import a class given a full module.class name or seperate module and options. If no class_name is given, it is assumed to diff --git a/tools/pip-requires b/tools/pip-requires index 6924166994..657d820309 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -13,6 +13,8 @@ sqlalchemy webob Routes httplib2 +#For Hashing +passlib # Optional backend: LDAP python-ldap==2.3.13