Rob Crittenden wrote:
Petr Vobornik wrote:
On 04/13/2012 09:28 PM, Rob Crittenden wrote:
When doing a forms-based login there is no notification that a password
needs to be reset. We don't currently provide a facility for that but we
should at least tell users what is going on.

This patch adds an LDAP bind to test the password to see if it is
expired and returns the string "Password Expired" along with the 401 if
it is. I'm told this is all the UI will need to be able to identify this


UI can work with it. I have a patch ready. I'll send it when this will
be ACKed.

Some notes:

1) The error templates and the 'Password Expired' message are hardcoded
to be English. It's fine at the moment. Will we internationalize them
sometime in future? If so, we will run into the same problem again.

No plans to. I can update the patch with a comment specifically to not
internationalize it if you'd like.

2) conn.destroy_connection() won't be called if an exception occurs. Not
sure if it is a problem, GC and __del__ should take care of it.

Hmm, this is due to a late stage change I made. I originally had this
broken out into two blocks where the only thing done in the first
try/except block was the connection, so the only exception that could
happen was a failed connection.

That isn't true any more. I'll update the patch.

And here you go.

>From c32ca62a84e233174b7e83aebe2923fa1ed7fac6 Mon Sep 17 00:00:00 2001
From: Rob Crittenden <>
Date: Fri, 13 Apr 2012 15:19:32 -0400
Subject: [PATCH] Return consistent expiration message for forms-based login

We need to inform users when a forms-based login fails due to the
password needing to be reset. Currently there is no way to distinguish
a reset case vs an incorrect password.

This will bind the user using a simple LDAP bind over ldapi (by default)
and if that is successful, check the expiration date against the current

The UI portion of this that uses this message will come later.
 ipaserver/ |   37 +++++++++++++++++++++++++++++++++++++
 1 files changed, 37 insertions(+), 0 deletions(-)

diff --git a/ipaserver/ b/ipaserver/
index 2fbd79f208320664ec3acb6c8c6004cff683650d..2b6b3c9727e49c5697afa6bf43b7bf9dc5f40f49 100644
--- a/ipaserver/
+++ b/ipaserver/
@@ -32,6 +32,8 @@ from ipalib.errors import PublicError, InternalError, CommandError, JSONError, C
 from ipalib.request import context, Connection, destroy_context
 from ipalib.rpc import xml_dumps, xml_loads
 from ipalib.util import make_repr, parse_time_duration
+from ipalib.dn import DN
+from ipaserver.plugins.ldap2 import ldap2
 from ipapython.compat import json
 from ipalib.session import session_mgr, AuthManager, get_ipa_ccache_name, load_ccache_data, bind_ipa_ccache, release_ipa_ccache, fmt_time, default_max_session_duration
 from ipalib.backend import Backend
@@ -45,6 +47,7 @@ import string
 import datetime
 from decimal import Decimal
 import urlparse
+import time
 HTTP_STATUS_SUCCESS = '200 Success'
 HTTP_STATUS_SERVER_ERROR = '500 Internal Server Error'
@@ -938,6 +941,40 @@ class login_password(Backend, KerberosSession, HTTP_Status):
             self.kinit(user, self.api.env.realm, password, ipa_ccache_name)
         except InvalidSessionPassword, e:
+            # Ok, now why is this bad. Is the password simply bad or is the
+            # password expired?
+            try:
+                dn = str(DN(('uid', user),
+                            self.api.env.container_user,
+                            self.api.env.basedn))
+                conn = ldap2(shared_instance=False,
+                             ldap_uri=self.api.env.ldap_uri)
+                conn.connect(bind_dn=dn, bind_pw=password)
+                # password is ok, must be expired, lets double-check
+                (userdn, entry_attrs) = conn.get_entry(dn,
+                    ['krbpasswordexpiration'])
+                if 'krbpasswordexpiration' in entry_attrs:
+                    expiration = entry_attrs['krbpasswordexpiration'][0]
+                    try:
+                        exp = time.strptime(expiration, '%Y%m%d%H%M%SZ')
+                        if exp <= time.gmtime():
+                            # Return a fixed value so the UI can
+                            # identify the problem. Do not internationalize
+                            # this string.
+                            e = "Password Expired"
+                    except ValueError, v:
+                        self.error('Unable to convert %s to a time string'
+                            % expiration)
+            except Exception:
+                # It doesn't really matter how we got here but the user's
+                # password is not accepted or the user is unknown.
+                pass
+            finally:
+                if conn.isconnected():
+                    conn.destroy_connection()
             return self.unauthorized(environ, start_response, str(e))
         return self.finalize_kerberos_acquisition('login_password', ipa_ccache_name, environ, start_response)

Freeipa-devel mailing list

Reply via email to