package er.openid;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.apache.log4j.Logger;
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.AuthSuccess;
import org.openid4java.message.MessageException;
import org.openid4java.message.MessageExtension;
import org.openid4java.message.Parameter;
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.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.util.HttpClientFactory;
import org.openid4java.util.ProxyProperties;
import com.webobjects.appserver.WOActionResults;
import com.webobjects.appserver.WOApplication;
import com.webobjects.appserver.WOContext;
import com.webobjects.appserver.WORedirect;
import com.webobjects.appserver.WORequest;
import com.webobjects.appserver.WOSession;
import com.webobjects.foundation.NSDictionary;
import er.extensions.appserver.ERXApplication;
import er.extensions.appserver.ERXRequest;
import er.extensions.foundation.ERXProperties;
/**
* EROpenIDManager is the primary interface to managing an OpenID connection.
*
* @property er.openid.proxyHostName the host name to use as a proxy (if necessary)
* @property er.openid.proxyPort the port to use as a proxy (if necessary)
* @property er.openid.formRedirectionPageName the name of the form redirection page to go to
* (defaults to EROFormRedirectionPage, should implement IEROFormRedirectionPage)
* @property er.openid.requireSecureReturnURL whether to require secure return URL; default is true
* @property er.openid.disableRealmVerifier whether to disable realm verifier. see OpenID 2.0 specification.
*
* @author mschrag
*/
public class EROpenIDManager {
public static final Logger log = Logger.getLogger(EROpenIDManager.class);
private static final String DISCOVERY_INFO_KEY = "openIDDiscoveryInfo";
private static EROpenIDManager _openIDManager;
private ConsumerManager _manager;
private EROpenIDManager.Delegate _delegate;
/**
* EROpenIDManager delegate
*/
public static interface Delegate {
/**
* Returns OpenID message extensions for the fetch request. These message extensions will be sent to the OP
* as requests for additional information.
*
* @param userSuppliedString the string the user supplied
* @param request the WORequest
* @param context the WOContext
* @return a FetchRequest
* @throws MessageException
*/
public List<MessageExtension> createFetchMessageExtensions(String userSuppliedString, WORequest request, WOContext context) throws MessageException;
/**
* Returns an OpenID fetch request.
*
* @param userSuppliedString the string the user supplied
* @param request the WORequest
* @param context the WOContext
* @return a FetchRequest
* @throws MessageException
* @deprecated Replaced by {@link #createFetchMessageExtensions(String, WORequest, WOContext)}
*/
@Deprecated
public MessageExtension createFetchRequest(String userSuppliedString, WORequest request, WOContext context) throws MessageException;
/**
* Called after a response is received from the OpenID server.
*
* @param verification the original verification result
* @param eroResponse the EROResponse wrapper
* @param request the WORequest
* @param context the WOContext
*/
public void responseReceived(VerificationResult verification, EROResponse eroResponse, WORequest request, WOContext context);
/**
* Returns the URL that the OpenID provider should come back to after authentication.
*
* @param request the WORequest
* @param context the WOContext
* @return the URL that the OpenID provider should come back to after authentication
*/
public String returnToUrl(WORequest request, WOContext context);
/**
* Gives the delegate an opportunity to rewrite the receiving URL from the return
* request after authentication. This may be necessary if you are using rewrite
* rules on your webserver, and the returnToUrl does not match the URL that
* WebObjects may see in the WORequest.
*
* @param request the WORequest
* @param context the WOContext
* @return the rewritten URL
*/
public String rewriteReceivingUrl(WORequest request, WOContext context);
}
/**
* The default delegate implementation.
*/
public static class DefaultDelegate implements EROpenIDManager.Delegate {
@SuppressWarnings("unused")
public List<MessageExtension> createFetchMessageExtensions(String userSuppliedString, WORequest request, WOContext context) throws MessageException {
MessageExtension fetchRequest = this.createFetchRequest(userSuppliedString, request, context);
ArrayList<MessageExtension> exts = new ArrayList<MessageExtension>();
if (fetchRequest != null)
exts.add(fetchRequest);
return exts;
}
@SuppressWarnings("unused")
@Deprecated
public MessageExtension createFetchRequest(String userSuppliedString, WORequest request, WOContext context) throws MessageException {
return null;
}
public void responseReceived(VerificationResult verification, EROResponse eroResponse, WORequest request, WOContext context) {
// DO NOTHING
}
public String returnToUrl(WORequest request, WOContext context) {
String returnToUrl;
boolean requireSecureReturnURL = ERXProperties.booleanForKeyWithDefault("er.openid.requireSecureReturnURL", true);
if (requireSecureReturnURL) {
returnToUrl = context.directActionURLForActionNamed("ERODirectAction/openIDResponse", null, true, true);
} else {
returnToUrl = context.directActionURLForActionNamed("ERODirectAction/openIDResponse", NSDictionary.EmptyDictionary);
}
EROpenIDManager.log.debug("Return to URL: " + returnToUrl);
return returnToUrl;
}
public String rewriteReceivingUrl(WORequest request, WOContext context) {
StringBuffer receivingUrlBuffer = new StringBuffer();
int serverPort = 0;
String serverPortStr = request._serverPort();
if (serverPortStr != null) {
serverPort = Integer.parseInt(serverPortStr);
}
request._completeURLPrefix(receivingUrlBuffer, ERXRequest.isRequestSecure(request), serverPort);
receivingUrlBuffer.append(request.uri());
return receivingUrlBuffer.toString();
}
}
/**
* A simple delegate implementation that requests the user's email address. This is for example purposes only and
* this code is not suitable for production use. To utilize this particular delegate, the client should ask the
* EROResponse for a list of MessageExtensions. Then the client should iterate through those and retrieve the
* specific extension parameter using methods appropriate to the type of the MessageExtension. For instance, if
* the MessageExtension is an instance of SRegResponse, then the email parameter could be retrieved by casting the
* MessageExtension to an SRegResponse and then using getAttributeValue("email") to retrieve the email.
*/
public static class EmailDelegate extends EROpenIDManager.DefaultDelegate {
@Override
public List<MessageExtension> createFetchMessageExtensions(String userSuppliedString, WORequest request, WOContext context) throws MessageException {
ArrayList<MessageExtension> exts = new ArrayList<MessageExtension>();
FetchRequest fetchRequest = FetchRequest.createFetchRequest();
fetchRequest.addAttribute("email-axschema", "http://axschema.org/contact/email", true);
fetchRequest.addAttribute("email-openid", "http://schema.openid.net/contact/email", true);
exts.add(fetchRequest);
SRegRequest sregRequest = SRegRequest.createFetchRequest();
sregRequest.addAttribute("email",true);
exts.add(sregRequest);
return exts;
}
}
/**
* Returns the singleton EROpenIDManager instance.
*
* @return the singleton EROpenIDManager instance
*/
public static synchronized EROpenIDManager manager() {
if (_openIDManager == null) {
try {
_openIDManager = new EROpenIDManager();
_openIDManager.setDelegate(new DefaultDelegate());
}
catch (ConsumerException e) {
throw new RuntimeException("Failed to create EROpenIDManager.", e);
}
}
return _openIDManager;
}
/**
* Constructs a new EROpenIDManager.
*
* @throws ConsumerException
*/
protected EROpenIDManager() throws ConsumerException {
_manager = new ConsumerManager();
boolean disableRealmVerifier = ERXProperties.booleanForKeyWithDefault("er.openid.disableRealmVerifier", false);
if (disableRealmVerifier) {
_manager.getRealmVerifier().setEnforceRpId(false);
EROpenIDManager.log.info("Disabling realm verifier.");
}
}
/**
* Set the delegate for this manager if you want to specify a custom
* FetchRequest.
*
* @param delegate the new delegate
*/
public void setDelegate(EROpenIDManager.Delegate delegate) {
_delegate = delegate;
}
/**
* Returns whether or not the given string looks like an OpenID auth string.
*
* @param userSuppliedString the string from the user
* @return whether or not the given string looks like an OpenID auth string
*/
public boolean isOpenIDAuth(String userSuppliedString) {
return userSuppliedString != null && userSuppliedString.toLowerCase().startsWith("http://");
}
/**
* Initiates the authentication request.
*
* @param userSuppliedString the string supplied by the user
* @param realm explicit realm to use for the authRequest. Allows nulls.
* @param request the WORequest
* @param context the WOContext
* @return the redirection action results
* @throws MessageException
* @throws DiscoveryException
* @throws ConsumerException
*/
public WOActionResults authRequest(String userSuppliedString, String realm, WORequest request, WOContext context) throws MessageException, DiscoveryException, ConsumerException {
WOSession session = context.session();
String proxyHostName = ERXProperties.stringForKey("er.openid.proxyHostName");
if (proxyHostName != null) {
int proxyPort = ERXProperties.intForKey("er.openid.proxyPort");
// --- Forward proxy setup (only if needed) ---
ProxyProperties proxyProps = new ProxyProperties();
proxyProps.setProxyHostName(proxyHostName);
proxyProps.setProxyPort(proxyPort);
HttpClientFactory.setProxyProperties(proxyProps);
}
// perform discovery on the user-supplied identifier
List discoveries = _manager.discover(userSuppliedString);
// attempt to associate with the OpenID provider
// and retrieve one service endpoint for authentication
DiscoveryInformation discovered = _manager.associate(discoveries);
// the next section will figure out where we go next, if anywhere.
WOActionResults results = null;
if (discovered != null)
{
// store the discovery information in the user's session
session.setObjectForKey(discovered, EROpenIDManager.DISCOVERY_INFO_KEY);
// configure the return_to URL where your application will receive
// the authentication responses from the OpenID provider
String returnToUrl = _delegate.returnToUrl(request, context);
// obtain a AuthRequest message to be sent to the OpenID provider
AuthRequest authReq = _manager.authenticate(discovered, returnToUrl, realm);
// add the message extensions
List<MessageExtension> exts = _delegate.createFetchMessageExtensions(userSuppliedString, request, context);
for (MessageExtension ext : exts) {
// attach the extension to the authentication request
EROpenIDManager.log.debug("Authentication request extension: " + ext);
authReq.addExtension(ext);
}
if (!discovered.isVersion2()) {
WORedirect redirect = new WORedirect(context);
String url = authReq.getDestinationUrl(true);
EROpenIDManager.log.debug("Request URL: " + url);
redirect.setUrl(url);
results = redirect;
}
else {
String formRedirectionPageName = ERXProperties.stringForKeyWithDefault("er.openid.formRedirectionPageName", EROFormRedirectionPage.class.getName());
EROFormRedirectionPage formRedirectionPage = (EROFormRedirectionPage)WOApplication.application().pageWithName(formRedirectionPageName, context);
formRedirectionPage.setParameters( authReq.getParameterMap() );
String url = authReq.getDestinationUrl(false);
EROpenIDManager.log.debug("Request URL: " + url);
formRedirectionPage.takeValueForKey(url, "redirectionUrl");
results = formRedirectionPage;
}
}
return results;
}
/**
* The callback for verifying the OpenID response.
*
* @param request the WORequest
* @param context the WOContext
* @return the OpenID response
* @throws MessageException
* @throws DiscoveryException
* @throws AssociationException
*/
public EROResponse verifyResponse(WORequest request, WOContext context) throws MessageException, DiscoveryException, AssociationException {
WOSession session = context.session();
// extract the parameters from the authentication response
// (which comes in as a HTTP request from the OpenID provider)
ParameterList responseParameters = new ParameterList();
Enumeration formValueKeyEnum = request.formValueKeys().objectEnumerator();
while (formValueKeyEnum.hasMoreElements()) {
String formValueKey = (String) formValueKeyEnum.nextElement();
String formValue = request.stringFormValueForKey(formValueKey);
responseParameters.set(new Parameter(formValueKey, formValue));
EROpenIDManager.log.debug("Response parameter: " + formValueKey + " => " + formValue);
}
// retrieve the previously stored discovery information
DiscoveryInformation discovered = (DiscoveryInformation) session.objectForKey(EROpenIDManager.DISCOVERY_INFO_KEY);
// extract the receiving URL from the HTTP request
String receivingUrl = _delegate.rewriteReceivingUrl(request, context);
// verify the response; ConsumerManager needs to be the same
// (static) instance used to place the authentication request
VerificationResult verification = _manager.verify(receivingUrl, responseParameters, discovered);
// examine the verification result and extract the verified identifier
FetchResponse fetchResponse = null;
List<MessageExtension> messageExtensions = new ArrayList<MessageExtension>();
Identifier identifier = verification.getVerifiedId();
if (identifier != null) {
AuthSuccess authSuccess = AuthSuccess.createAuthSuccess(responseParameters);
EROpenIDManager.log.debug("AuthSucess:" + authSuccess);
if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
MessageExtension ext = authSuccess.getExtension(AxMessage.OPENID_NS_AX);
messageExtensions.add(ext);
EROpenIDManager.log.debug("MessageExtension (AX):" + ext);
// handle backwards, deprecated compatibility
if (ext instanceof FetchResponse && fetchResponse == null)
fetchResponse = (FetchResponse)ext;
}
if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
MessageExtension ext = authSuccess.getExtension(SRegMessage.OPENID_NS_SREG);
messageExtensions.add(ext);
EROpenIDManager.log.debug("MessageExtension (SREG):" + ext);
}
if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG11)) {
MessageExtension ext = authSuccess.getExtension(SRegMessage.OPENID_NS_SREG11);
messageExtensions.add(ext);
EROpenIDManager.log.debug("MessageExtension (SREG11):" + ext);
}
}
EROResponse eroResponse = new EROResponse(identifier, fetchResponse, messageExtensions);
_delegate.responseReceived(verification, eroResponse, request, context);
return eroResponse;
}
}