/* Copyright 2014 MITRE Corporation
*
* 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.mitre.provenance.openid;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Logger;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.association.AssociationException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryException;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.Message;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.ParameterList;
import org.openid4java.message.ax.AxMessage;
import org.openid4java.message.ax.FetchRequest;
import org.openid4java.message.ax.FetchResponse;
import org.openid4java.util.HttpClientFactory;
import org.openid4java.util.ProxyProperties;
import org.springframework.security.openid.AxFetchListFactory;
import org.springframework.security.openid.OpenIDAttribute;
import org.springframework.security.openid.OpenIDAuthenticationStatus;
import org.springframework.security.openid.OpenIDAuthenticationToken;
import org.springframework.security.openid.OpenIDConsumer;
import org.springframework.security.openid.OpenIDConsumerException;
import org.springframework.util.StringUtils;
/**
* This is a copy of the Spring Security OpenID4JavaConsumer class, with additions to handle MITRE's proxy.
* Changes:
* @author Amanda Anganes
*
* Original:
* @author Ray Krueger
* @author Luke Taylor
*/
@SuppressWarnings("unchecked")
public class OpenId4JavaProxyConsumer implements OpenIDConsumer {
protected static Logger log = Logger.getLogger(OpenId4JavaProxyConsumer.class.getName());
protected static String appHome;
protected static String proxyHost;
protected static Integer proxyPort;
static {
proxyHost = System.getenv("http.proxyHost");
try {
proxyPort = (System.getenv("http.proxyPort") != null ? Integer.parseInt(System.getenv("http.proxyPort")) : null);
} catch(NumberFormatException exc) {
log.severe("Invalid proxy port specified via http.proxyPort: '" + System.getenv("http.proxyPort") + "'");
// Force fallback to defaults.
proxyHost = null;
proxyPort = null;
}
if(proxyHost != null && proxyPort != null) {
log.info("Using JVM properties to configure proxy: " + proxyHost + ":" + proxyPort);
} else {
log.info("Proxy settings not present via http.proxyHost and http.proxyPort. Please use those via -D in JVM settings.");
log.info("For now, we'll use the default MITRE proxy. If this doesn't match your environment, please configure your proxy!");
proxyHost = "gatekeeper-w.mitre.org";
proxyPort = 80;
}
}
private static final String DISCOVERY_INFO_KEY = DiscoveryInformation.class.getName();
private static final String ATTRIBUTE_LIST_KEY = "SPRING_SECURITY_OPEN_ID_ATTRIBUTES_FETCH_LIST";
//~ Instance fields ================================================================================================
protected final Log logger = LogFactory.getLog(getClass());
private final ConsumerManager consumerManager;
private final AxFetchListFactory attributesToFetchFactory;
public OpenId4JavaProxyConsumer() {
if(proxyHost != null) {
ProxyProperties proxyProps = new ProxyProperties();
proxyProps.setProxyHostName(proxyHost);
proxyProps.setProxyPort(proxyPort);
HttpClientFactory.setProxyProperties(proxyProps);
} // End if
this.consumerManager = new ConsumerManager();
OpenIDAttribute email = new OpenIDAttribute("email", "http://axschema.org/contact/email");
OpenIDAttribute email2 = new OpenIDAttribute("email2", "http://schema.openid.net/contact/email");
OpenIDAttribute name = new OpenIDAttribute("name", "http://axschema.org/namePerson");
OpenIDAttribute firstName = new OpenIDAttribute("firstName", "http://axschema.org/namePerson/first");
OpenIDAttribute lastName = new OpenIDAttribute("lastName", "http://axschema.org/namePerson/last");
final List<OpenIDAttribute> attributes = new ArrayList<OpenIDAttribute>();
attributes.add(email);
attributes.add(email2);
attributes.add(name);
attributes.add(firstName);
attributes.add(lastName);
this.attributesToFetchFactory = new AxFetchListFactory() {
private final List<OpenIDAttribute> fetchAttrs = Collections.unmodifiableList(attributes);
public List<OpenIDAttribute> createAttributeList(String identifier) {
return fetchAttrs;
}
};
}
//~ Methods ========================================================================================================
public String beginConsumption(HttpServletRequest req, String identityUrl, String returnToUrl, String realm)
throws OpenIDConsumerException {
List<DiscoveryInformation> discoveries;
try {
discoveries = consumerManager.discover(identityUrl);
} catch (DiscoveryException e) {
throw new OpenIDConsumerException("Error during discovery", e);
}
DiscoveryInformation information = consumerManager.associate(discoveries);
req.getSession().setAttribute(DISCOVERY_INFO_KEY, information);
AuthRequest authReq;
try {
authReq = consumerManager.authenticate(information, returnToUrl, realm);
logger.debug("Looking up attribute fetch list for identifier: " + identityUrl);
List<OpenIDAttribute> attributesToFetch = attributesToFetchFactory.createAttributeList(identityUrl);
if (!attributesToFetch.isEmpty()) {
req.getSession().setAttribute(ATTRIBUTE_LIST_KEY, attributesToFetch);
FetchRequest fetchRequest = FetchRequest.createFetchRequest();
for (OpenIDAttribute attr : attributesToFetch) {
if (logger.isDebugEnabled()) {
logger.debug("Adding attribute " + attr.getType() + " to fetch request");
}
fetchRequest.addAttribute(attr.getName(), attr.getType(), attr.isRequired(), attr.getCount());
}
authReq.addExtension(fetchRequest);
}
} catch (MessageException e) {
throw new OpenIDConsumerException("Error processing ConsumerManager authentication", e);
} catch (ConsumerException e) {
throw new OpenIDConsumerException("Error processing ConsumerManager authentication", e);
}
return authReq.getDestinationUrl(true);
}
public OpenIDAuthenticationToken endConsumption(HttpServletRequest request) throws OpenIDConsumerException {
// extract the parameters from the authentication response
// (which comes in as a HTTP request from the OpenID provider)
ParameterList openidResp = new ParameterList(request.getParameterMap());
// retrieve the previously stored discovery information
DiscoveryInformation discovered = (DiscoveryInformation) request.getSession().getAttribute(DISCOVERY_INFO_KEY);
if (discovered == null) {
throw new OpenIDConsumerException("DiscoveryInformation is not available. Possible causes are lost session or replay attack");
}
List<OpenIDAttribute> attributesToFetch = (List<OpenIDAttribute>) request.getSession().getAttribute(ATTRIBUTE_LIST_KEY);
request.getSession().removeAttribute(DISCOVERY_INFO_KEY);
request.getSession().removeAttribute(ATTRIBUTE_LIST_KEY);
// extract the receiving URL from the HTTP request
StringBuffer receivingURL = request.getRequestURL();
String queryString = request.getQueryString();
if (StringUtils.hasLength(queryString)) {
receivingURL.append("?").append(request.getQueryString());
}
// verify the response
VerificationResult verification;
try {
verification = consumerManager.verify(receivingURL.toString(), openidResp, discovered);
} catch (MessageException e) {
throw new OpenIDConsumerException("Error verifying openid response", e);
} catch (DiscoveryException e) {
throw new OpenIDConsumerException("Error verifying openid response", e);
} catch (AssociationException e) {
throw new OpenIDConsumerException("Error verifying openid response", e);
}
// examine the verification result and extract the verified identifier
Identifier verified = verification.getVerifiedId();
if (verified == null) {
Identifier id = discovered.getClaimedIdentifier();
return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.FAILURE,
id == null ? "Unknown" : id.getIdentifier(),
"Verification status message: [" + verification.getStatusMsg() + "]",
Collections.<OpenIDAttribute>emptyList());
}
List<OpenIDAttribute> attributes = fetchAxAttributes(verification.getAuthResponse(), attributesToFetch);
return new OpenIDAuthenticationToken(OpenIDAuthenticationStatus.SUCCESS, verified.getIdentifier(),
"some message", attributes);
}
List<OpenIDAttribute> fetchAxAttributes(Message authSuccess, List<OpenIDAttribute> attributesToFetch)
throws OpenIDConsumerException {
if (attributesToFetch == null || !authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
return Collections.emptyList();
}
logger.debug("Extracting attributes retrieved by attribute exchange");
List<OpenIDAttribute> attributes = Collections.emptyList();
try {
MessageExtension ext = authSuccess.getExtension(AxMessage.OPENID_NS_AX);
if (ext instanceof FetchResponse) {
FetchResponse fetchResp = (FetchResponse) ext;
attributes = new ArrayList<OpenIDAttribute>(attributesToFetch.size());
for (OpenIDAttribute attr : attributesToFetch) {
List<String> values = fetchResp.getAttributeValues(attr.getName());
if (!values.isEmpty()) {
OpenIDAttribute fetched = new OpenIDAttribute(attr.getName(), attr.getType(), values);
fetched.setRequired(attr.isRequired());
attributes.add(fetched);
}
}
}
} catch (MessageException e) {
throw new OpenIDConsumerException("Attribute retrieval failed", e);
}
if (logger.isDebugEnabled()) {
logger.debug("Retrieved attributes" + attributes);
}
return attributes;
}
}