Browse Source

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 năm trước cách đây
mục cha
commit
d81a806405
20 tập tin đã thay đổi với 1440 bổ sung0 xóa
  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>