2
0
Эх сурвалжийг харах

OpenID support contributed by Robin Bramley
Todo:
* Improve Test Coverage
* Replace Servlet with Filter
* Add support for providers other than JanRain as it may be dead

Ray Krueger 18 жил өмнө
parent
commit
d81a806405
20 өөрчлөгдсөн 1440 нэмэгдсэн , 0 устгасан
  1. 3 0
      pom.xml
  2. 83 0
      sandbox/openid/pom.xml
  3. 36 0
      sandbox/openid/src/main/java/org/acegisecurity/providers/openid/AuthenticationCancelledException.java
  4. 108 0
      sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProvider.java
  5. 69 0
      sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationStatus.java
  6. 88 0
      sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationToken.java
  7. 6 0
      sandbox/openid/src/main/java/org/acegisecurity/providers/openid/package.html
  8. 26 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConstants.java
  9. 62 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumer.java
  10. 32 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumerException.java
  11. 180 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDLoginInitiationServlet.java
  12. 84 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDResponseProcessingFilter.java
  13. 201 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/JanRainOpenIDConsumer.java
  14. 5 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/package.html
  15. 5 0
      sandbox/openid/src/main/java/org/acegisecurity/ui/openid/package.html
  16. 40 0
      sandbox/openid/src/test/java/org/acegisecurity/providers/openid/MockAuthoritiesPopulator.java
  17. 194 0
      sandbox/openid/src/test/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProviderTests.java
  18. 139 0
      sandbox/openid/src/test/java/org/acegisecurity/ui/openid/OpenIDResponseProcessingFilterTests.java
  19. 78 0
      sandbox/openid/src/test/java/org/acegisecurity/ui/openid/consumers/MockOpenIDConsumer.java
  20. 1 0
      sandbox/pom.xml

+ 3 - 0
pom.xml

@@ -199,6 +199,9 @@
     <contributor>
       <name>Mike Perham</name>
     </contributor>
+    <contributor>
+      <name>Robin Bramley</name>
+    </contributor>
   </contributors>
 
   <dependencies>

+ 83 - 0
sandbox/openid/pom.xml

@@ -0,0 +1,83 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.acegisecurity</groupId>
+        <artifactId>acegi-security-sandbox</artifactId>
+        <version>1.0.2</version>
+    </parent>
+    <artifactId>acegi-security-openid</artifactId>
+    <name>Acegi Security System for Spring - OpenID support</name>
+    <description>Acegi Security System for Spring - Support for OpenID</description>
+    <version>0.1-SNAPSHOT</version>
+
+    <repositories>
+        <repository>
+            <id>AcegiMaven</id>
+            <name>Acegi 3rd party repository</name>
+            <url>http://acegisecurity.sourceforge.net/maven</url>
+            <releases>
+                <enabled>true</enabled>
+            </releases>
+        </repository>
+    </repositories>
+
+    <scm>
+        <connection>scm:svn:https://svn.sourceforge.net/svnroot/acegisecurity/trunk/acegisecurity/sandbox/openid
+        </connection>
+        <developerConnection>
+            scm:svn:https://svn.sourceforge.net/svnroot/acegisecurity/trunk/acegisecurity/sandbox/openid
+        </developerConnection>
+        <url>http://svn.sourceforge.net/viewcvs.cgi/acegisecurity/trunk/acegisecurity/sandbox/openid/</url>
+    </scm>
+
+    <dependencies>
+        <dependency>
+          <groupId>org.springframework</groupId>
+          <artifactId>spring-mock</artifactId>
+          <optional>true</optional>
+        </dependency>
+        <dependency>
+            <groupId>com.janrain</groupId>
+            <artifactId>Janrain-Openid</artifactId>
+            <version>20070226</version>
+        </dependency>
+        <dependency>
+            <groupId>gnu</groupId>
+            <artifactId>libidn</artifactId>
+            <version>0.6.3</version>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.geronimo.specs</groupId>
+            <artifactId>geronimo-servlet_2.4_spec</artifactId>
+            <version>1.1.1</version>
+            <scope>compile</scope>
+            <optional>true</optional>
+        </dependency>
+        <!--
+        <dependency>
+            <groupId>javax.servlet</groupId>
+            <artifactId>servlet-api</artifactId>
+            <version>2.4</version>
+            <scope>provided</scope>
+        </dependency>
+-->
+    </dependencies>
+
+    <!--This doesn't even exist...-->
+<!--
+    <build>
+        <plugins>
+        <plugin>
+          <groupId>org.codehaus.mojo</groupId>
+          <artifactId>jalopy-maven-plugin</artifactId>
+          <version>1.0-SNAPSHOT</version>
+            <configuration>
+                <convention>../../jalopy.xml</convention>
+                <failOnError>false</failOnError>
+            </configuration>
+        </plugin>
+        </plugins>
+    </build>
+-->
+</project>

+ 36 - 0
sandbox/openid/src/main/java/org/acegisecurity/providers/openid/AuthenticationCancelledException.java

@@ -0,0 +1,36 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.providers.openid;
+
+import org.acegisecurity.AuthenticationException;
+
+
+/**
+ * Indicates that OpenID authentication was cancelled
+ *
+ * @author Robin Bramley, Opsera Ltd
+ * @version $Id:$
+ */
+public class AuthenticationCancelledException extends AuthenticationException {
+    //~ Constructors ===================================================================================================
+
+    public AuthenticationCancelledException(String msg) {
+        super(msg);
+    }
+
+    public AuthenticationCancelledException(String msg, Throwable t) {
+        super(msg, t);
+    }
+}

+ 108 - 0
sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProvider.java

