Require current password when using passwd to change your own password.

Add a new required parameter, current_password. In order to ask this
first I added a new parameter option, sortorder. The lower the value the
earlier it will be prompted for.

I also changed the way autofill works. It will attempt to get the default
and if it doesn't get anything will continue prompting interactively.

Since current_password is required I'm passing a magic value that
means changing someone else's password. We need to pass something
since current_password is required.

The python-ldap passwd command doesn't seem to use the old password at
all so I do a simple bind to validate it.

https://fedorahosted.org/freeipa/ticket/1808
This commit is contained in:
Rob Crittenden 2011-09-16 15:08:17 -04:00 committed by Martin Kosek
parent 651534087c
commit 844d4ff8bf
7 changed files with 59 additions and 8 deletions

View File

@ -1829,9 +1829,10 @@ output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), 'User-friendly
output: Entry('result', <type 'dict'>, Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None))
output: Output('value', <type 'unicode'>, "The primary_key value of the entry, e.g. 'jdoe' for a user")
command: passwd
args: 2,0,3
args: 3,0,3
arg: Str('principal', validate_principal, autofill=True, cli_name='user', create_default=<lambda>, label=Gettext('User name', domain='ipa', localedir=None), normalizer=<lambda>, primary_key=True)
arg: Password('password', label=Gettext('Password', domain='ipa', localedir=None))
arg: Password('password', label=Gettext('New Password', domain='ipa', localedir=None))
arg: Password('current_password', autofill=True, confirm=False, default_from=<lambda>, label=Gettext('Current Password', domain='ipa', localedir=None), sortorder=-1)
output: Output('summary', (<type 'unicode'>, <type 'NoneType'>), 'User-friendly description of action performed')
output: Output('result', <type 'bool'>, 'True means the operation was successful')
output: Output('value', <type 'unicode'>, "The primary_key value of the entry, e.g. 'jdoe' for a user")

View File

@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000
# #
########################################################
IPA_API_VERSION_MAJOR=2
IPA_API_VERSION_MINOR=11
IPA_API_VERSION_MINOR=12

View File

@ -1048,12 +1048,14 @@ class cli(backend.Executioner):
for param in cmd.params():
if (param.required and param.name not in kw) or \
(param.alwaysask and honor_alwaysask) or self.env.prompt_all:
if param.autofill:
kw[param.name] = param.get_default(**kw)
if param.name in kw and kw[param.name] is not None:
continue
if param.password:
kw[param.name] = self.Backend.textui.prompt_password(
param.label, param.confirm
)
elif param.autofill:
kw[param.name] = param.get_default(**kw)
else:
default = param.get_default(**kw)
error = None

View File

@ -777,6 +777,8 @@ class Command(HasParam):
self._create_param_namespace('options')
def get_key(p):
if p.required:
if p.sortorder < 0:
return p.sortorder
if p.default_from is None:
return 0
return 1

View File

@ -317,6 +317,7 @@ class Param(ReadOnly):
('flags', frozenset, frozenset()),
('hint', (str, Gettext), None),
('alwaysask', bool, False),
('sortorder', int, 2), # see finalize()
# The 'default' kwarg gets appended in Param.__init__():
# ('default', self.type, None),

View File

@ -23,6 +23,7 @@ from ipalib import Str, Password
from ipalib import _
from ipalib import output
from ipalib.plugins.user import split_principal, validate_principal, normalize_principal
from ipalib.request import context
__doc__ = _("""
Set a user's password
@ -43,6 +44,22 @@ EXAMPLES:
ipa passwd tuser1
""")
# We only need to prompt for the current password when changing a password
# for yourself, but the parameter is still required
MAGIC_VALUE = u'CHANGING_PASSWORD_FOR_ANOTHER_USER'
def get_current_password(principal):
"""
If the user is changing their own password then return None so the
current password is prompted for, otherwise return a fixed value to
be ignored later.
"""
current_principal = util.get_current_principal()
if current_principal == normalize_principal(principal):
return None
else:
return MAGIC_VALUE
class passwd(Command):
__doc__ = _("Set a user's password.")
@ -56,14 +73,21 @@ class passwd(Command):
normalizer=lambda value: normalize_principal(value),
),
Password('password',
label=_('Password'),
label=_('New Password'),
),
Password('current_password',
label=_('Current Password'),
confirm=False,
default_from=lambda principal: get_current_password(principal),
autofill=True,
sortorder=-1,
),
)
has_output = output.standard_value
msg_summary = _('Changed password for "%(value)s"')
def execute(self, principal, password):
def execute(self, principal, password, current_password):
"""
Execute the passwd operation.
@ -74,6 +98,7 @@ class passwd(Command):
:param principal: The login name or principal of the user
:param password: the new password
:param current_password: the existing password, if applicable
"""
ldap = self.api.Backend.ldap2
@ -82,7 +107,16 @@ class passwd(Command):
",".join([api.env.container_user, api.env.basedn])
)
if principal == getattr(context, 'principal') and \
current_password == MAGIC_VALUE:
# No cheating
self.log.warn('User attempted to change password using magic value')
raise errors.ACIError(info='Invalid credentials')
if current_password == MAGIC_VALUE:
ldap.modify_password(dn, password)
else:
ldap.modify_password(dn, password, current_password)
return dict(
result=True,

View File

@ -899,6 +899,17 @@ class ldap2(CrudBackend, Encoder):
def modify_password(self, dn, new_pass, old_pass=''):
"""Set user password."""
dn = self.normalize_dn(dn)
# The python-ldap passwd command doesn't verify the old password
# so we'll do a simple bind to validate it.
if old_pass != '':
try:
conn = _ldap.initialize(self.ldap_uri)
conn.simple_bind_s(dn, old_pass)
conn.unbind()
except _ldap.LDAPError, e:
_handle_errors(e, **{})
try:
self.conn.passwd_s(dn, old_pass, new_pass)
except _ldap.LDAPError, e: