Martin Kosek wrote:
On Mon, 2011-10-03 at 15:16 -0400, Rob Crittenden wrote:
Martin Kosek wrote:
On Mon, 2011-09-19 at 09:03 -0400, Rob Crittenden wrote:
Jan Cholasta wrote:
On 16.9.2011 21:16, Rob Crittenden wrote:
Prompt for the current password when changing your own password using
ipa passwd.

I had to jump through several hoops with this:

- Added a new sortorder option so the Current password is prompted first

IMO something like "before='password'" would be more readable and
probably less error-prone than "sortorder=-1".

The params are sorted numerically based on whether they are required,
have a default, etc. A negative value means it will appear first. This
is intended to be generic enough without having to worry about nested
resolution (A before B, B before C, C before A).


- Pass a magic value for current_password if changing someone else's
password

NOTE: This breaks the API for passwd. There is no way around it. I have
this as a minor update as it won't cause older clients to blow up too
badly, but their passwd command won't work.

rob


Honza


Generally, it works fine except for the case when user passes its own
user name. Do we want to support the following way?

# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: f...@idm.lab.bos.redhat.com

Valid starting     Expires            Service principal
09/23/11 09:48:05  09/24/11 09:48:05  
krbtgt/idm.lab.bos.redhat....@idm.lab.bos.redhat.com

# ipa passwd fbar
New Password:
Enter New Password again to verify:
ipa: ERROR: Insufficient access: Invalid credentials

Maybe we could throw an error when user passes its own principal to ipa
passwd command. After all, this argument is for changing _other_ user
passwords.

Martin


Fixed. The username wasn't being normalized into a principal until after
the default was set (where we determine whether to prompt for current
password).

rob

I don't think this is the correct patch :-)

Martin


Try this one.
>From 2ad7acc2b9cb1f1bc67e6ef35788cae69ca54715 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <rcrit...@redhat.com>
Date: Fri, 16 Sep 2011 15:08:17 -0400
Subject: [PATCH] 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 ac6560b..10b3f86 100644
--- a/API.txt
+++ b/API.txt
@@ -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")
diff --git a/VERSION b/VERSION
index a838058..ff8f92b 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 0a7d1a4..86365e7 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 c2ae4e7..61e7f49 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 e7e7578..f9e171b 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 b7d82f3..b26f7e9 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 a2e592d..b12403b 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:
-- 
1.7.6

_______________________________________________
Freeipa-devel mailing list
Freeipa-devel@redhat.com
https://www.redhat.com/mailman/listinfo/freeipa-devel

Reply via email to