@@ -0,0 +1,108 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.providers.openid;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.BadCredentialsException;
+
+import org.acegisecurity.providers.AuthenticationProvider;
+import org.acegisecurity.providers.cas.CasAuthoritiesPopulator;
+
+import org.acegisecurity.userdetails.UserDetails;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+
+/**
+ * Finalises the OpenID authentication by obtaining local roles
+ *
+ * @author Robin Bramley, Opsera Ltd.
+ */
+public class OpenIDAuthenticationProvider implements AuthenticationProvider, InitializingBean {
+    //~ Instance fields ================================================================================================
+
+    private CasAuthoritiesPopulator ssoAuthoritiesPopulator;
+
+    //~ Methods ========================================================================================================
+
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(this.ssoAuthoritiesPopulator, "The ssoAuthoritiesPopulator must be set");
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.providers.AuthenticationProvider#authenticate(org.acegisecurity.Authentication)
+     */
+    public Authentication authenticate(Authentication authentication)
+        throws AuthenticationException {
+        if (!supports(authentication.getClass())) {
+            return null;
+        }
+
+        if (authentication instanceof OpenIDAuthenticationToken) {
+            OpenIDAuthenticationToken response = (OpenIDAuthenticationToken) authentication;
+            OpenIDAuthenticationStatus status = response.getStatus();
+
+            // handle the various possibilites
+            if (status == OpenIDAuthenticationStatus.SUCCESS) {
+                //String message = "Log in succeeded: ";// + savedId;
+
+                /* TODO: allow for regex for mapping URL
+                 * e.g. http://mydomain.com/username
+                 * or http://{username}.mydomain.com
+                 */
+
+                // Lookup user details
+                UserDetails userDetails = this.ssoAuthoritiesPopulator.getUserDetails(response.getIdentityUrl());
+
+                authentication = new OpenIDAuthenticationToken(userDetails.getAuthorities(), response.getStatus(),
+                        response.getIdentityUrl());
+
+                return authentication;
+            } else if (status == OpenIDAuthenticationStatus.CANCELLED) {
+                throw new AuthenticationCancelledException("Log in cancelled");
+            } else if (status == OpenIDAuthenticationStatus.ERROR) {
+                throw new AuthenticationServiceException("Error message from server: " + response.getMessage());
+            } else if (status == OpenIDAuthenticationStatus.FAILURE) {
+                throw new BadCredentialsException("Log in failed - identity could not be verified");
+            } else if (status == OpenIDAuthenticationStatus.SETUP_NEEDED) {
+                throw new AuthenticationServiceException(
+                    "The server responded setup was needed, which shouldn't happen");
+            } else {
+                throw new AuthenticationServiceException("Unrecognized return value " + status.toString());
+            }
+        }
+
+        return null;
+    }
+
+    public void setSsoAuthoritiesPopulator(CasAuthoritiesPopulator ssoAuthoritiesPopulator) {
+        this.ssoAuthoritiesPopulator = ssoAuthoritiesPopulator;
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.providers.AuthenticationProvider#supports(java.lang.Class)
+     */
+    public boolean supports(Class authentication) {
+        if (OpenIDAuthenticationToken.class.isAssignableFrom(authentication)) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+}

+ 69 - 0
sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationStatus.java

@@ -0,0 +1,69 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.providers.openid;
+
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+
+
+/**
+ * Based on JanRain status codes
+ *
+ * @author JanRain Inc.
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class OpenIDAuthenticationStatus implements Serializable {
+    //~ Static fields/initializers =====================================================================================
+
+    private static final long serialVersionUID = -998877665544332211L;
+    private static int nextOrdinal = 0;
+
+    /** This code indicates a successful authentication request */
+    public static final OpenIDAuthenticationStatus SUCCESS = new OpenIDAuthenticationStatus("success");
+
+    /** This code indicates a failed authentication request */
+    public static final OpenIDAuthenticationStatus FAILURE = new OpenIDAuthenticationStatus("failure");
+
+    /** This code indicates the server reported an error */
+    public static final OpenIDAuthenticationStatus ERROR = new OpenIDAuthenticationStatus("error");
+
+    /** This code indicates that the user needs to do additional work to prove their identity */
+    public static final OpenIDAuthenticationStatus SETUP_NEEDED = new OpenIDAuthenticationStatus("setup needed");
+
+    /** This code indicates that the user cancelled their login request */
+    public static final OpenIDAuthenticationStatus CANCELLED = new OpenIDAuthenticationStatus("cancelled");
+    private static final OpenIDAuthenticationStatus[] PRIVATE_VALUES = {SUCCESS, FAILURE, ERROR, SETUP_NEEDED, CANCELLED};
+
+    //~ Instance fields ================================================================================================
+
+    private String name;
+    private final int ordinal = nextOrdinal++;
+
+    //~ Constructors ===================================================================================================
+
+    private OpenIDAuthenticationStatus(String name) {
+        this.name = name;
+    }
+
+    //~ Methods ========================================================================================================
+
+    private Object readResolve() throws ObjectStreamException {
+        return PRIVATE_VALUES[ordinal];
+    }
+
+    public String toString() {
+        return name;
+    }
+}

+ 88 - 0
sandbox/openid/src/main/java/org/acegisecurity/providers/openid/OpenIDAuthenticationToken.java

@@ -0,0 +1,88 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.providers.openid;
+
+import org.acegisecurity.GrantedAuthority;
+
+import org.acegisecurity.providers.AbstractAuthenticationToken;
+
+
+/**
+ * OpenID Authentication Token
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class OpenIDAuthenticationToken extends AbstractAuthenticationToken {
+    //~ Instance fields ================================================================================================
+
+    private OpenIDAuthenticationStatus status;
+    private String identityUrl;
+    private String message;
+
+    //~ Constructors ===================================================================================================
+
+    public OpenIDAuthenticationToken(OpenIDAuthenticationStatus status, String identityUrl, String message) {
+        super(new GrantedAuthority[0]);
+        this.status = status;
+        this.identityUrl = identityUrl;
+        this.message = message;
+        setAuthenticated(false);
+    }
+
+/**
+     * Created by the OpenIDAuthenticationProvider on successful authentication.
+     * <b>Do not use directly</b>
+     *
+     * @param authorities
+     * @param status
+     * @param identityUrl
+     */
+    public OpenIDAuthenticationToken(GrantedAuthority[] authorities, OpenIDAuthenticationStatus status,
+        String identityUrl) {
+        super(authorities);
+        this.status = status;
+        this.identityUrl = identityUrl;
+
+        setAuthenticated(true);
+    }
+
+    //~ Methods ========================================================================================================
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.Authentication#getCredentials()
+     */
+    public Object getCredentials() {
+        return null;
+    }
+
+    public String getIdentityUrl() {
+        return identityUrl;
+    }
+
+    public String getMessage() {
+        return message;
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.Authentication#getPrincipal()
+     */
+    public Object getPrincipal() {
+        return identityUrl;
+    }
+
+    public OpenIDAuthenticationStatus getStatus() {
+        return status;
+    }
+}

+ 6 - 0
sandbox/openid/src/main/java/org/acegisecurity/providers/openid/package.html

@@ -0,0 +1,6 @@
+<html>
+<body>
+An authentication provider that can process <a href="http://openid.net">OpenID</a> 
+Authentication Tokens as created by implementations of the OpenIDConsumer interface.
+</body>
+</html>

+ 26 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConstants.java

@@ -0,0 +1,26 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid;
+
+/**
+ * Constants required by OpenID classes
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class OpenIDConstants {
+    //~ Static fields/initializers =====================================================================================
+
+    public static final String OPENID_SESSION_MAP_KEY = "openid.session";
+}

+ 62 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumer.java

@@ -0,0 +1,62 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid;
+
+import org.acegisecurity.providers.openid.OpenIDAuthenticationToken;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * An interface for OpenID library implementations
+ *
+ * @author Robin Bramley, Opsera Ltd
+ *
+ */
+public interface OpenIDConsumer {
+    //~ Methods ========================================================================================================
+
+    /**
+     * Start the authentication process
+     *
+     * @param req
+     * @param identityUrl
+     *
+     * @return redirection URL
+     *
+     * @throws OpenIDConsumerException
+     */
+    public String beginConsumption(HttpServletRequest req, String identityUrl)
+        throws OpenIDConsumerException;
+
+    /**
+     * DOCUMENT ME!
+     *
+     * @param req
+     *
+     * @return
+     *
+     * @throws OpenIDConsumerException
+     */
+    public OpenIDAuthenticationToken endConsumption(HttpServletRequest req)
+        throws OpenIDConsumerException;
+
+    /**
+     * DOCUMENT ME!
+     *
+     * @param returnToUrl
+     */
+    public void setReturnToUrl(String returnToUrl);
+}

+ 32 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDConsumerException.java

@@ -0,0 +1,32 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid;
+
+/**
+ * Thrown by an OpenIDConsumer if it cannot process a request
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class OpenIDConsumerException extends Exception {
+    //~ Constructors ===================================================================================================
+
+    public OpenIDConsumerException(String message) {
+        super(message);
+    }
+
+    public OpenIDConsumerException(String message, Throwable t) {
+        super(message, t);
+    }
+}

+ 180 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDLoginInitiationServlet.java

@@ -0,0 +1,180 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.util.StringUtils;
+import org.springframework.web.context.WebApplicationContext;
+import org.springframework.web.context.support.WebApplicationContextUtils;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+
+
+/**
+ * This servlet starts the OpenID authentication process.
+ * <br/>
+ * <br/>Sample web.xml configuration:
+ * <br/>
+ * <br/>        &lt;servlet&gt;
+ * <br/>        &nbsp;&nbsp; &lt;servlet-name&gt;openid&lt;/servlet-name&gt;
+ * <br/>        &nbsp;&nbsp; &lt;servlet-class&gt;org.acegisecurity.ui.openid.OpenIDLoginInitiationServlet&lt;/servlet-class&gt;
+ * <br/>        &nbsp;&nbsp; &lt;load-on-startup&gt;1&lt;/load-on-startup&gt;
+ * <br/>        &nbsp;&nbsp; &lt;init-param&gt;
+ * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;description&gt;The error page - will receive error "message"&lt;/description&gt;
+ * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;param-name&gt;errorPage&lt;/param-name&gt;
+ * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;param-value&gt;index.jsp&lt;/param-value&gt;
+ * <br/>        &nbsp;&nbsp; &lt;/init-param&gt;
+ * <br/>        &lt;/servlet&gt;
+ * <br/>        &lt;servlet-mapping&gt;
+ * <br/>        &nbsp;&nbsp; &lt;servlet-name&gt;openid&lt;/servlet-name&gt;
+ * <br/>        &nbsp;&nbsp; &lt;url-pattern&gt;/j_acegi_openid_start&lt;/url-pattern&gt;
+ * <br/>        &lt;/servlet-mapping&gt;
+ * <br/>
+ * <br/>Sample login form:
+ * <br/>&lt;form method="POST" action="j_acegi_openid_start"&gt;
+ * <br/>&nbsp;&nbsp; &lt;input type="text" name="j_username" /&gt;
+ * <br/>&nbsp;&nbsp; &lt;input type="password" name="j_password" /&gt;
+ * <br/>&nbsp;&nbsp; &lt;input type="submit" value="Verify" /&gt;
+ * <br/>&lt;/form&gt;
+ * <br/>
+ * <br/>Usage notes:
+ * <li>Requires an <code>openIDConsumer</code> Spring bean implementing the {@link OpenIDConsumer} interface</li>
+ * <li>It will pass off to standard form-based authentication if appropriate</li>
+ * (note that <code>AuthenticationProcessingFilter</code> requires j_username, j_password)
+ * <br/>
+ * <br/>Outstanding items:
+ * TODO: config flag for whether OpenID only or dual mode?
+ * TODO: username matching logic
+ *
+ * @author Robin Bramley, Opsera Ltd
+ * @version $Id:$
+ */
+public class OpenIDLoginInitiationServlet extends HttpServlet {
+    final static long serialVersionUID = -997766L;
+    private static final Log logger = LogFactory.getLog(OpenIDLoginInitiationServlet.class);
+    private static final String passwordField = "j_password";
+
+    /**
+     * Servlet config key for looking up the the HttpServletRequest parameter name
+     * containing the OpenID Identity URL from the Servlet config.
+     * <br/><b>Only set the identityField servlet init-param if you are not using</b> <code>j_username</code>
+     * <br/>
+     * <br/>        &nbsp;&nbsp; &lt;init-param&gt;
+     * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;description&gt;The identity form field parameter&lt;/description&gt;
+     * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;param-name&gt;identityField&lt;/param-name&gt;
+     * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;param-value&gt;/openid_url&lt;/param-value&gt;
+     * <br/>        &nbsp;&nbsp; &lt;/init-param&gt;
+     */
+    public static final String IDENTITY_FIELD_KEY = "identityField";
+
+    /**
+     * Servlet config key for the return to URL
+     */
+    public static final String ERROR_PAGE_KEY = "errorPage";
+
+    /**
+     * Servlet config key for looking up the form login URL from the Servlet config.
+     * <br/><b>Only set the formLogin servlet init-param if you are not using</b> <code>/j_acegi_security_check</code>
+     * <br/>
+     * <br/>        &nbsp;&nbsp; &lt;init-param&gt;
+     * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;description&gt;The form login URL - for standard authentication&lt;/description&gt;
+     * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;param-name&gt;formLogin&lt;/param-name&gt;
+     * <br/>        &nbsp;&nbsp;&nbsp;&nbsp; &lt;param-value&gt;/custom_acegi_security_check&lt;/param-value&gt;
+     * <br/>        &nbsp;&nbsp; &lt;/init-param&gt;
+     */
+    public static final String FORM_LOGIN_URL_KEY = "formLogin";
+
+    /**
+     * Spring context key for the OpenID consumer bean
+     */
+    public static final String CONSUMER_KEY = "openIDConsumer";
+    private String errorPage = "index.jsp";
+    private String identityField = "j_username";
+    private String formLoginUrl = "/j_acegi_security_check";
+
+    /**
+     * Check for init-params
+     *
+     * @Override
+     */
+    public void init() throws ServletException {
+        super.init();
+
+        String configErrorPage = getServletConfig()
+                .getInitParameter(ERROR_PAGE_KEY);
+
+        if (StringUtils.hasText(configErrorPage)) {
+            errorPage = configErrorPage;
+        }
+
+        String configIdentityField = getServletConfig()
+                .getInitParameter(IDENTITY_FIELD_KEY);
+
+        if (StringUtils.hasText(configIdentityField)) {
+            identityField = configIdentityField;
+        }
+
+        String configFormLoginUrl = getServletConfig()
+                .getInitParameter(FORM_LOGIN_URL_KEY);
+
+        if (StringUtils.hasText(configFormLoginUrl)) {
+            formLoginUrl = configFormLoginUrl;
+        }
+    }
+
+    /**
+     * Process the form post - all the work is done by the OpenIDConsumer.beginConsumption method
+     *
+     * @Override
+     */
+    protected void doPost(HttpServletRequest req, HttpServletResponse res)
+            throws ServletException, IOException {
+        WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
+        OpenIDConsumer consumer = (OpenIDConsumer) webApplicationContext.getBean(CONSUMER_KEY);
+
+        // get the submitted id field
+        String id = req.getParameter(identityField);
+
+        // assume page will validate? 
+        //TODO: null checking!
+
+        //TODO: pattern matching
+        String password = req.getParameter(passwordField);
+
+        if ((password != null) && (password.length() > 0)) {
+            logger.debug("Attempting to authenticate using username/password");
+
+            // forward to authenticationProcessingFilter (/j_acegi_security_check - depends on param names)
+            req.getRequestDispatcher(formLoginUrl).forward(req, res);
+
+        } else {
+            // send the user the redirect url to proceed with OpenID authentication
+            try {
+                String redirect = consumer.beginConsumption(req, id);
+                logger.debug("Redirecting to: " + redirect);
+                res.sendRedirect(redirect);
+            } catch (OpenIDConsumerException oice) {
+                logger.error("Consumer error!", oice);
+                req.setAttribute("message", oice.getMessage());
+                req.getRequestDispatcher(errorPage).forward(req, res);
+            }
+        }
+    }
+}

+ 84 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/OpenIDResponseProcessingFilter.java

@@ -0,0 +1,84 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.AuthenticationServiceException;
+
+import org.acegisecurity.providers.openid.OpenIDAuthenticationToken;
+
+import org.acegisecurity.ui.AbstractProcessingFilter;
+import org.acegisecurity.ui.webapp.AuthenticationProcessingFilter;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * Process the response from the OpenID server to the returnTo URL.
+ *
+ * @author Robin Bramley, Opsera Ltd
+ * @version $Id:$
+ */
+public class OpenIDResponseProcessingFilter extends AbstractProcessingFilter {
+    //~ Instance fields ================================================================================================
+
+    private OpenIDConsumer consumer;
+
+    //~ Methods ========================================================================================================
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.AbstractProcessingFilter#attemptAuthentication(javax.servlet.http.HttpServletRequest)
+     * @Override
+     */
+    public Authentication attemptAuthentication(HttpServletRequest req)
+        throws AuthenticationException {
+        OpenIDAuthenticationToken token;
+
+        try {
+            token = consumer.endConsumption(req);
+        } catch (OpenIDConsumerException oice) {
+            throw new AuthenticationServiceException("Consumer error", oice);
+        }
+
+        // delegate to the auth provider
+        Authentication authentication = this.getAuthenticationManager().authenticate(token);
+
+        if (authentication.isAuthenticated()) {
+            req.getSession()
+               .setAttribute(AuthenticationProcessingFilter.ACEGI_SECURITY_LAST_USERNAME_KEY, token.getIdentityUrl());
+        }
+
+        return authentication;
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.AbstractProcessingFilter#getDefaultFilterProcessesUrl()
+     * @Override
+     */
+    public String getDefaultFilterProcessesUrl() {
+        return "/j_acegi_openid_security_check";
+    }
+
+    // dependency injection	
+    /**
+     * DOCUMENT ME!
+     *
+     * @param consumer The OpenIDConsumer to set.
+     */
+    public void setConsumer(OpenIDConsumer consumer) {
+        this.consumer = consumer;
+    }
+}

+ 201 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/JanRainOpenIDConsumer.java

@@ -0,0 +1,201 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid.consumers;
+
+import com.janrain.openid.consumer.AuthRequest;
+import com.janrain.openid.consumer.Consumer;
+import com.janrain.openid.consumer.ErrorResponse;
+import com.janrain.openid.consumer.Response;
+import com.janrain.openid.consumer.StatusCode;
+import com.janrain.openid.store.OpenIDStore;
+
+import org.acegisecurity.providers.openid.OpenIDAuthenticationStatus;
+import org.acegisecurity.providers.openid.OpenIDAuthenticationToken;
+
+import org.acegisecurity.ui.openid.OpenIDConstants;
+import org.acegisecurity.ui.openid.OpenIDConsumer;
+import org.acegisecurity.ui.openid.OpenIDConsumerException;
+
+import org.springframework.beans.factory.InitializingBean;
+
+import org.springframework.util.Assert;
+
+import java.io.IOException;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
+
+
+/**
+ * OpenIDConsumer implementation using the JanRain OpenID library
+ *
+ * @author Robin Bramley, Opsera Ltd
+ * @version $Id:$
+ */
+public class JanRainOpenIDConsumer implements OpenIDConsumer, InitializingBean {
+    //~ Static fields/initializers =====================================================================================
+
+    private static final String SAVED_ID_SESSION_KEY = "savedId";
+
+    //~ Instance fields ================================================================================================
+
+    private OpenIDStore store;
+    private String returnToUrl = "j_acegi_openid_security_check";
+
+    //~ Methods ========================================================================================================
+
+    public void afterPropertiesSet() throws Exception {
+        Assert.notNull(this.store, "An OpenIDStore must be set on the store property");
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.openid.OpenIDConsumer#beginConsumption(java.lang.String)
+     */
+    public String beginConsumption(HttpServletRequest req, String identityUrl)
+        throws OpenIDConsumerException {
+        // fetch/create a session Map for the consumer's use
+        HttpSession session = req.getSession();
+        Map sessionMap = (Map) session.getAttribute(OpenIDConstants.OPENID_SESSION_MAP_KEY);
+
+        if (sessionMap == null) {
+            sessionMap = new HashMap();
+            session.setAttribute(OpenIDConstants.OPENID_SESSION_MAP_KEY, sessionMap);
+        }
+
+        Consumer openIdConsumer = new Consumer(sessionMap, store);
+
+        // Create an Authrequest object from the submitted value
+        AuthRequest ar;
+
+        try {
+            ar = openIdConsumer.begin(identityUrl);
+        } catch (IOException ioe) {
+            req.getSession().setAttribute(SAVED_ID_SESSION_KEY, escapeAttr(identityUrl));
+            throw new OpenIDConsumerException("Error on begin consumption for " + identityUrl, ioe);
+        }
+
+        // construct trust root and return to URLs.
+        String port = "";
+
+        if (req.getServerPort() != 80) {
+            port = ":" + req.getServerPort();
+        }
+
+        String trustRoot = req.getScheme() + "://" + req.getServerName() + port + "/";
+        String cp = req.getContextPath();
+
+        if (!cp.equals("")) {
+            cp = cp.substring(1) + "/";
+        }
+
+        String returnTo = trustRoot + cp + returnToUrl;
+
+        // send the user the redirect url to proceed with OpenID authentication
+        return ar.redirectUrl(trustRoot, returnTo);
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.openid.OpenIDConsumer#endConsumption(javax.servlet.http.HttpServletRequest)
+     */
+    public OpenIDAuthenticationToken endConsumption(HttpServletRequest req)
+        throws OpenIDConsumerException {
+        HttpSession session = req.getSession();
+        Map sessionMap = (Map) session.getAttribute(OpenIDConstants.OPENID_SESSION_MAP_KEY);
+
+        if (sessionMap == null) {
+            sessionMap = new HashMap();
+            session.setAttribute(OpenIDConstants.OPENID_SESSION_MAP_KEY, sessionMap);
+        }
+
+        // get a Consumer instance
+        Consumer openIdConsumer = new Consumer(sessionMap, store);
+
+        // convert the argument map into the form the library uses with a handy
+        // convenience function
+        Map query = Consumer.filterArgs(req.getParameterMap());
+
+        // Check the arguments to see what the response was.
+        Response response = openIdConsumer.complete(query);
+
+        String message = "";
+        OpenIDAuthenticationStatus status;
+
+        StatusCode statusCode = response.getStatus();
+
+        if (statusCode == StatusCode.CANCELLED) {
+            status = OpenIDAuthenticationStatus.CANCELLED;
+        } else if (statusCode == StatusCode.ERROR) {
+            status = OpenIDAuthenticationStatus.ERROR;
+            message = ((ErrorResponse) response).getMessage();
+        } else if (statusCode == StatusCode.FAILURE) {
+            status = OpenIDAuthenticationStatus.FAILURE;
+        } else if (statusCode == StatusCode.SETUP_NEEDED) {
+            status = OpenIDAuthenticationStatus.SETUP_NEEDED;
+        } else if (statusCode == StatusCode.SUCCESS) {
+            status = OpenIDAuthenticationStatus.SUCCESS;
+        } else {
+            // unknown status code
+            throw new OpenIDConsumerException("Unknown response status " + statusCode.toString());
+        }
+
+        return new OpenIDAuthenticationToken(status, response.getIdentityUrl(), message);
+    }
+
+    /*
+     * This method escapes characters in a string that can cause problems in
+     * HTML
+     */
+    private String escapeAttr(String s) {
+        if (s == null) {
+            return "";
+        }
+
+        StringBuffer result = new StringBuffer();
+
+        for (int i = 0; i < s.length(); i++) {
+            char c = s.charAt(i);
+
+            if (c == '<') {
+                result.append("&lt;");
+            } else if (c == '>') {
+                result.append("&gt;");
+            } else if (c == '&') {
+                result.append("&amp;");
+            } else if (c == '\"') {
+                result.append("&quot;");
+            } else if (c == '\'') {
+                result.append("&#039;");
+            } else if (c == '\\') {
+                result.append("&#092;");
+            } else {
+                result.append(c);
+            }
+        }
+
+        return result.toString();
+    }
+
+    public void setReturnToUrl(String returnToUrl) {
+        this.returnToUrl = returnToUrl;
+    }
+
+    // dependency injection
+    public void setStore(OpenIDStore store) {
+        this.store = store;
+    }
+}

+ 5 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/consumers/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Implementations of the OpenIDConsumer interface using 3rd party libraries.
+</body>
+</html>

+ 5 - 0
sandbox/openid/src/main/java/org/acegisecurity/ui/openid/package.html

@@ -0,0 +1,5 @@
+<html>
+<body>
+Authenticates standard web browser users via <a href="http://openid.net">OpenID</a>.
+</body>
+</html>

+ 40 - 0
sandbox/openid/src/test/java/org/acegisecurity/providers/openid/MockAuthoritiesPopulator.java

@@ -0,0 +1,40 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.providers.openid;
+
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.GrantedAuthority;
+import org.acegisecurity.GrantedAuthorityImpl;
+
+import org.acegisecurity.providers.cas.CasAuthoritiesPopulator;
+
+import org.acegisecurity.userdetails.User;
+import org.acegisecurity.userdetails.UserDetails;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class MockAuthoritiesPopulator implements CasAuthoritiesPopulator {
+    //~ Methods ========================================================================================================
+
+    public UserDetails getUserDetails(String ssoUserId)
+        throws AuthenticationException {
+        return new User(ssoUserId, "password", true, true, true, true,
+            new GrantedAuthority[] {new GrantedAuthorityImpl("ROLE_A"), new GrantedAuthorityImpl("ROLE_B")});
+    }
+}

+ 194 - 0
sandbox/openid/src/test/java/org/acegisecurity/providers/openid/OpenIDAuthenticationProviderTests.java

@@ -0,0 +1,194 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.providers.openid;
+
+import junit.framework.TestCase;
+
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationServiceException;
+import org.acegisecurity.BadCredentialsException;
+
+import org.acegisecurity.providers.UsernamePasswordAuthenticationToken;
+
+
+/**
+ * Tests {@link OpenIDAuthenticationProvider}
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class OpenIDAuthenticationProviderTests extends TestCase {
+    //~ Static fields/initializers =====================================================================================
+
+    private static final String USERNAME = "user.acegiopenid.com";
+
+    //~ Methods ========================================================================================================
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.authenticate(Authentication)'
+     */
+    public void testAuthenticateCancel() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.CANCELLED, USERNAME, "");
+
+        assertFalse(preAuth.isAuthenticated());
+
+        try {
+            provider.authenticate(preAuth);
+            fail("Should throw an AuthenticationException");
+        } catch (AuthenticationCancelledException expected) {
+            assertEquals("Log in cancelled", expected.getMessage());
+        }
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.authenticate(Authentication)'
+     */
+    public void testAuthenticateError() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.ERROR, USERNAME, "");
+
+        assertFalse(preAuth.isAuthenticated());
+
+        try {
+            provider.authenticate(preAuth);
+            fail("Should throw an AuthenticationException");
+        } catch (AuthenticationServiceException expected) {
+            assertEquals("Error message from server: ", expected.getMessage());
+        }
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.authenticate(Authentication)'
+     */
+    public void testAuthenticateFailure() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE, USERNAME, "");
+
+        assertFalse(preAuth.isAuthenticated());
+
+        try {
+            provider.authenticate(preAuth);
+            fail("Should throw an AuthenticationException");
+        } catch (BadCredentialsException expected) {
+            assertEquals("Log in failed - identity could not be verified", expected.getMessage());
+        }
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.authenticate(Authentication)'
+     */
+    public void testAuthenticateSetupNeeded() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SETUP_NEEDED, USERNAME, "");
+
+        assertFalse(preAuth.isAuthenticated());
+
+        try {
+            provider.authenticate(preAuth);
+            fail("Should throw an AuthenticationException");
+        } catch (AuthenticationServiceException expected) {
+            assertEquals("The server responded setup was needed, which shouldn't happen", expected.getMessage());
+        }
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.authenticate(Authentication)'
+     */
+    public void testAuthenticateSuccess() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        Authentication preAuth = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, USERNAME, "");
+
+        assertFalse(preAuth.isAuthenticated());
+
+        Authentication postAuth = provider.authenticate(preAuth);
+
+        assertNotNull(postAuth);
+        assertTrue(postAuth instanceof OpenIDAuthenticationToken);
+        assertTrue(postAuth.isAuthenticated());
+        assertNotNull(postAuth.getPrincipal());
+        assertEquals(preAuth.getPrincipal(), postAuth.getPrincipal());
+        assertNotNull(postAuth.getAuthorities());
+        assertTrue(postAuth.getAuthorities().length > 0);
+        assertTrue(((OpenIDAuthenticationToken) postAuth).getStatus() == OpenIDAuthenticationStatus.SUCCESS);
+        assertTrue(((OpenIDAuthenticationToken) postAuth).getMessage() == null);
+    }
+
+    public void testDetectsMissingAuthoritiesPopulator() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+
+        try {
+            provider.afterPropertiesSet();
+            fail("Should have thrown Exception");
+        } catch (Exception expected) {
+            assertEquals("The ssoAuthoritiesPopulator must be set", expected.getMessage());
+        }
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.supports(Class)'
+     */
+    public void testDoesntSupport() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        assertFalse(provider.supports(UsernamePasswordAuthenticationToken.class));
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.authenticate(Authentication)'
+     */
+    public void testIgnoresUserPassAuthToken() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(USERNAME, "password");
+        assertEquals(null, provider.authenticate(token));
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.providers.openid.OpenIDAuthenticationProvider.supports(Class)'
+     */
+    public void testSupports() {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+
+        assertTrue(provider.supports(OpenIDAuthenticationToken.class));
+    }
+
+    public void testValidation() throws Exception {
+        OpenIDAuthenticationProvider provider = new OpenIDAuthenticationProvider();
+        provider.setSsoAuthoritiesPopulator(new MockAuthoritiesPopulator());
+        provider.afterPropertiesSet();
+
+        provider.setSsoAuthoritiesPopulator(null);
+
+        try {
+            provider.afterPropertiesSet();
+            fail("IllegalArgumentException expected, ssoAuthoritiesPopulator is null");
+        } catch (IllegalArgumentException e) {
+            //expected
+        }
+    }
+}

