/**
 * Created on 2007-4-14 上午12:54:50
 */
package org.openid4java.samples.consumerservlet;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openid4java.OpenIDException;
import org.openid4java.consumer.ConsumerException;
import org.openid4java.consumer.ConsumerManager;
import org.openid4java.consumer.InMemoryConsumerAssociationStore;
import org.openid4java.consumer.InMemoryNonceVerifier;
import org.openid4java.consumer.VerificationResult;
import org.openid4java.discovery.DiscoveryInformation;
import org.openid4java.discovery.Identifier;
import org.openid4java.message.AuthRequest;
import org.openid4java.message.AuthSuccess;
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.message.sreg.SRegMessage;
import org.openid4java.message.sreg.SRegRequest;
import org.openid4java.message.sreg.SRegResponse;

/**
 * Sample Consumer (OpenID Relying Party) implementation.
 */
public class ConsumerServlet extends javax.servlet.http.HttpServlet {
	private static final String OPENID_DISC = "openid-disc";

	private static final long serialVersionUID = -5998885243419513055L;

	private final Log log = LogFactory.getLog(this.getClass());

	private ServletContext context;

	private static ConsumerManager manager;

	// OpenID AX or SReg
	private static final String OPENID_ATT = "AX";

	public void init(ServletConfig config) throws ServletException {
		super.init(config);

		context = config.getServletContext();
		log.debug("context: " + context);

		try {
			// --- Forward proxy setup (only if needed) ---
			/*
			 * ProxyProperties proxyProps = new ProxyProperties();
			 * proxyProps.setProxyHostName("proxy.dpeb.de");
			 * proxyProps.setProxyPort(3128);
			 * HttpClientFactory.setProxyProperties(proxyProps);
			 */

			if (manager == null) {
				manager = new ConsumerManager();
			}

			manager.setConnectTimeout(6000);
			manager.setAssociations(new InMemoryConsumerAssociationStore());
			manager.setNonceVerifier(new InMemoryNonceVerifier(90000));
		} catch (ConsumerException e) {
			throw new ServletException(e);
		}
	}

	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		doPost(req, resp);
	}

	protected void doPost(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		if ("true".equals(req.getParameter("is_return"))) {
			processReturn(req, resp);
		} else {
			String identifier = req.getParameter("openid_identifier");
			if (identifier != null) {
				authRequest(identifier, req, resp);
			} else {
				this.getServletContext().getRequestDispatcher("/index.jsp")
						.forward(req, resp);
			}
		}
	}

	private void processReturn(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		Identifier identifier = this.verifyResponse(req);
		log.info("Consumer Servlet Identifier: " + identifier);
		if (identifier == null) {
			this.getServletContext().getRequestDispatcher("/index.jsp")
					.forward(req, resp);
		} else {
			req.setAttribute("identifier", identifier.getIdentifier());
			this.getServletContext().getRequestDispatcher("/return.jsp")
					.forward(req, resp);
		}
	}

	private void authRequest(String userSuppliedString,
			HttpServletRequest httpReq, HttpServletResponse httpResp)
			throws IOException, ServletException {
		// --- Placing the authentication request ---
		try {
			// Configure the return_to URL where your application will receive
			// the authentication responses from the OpenID provider
			// String returnToUrl = "http://example.com/openid";
			String returnToUrl = httpReq.getRequestURL().toString()
					+ "?is_return=true";
			log.info("Return URL: " + returnToUrl);

			// 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);

			// Store the discovery information in the user's session
			httpReq.getSession().setAttribute(OPENID_DISC, discovered);

			// Obtain a AuthRequest message to be sent to the OpenID provider
			AuthRequest authReq = manager.authenticate(discovered, returnToUrl);

			// SReg and AX example: fetching the attributes
			FetchRequest fetch = FetchRequest.createFetchRequest();
			SRegRequest sregReq = SRegRequest.createFetchRequest();

			if ("1".equals(httpReq.getParameter("nickname"))) {
				fetch.addAttribute("nickname",
						"http://schema.openid.net/contact/nickname", false);
				sregReq.addAttribute("nickname", false);
			}
			if ("1".equals(httpReq.getParameter("email"))) {
				fetch.addAttribute("email",
						"http://schema.openid.net/contact/email", false);
				sregReq.addAttribute("email", false);
			}
			if ("1".equals(httpReq.getParameter("fullname"))) {
				fetch.addAttribute("fullname",
						"http://schema.openid.net/contact/fullname", false);
				sregReq.addAttribute("fullname", false);
			}
			if ("1".equals(httpReq.getParameter("dob"))) {
				fetch.addAttribute("dob",
						"http://schema.openid.net/contact/dob", true);
				sregReq.addAttribute("dob", false);
			}
			if ("1".equals(httpReq.getParameter("gender"))) {
				fetch.addAttribute("gender",
						"http://schema.openid.net/contact/gender", false);
				sregReq.addAttribute("gender", false);
			}
			if ("1".equals(httpReq.getParameter("postcode"))) {
				fetch.addAttribute("postcode",
						"http://schema.openid.net/contact/postcode", false);
				sregReq.addAttribute("postcode", false);
			}
			if ("1".equals(httpReq.getParameter("country"))) {
				fetch.addAttribute("country",
						"http://schema.openid.net/contact/country", false);
				sregReq.addAttribute("country", false);
			}
			if ("1".equals(httpReq.getParameter("language"))) {
				fetch.addAttribute("language",
						"http://schema.openid.net/contact/language", false);
				sregReq.addAttribute("language", false);
			}
			if ("1".equals(httpReq.getParameter("timezone"))) {
				fetch.addAttribute("timezone",
						"http://schema.openid.net/contact/timezone", false);
				sregReq.addAttribute("timezone", false);
			}

			// Attach the extension to the authentication request
			if (OPENID_ATT.equalsIgnoreCase("SReg")) {
				log.info("RP using SReg...");
				if (!sregReq.getAttributes().isEmpty()) {
					authReq.addExtension(sregReq);
				}
			} else {
				log.info("RP using AX...");
				if (!fetch.getAttributes().isEmpty()) {
					authReq.addExtension(fetch);
				}
			}

			log.info("Redirect to OpenID Provider: "
					+ authReq.getDestinationUrl(true));

			if (!discovered.isVersion2()) {
				// Option 1: GET HTTP-redirect to the OpenID Provider endpoint
				// The only method supported in OpenID 1.x
				// redirect-URL usually limited ~2048 bytes
				log.info("Redirect GET HTTP: "
						+ authReq.getDestinationUrl(true));
				httpResp.sendRedirect(authReq.getDestinationUrl(true));
			} else {
				// Option 2: HTML FORM Redirection (Allows payloads > 2048
				// bytes)
				log.info("Redirect HTML FORM: "
						+ authReq.getDestinationUrl(true));
				RequestDispatcher dispatcher = getServletContext()
						.getRequestDispatcher("/formredirection.jsp");
				httpReq.setAttribute("parameterMap", httpReq.getParameterMap());
				httpReq.setAttribute("message", authReq);

				dispatcher.forward(httpReq, httpResp);
			}
		} catch (OpenIDException e) {
			// Present error to the user
			log.info("Error OpenID Return: " + e.toString());
		}
	}

	private Identifier verifyResponse(HttpServletRequest httpReq) {
		// --- Processing the authentication response ---
		try {
			// Extract the parameters from the authentication response
			// (which comes in as a HTTP request from the OpenID provider)
			ParameterList response = new ParameterList(httpReq
					.getParameterMap());

			// Retrieve the previously stored discovery information
			DiscoveryInformation discovered = (DiscoveryInformation) httpReq
					.getSession().getAttribute(OPENID_DISC);

			// Extract the receiving URL from the HTTP request
			StringBuffer receivingURL = httpReq.getRequestURL();
			String queryString = httpReq.getQueryString();
			if (queryString != null && queryString.length() > 0)
				receivingURL.append("?").append(httpReq.getQueryString());

			// Verify the response; ConsumerManager needs to be the same
			// (static) instance used to place the authentication request
			VerificationResult verification = manager.verify(receivingURL
					.toString(), response, discovered);

			// Examine the verification result and extract the verified
			// identifier
			Identifier verified = verification.getVerifiedId();

			if (verified != null) {
				AuthSuccess authSuccess = (AuthSuccess) verification
						.getAuthResponse();
				// SReg
				if (authSuccess.hasExtension(SRegMessage.OPENID_NS_SREG)) {
					MessageExtension ext = authSuccess
							.getExtension(SRegMessage.OPENID_NS_SREG);
					if (ext instanceof SRegResponse) {
						SRegResponse sregResp = (SRegResponse) ext;
						for (Iterator<?> iter = sregResp.getAttributeNames()
								.iterator(); iter.hasNext();) {
							String name = (String) iter.next();
							String value = sregResp.getParameterValue(name);
							httpReq.setAttribute(name, value);
						}
					}
				}

				// AX
				if (authSuccess.hasExtension(AxMessage.OPENID_NS_AX)) {
					FetchResponse fetchResp = (FetchResponse) authSuccess
							.getExtension(AxMessage.OPENID_NS_AX);

					// Example AX:
					// List emails = fetchResp.getAttributeValues("email");
					// String email = (String) emails.get(0);

					List<?> aliases = fetchResp.getAttributeAliases();
					for (Iterator<?> iter = aliases.iterator(); iter.hasNext();) {
						String alias = (String) iter.next();
						List<?> values = fetchResp.getAttributeValues(alias);
						if (values.size() > 0) {
							log.debug(alias + " : " + values.get(0));
							httpReq.setAttribute(alias, values.get(0));
						}
					}
				}

				return verified; // Success
			}
		} catch (OpenIDException e) {
			// Present error to the user
			log.info("Error OpenID Return: " + e.toString());
		}

		return null;
	}
}
