Permalink
Browse files

CFID-80: add Legacy* components for authentication with CF.com

Change-Id: I020059d7847633d01dcfeb6d7e4d766c2aa0c89f
  • Loading branch information...
1 parent 67aa3fb commit 33131070c7066364e60538521bdc0864a2af2de0 @dsyer dsyer committed Jan 1, 2012
View
@@ -136,7 +136,12 @@ To launch in a microcloud type environment you need the SCIM user
endpoints to be unsecure so that a user can create an account and set
its password to bootstrap the system. For this use the Spring profile
`private`. The opposite is `!private` which needs to be specified
-excplicitly if the any other profiles are active.
+explicitly if the any other profiles are active.
+
+To launch in legacy mode with the CF.com cloud controller as the
+authentication and token source use profile `legacy`. The opposite is
+`!legacy` which needs to be specified explicitly if the any other
+profiles are active.
## The API Application
@@ -1,9 +1,10 @@
log4j.rootCategory=INFO, CONSOLE
+PID=????
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm:ss,SSS} %p [%c{1}] - %m%n
+log4j.appender.CONSOLE.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] samples/api - ${PID} [%t] .... %5p --- %c{1}: %m%n
log4j.category.org.springframework.security=DEBUG
log4j.category.org.springframework.web=DEBUG
@@ -1,9 +1,10 @@
log4j.rootCategory=INFO, CONSOLE
+PID=????
# CONSOLE is set to be a ConsoleAppender using a PatternLayout.
log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
-log4j.appender.CONSOLE.layout.ConversionPattern=%d{HH:mm:ss,SSS} %p [%c{1}] - %m%n
+log4j.appender.CONSOLE.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss.SSS}] samples/app - ${PID} [%t] .... %5p --- %c{1}: %m%n
log4j.category.org.springframework.security=DEBUG
log4j.category.org.springframework.web=DEBUG
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2002-2011 the original author or authors.
+ *
+ * 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.cloudfoundry.identity.uaa.authentication;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.cloudfoundry.identity.uaa.user.UaaUser;
+import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
+import org.springframework.context.support.MessageSourceAccessor;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.core.SpringSecurityMessageSource;
+import org.springframework.security.web.authentication.WebAuthenticationDetails;
+import org.springframework.util.StringUtils;
+import org.springframework.web.client.HttpClientErrorException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.WebUtils;
+
+/**
+ * Provider which delegates authentication to an existing api for user accounts. By default the
+ * {@link #setCloudControllerUrl(String) url} points to the cloud controller on cloudfoundry.com. The remote api is a
+ * cloud controller, so it accpets <code>(email, password)</code> form inputs and returns a token as a JSON property
+ * "token". The token is added to the successful {@link Authentication#getDetails() authentication details} as a map
+ * entry (i.e. the details are a map).
+ *
+ * @author Dave Syer
+ */
+public class LegacyAuthenticationProvider implements AuthenticationProvider,
+ AuthenticationDetailsSource<HttpServletRequest, Map<String, String>> {
+
+ private String url = "http://api.cloudfoundry.com/users/{username}/tokens";
+
+ private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
+
+ private List<String> parameterKeys = Arrays.asList("email", "password");
+
+ private UaaUserDatabase userDatabase = new LegacyUaaUserDatabase();
+
+ public void setCloudControllerUrl(String url) {
+ this.url = url;
+ }
+
+ public Authentication authenticate(Authentication authentication) throws AuthenticationException {
+
+ UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) authentication;
+ String username = usernamePasswordAuthenticationToken.getName();
+ String password = usernamePasswordAuthenticationToken.getCredentials().toString();
+
+ Map<String, String> details = extractDetails(usernamePasswordAuthenticationToken);
+
+ Map<String, String> result = doAuthentication(username, password);
+ result.putAll(details);
+
+ UaaUser user = userDatabase.retrieveUserByName(username);
+ Authentication success = new UaaAuthentication(new UaaPrincipal(user), user.getAuthorities(), result);
+
+ return success;
+
+ }
+
+ public boolean supports(Class<?> authentication) {
+ return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
+ }
+
+ private Map<String, String> doAuthentication(String username, String password) {
+
+ Map<String, String> body = new HashMap<String, String>();
+ body.put("password", password);
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ @SuppressWarnings("rawtypes")
+ HttpEntity<Map> request = new HttpEntity<Map>(body, headers);
+
+ Map<String, String> result;
+ try {
+ @SuppressWarnings("unchecked")
+ Map<String, String> object = new RestTemplate().postForObject(url, request, Map.class, username);
+ result = new HashMap<String, String>(object);
+ }
+ catch (HttpClientErrorException e) {
+ throw new BadCredentialsException(messages.getMessage(
+ "AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
+ }
+
+ if (StringUtils.hasLength(result.get("token"))) {
+ result.put("tokenized", "true");
+ }
+ return result;
+ }
+
+ public Map<String, String> buildDetails(HttpServletRequest context) {
+ WebAuthenticationDetails webAuthenticationDetails = new WebAuthenticationDetails(context);
+ Map<String, String> map = new HashMap<String, String>();
+ map.put("remote_addess", webAuthenticationDetails.getRemoteAddress());
+ map.put("session_id", webAuthenticationDetails.getSessionId());
+ @SuppressWarnings("unchecked")
+ Map<String, String[]> parameterMap = context.getParameterMap();
+ for (String key : parameterKeys) {
+ if (parameterMap.containsKey(key)) {
+ map.put(key, WebUtils.findParameterValue(parameterMap, key));
+ }
+ }
+ return map;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, String> extractDetails(Authentication authentication) {
+ return authentication.getDetails() instanceof Map ? new HashMap<String, String>(
+ (Map<String, String>) authentication.getDetails()) : new HashMap<String, String>();
+ }
+
+}
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2006-2011 the original author or authors.
+ *
+ * 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.cloudfoundry.identity.uaa.authentication;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.common.OAuth2AccessToken;
+import org.springframework.security.oauth2.common.OAuth2RefreshToken;
+import org.springframework.security.oauth2.provider.OAuth2Authentication;
+import org.springframework.security.oauth2.provider.token.RandomValueTokenServices;
+
+/**
+ * OAuth2 token services for authorization and resource server. The token value has to be passed in as part of the
+ * authentication details, so it is assumed to be populated during authentication somehow. The authentication details
+ * should be a map with the token stored under key "token".
+ *
+ * @author Dave Syer
+ *
+ */
+public class LegacyTokenServices extends RandomValueTokenServices {
+
+ @Override
+ public void afterPropertiesSet() throws Exception {
+ super.afterPropertiesSet();
+ setSupportRefreshToken(false);
+ }
+
+ @Override
+ protected OAuth2AccessToken createAccessToken(OAuth2Authentication authentication, OAuth2RefreshToken refreshToken) {
+
+ OAuth2AccessToken accessToken = super.createAccessToken(authentication, refreshToken);
+ if (authentication.getUserAuthentication() == null) {
+ return accessToken;
+ }
+
+ Map<String, String> details = extractDetails(authentication.getUserAuthentication());
+ if (!details.containsKey("token")) {
+ throw new IllegalStateException("Expected token to be part of authentication details");
+ }
+
+ OAuth2AccessToken result = new OAuth2AccessToken(details.get("token"));
+ result.setScope(accessToken.getScope());
+ result.setExpiration(accessToken.getExpiration());
+ return result;
+
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map<String, String> extractDetails(Authentication authentication) {
+ return authentication.getDetails() instanceof Map ? new HashMap<String, String>(
+ (Map<String, String>) authentication.getDetails()) : new HashMap<String, String>();
+ }
+
+}
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2006-2011 the original author or authors.
+ *
+ * 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.cloudfoundry.identity.uaa.authentication;
+
+import org.cloudfoundry.identity.uaa.user.UaaUser;
+import org.cloudfoundry.identity.uaa.user.UaaUserDatabase;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+
+/**
+ * Really simple {@link UaaUserDatabase} that doesn't use any backend store. All users retrieved have the same name
+ * ("Legacy User") and differ only in their username. Used in conjuction with other legacy components to allow delegated
+ * authentication (i.e. this app doesn't do authentication, it just trusts a remote source).
+ *
+ * @author Dave Syer
+ *
+ */
+public class LegacyUaaUserDatabase implements UaaUserDatabase {
+
+ @Override
+ public UaaUser retrieveUserByName(String username) throws UsernameNotFoundException {
+ return new UaaUser(username, "", username, "Legacy", "User");
+ }
+
+}
@@ -33,4 +33,5 @@ public String getName() {
public String getEmail() {
return email;
}
+
}
@@ -38,10 +38,18 @@ public void afterPropertiesSet() throws Exception {
@ResponseBody
public Map<String, String> loginInfo(Principal principal) {
OAuth2Authentication authentication = (OAuth2Authentication) principal;
- UaaPrincipal uaaPrincipal = (UaaPrincipal) authentication.getUserAuthentication().getPrincipal();
+ UaaPrincipal uaaPrincipal = extractUaaPrincipal(authentication);
return getResponse(uaaPrincipal);
}
+ protected UaaPrincipal extractUaaPrincipal(OAuth2Authentication authentication) {
+ Object object = authentication.getUserAuthentication().getPrincipal();
+ if (object instanceof UaaPrincipal) {
+ return (UaaPrincipal) object;
+ }
+ throw new IllegalStateException("User authentication could not be converted to UaaPrincipal");
+ }
+
protected Map<String, String> getResponse(UaaPrincipal principal) {
UaaUser user = userDatabase.retrieveUserByName(principal.getName());
Map<String, String> response = new LinkedHashMap<String, String>() {
@@ -1,36 +0,0 @@
-dn: ou=groups,dc=springframework,dc=org
-objectclass: top
-objectclass: organizationalUnit
-ou: groups
-
-dn: ou=people,dc=springframework,dc=org
-objectclass: top
-objectclass: organizationalUnit
-ou: people
-
-dn: uid=marissa,ou=people,dc=springframework,dc=org
-objectclass: top
-objectclass: person
-objectclass: organizationalPerson
-objectclass: inetOrgPerson
-cn: Marissa
-sn: Marissa
-uid: marissa
-userPassword: koala
-
-dn: uid=paul,ou=people,dc=springframework,dc=org
-objectclass: top
-objectclass: person
-objectclass: organizationalPerson
-objectclass: inetOrgPerson
-cn: Paul
-sn: Paul
-uid: paul
-userPassword: emu
-
-dn: cn=user,ou=groups,dc=springframework,dc=org
-objectclass: top
-objectclass: groupOfNames
-cn: user
-member: uid=marissa,ou=people,dc=springframework,dc=org
-member: uid=paul,ou=people,dc=springframework,dc=org
Oops, something went wrong.

0 comments on commit 3313107

Please sign in to comment.