+ 139 - 0
sandbox/openid/src/test/java/org/acegisecurity/ui/openid/OpenIDResponseProcessingFilterTests.java

@@ -0,0 +1,139 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid;
+
+import junit.framework.TestCase;
+
+import org.acegisecurity.AbstractAuthenticationManager;
+import org.acegisecurity.Authentication;
+import org.acegisecurity.AuthenticationException;
+import org.acegisecurity.BadCredentialsException;
+
+import org.acegisecurity.providers.cas.CasAuthoritiesPopulator;
+import org.acegisecurity.providers.openid.MockAuthoritiesPopulator;
+import org.acegisecurity.providers.openid.OpenIDAuthenticationStatus;
+import org.acegisecurity.providers.openid.OpenIDAuthenticationToken;
+
+import org.acegisecurity.ui.openid.consumers.MockOpenIDConsumer;
+
+import org.springframework.mock.web.MockHttpServletRequest;
+
+
+/**
+ * Tests {@link OpenIDResponseProcessingFilter}
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class OpenIDResponseProcessingFilterTests extends TestCase {
+    //~ Static fields/initializers =====================================================================================
+
+    private static final String USERNAME = "user.acegiopenid.com";
+
+    //~ Methods ========================================================================================================
+
+    /*
+     * Test method for 'org.acegisecurity.ui.openid.OpenIDResponseProcessingFilter.attemptAuthentication(HttpServletRequest)'
+     */
+    public void testAttemptAuthenticationFailure() {
+        // set up mock objects
+        MockOpenIDAuthenticationManager mockAuthManager = new MockOpenIDAuthenticationManager(false);
+
+        OpenIDAuthenticationToken token = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE, USERNAME, "");
+        MockOpenIDConsumer mockConsumer = new MockOpenIDConsumer();
+        mockConsumer.setToken(token);
+
+        MockHttpServletRequest req = new MockHttpServletRequest();
+
+        OpenIDResponseProcessingFilter filter = new OpenIDResponseProcessingFilter();
+        filter.setConsumer(mockConsumer);
+        filter.setAuthenticationManager(mockAuthManager);
+
+        // run test
+        try {
+            filter.attemptAuthentication(req);
+            fail("Should've thrown exception");
+        } catch (BadCredentialsException expected) {
+            assertEquals("MockOpenIDAuthenticationManager instructed to deny access", expected.getMessage());
+        }
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.ui.openid.OpenIDResponseProcessingFilter.attemptAuthentication(HttpServletRequest)'
+     */
+    public void testAttemptAuthenticationHttpServletRequest() {
+        // set up mock objects
+        MockOpenIDAuthenticationManager mockAuthManager = new MockOpenIDAuthenticationManager(true);
+
+        OpenIDAuthenticationToken token = new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, USERNAME, "");
+        MockOpenIDConsumer mockConsumer = new MockOpenIDConsumer();
+        mockConsumer.setToken(token);
+
+        MockHttpServletRequest req = new MockHttpServletRequest();
+
+        OpenIDResponseProcessingFilter filter = new OpenIDResponseProcessingFilter();
+        filter.setConsumer(mockConsumer);
+        filter.setAuthenticationManager(mockAuthManager);
+
+        // run test
+        Authentication authentication = filter.attemptAuthentication(req);
+
+        // assertions
+        assertNotNull(authentication);
+        assertTrue(authentication.isAuthenticated());
+        assertTrue(authentication instanceof OpenIDAuthenticationToken);
+        assertNotNull(authentication.getPrincipal());
+        assertEquals(USERNAME, authentication.getPrincipal());
+        assertNotNull(authentication.getAuthorities());
+        assertTrue(authentication.getAuthorities().length > 0);
+        assertTrue(((OpenIDAuthenticationToken) authentication).getStatus() == OpenIDAuthenticationStatus.SUCCESS);
+        assertTrue(((OpenIDAuthenticationToken) authentication).getMessage() == null);
+    }
+
+    /*
+     * Test method for 'org.acegisecurity.ui.openid.OpenIDResponseProcessingFilter.getDefaultFilterProcessesUrl()'
+     */
+    public void testGetDefaultFilterProcessesUrl() {
+        OpenIDResponseProcessingFilter filter = new OpenIDResponseProcessingFilter();
+        assertEquals("/j_acegi_openid_security_check", filter.getDefaultFilterProcessesUrl());
+    }
+
+    //~ Inner Classes ==================================================================================================
+
+    // private mock AuthenticationManager
+    private class MockOpenIDAuthenticationManager extends AbstractAuthenticationManager {
+        private CasAuthoritiesPopulator ssoAuthoritiesPopulator;
+        private boolean grantAccess = true;
+
+        public MockOpenIDAuthenticationManager(boolean grantAccess) {
+            this.grantAccess = grantAccess;
+            ssoAuthoritiesPopulator = new MockAuthoritiesPopulator();
+        }
+
+        public MockOpenIDAuthenticationManager() {
+            super();
+            ssoAuthoritiesPopulator = new MockAuthoritiesPopulator();
+        }
+
+        public Authentication doAuthentication(Authentication authentication)
+            throws AuthenticationException {
+            if (grantAccess) {
+                return new OpenIDAuthenticationToken(ssoAuthoritiesPopulator.getUserDetails(USERNAME).getAuthorities(),
+                    OpenIDAuthenticationStatus.SUCCESS, USERNAME);
+            } else {
+                throw new BadCredentialsException("MockOpenIDAuthenticationManager instructed to deny access");
+            }
+        }
+    }
+}

