From bd227b356280f54f48bc01901275833a51f87fd7 Mon Sep 17 00:00:00 2001 From: Rob Crittenden Date: Fri, 16 Sep 2011 15:08:17 -0400 Subject: 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 --- API.txt | 5 +++-- VERSION | 2 +- ipalib/cli.py | 6 ++++-- ipalib/frontend.py | 2 ++ ipalib/parameters.py | 1 + ipalib/plugins/passwd.py | 40 +++++++++++++++++++++++++++++++++++++--- ipaserver/plugins/ldap2.py | 11 +++++++++++ 7 files changed, 59 insertions(+), 8 deletions(-) diff --git a/API.txt b/API.txt index ac6560b0b..10b3f86a8 100644 --- a/API.txt +++ b/API.txt @@ -1829,9 +1829,10 @@ output: Output('summary', (, ), 'User-friendly output: Entry('result', , Gettext('A dictionary representing an LDAP entry', domain='ipa', localedir=None)) output: Output('value', , "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=, label=Gettext('User name', domain='ipa', localedir=None), normalizer=, 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=, label=Gettext('Current Password', domain='ipa', localedir=None), sortorder=-1) output: Output('summary', (, ), 'User-friendly description of action performed') output: Output('result', , 'True means the operation was successful') output: Output('value', , "The primary_key value of the entry, e.g. 'jdoe' for a user") diff --git a/VERSION b/VERSION index d7eaa8611..51711efe3 100644 --- a/VERSION +++ b/VERSION @@ -79,4 +79,4 @@ IPA_DATA_VERSION=20100614120000 # # ######################################################## IPA_API_VERSION_MAJOR=2 -IPA_API_VERSION_MINOR=11 +IPA_API_VERSION_MINOR=12 diff --git a/ipalib/cli.py b/ipalib/cli.py index 0a7d1a4cf..86365e7ca 100644 --- a/ipalib/cli.py +++ b/ipalib/cli.py @@ -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 diff --git a/ipalib/frontend.py b/ipalib/frontend.py index c2ae4e744..61e7f493f 100644 --- a/ipalib/frontend.py +++ b/ipalib/frontend.py @@ -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 diff --git a/ipalib/parameters.py b/ipalib/parameters.py index e7e75782a..f9e171b0e 100644 --- a/ipalib/parameters.py +++ b/ipalib/parameters.py @@ -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), diff --git a/ipalib/plugins/passwd.py b/ipalib/plugins/passwd.py index b7d82f355..b26f7e9fd 100644 --- a/ipalib/plugins/passwd.py +++ b/ipalib/plugins/passwd.py @@ -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]) ) - ldap.modify_password(dn, password) + 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, diff --git a/ipaserver/plugins/ldap2.py b/ipaserver/plugins/ldap2.py index a2e592d30..b12403b93 100644 --- a/ipaserver/plugins/ldap2.py +++ b/ipaserver/plugins/ldap2.py @@ -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: -- cgit