You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by fm...@apache.org on 2010/06/21 11:36:00 UTC

svn commit: r956515 [1/2] - in /sling/trunk/bundles/extensions/openidauth: ./ src/main/java/org/apache/sling/openidauth/ src/main/java/org/apache/sling/openidauth/impl/ src/main/resources/ src/main/resources/OSGI-INF/metatype/ src/main/resources/SLING-...

Author: fmeschbe
Date: Mon Jun 21 09:35:58 2010
New Revision: 956515

URL: http://svn.apache.org/viewvc?rev=956515&view=rev
Log:
SLING-1374 Refactor OpenID authentication handler to comply with the new  Commons Auth authentication framework. Mostly this means simplification of login form, removal of the logout form, and reorganization of the OpenIDAuthenticationHandler to conform to the new API.

Added:
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java   (with props)
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java   (with props)
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java   (with props)
    sling/trunk/bundles/extensions/openidauth/src/main/resources/org/
    sling/trunk/bundles/extensions/openidauth/src/main/resources/org/apache/
    sling/trunk/bundles/extensions/openidauth/src/main/resources/org/apache/sling/
    sling/trunk/bundles/extensions/openidauth/src/main/resources/org/apache/sling/openidauth/
    sling/trunk/bundles/extensions/openidauth/src/main/resources/org/apache/sling/openidauth/impl/
    sling/trunk/bundles/extensions/openidauth/src/main/resources/org/apache/sling/openidauth/impl/login.html   (with props)
    sling/trunk/bundles/extensions/openidauth/src/test/
    sling/trunk/bundles/extensions/openidauth/src/test/java/
    sling/trunk/bundles/extensions/openidauth/src/test/java/org/
    sling/trunk/bundles/extensions/openidauth/src/test/java/org/apache/
    sling/trunk/bundles/extensions/openidauth/src/test/java/org/apache/sling/
    sling/trunk/bundles/extensions/openidauth/src/test/java/org/apache/sling/openidauth/
    sling/trunk/bundles/extensions/openidauth/src/test/java/org/apache/sling/openidauth/impl/
    sling/trunk/bundles/extensions/openidauth/src/test/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationHandlerTest.java   (with props)
Removed:
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDUserUtil.java
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationPlugin.java
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDPrincipal.java
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIdCredentials.java
    sling/trunk/bundles/extensions/openidauth/src/main/resources/SLING-INF/
    sling/trunk/bundles/extensions/openidauth/src/main/resources/openid.properties
Modified:
    sling/trunk/bundles/extensions/openidauth/pom.xml
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDConstants.java
    sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationHandler.java
    sling/trunk/bundles/extensions/openidauth/src/main/resources/OSGI-INF/metatype/metatype.properties

Modified: sling/trunk/bundles/extensions/openidauth/pom.xml
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/openidauth/pom.xml?rev=956515&r1=956514&r2=956515&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/openidauth/pom.xml (original)
+++ sling/trunk/bundles/extensions/openidauth/pom.xml Mon Jun 21 09:35:58 2010
@@ -55,33 +55,22 @@
                 <extensions>true</extensions>
                 <configuration>
                     <instructions>
+                        <Bundle-DocURL>
+                            http://sling.apache.org/site/openid-authenticationhandler.html
+                        </Bundle-DocURL>
                         <Private-Package>
-                            org.apache.sling.openidauth.impl.*,
-                            org.apache.commons.codec,
-                            org.apache.commons.codec.binary,
-                            org.apache.commons.codec.net
+                            org.apache.sling.openidauth.impl.*
                         </Private-Package>
-                        <!-- initial content to be loaded on bundle installation -->
-                        <Sling-Initial-Content>SLING-INF/content</Sling-Initial-Content>
-
-                        <!-- Bundle supplied resource prefixes -->
-                        <Include-Resource>{maven-resources}</Include-Resource>
 
                         <Export-Package>
-	                        org.apache.sling.openidauth,
-                        	!org.apache.sling.openidauth.impl
+	                        org.apache.sling.openidauth;version=1.0
                         </Export-Package>
-                        <_exportcontents>
-                        	com.dyuproject.openid
-                        </_exportcontents>
                         <Import-Package>
                         	*;resolution:=optional
                         </Import-Package>
-                        <DynamicImport-Package>*</DynamicImport-Package>
-						<Embed-Transitive>true</Embed-Transitive>
                         <!-- Embed OpenID completely -->
                         <Embed-Dependency>
-                            dyuproject-openid,dyuproject-util,jetty-util
+                            dyuproject-openid,dyuproject-json,dyuproject-util,jetty-util
                         </Embed-Dependency>
                     </instructions>
                 </configuration>
@@ -135,12 +124,6 @@
             <scope>provided</scope>
         </dependency>
 		<dependency>
-            <groupId>commons-codec</groupId>
-            <artifactId>commons-codec</artifactId>
-            <version>1.3</version>
-            <scope>compile</scope>
-        </dependency>
-		<dependency>
             <groupId>org.osgi</groupId>
             <artifactId>org.osgi.core</artifactId>
         </dependency>
@@ -157,23 +140,24 @@
             <artifactId>slf4j-api</artifactId>
         </dependency>
 		<dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-        </dependency>
-		<dependency>
 			<groupId>com.dyuproject</groupId>
 			<artifactId>dyuproject-openid</artifactId>
-			<version>1.1.1</version>
+			<version>1.1.7</version>
 	    </dependency>
 	    <dependency>
 			<groupId>com.dyuproject</groupId>
 			<artifactId>dyuproject-util</artifactId>
-			<version>1.1.1</version>
+			<version>1.1.7</version>
+	    </dependency>
+	    <dependency>
+			<groupId>com.dyuproject</groupId>
+			<artifactId>dyuproject-json</artifactId>
+			<version>1.1.7</version>
 	    </dependency>
 	    <dependency>
 	    	<groupId>org.mortbay.jetty</groupId>
 	    	<artifactId>jetty-util</artifactId>
-	    	<version>7.0.0.pre5</version>
+	    	<version>6.1.19</version>
 	    </dependency>
 	    <dependency>
 	    	<groupId>org.apache.sling</groupId>
@@ -181,5 +165,19 @@
 	    	<version>2.0.6</version>
             <scope>provided</scope>
 	    </dependency>
+        
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jmock</groupId>
+            <artifactId>jmock-junit4</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-simple</artifactId>
+        </dependency>
+        
     </dependencies>
 </project>

Modified: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDConstants.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDConstants.java?rev=956515&r1=956514&r2=956515&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDConstants.java (original)
+++ sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDConstants.java Mon Jun 21 09:35:58 2010
@@ -18,20 +18,54 @@
  */
 package org.apache.sling.openidauth;
 
-public class OpenIDConstants {
-	public static final String LOGOUT_REQUEST_PATH = "/system/sling/openid/logout";
-	
-    public static final String OPEN_ID_AUTH_TYPE = "OpenID";
+/**
+ * The <code>OpenIDConstants</code> class defines useful constants for
+ * implementors of login forms for OpenID authentication.
+ */
+public final class OpenIDConstants {
+
+    /**
+     * Identification of this authentication handler. This value is set by the
+     * handler as the authentication type of the <code>AuthenticationInfo</code>
+     * object returned from the <code>extractCredentials</code> method.
+     * <p>
+     * To explicitly request OpenID authentication handling, this should be used
+     * as the value of the <code>sling:authRequestLogin</code> request
+     * parameter.
+     */
+    public static final String OPENID_AUTH = "OpenID";
+
+    /**
+     * The name of the request parameter set by the
+     * <code>requestCredentials</code> method when redirecting to the login
+     * request form. The value of the parameter is the name of one of the
+     * {@link OpenIDFailure} constants.
+     * <p>
+     * This parameter is intended to be used by the login form to provide
+     * information to the client as to why OpenID authentication has failed. For
+     * example a login form implemented as a JSP may use the parameter to write
+     * the message like this:
+     *
+     * <pre>
+     * &lt;%
+     *     String reason = request.getParameter(OPENID_FAILURE_REASON_ATTRIBUTE);
+     *     OpenIDFailure fReason = OpenIDFailure.valueOf(reason);
+     * %>
+     * &lt;div id="err">
+     *   &lt;p>&lt;%= fReason %>&lt;/p>
+     * &lt;/div>
+     * </pre>
+     */
+    public static final String OPENID_FAILURE_REASON = "j_reason";
 
-    public static final String OPEN_ID_USER_ATTRIBUTE = "openid_user";
+    /**
+     * The name of the request parameter set by the
+     * <code>requestCredentials</code> method providing to authenticated OpenID
+     * identity. This parameter is only set if the
+     * {@link #OPENID_FAILURE_REASON} is {@link OpenIDFailure#REPOSITORY} and
+     * can be used to offer the user assistence with associating an existing JCR
+     * user with the OpenID identity.
+     */
+    public static final String OPENID_IDENTITY = "j_openid_identity";
 
-    public static final String ORIGINAL_URL_ATTRIBUTE = OPEN_ID_AUTH_TYPE + ".original.url";
-    
-    public static final String REDIRECT_URL_PARAMETER = OPEN_ID_AUTH_TYPE + ".redirect";
-    
-    public static final String OPENID_FAILURE_REASON_ATTRIBUTE = OPEN_ID_AUTH_TYPE + ".failure";
-    
-    public enum OpenIDFailure {
-    	DISCOVERY, ASSOCIATION, COMMUNICATION, AUTHENTICATION, VERIFICATION, REPOSITORY, OTHER
-    }
 }

Added: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java?rev=956515&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java (added)
+++ sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java Mon Jun 21 09:35:58 2010
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.openidauth;
+
+/**
+ * The <code>OpenIDFailure</code> defines the OpenID authentication failure
+ * codes which may be set as the
+ * <code>{@link OpenIDConstants#OPENID_FAILURE_REASON j_reason}</code> request
+ * parameter for the login form.
+ * <p>
+ * Note that the
+ * <code>{@link OpenIDConstants#OPENID_FAILURE_REASON j_reason}</code> request
+ * attribute provides the name of the constant, which can be converted to the
+ * actual constant by calling the <code>OpenIDFailure.valueOf(String)</code>
+ * method. Internationalization is not built into the OpenID authentication
+ * handler. Providers of login forms should implement their own mechanism and
+ * may either use the constant name or the {@link #toString() message} of the
+ * constant as a key for translation.
+ */
+public enum OpenIDFailure {
+
+    /**
+     * Indicates failure to discover an OpenID Provider for the supplied OpenID
+     * identifier.
+     */
+    DISCOVERY("Failed discovering OpenID Provider for identifier"),
+
+    /**
+     * Indicates failure to associate with the OpenID provider to validate the
+     * identifier.
+     */
+    ASSOCIATION("Failed associating with OpenID Provider with idenfier"),
+
+    /**
+     * Indicates a generic communication problem with the OpenID Provider.
+     */
+    COMMUNICATION("Generic communication problem"),
+
+    /**
+     * Indicates failure of the user to authenticate with the OpenID Provider.
+     */
+    AUTHENTICATION("Authentication failed"),
+
+    /**
+     * Indicates failure of the verification of the supplied authentication
+     * information with the OpenID Provider.
+     */
+    VERIFICATION("Authentication verfication failed"),
+
+    /**
+     * Indicates failure to find a matching Repository user for the supplied
+     * OpenID identifier.
+     */
+    REPOSITORY("Cannot associate Repository User with OpenID identifier"),
+
+    /**
+     * Indicates any other failure during authentication which is not captured
+     * by the other failure reasons.
+     */
+    OTHER("Generic OpenID authentication failure");
+
+    // The user readable (english) error message
+    private final String message;
+
+    /**
+     * Creates an instance of the reason conveying the given descriptive reason.
+     *
+     * @param message The descriptive reason.
+     */
+    private OpenIDFailure(String message) {
+        this.message = message;
+    }
+
+    /**
+     * Returns the message set when constructing this instance. To get the
+     * official name call the <code>name()</code> method.
+     */
+    @Override
+    public String toString() {
+        return message;
+    }
+}
\ No newline at end of file

Propchange: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/OpenIDFailure.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Added: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java?rev=956515&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java (added)
+++ sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java Mon Jun 21 09:35:58 2010
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.openidauth.impl;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.sling.openidauth.OpenIDConstants;
+import org.apache.sling.openidauth.OpenIDFailure;
+
+/**
+ * The <code>AuthenticationFormServlet</code> provides the default login form
+ * used for OpenID Authentication.
+ *
+ * @scr.component metatype="no"
+ * @scr.service interface="javax.servlet.Servlet"
+ * @scr.property name="service.vendor" value="The Apache Software Foundation"
+ * @scr.property name="service.description"
+ *               value="Default Login Form for OpenID Authentication"
+ */
+@SuppressWarnings("serial")
+public class AuthenticationFormServlet extends HttpServlet {
+
+    /**
+     * The constant is used to provide the service registration path
+     *
+     * @scr.property name="sling.servlet.paths"
+     */
+    static final String SERVLET_PATH = "/system/sling/openid/login";
+
+    /**
+     * This constant is used to provide the service registration property
+     * indicating to pass requests to this servlet unauthenticated.
+     *
+     * @scr.property name="sling.auth.requirements"
+     */
+    @SuppressWarnings("unused")
+    private static final String AUTH_REQUIREMENT = "-" + SERVLET_PATH;
+
+    /**
+     * The raw form used by the {@link #getForm(HttpServletRequest)} method to
+     * fill in with per-request data. This field is set by the
+     * {@link #getRawForm()} method when first loading the form.
+     */
+    private volatile String rawForm;
+
+    /**
+     * Prepares and returns the login form. The response is sent as an UTF-8
+     * encoded <code>text/html</code> page with all known cache control headers
+     * set to prevent all caching.
+     * <p>
+     * This servlet is to be called to handle the request directly, that is it
+     * expected to not be included and for the response to not be committed yet
+     * because it first resets the response.
+     *
+     * @throws IOException if an error occurrs preparing or sending back the
+     *             login form
+     * @throws IllegalStateException if the response has already been committed
+     *             and thus response reset is not possible.
+     */
+    @Override
+    protected void doGet(HttpServletRequest request,
+            HttpServletResponse response) throws IOException {
+
+        // reset the response first
+        response.reset();
+
+        // setup the response for HTML and cache prevention
+        response.setContentType("text/html");
+        response.setCharacterEncoding("UTF-8");
+        response.setHeader("Cache-Control", "no-cache");
+        response.addHeader("Cache-Control", "no-store");
+        response.setHeader("Pragma", "no-cache");
+        response.setHeader("Expires", "0");
+
+        // send the form and flush
+        response.getWriter().print(getForm(request));
+        response.flushBuffer();
+    }
+
+    /**
+     * Returns the form to be sent back to the client for login providing an
+     * optional informational message and the optional target to redirect to
+     * after successfully logging in.
+     *
+     * @param request The request providing parameters indicating the
+     *            informational message and redirection target.
+     * @return The login form to be returned to the client
+     * @throws IOException If the login form cannot be loaded
+     */
+    private String getForm(final HttpServletRequest request) throws IOException {
+        String form = getRawForm();
+
+        form = form.replace("${resource}", getResource(request));
+        form = form.replace("${j_reason}", getReason(request));
+
+        return form;
+    }
+
+    /**
+     * Returns the path to the resource to which the request should be
+     * redirected after successfully completing the form or the servlet context
+     * root path if there is no <code>resource</code> request parameter.
+     *
+     * @param request The request providing the <code>resource</code> parameter.
+     * @return The target to redirect after sucessfully login or the servlet
+     *         context root path if no specific target has been requested.
+     */
+    private String getResource(final HttpServletRequest request) {
+        return OpenIDAuthenticationHandler.getLoginResource(request,
+            request.getContextPath());
+    }
+
+    /**
+     * Returns an informational message according to the value provided in the
+     * <code>j_reason</code> request parameter. Supported reasons are invalid
+     * credentials and session timeout.
+     *
+     * @param request The request providing the parameter
+     * @return The "translated" reason to render the login form or an empty
+     *         string if there is no specific reason
+     */
+    private String getReason(final HttpServletRequest request) {
+        final String reason = request.getParameter(OpenIDConstants.OPENID_FAILURE_REASON);
+        if (reason != null) {
+
+            try {
+
+                OpenIDFailure failure = OpenIDFailure.valueOf(reason);
+
+                // in case of missing repository user association, tell that
+                // the identity is missing
+                if (failure == OpenIDFailure.REPOSITORY) {
+                    Object idObject = request.getParameter(OpenIDConstants.OPENID_IDENTITY);
+                    if (idObject instanceof String) {
+                        return "Cannot associate Repository User with OpenID identifier "
+                            + idObject;
+                    }
+                }
+
+                // else (or if there is no identity) use the failure type message
+                return failure.toString();
+
+            } catch (IllegalArgumentException iae) {
+                // thrown if the reason is not an expected value, assume none
+            }
+
+            // raw reason string if not a failure value
+            return reason;
+        }
+
+        return "";
+    }
+
+    /**
+     * Load the raw unmodified form from the bundle (through the class loader).
+     *
+     * @return The raw form as a string
+     * @throws IOException If an error occurrs reading the "file" or if the
+     *             class loader cannot provide the form data.
+     */
+    private String getRawForm() throws IOException {
+        if (rawForm == null) {
+            InputStream ins = null;
+            try {
+                ins = getClass().getResourceAsStream("login.html");
+                if (ins != null) {
+                    StringBuilder builder = new StringBuilder();
+                    Reader r = new InputStreamReader(ins, "UTF-8");
+                    char[] cbuf = new char[1024];
+                    int rd = 0;
+                    while ((rd = r.read(cbuf)) >= 0) {
+                        builder.append(cbuf, 0, rd);
+                    }
+
+                    rawForm = builder.toString();
+                }
+            } finally {
+                if (ins != null) {
+                    try {
+                        ins.close();
+                    } catch (IOException ignore) {
+                    }
+                }
+            }
+
+            if (rawForm == null) {
+                throw new IOException("Failed reading form template");
+            }
+        }
+
+        return rawForm;
+    }
+}

Propchange: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/AuthenticationFormServlet.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url

Modified: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationHandler.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationHandler.java?rev=956515&r1=956514&r2=956515&view=diff
==============================================================================
--- sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationHandler.java (original)
+++ sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDAuthenticationHandler.java Mon Jun 21 09:35:58 2010
@@ -19,24 +19,32 @@
 package org.apache.sling.openidauth.impl;
 
 import java.io.IOException;
-import java.security.Principal;
-import java.util.Map;
+import java.net.URLEncoder;
+import java.net.UnknownHostException;
+import java.util.Dictionary;
+import java.util.Iterator;
 import java.util.Properties;
-import java.util.Set;
 
 import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
 import javax.jcr.Session;
-import javax.security.auth.callback.CallbackHandler;
+import javax.jcr.SimpleCredentials;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.jackrabbit.api.JackrabbitSession;
+import org.apache.jackrabbit.api.security.user.User;
+import org.apache.jackrabbit.api.security.user.UserManager;
+import org.apache.sling.commons.auth.Authenticator;
+import org.apache.sling.commons.auth.spi.AuthenticationFeedbackHandler;
 import org.apache.sling.commons.auth.spi.AuthenticationHandler;
 import org.apache.sling.commons.auth.spi.AuthenticationInfo;
+import org.apache.sling.commons.auth.spi.DefaultAuthenticationFeedbackHandler;
 import org.apache.sling.commons.osgi.OsgiUtil;
-import org.apache.sling.jcr.jackrabbit.server.security.AuthenticationPlugin;
-import org.apache.sling.jcr.jackrabbit.server.security.LoginModulePlugin;
+import org.apache.sling.jcr.api.SlingRepository;
 import org.apache.sling.openidauth.OpenIDConstants;
-import org.apache.sling.openidauth.OpenIDConstants.OpenIDFailure;
+import org.apache.sling.openidauth.OpenIDFailure;
+import org.osgi.framework.ServiceRegistration;
 import org.osgi.service.component.ComponentContext;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -53,25 +61,24 @@ import com.dyuproject.openid.manager.Coo
  *
  * @scr.component immediate="false" label="%auth.openid.name"
  *                description="%auth.openid.description"
- * @scr.property name="service.description" value="Apache Sling OpenID Authentication Handler"
+ *                name="org.apache.sling.openidauth.OpenIDAuthenticationHandler"
+ * @scr.property name="service.description"
+ *               value="Apache Sling OpenID Authentication Handler"
  * @scr.property name="service.vendor" value="The Apache Software Foundation"
  * @scr.property nameRef="AuthenticationHandler.PATH_PROPERTY" values.0="/"
  * @scr.service
  */
-public class OpenIDAuthenticationHandler implements
-        AuthenticationHandler, LoginModulePlugin {
+public class OpenIDAuthenticationHandler implements AuthenticationHandler,
+        AuthenticationFeedbackHandler {
 
     /** default log */
     private final Logger log = LoggerFactory.getLogger(getClass());
 
     /**
-     * @scr.property valueRef="DEFAULT_LOGIN_FORM"
+     * @scr.property valueRef="AuthenticationFormServlet.SERVLET_PATH"
      */
     public static final String PROP_LOGIN_FORM = "openid.login.form";
 
-    public static final String DEFAULT_LOGIN_FORM = "/system/sling/openid/loginform.html";
-
-
     /**
      * @scr.property valueRef="DEFAULT_LOGIN_IDENTIFIER_FORM_FIELD"
      */
@@ -79,123 +86,138 @@ public class OpenIDAuthenticationHandler
 
     public static final String DEFAULT_LOGIN_IDENTIFIER_FORM_FIELD = RelyingParty.DEFAULT_IDENTIFIER_PARAMETER;
 
-
     /**
-     * @scr.property valueRef="DEFAULT_ORIGINAL_URL_ON_SUCCESS" type="Boolean"
+     * @scr.property valueRef="DEFAULT_EXTERNAL_URL_PREFIX"
      */
-    public static final String PROP_ORIGINAL_URL_ON_SUCCESS = "openid.original.url.onsuccess";
-
-    public static final boolean DEFAULT_ORIGINAL_URL_ON_SUCCESS = true;
+    public static final String PROP_EXTERNAL_URL_PREFIX = "openid.external.url.prefix";
 
+    public static final String DEFAULT_EXTERNAL_URL_PREFIX = "";
 
     /**
-     * @scr.property valueRef="DEFAULT_AUTH_SUCCESS_URL"
+     * @scr.property valueRef="DEFAULT_USE_COOKIE" type="Boolean"
      */
-    public static final String PROP_AUTH_SUCCESS_URL = "openid.login.success";
-
-    public static final String DEFAULT_AUTH_SUCCESS_URL = "/system/sling/openid/authsuccess.html";
+    public static final String PROP_USE_COOKIE = "openid.use.cookie";
 
+    public static final boolean DEFAULT_USE_COOKIE = true;
 
     /**
-     * @scr.property valueRef="DEFAULT_AUTH_FAIL_URL"
+     * @scr.property valueRef="DEFAULT_COOKIE_DOMAIN"
      */
-    public static final String PROP_AUTH_FAIL_URL = "openid.login.fail";
-
-    public static final String DEFAULT_AUTH_FAIL_URL = "/system/sling/openid/authfail.html";
+    public static final String PROP_COOKIE_DOMAIN = "openid.cookie.domain";
 
+    public static final String DEFAULT_COOKIE_DOMAIN = "";
 
     /**
-     * @scr.property valueRef="DEFAULT_LOGOUT_URL"
+     * @scr.property valueRef="DEFAULT_COOKIE_NAME"
      */
-    public static final String PROP_LOGOUT_URL = "openid.logout";
-
-    public static final String DEFAULT_LOGOUT_URL = "/system/sling/openid/logout.html";
+    public static final String PROP_COOKIE_NAME = "openid.cookie.name";
 
+    public static final String DEFAULT_COOKIE_NAME = "sling.openid";
 
     /**
-     * @scr.property valueRef="DEFAULT_EXTERNAL_URL_PREFIX"
+     * @scr.property valueRef="DEFAULT_COOKIE_SECRET_KEY"
      */
-    public static final String PROP_EXTERNAL_URL_PREFIX = "openid.external.url.prefix";
-
-    public static final String DEFAULT_EXTERNAL_URL_PREFIX = "http://my.external.sling.com";
+    public static final String PROP_COOKIE_SECRET_KEY = "openid.cookie.secret.key";
 
+    public static final String DEFAULT_COOKIE_SECRET_KEY = "secret";
 
     /**
-     * @scr.property valueRef="DEFAULT_OPENID_USERS_PASSWORD"
+     * @scr.property valueRef="DEFAULT_OPENID_USER_ATTR"
      */
-    public static final String PROP_OPENID_USERS_PASSWORD = "openid.users.password";
-
-    public static final String DEFAULT_OPENID_USERS_PASSWORD = "changeme";
+    private static final String PROP_OPENID_USER_ATTR = "openid.user.attr";
 
+    private static final String DEFAULT_OPENID_USER_ATTR = "openid.user";
 
     /**
-     * @scr.property valueRef="DEFAULT_ANONYMOUS_AUTH_RESOURCES" type="Boolean"
+     * @scr.property valueRef="DEFAULT_OPEN_ID_IDENTIFIER_PROPERTY"
      */
-    public static final String PROP_ANONYMOUS_AUTH_RESOURCES = "openid.anon.auth.resources";
-
-    public static final boolean DEFAULT_ANONYMOUS_AUTH_RESOURCES = true;
+    private static final String PROP_OPEN_ID_IDENTIFIER_PROPERTY = "openid.property.identity";
 
+    private static final String DEFAULT_OPEN_ID_IDENTIFIER_PROPERTY = "openid.identity";
 
     /**
-     * @scr.property valueRef="DEFAULT_USE_COOKIE" type="Boolean"
+     * The name of the attribute set on the OpenID user object to cache the
+     * mapping from the OpenID identifier to the JCR user id to prevent repeated
+     * time-consuming search for a matching user.
      */
-    public static final String PROP_USE_COOKIE = "openid.use.cookie";
-
-    public static final boolean DEFAULT_USE_COOKIE = false;
+    private static final String ATTR_USER_ID = "jcr.userid";
 
+    static final String SLASH = "/";
 
-    /**
-     * @scr.property valueRef="DEFAULT_COOKIE_DOMAIN"
-     */
-    public static final String PROP_COOKIE_DOMAIN = "openid.cookie.domain";
+    /** @scr.reference */
+    private SlingRepository repository;
 
-    public static final String DEFAULT_COOKIE_DOMAIN = ".sling.com";
+    private Session session;
 
+    private UserManager userManager;
 
-    /**
-     * @scr.property valueRef="DEFAULT_COOKIE_NAME"
-     */
-    public static final String PROP_COOKIE_NAME = "openid.cookie.name";
-
-    public static final String DEFAULT_COOKIE_NAME = "sling.openid";
+    private ComponentContext context;
 
+    private String loginForm;
 
     /**
-     * @scr.property valueRef="DEFAULT_COOKIE_PATH"
+     * The prefix used to create an external URL for the resource to which the
+     * client should be returned to after successfully authenticating with the
+     * OpenID provider. This parameter is used as the basis for the
+     * <code>return_to</code> parameter of the OpenID authentication request.
+     * <p>
+     * If this is not set, it defaults to the created from the request as
+     * follows:
+     *
+     * <pre>
+     * request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath()</code>
+     * </pre>
+     * <p>
+     * where the port part is omitted if it is the default port for the scheme.
      */
-    public static final String PROP_COOKIE_PATH = "openid.cookie.path";
-
-    public static final String DEFAULT_COOKIE_PATH = "/";
-
+    private String externalUrlPrefix;
 
     /**
-     * @scr.property valueRef="DEFAULT_COOKIE_SECRET_KEY"
+     * The OpenID realm to authenticate with. This String is presented to the
+     * client authenticating against the OpenID provider to inform about the
+     * site wishing to authenticate a user. This parameter is used as the value
+     * of the <code>realm</code> (and <code>trust_root</code> parameter of the
+     * OpenID authentication request.
+     * <p>
+     * If this is not set, the {@link #externalUrlPrefix} is used as the realm.
+     * <p>
+     * This field may be used to convey a wildcard realm, such as
+     * <code>http://*.apache.org</code>.
      */
-    public static final String PROP_COOKIE_SECRET_KEY = "openid.cookie.secret.key";
-
-    public static final String DEFAULT_COOKIE_SECRET_KEY = "secret";
-
+    private String realm;
 
-    static final String SLASH = "/";
+    private boolean useCookie;
 
-    private ComponentContext context;
+    private String cookieDomain;
 
-    private String loginForm;
-    private String authSuccessUrl;
-    private String authFailUrl;
-    private String logoutUrl;
-    private boolean accessAuthPageAnon;
+    private char[] cookieSecret;
 
-    private boolean redirectToOriginalUrl;
-    private String externalUrlPrefix;
-    private boolean useCookie;
-    private String cookieDomain;
     private String cookieName;
-    private String cookiePath;
+
+    /**
+     * Name of the request parameter used provide the OpenID identifier.
+     * Configured by {@link #PROP_LOGIN_IDENTIFIER_FORM_FIELD}, defaults to
+     * {@link #DEFAULT_LOGIN_IDENTIFIER_FORM_FIELD}.
+     */
     private String identifierParam;
 
-	private RelyingParty relyingParty;
+    /**
+     * Name of the JCR User property listing the OpenID identities with which
+     * the user is related. Configured by
+     * {@link #PROP_OPEN_ID_IDENTIFIER_PROPERTY}, defaults to
+     * {@link #DEFAULT_OPEN_ID_IDENTIFIER_PROPERTY}. This property may be
+     * multi-valued if the user is associated with more than one OpenID
+     * identifiers.
+     *
+     * @see #getUserName(OpenIdUser)
+     */
+    private String identityProperty;
+
+    private String openIdAttribute;
 
+    private RelyingParty relyingParty;
+
+    private ServiceRegistration loginModule;
 
     public OpenIDAuthenticationHandler() {
         log.info("OpenIDAuthenticationHandler created");
@@ -208,21 +230,25 @@ public class OpenIDAuthenticationHandler
      * is only based on the original request object, no URI translation has
      * taken place yet.
      * <p>
-     * The method returns any of the following values : <table>
+     * The method returns any of the following values :
+     * <table>
      * <tr>
      * <th>value
-     * <th>description</tr>
+     * <th>description
+     * </tr>
      * <tr>
      * <td><code>null</code>
-     * <td>no user details were contained in the request </tr>
+     * <td>no user details were contained in the request
+     * </tr>
      * <tr>
      * <td>{@link AuthenticationInfo#DOING_AUTH}
-     * <td>the handler is in an ongoing authentication exchange with the
-     * client. The request handling is terminated.
+     * <td>the handler is in an ongoing authentication exchange with the client.
+     * The request handling is terminated.
      * <tr>
      * <tr>
      * <td>valid credentials
-     * <td>The user sent credentials.</tr>
+     * <td>The user sent credentials.
+     * </tr>
      * </table>
      * <p>
      * The method must not request credential information from the client, if
@@ -238,147 +264,107 @@ public class OpenIDAuthenticationHandler
      * @param response The response object which may be used to send the
      *            information on the request failure to the user.
      * @return A valid Credentials instance identifying the request user,
-     *         DOING_AUTH if the handler is in an authentication transaction with
-     *         the client or null if the request does not contain authentication
-     *         information. In case of DOING_AUTH, the method must have sent a
-     *         response indicating that fact to the client.
+     *         DOING_AUTH if the handler is in an authentication transaction
+     *         with the client or null if the request does not contain
+     *         authentication information. In case of DOING_AUTH, the method
+     *         must have sent a response indicating that fact to the client.
      */
     public AuthenticationInfo extractCredentials(HttpServletRequest request,
             HttpServletResponse response) {
 
-        OpenIdUser user = null;
+        try {
+            final RelyingParty relyingParty = getRelyingParty(request);
 
-        try
-        {
-            user = relyingParty.discover(request);
-
-            // Authentication timeout
-            if(user == null && RelyingParty.isAuthResponse(request))
-            {
-                log.debug("OpenID authentication timeout");
-                response.sendRedirect(request.getRequestURI());
-                return AuthenticationInfo.DOING_AUTH;
-            }
+            // this may throw a ClassCastException after an update of the
+            // bundle if the HTTP Session object still holds on to an
+            // OpenIdUser instance created by the old bundle.
+            final OpenIdUser user = discover(relyingParty, request);
 
-            if(request.getPathInfo() != null) {
-                String requestPath = request.getPathInfo();
-                if(requestPath != null) {
-                    if(OpenIDConstants.LOGOUT_REQUEST_PATH.equals(requestPath)) {
-                        relyingParty.invalidate(request, response);
-                        user = null;
-                        return handleLogout(request, response);
-                    }
-                    // handle (possibly)anon auth resources
-                    else if (loginForm.equals(requestPath) ||
-                            authFailUrl.equals(requestPath) ||
-                            logoutUrl.equals(requestPath)) {
-
-                        if (loginForm.equals(requestPath)) {
-                            // can force a login with Allow Anonymous enabled, by requesting
-                            // login form directly.  Checking this parameter allows us
-                            // to redirect user somewhere useful if login is successful
-                            if(request.getParameter(OpenIDConstants.REDIRECT_URL_PARAMETER) != null) {
-                                request.getSession().setAttribute(OpenIDConstants.ORIGINAL_URL_ATTRIBUTE,
-                                        request.getParameter(OpenIDConstants.REDIRECT_URL_PARAMETER));
-                            }
-
-                            moveAttributeFromSessionToRequest(
-                                    OpenIDConstants.OPENID_FAILURE_REASON_ATTRIBUTE,
-                                    OpenIDConstants.OpenIDFailure.class,
-                                    request);
-
-                            moveAttributeFromSessionToRequest(
-                                    OpenIDConstants.ORIGINAL_URL_ATTRIBUTE,
-                                    String.class,
-                                    request);
-
-                        } else if (authFailUrl.equals(requestPath)) {
-                            // move the failure reason attribute from session to request
-                            moveAttributeFromSessionToRequest(
-                                    OpenIDConstants.OPENID_FAILURE_REASON_ATTRIBUTE,
-                                    OpenIDConstants.OpenIDFailure.class,
-                                    request);
-
-                            moveAttributeFromSessionToRequest(
-                                    OpenIDConstants.ORIGINAL_URL_ATTRIBUTE,
-                                    String.class,
-                                    request);
-                        }
-
-                        if (accessAuthPageAnon) {
-                            // Causes anonymous login but does not respect
-                            // SlingAuthenticator allowAnonymous
-                            return new AuthenticationInfo(
-                                OpenIDConstants.OPEN_ID_AUTH_TYPE);
-                        }
-                    }
+            // no OpenID user in the request, check whether this is an
+            // OpenID response at all
+            if (user == null) {
+
+                if (RelyingParty.isAuthResponse(request)) {
+
+                    log.debug("OpenID authentication timeout");
+                    response.sendRedirect(request.getRequestURI());
+                    return AuthenticationInfo.DOING_AUTH;
+
+                } else if (RelyingParty.isAuthCancel(request)) {
+
+                    log.info("OpenID authentication cancelled by user");
+                    return handleAuthFailure(OpenIDFailure.AUTHENTICATION,
+                        request);
                 }
-            }
 
-            if(user != null) {
-                if(user.isAuthenticated()) {
-                    // user already authenticated
-                    request.setAttribute(OpenIdUser.ATTR_NAME, user);
-                    return getAuthInfoFromUser(user);
-                } else if(user.isAssociated()) {
-                    if(RelyingParty.isAuthResponse(request)) {
-                        if(relyingParty.verifyAuth(user, request, response)) {
-                            // authenticated
-                            response.sendRedirect(request.getRequestURI());
-                            return AuthenticationInfo.DOING_AUTH;
-                        }
-                        // failed verification
-                        AuthenticationInfo authInfo = handleAuthFailure(OpenIDFailure.VERIFICATION, request, response);
-                        if(authInfo != null) {
-                            return authInfo;
-                        }
-                    } else {
-                        // Assume a cancel or some other non-successful response from provider
-                        // failed verification
-                        relyingParty.invalidate(request, response);
-                        user = null;
-
-                        AuthenticationInfo authInfo = handleAuthFailure(OpenIDFailure.AUTHENTICATION, request, response);
-                        if(authInfo != null) {
-                            return authInfo;
-                        }
-                    }
-                } else {
-                    // associate and authenticate user
-                    StringBuffer url = null;
-                    String trustRoot = null;
-                    String returnTo = null;
-
-                    if(externalUrlPrefix != null && !"".equals(externalUrlPrefix.trim())) {
-                        url = new StringBuffer(externalUrlPrefix).append(request.getRequestURI());
-                        trustRoot = externalUrlPrefix;
-                    } else {
-                        url = request.getRequestURL();
-                        trustRoot = url.substring(0, url.indexOf(SLASH, 9));
-                    }
+                // check whether the request has an OpenID identifier
+                // request parameter not leading to a valid OpenID
+                // transaction; fail authentication in this case
+                final String identifier = request.getParameter(identifierParam);
+                if (identifier != null) {
+                    log.info("OpenID authentication failed (probably failed to discover OpenID Provider)");
+                    return handleAuthFailure(OpenIDFailure.DISCOVERY, request);
+                }
 
-                    String realm = url.substring(0, url.lastIndexOf(SLASH));
+            } else if (user.isAuthenticated()) {
 
-                    if(redirectToOriginalUrl) {
-                        returnTo = url.toString();
-                    } else {
-                        request.setAttribute(OpenIDConstants.ORIGINAL_URL_ATTRIBUTE, request.getRequestURI());
-                        returnTo =  authSuccessUrl;
-                    }
+                // user already authenticated
+                return getAuthInfoFromUser(user);
 
-                    if(relyingParty.associateAndAuthenticate(user, request, response, trustRoot, realm,
-                            returnTo)) {
-                        // user is associated and then redirected to his openid provider for authentication
+            } else if (user.isAssociated()) {
+
+                if (RelyingParty.isAuthResponse(request)) {
+
+                    if (relyingParty.verifyAuth(user, request, response)) {
+                        // authenticated
+                        response.sendRedirect(request.getRequestURI());
                         return AuthenticationInfo.DOING_AUTH;
                     }
-                    // failed association or auth request generation
-                    AuthenticationInfo authInfo = handleAuthFailure(OpenIDFailure.ASSOCIATION, request, response);
-                    if(authInfo != null) {
-                        return authInfo;
-                    }
+
+                    // failed verification
+                    return handleAuthFailure(OpenIDFailure.VERIFICATION,
+                        request);
+
+                }
+
+                // Assume a cancel or some other non-successful response
+                // from provider failed verification
+                relyingParty.invalidate(request, response);
+
+                return handleAuthFailure(OpenIDFailure.AUTHENTICATION, request);
+
+            } else {
+
+                // associate and authenticate user
+
+                // prepare the url for the return_to parameter
+                final String url = getBaseUrl(request);
+
+                // set the ream/trustroot from configuraiton or the root url
+                final String trustRoot = (realm == null) ? url : realm;
+
+                // append the resource URL to the returnTo address
+                final String returnTo = url + getLoginResource(request, "/");
+
+                if (relyingParty.associateAndAuthenticate(user, request,
+                    response, trustRoot, trustRoot, returnTo)) {
+                    // user is associated and then redirected to his openid
+                    // provider for authentication
+                    return AuthenticationInfo.DOING_AUTH;
                 }
+
+                // failed association or auth request generation
+                return handleAuthFailure(OpenIDFailure.ASSOCIATION, request);
             }
-        } catch(Exception e) {
+
+        } catch (ClassCastException cce) {
+            // expected after bundle update when using HTTP Sessions
+            log.warn("extractCredentials: Found OpenID user data in HTTP Session which cannot be used; failing credentials extraction");
+            log.debug("extractCredentials: dump", cce);
+            dropCredentials(request, response);
+            return handleAuthFailure(OpenIDFailure.OTHER, request);
+
+        } catch (Exception e) {
             log.error("Error processing OpenID request", e);
         }
 
@@ -400,36 +386,59 @@ public class OpenIDAuthenticationHandler
     public boolean requestCredentials(HttpServletRequest request,
             HttpServletResponse response) throws IOException {
 
-        // if the response is already committed, we have a problem !!
-        if (!response.isCommitted()) {
+        // 0. ignore this handler if an authentication handler is requested
+        if (ignoreRequestCredentials(request)) {
+            // consider this handler is not used
+            return false;
+        }
+
+        // requestAuthentication is only called after a failedauthentication
+        // so it makes sense to remove any existing login
+        final RelyingParty relyingParty = getRelyingParty(request);
+        relyingParty.invalidate(request, response);
+
+        // prepare the login form redirection target
+        final StringBuilder targetBuilder = new StringBuilder();
+        targetBuilder.append(request.getContextPath());
+        targetBuilder.append(loginForm);
+
+        // append originally requested resource (for redirect after login)
+        char parSep = '?';
+        final String resource = getLoginResource(request, null);
+        if (resource != null) {
+            targetBuilder.append(parSep).append(Authenticator.LOGIN_RESOURCE);
+            targetBuilder.append("=").append(
+                URLEncoder.encode(resource, "UTF-8"));
+            parSep = '&';
+        }
 
-        	// If we're here & we have a valid authenticated user
-        	// probably we failed the repository login (no repo user
-        	// configured for the authenticated principal)
-        	OpenIdUser user = (OpenIdUser)request.getAttribute(OpenIDConstants.OPEN_ID_USER_ATTRIBUTE);
-        	if(user != null && user.isAuthenticated()) {
-        		request.getSession().setAttribute(
-        				OpenIDConstants.OPENID_FAILURE_REASON_ATTRIBUTE,
-        				OpenIDConstants.OpenIDFailure.REPOSITORY);
-        	}
-
-        	// requestAuthentication is only called after a failed authentication
-        	// so it makes sense to remove any existing login
-        	relyingParty.invalidate(request, response);
-
-        	// original URL is set only if it doesn't already exist
-        	if(request.getSession().getAttribute(OpenIDConstants.ORIGINAL_URL_ATTRIBUTE) == null) {
-        		String originalUrl = request.getRequestURI() +
-        			(request.getQueryString() != null ? "?" + request.getQueryString() : "");
-
-        		// handle corner case where login form requested directly
-        		if(!originalUrl.equals(loginForm)) {
-        			request.getSession().setAttribute(OpenIDConstants.ORIGINAL_URL_ATTRIBUTE, originalUrl);
-        		}
-        	}
-        	response.sendRedirect(loginForm);
-        } else {
-            log.error("requestAuthentication: Response is committed, cannot request authentication");
+        // append indication of previous login failure
+        if (request.getAttribute(OpenIDConstants.OPENID_FAILURE_REASON) != null) {
+            final Object jReason = request.getAttribute(OpenIDConstants.OPENID_FAILURE_REASON);
+            @SuppressWarnings("unchecked")
+            final String reason = (jReason instanceof Enum)
+                    ? ((Enum) jReason).name()
+                    : jReason.toString();
+            targetBuilder.append(parSep).append(
+                OpenIDConstants.OPENID_FAILURE_REASON);
+            targetBuilder.append("=").append(URLEncoder.encode(reason, "UTF-8"));
+            parSep = '&';
+        }
+
+        final Object paramIdentifier = request.getAttribute(OpenIDConstants.OPENID_IDENTITY);
+        if (paramIdentifier instanceof String) {
+            targetBuilder.append(parSep).append(
+                OpenIDConstants.OPENID_IDENTITY);
+            targetBuilder.append("=").append(
+                URLEncoder.encode((String) paramIdentifier, "UTF-8"));
+        }
+
+        // finally redirect to the login form
+        final String target = targetBuilder.toString();
+        try {
+            response.sendRedirect(target);
+        } catch (IOException e) {
+            log.error("Failed to redirect to the page: " + target, e);
         }
 
         return true;
@@ -442,180 +451,396 @@ public class OpenIDAuthenticationHandler
     public void dropCredentials(HttpServletRequest request,
             HttpServletResponse response) {
         try {
-            final OpenIdUser user = relyingParty.discover(request);
-            if (user != null) {
-                relyingParty.invalidate(request, response);
-            }
+            getRelyingParty(request).invalidate(request, response);
         } catch (Exception e) {
             log.warn("dropAuthentication: Problem checking whether the user is logged in at all, assuming not logged in and therefore not logging out");
         }
     }
 
-    protected AuthenticationInfo handleAuthFailure(OpenIDFailure failure, HttpServletRequest request, HttpServletResponse response)
-    	throws IOException {
+    public void authenticationFailed(HttpServletRequest request,
+            HttpServletResponse response, AuthenticationInfo authInfo) {
 
-    	request.getSession().setAttribute(OpenIDConstants.OPENID_FAILURE_REASON_ATTRIBUTE, failure);
+        /*
+         * Called if extractCredentials provided an authenticated OpenID user
+         * which could not be mapped to a valid repository user !!
+         *
+         * invalidate the curren OpenID user and set a failure reason
+         */
 
-		if(authFailUrl != null && !"".equals(authFailUrl)) {
-			response.sendRedirect(authFailUrl);
-			return AuthenticationInfo.DOING_AUTH;
+        OpenIdUser user = null;
+        try {
+            user = getRelyingParty(request).discover(request);
+        } catch (Exception e) {
+            // don't care ...
+        }
+
+        dropCredentials(request, response);
+
+        request.setAttribute(OpenIDConstants.OPENID_FAILURE_REASON,
+            OpenIDFailure.REPOSITORY);
+
+        if (user != null && user.getIdentity() != null) {
+            request.setAttribute(OpenIDConstants.OPENID_IDENTITY,
+                user.getIdentity());
         }
-		return null;
     }
 
-    protected AuthenticationInfo handleLogout(HttpServletRequest request, HttpServletResponse response)
-    	throws IOException {
-		String redirectUrl = null;
+    public boolean authenticationSucceeded(HttpServletRequest request,
+            HttpServletResponse response, AuthenticationInfo authInfo) {
+
+        // FIXME: check redirect after login !
+        return DefaultAuthenticationFeedbackHandler.handleRedirect(request,
+            response);
+    }
+
+    // ---------- internal
+
+    /**
+     * Tries to discover the OpenID user from the request. This involves any of
+     * the following steps:
+     * <ul>
+     * <li>The user is already available as a request attribute</li>
+     * <li>The user is available from the HTTP Session or the OpenID cookie</li>
+     * <li>The user is identifier with an OpenID identifier supplied with the
+     * {@link #identifierParam} request parameter</li>
+     * <li>No user is available from the request at all</li>
+     * </ul>
+     * <p>
+     * If no user is available or any error occurrs while trying to discover the
+     * user from the request, <code>null</code> is returned.
+     *
+     * @param relyingParty The <code>RelyingParty</code> object used to discover
+     *            the OpenID user from the request
+     * @param request The <code>HttpServletRequest</code> from which the user is
+     *            to be discovered
+     * @return the <code>OpenIdUser</code> discovered from the request or
+     *         <code>null</code> if no user can be discovered or the discovery
+     *         failed.
+     * @throws ClassCastException may be thrown if an OpenID user object is
+     *             still stored in the HTTP Session after the authentication
+     *             handler bundle has been updated.
+     */
+    private OpenIdUser discover(final RelyingParty relyingParty,
+            final HttpServletRequest request) {
+        try {
+            // this may throw a ClassCastException after an update of the
+            // bundle if the HTTP Session object still holds on to an
+            // OpenIdUser instance created by the old bundle.
+            return relyingParty.discover(request);
+
+        } catch (UnknownHostException uhe) {
+            // openid_identifier names an invalid host
+            log.info(
+                "discover: The OpenID identifier cannot be resolved because it designates an unknown host {}",
+                uhe.getMessage());
+
+        } catch (IOException ioe) {
+            // another IO problem talking to the OpenID provider
+            log.info("discover: Failure to communicate with OpenID provider",
+                ioe);
+
+        } catch (ClassCastException cce) {
+            // rethrow class cast exception from failure to use OpenID user
+            // from Http Session created prior to authentication handler update
+            throw cce;
+
+        } catch (Exception e) {
+            // any other problem discovering the identifier
+            log.warn(
+                "discover: Unexpected failure discovering the OpenID user", e);
+        }
 
-		if(request.getParameter(OpenIDConstants.REDIRECT_URL_PARAMETER) != null) {
-			redirectUrl = request.getParameter(OpenIDConstants.REDIRECT_URL_PARAMETER);
-		} else {
-			redirectUrl = logoutUrl;
-		}
+        // exception discovering the identifier
+        return null;
+    }
 
-		// fallback
-		if(redirectUrl == null) {
-			redirectUrl = "/";
-		}
+    private AuthenticationInfo handleAuthFailure(OpenIDFailure failure,
+            HttpServletRequest request) {
 
-		response.sendRedirect(redirectUrl);
-		return AuthenticationInfo.DOING_AUTH;
+        request.setAttribute(OpenIDConstants.OPENID_FAILURE_REASON,
+            failure);
+        return AuthenticationInfo.FAIL_AUTH;
     }
 
-    // ---------- SCR Integration ----------------------------------------------
+    // ---------- SCR Integration
 
     protected void activate(ComponentContext componentContext) {
-    	context = componentContext;
+        context = componentContext;
+        Dictionary<?, ?> props = context.getProperties();
 
-    	loginForm = OsgiUtil.toString(
-         		context.getProperties().get(PROP_LOGIN_FORM),
-         		DEFAULT_LOGIN_FORM);
+        loginForm = OsgiUtil.toString(props.get(
+            PROP_LOGIN_FORM), AuthenticationFormServlet.SERVLET_PATH);
 
-    	authSuccessUrl = OsgiUtil.toString(
-         		context.getProperties().get(PROP_AUTH_SUCCESS_URL),
-         		DEFAULT_AUTH_SUCCESS_URL);
+        externalUrlPrefix = OsgiUtil.toString(props.get(
+            PROP_EXTERNAL_URL_PREFIX), DEFAULT_EXTERNAL_URL_PREFIX);
 
-    	authFailUrl = OsgiUtil.toString(
-         		context.getProperties().get(PROP_AUTH_FAIL_URL),
-         		DEFAULT_AUTH_FAIL_URL);
+        // JCR user properties used to match OpenID users
+        identityProperty = OsgiUtil.toString(
+            props.get(PROP_OPEN_ID_IDENTIFIER_PROPERTY),
+            DEFAULT_OPEN_ID_IDENTIFIER_PROPERTY);
 
-    	logoutUrl = OsgiUtil.toString(
-         		context.getProperties().get(PROP_LOGOUT_URL),
-         		DEFAULT_LOGOUT_URL);
+        // DYU OpenID properties
+        useCookie = OsgiUtil.toBoolean(props.get(
+            PROP_USE_COOKIE), DEFAULT_USE_COOKIE);
 
-    	redirectToOriginalUrl = OsgiUtil.toBoolean(
-         		context.getProperties().get(PROP_ORIGINAL_URL_ON_SUCCESS),
-         		DEFAULT_ORIGINAL_URL_ON_SUCCESS);
+        cookieDomain = OsgiUtil.toString(props.get(
+            PROP_COOKIE_DOMAIN), DEFAULT_COOKIE_DOMAIN);
 
-    	accessAuthPageAnon = OsgiUtil.toBoolean(
-         		context.getProperties().get(PROP_ANONYMOUS_AUTH_RESOURCES),
-         		DEFAULT_ANONYMOUS_AUTH_RESOURCES);
+        cookieName = OsgiUtil.toString(props.get(
+            PROP_COOKIE_NAME), DEFAULT_COOKIE_NAME);
 
-    	externalUrlPrefix = OsgiUtil.toString(
-    			context.getProperties().get(PROP_EXTERNAL_URL_PREFIX),
-    			DEFAULT_EXTERNAL_URL_PREFIX);
+        identifierParam = OsgiUtil.toString(props.get(
+            PROP_LOGIN_IDENTIFIER_FORM_FIELD),
+            DEFAULT_LOGIN_IDENTIFIER_FORM_FIELD);
 
-    	// DYU OpenID properties
-    	useCookie = OsgiUtil.toBoolean(
-         		context.getProperties().get(PROP_USE_COOKIE),
-         		DEFAULT_USE_COOKIE);
+        cookieSecret = OsgiUtil.toString(
+            props.get(PROP_COOKIE_SECRET_KEY),
+            DEFAULT_COOKIE_SECRET_KEY).toCharArray();
 
-    	cookieDomain = OsgiUtil.toString(
-    			context.getProperties().get(PROP_COOKIE_DOMAIN),
-    			DEFAULT_COOKIE_DOMAIN);
+        openIdAttribute = OsgiUtil.toString(props.get(
+            PROP_OPENID_USER_ATTR), DEFAULT_OPENID_USER_ATTR);
 
-    	cookieName = OsgiUtil.toString(
-    			context.getProperties().get(PROP_COOKIE_NAME),
-    			DEFAULT_COOKIE_NAME);
+        this.loginModule = null;
+        try {
+            this.loginModule = OpenIDLoginModulePlugin.register(this,
+                componentContext.getBundleContext());
+        } catch (Throwable t) {
+            log.info("Cannot register OpenIDLoginModulePlugin. This is expected if Sling LoginModulePlugin services are not supported");
+            log.debug("dump", t);
+        }
+    }
 
-    	cookiePath = OsgiUtil.toString(
-    			context.getProperties().get(PROP_COOKIE_PATH),
-    			DEFAULT_COOKIE_PATH);
+    protected void deactivate(
+            @SuppressWarnings("unused") ComponentContext componentContext) {
+        if (loginModule != null) {
+            loginModule.unregister();
+            loginModule = null;
+        }
 
-    	identifierParam = OsgiUtil.toString(
-        		context.getProperties().get(PROP_LOGIN_IDENTIFIER_FORM_FIELD),
-        		DEFAULT_LOGIN_IDENTIFIER_FORM_FIELD);
+        if (session != null) {
+            try {
+                if (session.isLive()) {
+                    session.logout();
+                }
+            } catch (Throwable t) {
+                log.error("deactivate: Unexpected problem logging out session",
+                    t);
+            }
+            userManager = null;
+            session = null;
+        }
+    }
 
-    	String cookieSecret = OsgiUtil.toString(
-    			context.getProperties().get(PROP_COOKIE_SECRET_KEY),
-    			DEFAULT_COOKIE_SECRET_KEY);
+    // ---------- internal -----------------------------------------------------
 
-        Properties openIdProps = new Properties();
+    /**
+     * Returns <code>true</code> if this authentication handler should ignore
+     * the call to
+     * {@link #requestCredentials(HttpServletRequest, HttpServletResponse)}.
+     * <p>
+     * This method returns <code>true</code> if the
+     * {@link #REQUEST_LOGIN_PARAMETER} is set to any value other than "Form"
+     * (HttpServletRequest.FORM_AUTH).
+     */
+    private boolean ignoreRequestCredentials(final HttpServletRequest request) {
+        final String requestLogin = request.getParameter(REQUEST_LOGIN_PARAMETER);
+        return requestLogin != null
+            && !OpenIDConstants.OPENID_AUTH.equals(requestLogin);
+    }
 
-        openIdProps.setProperty("openid.identifier.parameter", identifierParam);
+    private AuthenticationInfo getAuthInfoFromUser(final OpenIdUser user) {
+        final SimpleCredentials credentials = getCredentials(user);
+        final AuthenticationInfo info = new AuthenticationInfo(
+            OpenIDConstants.OPENID_AUTH, credentials.getUserID());
+        info.put(AuthenticationInfo.CREDENTIALS, credentials);
+        return info;
+    }
 
-        if(useCookie) {
-        	openIdProps.setProperty("openid.user.manager", CookieBasedUserManager.class.getName());
-        	openIdProps.setProperty("openid.user.manager.cookie.name", cookieName);
-        	openIdProps.setProperty("openid.user.manager.cookie.path", cookiePath);
-        	openIdProps.setProperty("openid.user.manager.cookie.domain", cookieDomain);
-        	openIdProps.setProperty("openid.user.manager.cookie.security.secret_key", cookieSecret);
+    private SimpleCredentials getCredentials(final OpenIdUser user) {
+        final String userName = getUserName(user);
+        final SimpleCredentials creds = new SimpleCredentials(userName,
+            new char[0]);
+
+        // if there is no login module plugin service, set the credentials
+        // attribute to the user's OpenID identity, otherwise set it to
+        // the actual OpenIDUser object
+        if (loginModule == null) {
+            creds.setAttribute(openIdAttribute, user.getIdentity());
+        } else {
+            creds.setAttribute(openIdAttribute, user);
         }
 
-		relyingParty = RelyingParty.newInstance(openIdProps);
+        return creds;
     }
 
-    // ---------- internal -----------------------------------------------------
+    OpenIdUser getOpenIdUser(final Credentials credentials) {
+        if (credentials instanceof SimpleCredentials) {
+            SimpleCredentials creds = (SimpleCredentials) credentials;
+            return (OpenIdUser) creds.getAttribute(openIdAttribute);
+        }
+        return null;
+    }
 
-    @SuppressWarnings("unchecked")
-    private <T> T removeAttributeFromSession(String attrName, Class<T> type, HttpServletRequest request) {
-    	T attr = (T)request.getSession().getAttribute(attrName);
-		request.getSession().removeAttribute(attrName);
-		return attr;
-    }
-
-    private <T> T moveAttributeFromSessionToRequest(String attrName, Class<T> type, HttpServletRequest request) {
-		T attr = removeAttributeFromSession(attrName, type, request);
-		request.setAttribute(attrName, attr);
-		return attr;
-    }
-
-    private AuthenticationInfo getAuthInfoFromUser(OpenIdUser user) {
-    	final AuthenticationInfo info = new AuthenticationInfo(OpenIDConstants.OPEN_ID_AUTH_TYPE);
-        info.put(AuthenticationInfo.CREDENTIALS, new OpenIdCredentials(user));
-        return info;
+    /**
+     * Find a JCR Repository user name for the given OpenIdUser. Derives a name
+     * from the user identifier if none can be found.
+     */
+    private String getUserName(final OpenIdUser user) {
+
+        final Object nickname = user.getAttribute(ATTR_USER_ID);
+        if (nickname instanceof String) {
+            return (String) nickname;
+        }
+
+        final String identity = user.getIdentity();
+        String userId = null;
+        UserManager userManager = getUserManager();
+        if (userManager != null) {
+            userId = getUserIdByProperty(userManager, identityProperty,
+                identity);
+        }
+
+        // still null, use some dummy value to fail login and be able
+        // to associate user afterwards
+        if (userId == null) {
+            userId = "::not_valid_for_login::";
+        } else {
+            // store the id in the attribute
+            user.setAttribute(ATTR_USER_ID, userId);
+        }
+
+        return userId;
     }
 
-	public boolean canHandle(Credentials credentials) {
-		if(credentials instanceof OpenIdCredentials) {
-		    OpenIdCredentials creds = (OpenIdCredentials)credentials;
-			OpenIdUser user = creds.getUser();
-			if(user != null) {
-				return user.isAssociated();
-			}
-		}
-		return false;
-	}
-
-	@SuppressWarnings("unchecked")
-    public void doInit(CallbackHandler callbackHandler, Session session,
-			Map options) {
-		return;
-	}
-
-	public AuthenticationPlugin getAuthentication(Principal principal,
-			Credentials creds) {
-		return new OpenIDAuthenticationPlugin(principal);
-	}
-
-	public Principal getPrincipal(Credentials credentials) {
-		if(credentials instanceof OpenIdCredentials) {
-            OpenIdCredentials creds = (OpenIdCredentials) credentials;
-            OpenIdUser user = creds.getUser();
-            if (user != null) {
-                return new OpenIDPrincipal(user);
+    private UserManager getUserManager() {
+        if (userManager == null) {
+            try {
+                if (session == null) {
+                    session = repository.loginAdministrative(null);
+                }
+                if (session instanceof JackrabbitSession) {
+                    userManager = ((JackrabbitSession) session).getUserManager();
+                }
+            } catch (RepositoryException re) {
+                log.error("getUserManager: Cannot get UserManager", re);
             }
-		}
-		return null;
-	}
-
-	@SuppressWarnings("unchecked")
-    public void addPrincipals(Set principals) {
-        // Nothing to do
+        }
+        return userManager;
     }
 
-	public int impersonate(Principal principal, Credentials credentials) {
-		return LoginModulePlugin.IMPERSONATION_DEFAULT;
-	}
+    private String getUserIdByProperty(final UserManager userManager,
+            final String propName, final String propValue) {
+        String userId = null;
+        try {
+            Iterator<?> users = userManager.findAuthorizables(propName,
+                propValue, UserManager.SEARCH_TYPE_USER);
 
+            // use the first user found
+            if (users.hasNext()) {
+                userId = ((User) users.next()).getID();
+
+                // warn if more than one user found
+                if (users.hasNext()) {
+                    log.warn(
+                        "getUserName: Multiple users found with property {}={}; using {}",
+                        new Object[] { propName, propValue, userId });
+                }
+            }
+        } catch (RepositoryException re) {
+            log.warn("getUserName: Problem finding user with property {}={}",
+                new Object[] { propName, propValue }, re);
+        }
+
+        return userId;
+    }
+
+    /**
+     * Returns any resource target to redirect to after successful
+     * authentication. This method either returns a non-empty string or the
+     * <code>defaultLoginResource</code> parameter. First the
+     * <code>resource</code> request attribute is checked. If it is a non-empty
+     * string, it is returned. Second the <code>resource</code> request
+     * parameter is checked and returned if it is a non-empty string.
+     *
+     * @param request The request providing the attribute or parameter
+     * @param defaultLoginResource The default login resource value
+     * @return The non-empty redirection target or
+     *         <code>defaultLoginResource</code>.
+     */
+    static String getLoginResource(final HttpServletRequest request,
+            String defaultLoginResource) {
+
+        // return the resource attribute if set to a non-empty string
+        Object resObj = request.getAttribute(Authenticator.LOGIN_RESOURCE);
+        if ((resObj instanceof String) && ((String) resObj).length() > 0) {
+            return (String) resObj;
+        }
+
+        // return the resource parameter if not set or set to a non-empty value
+        final String resource = request.getParameter(Authenticator.LOGIN_RESOURCE);
+        if (resource != null && resource.length() > 0) {
+            return resource;
+        }
+
+        // normalize empty resource string to null
+        return defaultLoginResource;
+    }
+
+    private RelyingParty getRelyingParty(final HttpServletRequest request) {
+        if (relyingParty == null) {
+            Properties openIdProps = new Properties();
+            openIdProps.setProperty("openid.identifier.parameter",
+                identifierParam);
+
+            if (useCookie) {
+
+                final String ctxPath = request.getContextPath();
+                final String cookiePath = (ctxPath == null || ctxPath.length() == 0)
+                        ? "/"
+                        : ctxPath;
+
+                openIdProps.setProperty("openid.user.manager",
+                    CookieBasedUserManager.class.getName());
+                openIdProps.setProperty("openid.user.manager.cookie.name",
+                    cookieName);
+                openIdProps.setProperty("openid.user.manager.cookie.path",
+                    cookiePath);
+
+                if (cookieDomain != null) {
+                    openIdProps.setProperty(
+                        "openid.user.manager.cookie.domain", cookieDomain);
+                }
+
+                openIdProps.setProperty(
+                    "openid.user.manager.cookie.security.secret_key",
+                    new String(cookieSecret));
+            }
+
+            relyingParty = RelyingParty.newInstance(openIdProps);
+        }
+        return relyingParty;
+    }
+
+    String getBaseUrl(HttpServletRequest request) {
+        /*
+         * package private for unit testing
+         */
+        if (externalUrlPrefix == null || externalUrlPrefix.length() == 0) {
+            final String scheme = request.getScheme();
+            final String host = request.getServerName();
+            final int port = request.getServerPort();
+            final String ctx = request.getContextPath();
+
+            StringBuilder url = new StringBuilder();
+            url.append(scheme).append("://");
+            url.append(host);
+            if ((port > 0) && (!"http".equals(scheme) || port != 80)
+                && (!"https".equals(scheme) || port != 443)) {
+                url.append(':').append(port);
+            }
+            url.append(ctx);
+            return url.toString();
+        }
+        return externalUrlPrefix;
+    }
 }
\ No newline at end of file

Added: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java
URL: http://svn.apache.org/viewvc/sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java?rev=956515&view=auto
==============================================================================
--- sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java (added)
+++ sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java Mon Jun 21 09:35:58 2010
@@ -0,0 +1,153 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.sling.openidauth.impl;
+
+import java.security.Principal;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.Credentials;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.security.auth.callback.CallbackHandler;
+
+import org.apache.sling.jcr.jackrabbit.server.security.AuthenticationPlugin;
+import org.apache.sling.jcr.jackrabbit.server.security.LoginModulePlugin;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.Constants;
+import org.osgi.framework.ServiceRegistration;
+
+import com.dyuproject.openid.OpenIdUser;
+
+/**
+ * The <code>OpenIDLoginModulePlugin</code> is a simple Sling LoginModulePlugin
+ * enabling authentication of OpenID identifiers as Jackrabbit Repository users
+ */
+class OpenIDLoginModulePlugin implements LoginModulePlugin {
+
+    private final OpenIDAuthenticationHandler authHandler;
+
+    /**
+     * Creates an instance of this class and registers it as a
+     * <code>LoginModulePlugin</code> service to handle login requests with
+     * <code>SimpleCredentials</code> provided by the
+     * {@link OpenIDAuthenticationHandler}.
+     *
+     * @param authHandler The {@link OpenIDAuthenticationHandler} providing
+     *            support to validate the credentials
+     * @param bundleContext The <code>BundleContext</code> to register the
+     *            service
+     * @return The <code>ServiceRegistration</code> of the registered service
+     *         for the {@link OpenIDAuthenticationHandler} to unregister the
+     *         service on shutdown.
+     */
+    static ServiceRegistration register(
+            final OpenIDAuthenticationHandler authHandler,
+            final BundleContext bundleContext) {
+        OpenIDLoginModulePlugin plugin = new OpenIDLoginModulePlugin(
+            authHandler);
+
+        Hashtable<String, Object> properties = new Hashtable<String, Object>();
+        properties.put(Constants.SERVICE_DESCRIPTION,
+            "LoginModulePlugin Support for OpenIDAuthenticationHandler");
+        properties.put(Constants.SERVICE_VENDOR,
+            bundleContext.getBundle().getHeaders().get(Constants.BUNDLE_VENDOR));
+
+        return bundleContext.registerService(LoginModulePlugin.class.getName(),
+            plugin, properties);
+    }
+
+    private OpenIDLoginModulePlugin(
+            final OpenIDAuthenticationHandler authHandler) {
+        this.authHandler = authHandler;
+    }
+
+    /**
+     * This implementation does nothing.
+     */
+    @SuppressWarnings("unchecked")
+    public void doInit(final CallbackHandler callbackHandler,
+            final Session session, final Map options) {
+        return;
+    }
+
+    /**
+     * Returns <code>true</code> indicating support if the credentials is a
+     * <code>SimplerCredentials</code> object and has an authentication data
+     * attribute.
+     * <p>
+     * This method does not validate the data just checks its presence.
+     *
+     * @see CookieAuthenticationHandler#hasAuthData(Credentials)
+     */
+    public boolean canHandle(Credentials credentials) {
+        return authHandler.getOpenIdUser(credentials) != null;
+    }
+
+    /**
+     * Returns an authentication plugin which validates the authentication data
+     * contained as an attribute in the credentials object. The
+     * <code>authenticate</code> method returns <code>true</code> only if
+     * authentication data is contained in the credentials (expected because
+     * this method should only be called if {@link #canHandle(Credentials)}
+     * returns <code>true</code>) and the authentication data is valid.
+     */
+    public AuthenticationPlugin getAuthentication(final Principal principal,
+            final Credentials creds) {
+        return new AuthenticationPlugin() {
+            public boolean authenticate(Credentials credentials)
+                    throws RepositoryException {
+                OpenIdUser user = authHandler.getOpenIdUser(credentials);
+                if (user != null) {
+                    return user.isAssociated();
+                }
+                throw new RepositoryException(
+                    "Can't authenticate credentials of type: "
+                        + credentials.getClass());
+            }
+
+        };
+    }
+
+    /**
+     * Returns <code>null</code> to have the <code>DefaultLoginModule</code>
+     * provide a principal based on an existing user defined in the repository.
+     */
+    public Principal getPrincipal(final Credentials credentials) {
+        return null;
+    }
+
+    /**
+     * This implementation does nothing.
+     */
+    @SuppressWarnings("unchecked")
+    public void addPrincipals(final Set principals) {
+    }
+
+    /**
+     * Returns <code>LoginModulePlugin.IMPERSONATION_DEFAULT</code> to indicate
+     * that this plugin does not itself handle impersonation requests.
+     */
+    public int impersonate(final Principal principal,
+            final Credentials credentials) {
+        return LoginModulePlugin.IMPERSONATION_DEFAULT;
+    }
+
+}
\ No newline at end of file

Propchange: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: sling/trunk/bundles/extensions/openidauth/src/main/java/org/apache/sling/openidauth/impl/OpenIDLoginModulePlugin.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev Url