Add ldap support via Spring profile #11

Merged
merged 3 commits into from Mar 21, 2013
View
@@ -319,6 +319,13 @@
<scope>runtime</scope>
</dependency>
+ <dependency>
+ <groupId>org.springframework.security</groupId>
+ <artifactId>spring-security-ldap</artifactId>
+ <version>${spring.security.version}</version>
+ <scope>runtime</scope>
+ </dependency>
+
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
@@ -74,6 +74,12 @@
private static final String CONTENT_LENGTH = "Content-Length";
+ private static final String CONTENT_TYPE = "Content-Type";
+
+ private static final String ACCEPT = "Accept";
+
+ private static final String AUTHORIZATION = "Authorization";
+
private static final String TRANSFER_ENCODING = "Transfer-Encoding";
private static final String HOST = "Host";
@@ -271,13 +277,17 @@ public ModelAndView startAuthorization(HttpServletRequest request, @RequestParam
if (principal != null) {
map.set("source", "login");
map.setAll(getLoginCredentials(principal));
+ map.remove("credentials"); // legacy vmc might break otherwise
}
else {
throw new BadCredentialsException("No principal found in authorize endpoint");
}
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.putAll(getRequestHeaders(headers));
+ requestHeaders.remove(AUTHORIZATION.toLowerCase());
+ requestHeaders.remove(ACCEPT.toLowerCase());
+ requestHeaders.remove(CONTENT_TYPE.toLowerCase());
requestHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
requestHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
requestHeaders.remove(COOKIE);
@@ -0,0 +1,60 @@
+/*
+ * Cloud Foundry 2012.02.03 Beta
+ * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
+ *
+ * This product is licensed to you under the Apache License, Version 2.0 (the "License").
+ * You may not use this product except in compliance with the License.
+ *
+ * This product includes a number of subcomponents with
+ * separate copyright notices and license terms. Your use of these
+ * subcomponents is subject to the terms and conditions of the
+ * subcomponent's license, as noted in the LICENSE file.
+ */
+
+package org.cloudfoundry.identity.uaa.login;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+
+/**
+ * Authentication filter translating a generic Authentication into a UsernamePasswordAuthenticationToken.
+ *
+ * @author Dave Syer
+ *
+ */
+public class UsernamePasswordExtractingAuthenticationManager implements AuthenticationManager {
+
+ protected final Log logger = LogFactory.getLog(getClass());
+
+ private final AuthenticationManager delegate;
+
+ /**
+ * @param delegate
+ */
+ public UsernamePasswordExtractingAuthenticationManager(AuthenticationManager delegate) {
+ super();
+ this.delegate = delegate;
+ }
+
+ /* (non-Javadoc)
+ * @see org.springframework.security.authentication.AuthenticationManager#authenticate(org.springframework.security.core.Authentication)
+ */
+ @Override
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+ if (authentication == null || authentication instanceof UsernamePasswordAuthenticationToken) {
+ return authentication;
+ }
+
+ UsernamePasswordAuthenticationToken output = new UsernamePasswordAuthenticationToken(authentication, authentication.getCredentials(), authentication.getAuthorities());
+ output.setAuthenticated(authentication.isAuthenticated());
+ output.setDetails(authentication.getDetails());
+ return delegate.authenticate(output);
+
+ }
+
+}
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8" ?>
-<!-- Cloud Foundry 2012.02.03 Beta Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. This product is licensed to
- you under the Apache License, Version 2.0 (the "License"). You may not use this product except in compliance with the License.
- This product includes a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents
+<!-- Cloud Foundry 2012.02.03 Beta Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved. This product is licensed to
+ you under the Apache License, Version 2.0 (the "License"). You may not use this product except in compliance with the License.
+ This product includes a number of subcomponents with separate copyright notices and license terms. Your use of these subcomponents
is subject to the terms and conditions of the subcomponent's license, as noted in the LICENSE file. -->
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
@@ -33,7 +33,9 @@
<property name="requireHttps" value="${require_https:false}" />
<property name="dumpRequests" value="${dump_requests:false}" />
<property name="redirectToHttps">
- <list><value>uiSecurity</value></list>
+ <list>
+ <value>uiSecurity</value>
+ </list>
</property>
</bean>
@@ -103,8 +105,9 @@
</bean>
<!-- For backwards compatibility to the old way of posting credentials to /authorize endpoint -->
- <http name="oldAuthzEndpointSecurity" request-matcher-ref="oauthAuthorizeRequestMatcherOld" create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint"
- authentication-manager-ref="remoteAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
+ <http name="oldAuthzEndpointSecurity" request-matcher-ref="oauthAuthorizeRequestMatcherOld" create-session="stateless"
+ entry-point-ref="oauthAuthenticationEntryPoint" authentication-manager-ref="remoteAuthenticationManager"
+ xmlns="http://www.springframework.org/schema/security">
<intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<custom-filter ref="authzAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<anonymous enabled="false" />
@@ -144,17 +147,17 @@
</bean>
<!-- Version of the /authorize endpoint for stateless clients such as VMC -->
- <http name="statelessAuthzEndpointSecurity" request-matcher-ref="oauthAuthorizeRequestMatcher" create-session="stateless" entry-point-ref="oauthAuthenticationEntryPoint"
- authentication-manager-ref="remoteAuthenticationManager" xmlns="http://www.springframework.org/schema/security">
+ <http name="statelessAuthzEndpointSecurity" request-matcher-ref="oauthAuthorizeRequestMatcher" create-session="stateless"
+ entry-point-ref="oauthAuthenticationEntryPoint" authentication-manager-ref="remoteAuthenticationManager"
+ xmlns="http://www.springframework.org/schema/security">
<intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<custom-filter ref="authzAuthenticationFilter" position="FORM_LOGIN_FILTER" />
<anonymous enabled="false" />
<access-denied-handler ref="oauthAccessDeniedHandler" />
</http>
- <http name="uiSecurity" xmlns="http://www.springframework.org/schema/security"
- authentication-manager-ref="remoteAuthenticationManager" disable-url-rewriting="true"
- entry-point-ref="loginEntryPoint">
+ <http name="uiSecurity" xmlns="http://www.springframework.org/schema/security" authentication-manager-ref="remoteAuthenticationManager"
+ disable-url-rewriting="true" entry-point-ref="loginEntryPoint">
<intercept-url pattern="/login*" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/logout.do" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
@@ -176,10 +179,6 @@
<constructor-arg value="/login" />
</bean>
- <bean id="remoteAuthenticationManager" class="org.cloudfoundry.identity.uaa.login.RemoteUaaAuthenticationManager">
- <property name="loginUrl" value="${uaa.login.url:http://localhost:8080/uaa/authenticate}" />
- </bean>
-
<bean id="oauthAuthenticationEntryPoint" class="org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint">
<property name="realmName" value="UAA/oauth" />
</bean>
@@ -226,13 +225,13 @@
</bean>
<util:map id="links">
- <entry key="home" value="${links.home:https://www.cloudfoundry.com}"/>
- <entry key="passwd" value="${links.passwd:https://www.cloudfoundry.com/passwd}"/>
- <entry key="register" value="${links.signup:https://www.cloudfoundry.com/signup}"/>
+ <entry key="home" value="${links.home:https://www.cloudfoundry.com}" />
+ <entry key="passwd" value="${links.passwd:https://www.cloudfoundry.com/passwd}" />
+ <entry key="register" value="${links.signup:https://www.cloudfoundry.com/signup}" />
</util:map>
<bean id="homeController" class="org.cloudfoundry.identity.uaa.login.HomeController">
- <property name="links" ref="links"/>
+ <property name="links" ref="links" />
</bean>
<bean id="autologinController" class="org.cloudfoundry.identity.uaa.login.AutologinController">
@@ -247,10 +246,10 @@
<constructor-arg ref="uaa" />
</bean>
</property>
- <property name="links" ref="links"/>
+ <property name="links" ref="links" />
</bean>
- <bean id="healthzEndpoint" class="org.cloudfoundry.identity.uaa.web.HealthzEndpoint"/>
+ <bean id="healthzEndpoint" class="org.cloudfoundry.identity.uaa.web.HealthzEndpoint" />
<oauth:resource id="uaa" access-token-uri="${uaa.token.url:http://localhost:8080/uaa/oauth/token}"
client-id="login" client-secret="${LOGIN_SECRET:loginsecret}" type="client_credentials" />
@@ -272,4 +271,48 @@
<property name="links" ref="links" />
</bean>
+ <beans profile="default">
+ <bean id="remoteAuthenticationManager" class="org.cloudfoundry.identity.uaa.login.RemoteUaaAuthenticationManager">
+ <property name="loginUrl" value="${uaa.login.url:http://localhost:8080/uaa/authenticate}" />
+ </bean>
+ </beans>
+
+ <beans profile="ldap">
+
+ <sec:authentication-manager alias="ldapAuthenticationManager">
+ <sec:authentication-provider ref="ldapAuthProvider" />
+ </sec:authentication-manager>
+
+ <bean id="remoteAuthenticationManager" class="org.cloudfoundry.identity.uaa.login.UsernamePasswordExtractingAuthenticationManager">
+ <constructor-arg ref="ldapAuthenticationManager" />
+ </bean>
+
+ <bean id="contextSource" class="org.springframework.security.ldap.DefaultSpringSecurityContextSource">
+ <constructor-arg value="${ldap.base.url:ldap://localhost:33389/dc=springframework,dc=org}" />
+ </bean>
+
+ <bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
+ <constructor-arg>
+ <bean class="org.springframework.security.ldap.authentication.BindAuthenticator">
+ <constructor-arg ref="contextSource" />
+ <property name="userDnPatterns">
+ <list>
+ <value>uid={0},ou=people</value>
+ </list>
+ </property>
+ </bean>
+ </constructor-arg>
+ <constructor-arg>
+ <bean class="org.springframework.security.ldap.authentication.NullLdapAuthoritiesPopulator" />
+ </constructor-arg>
+ <property name="authoritiesMapper">
+ <bean class="org.springframework.security.core.authority.mapping.SimpleAuthorityMapper">
+ <property name="defaultAuthority" value="ROLE_USER" />
+ </bean>
+ </property>
+ </bean>
+
+ </beans>
+
+
</beans>
@@ -52,6 +52,12 @@ public void testRootContextDefaults() throws Exception {
assertNotNull(context.getBean("viewResolver", ViewResolver.class));
}
+ @Test
+ public void testLdapProfile() throws Exception {
+ context = getServletContext("ldap", "file:./src/main/webapp/WEB-INF/spring-servlet.xml");
+ assertNotNull(context.getBean("viewResolver", ViewResolver.class));
+ }
+
private GenericXmlApplicationContext getServletContext(String... resources) {
String profiles = null;
@@ -0,0 +1,55 @@
+/*
+ * Cloud Foundry 2012.02.03 Beta
+ * Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
+ *
+ * This product is licensed to you under the Apache License, Version 2.0 (the "License").
+ * You may not use this product except in compliance with the License.
+ *
+ * This product includes a number of subcomponents with
+ * separate copyright notices and license terms. Your use of these
+ * subcomponents is subject to the terms and conditions of the
+ * subcomponent's license, as noted in the LICENSE file.
+ */
+
+package org.cloudfoundry.identity.uaa.login;
+
+import static org.junit.Assert.assertSame;
+
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.authority.AuthorityUtils;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class UsernamePasswordExtractingAuthenticationManagerTests {
+
+ private AuthenticationManager delegate = Mockito.mock(AuthenticationManager.class);
+
+ private UsernamePasswordExtractingAuthenticationManager manager = new UsernamePasswordExtractingAuthenticationManager(
+ delegate);
+
+ @Test
+ public void testAuthenticate() {
+ Authentication expected = new TestingAuthenticationToken("bar", "foo",
+ AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
+ Mockito.when(delegate.authenticate(Mockito.any(UsernamePasswordAuthenticationToken.class)))
+ .thenReturn(expected);
+ Authentication output = manager.authenticate(new TestingAuthenticationToken("foo", "bar"));
+ assertSame(expected, output);
+ }
+
+ @Test
+ public void testUsernamePassword() {
+ Authentication expected = new UsernamePasswordAuthenticationToken("bar", "foo",
+ AuthorityUtils.commaSeparatedStringToAuthorityList("USER"));
+ Authentication output = manager.authenticate(expected);
+ assertSame(expected, output);
+ }
+
+}