Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Modified code to properly propogate openldap errors, also added a reg… #2

Open
wants to merge 1 commit into
base: stable/kilo
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions keystone/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@

FILE_OPTIONS = {
None: [

cfg.StrOpt('password_regex',default='',
help='The regular expression for strength test of the password'),

cfg.StrOpt('password_regex_message',default='Password not strong enough',
help='The message shown to the user when the password strength check fails '),


cfg.StrOpt('admin_token', secret=True, default='ADMIN',
help='A "shared secret" that can be used to bootstrap '
'Keystone. This "token" does not represent a user, '
Expand Down Expand Up @@ -635,6 +643,9 @@
cfg.StrOpt('user_default_project_id_attribute',
help='LDAP attribute mapped to default_project_id for '
'users.'),
cfg.StrOpt('user_locked_time_attribute',
help='Time user account was'
' locked'),
cfg.BoolOpt('user_allow_create', default=True,
help='Allow user creation in LDAP backend.'),
cfg.BoolOpt('user_allow_update', default=True,
Expand Down
3 changes: 2 additions & 1 deletion keystone/common/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,12 @@ class User(Model):
email
enabled (bool, default True)
default_project_id
locked_time
"""

required_keys = ('id', 'name', 'domain_id')
optional_keys = ('password', 'description', 'email', 'enabled',
'default_project_id')
'default_project_id', 'locked_time')


class Group(Model):
Expand Down
21 changes: 17 additions & 4 deletions keystone/identity/backends/ldap.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def generates_uuids(self):
def authenticate(self, user_id, password):
try:
user_ref = self._get_user(user_id)
if('locked_time' in user_ref):
raise AssertionError(_('User Account for %s is locked' % user_ref['name']))
except exception.UserNotFound:
raise AssertionError(_('Invalid user / password'))
if not user_id or not password:
Expand Down Expand Up @@ -88,8 +90,13 @@ def get_user_by_name(self, user_name, domain_id):

# CRUD
def create_user(self, user_id, user):
self.user.check_allow_create()
user_ref = self.user.create(user)
try:
user_ref = self.user.create(user)
return self.user.filter_attributes(user_ref)
except ldap.CONSTRAINT_VIOLATION as e:
LOG.exception(e)
raise exception.ValidationError(e.message['info'])
LOG.info(e.message['info'])
return self.user.filter_attributes(user_ref)

def update_user(self, user_id, user):
Expand All @@ -106,8 +113,13 @@ def update_user(self, user_id, user):
# values are already equal.
user['enabled'] = not user['enabled']
old_obj['enabled'] = not old_obj['enabled']
try:
self.user.update(user_id, user, old_obj)
except ldap.CONSTRAINT_VIOLATION as e:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when there is no ldap backend?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code itself is in the driver for the ldap backend, if there is no ldap backend this code is never called.

LOG.exception(e)
raise exception.ValidationError(e.message['info'])
LOG.info(e.message['info'])

self.user.update(user_id, user, old_obj)
return self.user.get_filtered(user_id)

def delete_user(self, user_id):
Expand Down Expand Up @@ -203,7 +215,8 @@ class UserApi(common_ldap.EnabledEmuMixIn, common_ldap.BaseLdap):
'email': 'mail',
'name': 'name',
'enabled': 'enabled',
'default_project_id': 'default_project_id'}
'default_project_id': 'default_project_id',
'locked_time': 'locked_time'}
immutable_attrs = ['id']

model = models.User
Expand Down
42 changes: 42 additions & 0 deletions keystone/identity/controllers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

"""Workflow Logic the Identity service."""

import re
from oslo_config import cfg
from oslo_log import log

Expand Down Expand Up @@ -65,6 +66,9 @@ def create_user(self, context, user):
user = self._normalize_dict(user)
self.assert_admin(context)

if user.get('password') is not None:
User.check_pwd_policies(user['password'], user['name'])

if 'name' not in user or not user['name']:
msg = _('Name field is required and cannot be empty')
raise exception.ValidationError(message=msg)
Expand Down Expand Up @@ -105,6 +109,10 @@ def update_user(self, context, user_id, user):
old_user_ref = self.v3_to_v2_user(
self.identity_api.get_user(user_id))

#Name and password are both being changed
if 'password' in user and 'name' in user:
User.check_pwd_policies(user['password'], user['name'])

# Check whether a tenant is being added or changed for the user.
# Catch the case where the tenant is being changed for a user and also
# where a user previously had no tenant but a tenant is now being
Expand All @@ -118,6 +126,9 @@ def update_user(self, context, user_id, user):
# user update.
self.resource_api.get_project(default_project_id)

if 'password' in user and 'name' not in user:
User.check_pwd_policies(user['password'],old_user_ref['name'])

user_ref = self.v3_to_v2_user(
self.identity_api.update_user(user_id, user))

Expand Down Expand Up @@ -187,6 +198,22 @@ def _normalize_OSKSADM_password_on_request(ref):
ref['password'] = ref.pop('OS-KSADM:password')
return ref

@staticmethod
def check_syntax(password):
a = re.match(CONF.password_regex, password)
if not a:
raise exception.ValidationError(CONF.password_regex_message)

@staticmethod
def check_pwd_policies(password, name):

#if passsword is empty allow it,
#since empty password wont allow user to login
if password is None:
return
if name in password or password in name:
raise exception.ValidationError("Password not strong enough: user name cannot be part of the password")
User.check_syntax(password)

@dependency.requires('identity_api')
class UserV3(controller.V3Controller):
Expand All @@ -208,6 +235,9 @@ def _check_user_and_group_protection(self, context, prep_info,
def create_user(self, context, user):
self._require_attribute(user, 'name')

if user.get('password') is not None:
User.check_pwd_policies(user['password'], user['name'])

# The manager layer will generate the unique ID for users
ref = self._normalize_dict(user)
ref = self._normalize_domain_id(context, ref)
Expand Down Expand Up @@ -235,6 +265,18 @@ def get_user(self, context, user_id):
return UserV3.wrap_member(context, ref)

def _update_user(self, context, user_id, user):

#if password is being changed
#then check if name is not part of password
if 'password' in user:
#if name is not present then get it from the backend
if 'name' not in user:
old_user_ref = self.identity_api.get_user(user_id)
name = old_user_ref['name']
else:
name = user['name']
User.check_pwd_policies(user['password'], name)

self._require_matching_id(user_id, user)
self._require_matching_domain_id(
user_id, user, self.identity_api.get_user)
Expand Down