+ 78 - 0
sandbox/openid/src/test/java/org/acegisecurity/ui/openid/consumers/MockOpenIDConsumer.java

@@ -0,0 +1,78 @@
+/* Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
+ *
+ * Licensed 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.acegisecurity.ui.openid.consumers;
+
+import org.acegisecurity.providers.openid.OpenIDAuthenticationToken;
+
+import org.acegisecurity.ui.openid.OpenIDConsumer;
+import org.acegisecurity.ui.openid.OpenIDConsumerException;
+
+import javax.servlet.http.HttpServletRequest;
+
+
+/**
+ * DOCUMENT ME!
+ *
+ * @author Robin Bramley, Opsera Ltd
+ */
+public class MockOpenIDConsumer implements OpenIDConsumer {
+    //~ Instance fields ================================================================================================
+
+    private OpenIDAuthenticationToken token;
+    private String redirectUrl;
+
+    //~ Methods ========================================================================================================
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.openid.OpenIDConsumer#beginConsumption(javax.servlet.http.HttpServletRequest, java.lang.String)
+     */
+    public String beginConsumption(HttpServletRequest req, String identityUrl)
+        throws OpenIDConsumerException {
+        return redirectUrl;
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.openid.OpenIDConsumer#endConsumption(javax.servlet.http.HttpServletRequest)
+     */
+    public OpenIDAuthenticationToken endConsumption(HttpServletRequest req)
+        throws OpenIDConsumerException {
+        return token;
+    }
+
+    /**
+     * Set the redirectUrl to be returned by beginConsumption
+     *
+     * @param redirectUrl
+     */
+    public void setRedirectUrl(String redirectUrl) {
+        this.redirectUrl = redirectUrl;
+    }
+
+    /* (non-Javadoc)
+     * @see org.acegisecurity.ui.openid.OpenIDConsumer#setReturnToUrl(java.lang.String)
+     */
+    public void setReturnToUrl(String returnToUrl) {
+        // TODO Auto-generated method stub
+    }
+
+    /**
+     * Set the token to be returned by endConsumption
+     *
+     * @param token
+     */
+    public void setToken(OpenIDAuthenticationToken token) {
+        this.token = token;
+    }
+}

+ 1 - 0
sandbox/pom.xml

@@ -20,6 +20,7 @@
 
   <modules>
     <module>webwork</module>
+    <module>openid</module>
     <module>other</module>
   </modules>