diff --git a/changelog.txt b/changelog.txt index 054ef35f95..a9bb74f532 100644 --- a/changelog.txt +++ b/changelog.txt @@ -11,6 +11,7 @@ Changes in version 0.6 (2004-xx-xx) * Added support for EL expressions in the authz tag library * Added failed Authentication object to AuthenticationExceptions * Added signed JARs to all official release builds (see readme.txt) +* Added remote client authentication validation package * Updated Authentication to be serializable (Weblogic support) * Updated to Clover 1.3 * Updated to HSQLDB version 1.7.2 Release Candidate 6D diff --git a/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationException.java b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationException.java new file mode 100644 index 0000000000..b914f90291 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationException.java @@ -0,0 +1,46 @@ +/* Copyright 2004 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 net.sf.acegisecurity.providers.rcp; + +import net.sf.acegisecurity.AcegiSecurityException; + + +/** + * Thrown if a RemoteAuthenticationManager cannot validate the + * presented authentication request. + * + *

+ * This is thrown rather than the normal AuthenticationException + * because AuthenticationException contains additional properties + * which may cause issues for the remoting protocol. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class RemoteAuthenticationException extends AcegiSecurityException { + //~ Constructors =========================================================== + + /** + * Constructs a RemoteAuthenticationException with the + * specified message and no root cause. + * + * @param msg the detail message + */ + public RemoteAuthenticationException(String msg) { + super(msg); + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManager.java b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManager.java new file mode 100644 index 0000000000..12e51bc3b3 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManager.java @@ -0,0 +1,56 @@ +/* Copyright 2004 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 net.sf.acegisecurity.providers.rcp; + +import net.sf.acegisecurity.GrantedAuthority; + + +/** + * Allows remote clients to attempt authentication. + * + * @author Ben Alex + * @version $Id$ + */ +public interface RemoteAuthenticationManager { + //~ Methods ================================================================ + + /** + * Attempts to authenticate the remote client using the presented username + * and password. If authentication is successful, an array of + * GrantedAuthority[] objects will be returned. + * + *

+ * In order to maximise remoting protocol compatibility, a design decision + * was taken to operate with minimal arguments and return only the minimal + * amount information required for remote clients to enable/disable + * relevant user interface commands etc. There is nothing preventing users + * from implementing their own equivalent package that works with more + * complex object types. + *

+ * + * @param username the username the remote client wishes to authenticate + * with + * @param password the password the remote client wishes to authenticate + * wish + * + * @return all of the granted authorities the specified username and + * password have access to + * + * @throws RemoteAuthenticationException if the authentication failed + */ + public GrantedAuthority[] attemptAuthentication(String username, + String password) throws RemoteAuthenticationException; +} diff --git a/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManagerImpl.java b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManagerImpl.java new file mode 100644 index 0000000000..70f4168b84 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManagerImpl.java @@ -0,0 +1,73 @@ +/* Copyright 2004 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 net.sf.acegisecurity.providers.rcp; + +import net.sf.acegisecurity.AuthenticationException; +import net.sf.acegisecurity.AuthenticationManager; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; + +import org.springframework.beans.factory.InitializingBean; + + +/** + * Server-side processor of a remote authentication request. + * + *

+ * This bean requires no security interceptor to protect it. Instead, the bean + * uses the configured AuthenticationManager to resolve an + * authentication request. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class RemoteAuthenticationManagerImpl + implements RemoteAuthenticationManager, InitializingBean { + //~ Instance fields ======================================================== + + private AuthenticationManager authenticationManager; + + //~ Methods ================================================================ + + public void setAuthenticationManager( + AuthenticationManager authenticationManager) { + this.authenticationManager = authenticationManager; + } + + public AuthenticationManager getAuthenticationManager() { + return authenticationManager; + } + + public void afterPropertiesSet() throws Exception { + if (this.authenticationManager == null) { + throw new IllegalArgumentException( + "authenticationManager is required"); + } + } + + public GrantedAuthority[] attemptAuthentication(String username, + String password) throws RemoteAuthenticationException { + UsernamePasswordAuthenticationToken request = new UsernamePasswordAuthenticationToken(username, + password); + + try { + return authenticationManager.authenticate(request).getAuthorities(); + } catch (AuthenticationException authEx) { + throw new RemoteAuthenticationException(authEx.getMessage()); + } + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationProvider.java new file mode 100644 index 0000000000..3bea7b33fb --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rcp/RemoteAuthenticationProvider.java @@ -0,0 +1,102 @@ +/* Copyright 2004 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 net.sf.acegisecurity.providers.rcp; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.AuthenticationException; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.providers.AuthenticationProvider; +import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.factory.InitializingBean; + + +/** + * Client-side object which queries a {@link RemoteAuthenticationManager} to + * validate an authentication request. + * + *

+ * A new Authentication object is created by this class comprising + * the request Authentication object's principal, + * credentials and the GrantedAuthority[]s returned + * by the RemoteAuthenticationManager. + *

+ * + *

+ * The RemoteAuthenticationManager should not require any special + * username or password setting on the remoting client proxy factory to + * execute the call. Instead the entire authentication request must be + * encapsulated solely within the Authentication request object. + * In practical terms this means the RemoteAuthenticationManager + * will not be protected by BASIC or any other HTTP-level + * authentication. + *

+ * + *

+ * If authentication fails, a RemoteAuthenticationException will + * be thrown. This exception should be caught and displayed to the user, + * enabling them to retry with alternative credentials etc. + *

+ * + * @author Ben Alex + * @version $Id$ + */ +public class RemoteAuthenticationProvider implements AuthenticationProvider, + InitializingBean { + //~ Static fields/initializers ============================================= + + private static final Log logger = LogFactory.getLog(RemoteAuthenticationProvider.class); + + //~ Instance fields ======================================================== + + private RemoteAuthenticationManager remoteAuthenticationManager; + + //~ Methods ================================================================ + + public void setRemoteAuthenticationManager( + RemoteAuthenticationManager remoteAuthenticationManager) { + this.remoteAuthenticationManager = remoteAuthenticationManager; + } + + public RemoteAuthenticationManager getRemoteAuthenticationManager() { + return remoteAuthenticationManager; + } + + public void afterPropertiesSet() throws Exception { + if (this.remoteAuthenticationManager == null) { + throw new IllegalArgumentException( + "remoteAuthenticationManager is mandatory"); + } + } + + public Authentication authenticate(Authentication authentication) + throws AuthenticationException { + String username = authentication.getPrincipal().toString(); + String password = authentication.getCredentials().toString(); + GrantedAuthority[] authorities = remoteAuthenticationManager + .attemptAuthentication(username, password); + + return new UsernamePasswordAuthenticationToken(username, password, + authorities); + } + + public boolean supports(Class authentication) { + return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); + } +} diff --git a/core/src/main/java/org/acegisecurity/providers/rcp/package.html b/core/src/main/java/org/acegisecurity/providers/rcp/package.html new file mode 100644 index 0000000000..3fbed2f919 --- /dev/null +++ b/core/src/main/java/org/acegisecurity/providers/rcp/package.html @@ -0,0 +1,7 @@ + + +Allows remote clients to authenticate and obtain a populated +Authentication object. + + + diff --git a/core/src/test/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManagerImplTests.java b/core/src/test/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManagerImplTests.java new file mode 100644 index 0000000000..e6c4ecc8f7 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/rcp/RemoteAuthenticationManagerImplTests.java @@ -0,0 +1,83 @@ +/* Copyright 2004 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 net.sf.acegisecurity.providers.rcp; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.MockAuthenticationManager; + + +/** + * Tests {@link RemoteAuthenticationManagerImpl}. + * + * @author Ben Alex + * @version $Id$ + */ +public class RemoteAuthenticationManagerImplTests extends TestCase { + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(RemoteAuthenticationManagerImplTests.class); + } + + public void testFailedAuthenticationReturnsRemoteAuthenticationException() { + RemoteAuthenticationManagerImpl manager = new RemoteAuthenticationManagerImpl(); + manager.setAuthenticationManager(new MockAuthenticationManager(false)); + + try { + manager.attemptAuthentication("marissa", "password"); + fail("Should have thrown RemoteAuthenticationException"); + } catch (RemoteAuthenticationException expected) { + assertTrue(true); + } + } + + public void testGettersSetters() { + RemoteAuthenticationManagerImpl manager = new RemoteAuthenticationManagerImpl(); + manager.setAuthenticationManager(new MockAuthenticationManager(true)); + assertNotNull(manager.getAuthenticationManager()); + } + + public void testStartupChecksAuthenticationManagerSet() + throws Exception { + RemoteAuthenticationManagerImpl manager = new RemoteAuthenticationManagerImpl(); + + try { + manager.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + manager.setAuthenticationManager(new MockAuthenticationManager(true)); + manager.afterPropertiesSet(); + assertTrue(true); + } + + public void testSuccessfulAuthentication() { + RemoteAuthenticationManagerImpl manager = new RemoteAuthenticationManagerImpl(); + manager.setAuthenticationManager(new MockAuthenticationManager(true)); + + GrantedAuthority[] result = manager.attemptAuthentication("marissa", + "password"); + assertTrue(true); + } +} diff --git a/core/src/test/java/org/acegisecurity/providers/rcp/RemoteAuthenticationProviderTests.java b/core/src/test/java/org/acegisecurity/providers/rcp/RemoteAuthenticationProviderTests.java new file mode 100644 index 0000000000..060bcbfb36 --- /dev/null +++ b/core/src/test/java/org/acegisecurity/providers/rcp/RemoteAuthenticationProviderTests.java @@ -0,0 +1,117 @@ +/* Copyright 2004 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 net.sf.acegisecurity.providers.rcp; + +import junit.framework.TestCase; + +import net.sf.acegisecurity.Authentication; +import net.sf.acegisecurity.GrantedAuthority; +import net.sf.acegisecurity.GrantedAuthorityImpl; +import net.sf.acegisecurity.providers.UsernamePasswordAuthenticationToken; + + +/** + * Tests {@link RemoteAuthenticationProvider}. + * + * @author Ben Alex + * @version $Id$ + */ +public class RemoteAuthenticationProviderTests extends TestCase { + //~ Methods ================================================================ + + public final void setUp() throws Exception { + super.setUp(); + } + + public static void main(String[] args) { + junit.textui.TestRunner.run(RemoteAuthenticationProviderTests.class); + } + + public void testExceptionsGetPassedBackToCaller() { + RemoteAuthenticationProvider provider = new RemoteAuthenticationProvider(); + provider.setRemoteAuthenticationManager(new MockRemoteAuthenticationManager( + false)); + + try { + provider.authenticate(new UsernamePasswordAuthenticationToken( + "marissa", "password")); + fail("Should have thrown RemoteAuthenticationException"); + } catch (RemoteAuthenticationException expected) { + assertTrue(true); + } + } + + public void testGettersSetters() { + RemoteAuthenticationProvider provider = new RemoteAuthenticationProvider(); + provider.setRemoteAuthenticationManager(new MockRemoteAuthenticationManager( + true)); + assertNotNull(provider.getRemoteAuthenticationManager()); + } + + public void testStartupChecksAuthenticationManagerSet() + throws Exception { + RemoteAuthenticationProvider provider = new RemoteAuthenticationProvider(); + + try { + provider.afterPropertiesSet(); + fail("Should have thrown IllegalArgumentException"); + } catch (IllegalArgumentException expected) { + assertTrue(true); + } + + provider.setRemoteAuthenticationManager(new MockRemoteAuthenticationManager( + true)); + provider.afterPropertiesSet(); + assertTrue(true); + } + + public void testSuccessfulAuthenticationCreatesObject() { + RemoteAuthenticationProvider provider = new RemoteAuthenticationProvider(); + provider.setRemoteAuthenticationManager(new MockRemoteAuthenticationManager( + true)); + + Authentication result = provider.authenticate(new UsernamePasswordAuthenticationToken( + "marissa", "password")); + assertEquals("marissa", result.getPrincipal()); + assertEquals("password", result.getCredentials()); + assertEquals("foo", result.getAuthorities()[0].getAuthority()); + } + + public void testSupports() { + RemoteAuthenticationProvider provider = new RemoteAuthenticationProvider(); + assertTrue(provider.supports(UsernamePasswordAuthenticationToken.class)); + } + + //~ Inner Classes ========================================================== + + private class MockRemoteAuthenticationManager + implements RemoteAuthenticationManager { + private boolean grantAccess; + + public MockRemoteAuthenticationManager(boolean grantAccess) { + this.grantAccess = grantAccess; + } + + public GrantedAuthority[] attemptAuthentication(String username, + String password) throws RemoteAuthenticationException { + if (grantAccess) { + return new GrantedAuthority[] {new GrantedAuthorityImpl("foo")}; + } else { + throw new RemoteAuthenticationException("as requested"); + } + } + } +}