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
- 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
>From 8808faac321637377a03dc003e760e98a4f77ff4 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..2c4a63e 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 == 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