Index: main/waeup.sirp/branches/uli-studentpw/src/waeup/sirp/students/authentication.py
===================================================================
--- main/waeup.sirp/branches/uli-studentpw/src/waeup/sirp/students/authentication.py	(revision 6713)
+++ main/waeup.sirp/branches/uli-studentpw/src/waeup/sirp/students/authentication.py	(revision 6721)
@@ -28,5 +28,8 @@
 from zope.pluggableauth.interfaces import (
     IAuthenticatorPlugin, ICredentialsPlugin)
-from zope.pluggableauth.plugins.session import SessionCredentialsPlugin
+from zope.pluggableauth.plugins.session import (
+    SessionCredentialsPlugin, SessionCredentials)
+from zope.publisher.interfaces.http import IHTTPRequest
+from zope.session.interfaces import ISession
 from waeup.sirp.authentication import PrincipalInfo, get_principal_role_manager
 from waeup.sirp.interfaces import IAuthPluginUtility, IUserAccount
@@ -119,7 +122,5 @@
             return None
         if not account.checkPassword(credentials['password']):
-            # XXX: This is not a proper solution. Works, however, basically
-            if credentials.get('reset', False) is not True:
-                return None
+            return None
         return PrincipalInfo(id=account.name,
                              title=account.title,
@@ -163,4 +164,43 @@
 class PasswordChangeCredentialsPlugin(grok.GlobalUtility,
                                       SessionCredentialsPlugin):
+    """A session credentials plugin that handles the case of a user
+       changing his/her own password.
+
+    When users change their own password they might find themselves
+    logged out on next request.
+
+    To avoid this, we support to use a 'change password' page a bit
+    like a regular login page. That means, on each request we lookup
+    the sent data for a login field called 'student_id' and a
+    password.
+
+    If both exist, this means someone sent new credentials.
+
+    We then look for the old credentials stored in the user session.
+    If the new credentials' login (the student_id) matches the old
+    one's, we set the new credentials in session _but_ we return the
+    old credentials for the authentication plugins to check as for the
+    current request (and for the last time) the old credentials apply.
+
+    No valid credentials are returned by this plugin if one of the
+    follwing circumstances is true
+
+    - the sent request is not a regular IHTTPRequest
+
+    - the credentials to set do not match the old ones
+
+    - no credentials are sent with the request
+
+    - no credentials were set before (i.e. the user has no session
+      with credentials set before)
+
+    Therefore it is mandatory to put this plugin in the line of all
+    credentials plugins _before_ other plugins, so that the regular
+    credentials plugins can drop in as a 'fallback'.
+
+    This plugin was designed for students to change their passwords,
+    but might be used to allow password resets for other types of
+    accounts as well.
+    """
     grok.provides(ICredentialsPlugin)
     grok.name('student_pw_change')
@@ -171,11 +211,30 @@
 
     def extractCredentials(self, request):
-        result = super(
-            PasswordChangeCredentialsPlugin, self).extractCredentials(
-            request)
-        if isinstance(result, dict):
-            # Add a marker for the authentication plugin
-            result['reset'] = True
-        return result
+        if not IHTTPRequest.providedBy(request):
+            return None
+        login = request.get(self.loginfield, None)
+        password = request.get(self.passwordfield, None)
+        if not login or not password:
+            return None
+        session = ISession(request)
+        sessionData = session.get(
+            'zope.pluggableauth.browserplugins')
+        old_credentials = sessionData.get('credentials', None)
+        if old_credentials is None:
+            # Password changes for already authenticated users only!
+            return None
+        if old_credentials.getLogin() != login:
+            # Special treatment only for users that change their own pw.
+            return None
+        old_credentials = {
+            'login': old_credentials.getLogin(),
+            'password': old_credentials.getPassword()}
+
+        # Set new credentials in session. These will be active on next request
+        new_credentials = SessionCredentials(login, password)
+        sessionData['credentials'] = new_credentials
+
+        # Return old credentials for this one request only
+        return old_credentials
 
 class StudentsAuthenticatorSetup(grok.GlobalUtility):
@@ -188,4 +247,5 @@
     def register(self, pau):
         plugins = list(pau.credentialsPlugins)
+        # this plugin must come before the regular credentials plugins
         plugins.insert(0, 'student_pw_change')
         pau.credentialsPlugins = tuple(plugins)
