Java tutorial
package uk.ac.ebi.arrayexpress.servlets; /* * Copyright 2009-2014 European Molecular Biology Laboratory * * 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. * */ import org.apache.commons.lang.text.StrSubstitutor; import org.openid4java.OpenIDException; import org.openid4java.association.AssociationSessionType; 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.message.*; 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; import org.openid4java.util.HttpClientFactory; import org.openid4java.util.ProxyProperties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import uk.ac.ebi.arrayexpress.app.Application; import uk.ac.ebi.arrayexpress.app.ApplicationServlet; import uk.ac.ebi.arrayexpress.components.GenomeSpace; import uk.ac.ebi.arrayexpress.utils.CookieMap; import uk.ac.ebi.arrayexpress.utils.StringTools; import uk.ac.ebi.arrayexpress.utils.genomespace.GenomeSpaceMessageExtension; import uk.ac.ebi.arrayexpress.utils.genomespace.GenomeSpaceMessageExtensionFactory; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; /* * This servlet supports openId authentication to GenomeSpace * and experiment upload functionality */ public class GenomeSpaceAuthServlet extends ApplicationServlet { private static final long serialVersionUID = -3446967645131419250L; private transient final Logger logger = LoggerFactory.getLogger(getClass()); // These flags control which extension type to use. If neither is set the // the GenomeSpace Provider will create a custom extension and pass the // token, username, and email. private static final String USE_SREG = null; // can be "1.0" or "1.1" private static final boolean USE_AX = false; private ConsumerManager manager; private static final String GS_TOKEN_COOKIE = "gs-token"; private static final String GS_USERNAME_COOKIE = "gs-username"; private static final String GS_AUTH_MESSAGE_COOKIE = "gs-auth-message"; private static final String GS_RETURN_URL_COOKIE = "gs-auth-return-url"; // private static final String GS_XRD_URL = "https://identity.genomespace.org/identityServer/xrd.jsp"; // private static final String LOGOUT_RETURN_TO = "logout_return_to"; private static final String REFERER_HEADER = "Referer"; @Override public void init(ServletConfig config) throws ServletException { super.init(config); // --- Forward proxy setup (only if needed) --- ProxyProperties proxyProps = getProxyProperties(); if (proxyProps != null) { logger.debug("ProxyProperties: [{}]", proxyProps); HttpClientFactory.setProxyProperties(proxyProps); } try { Message.addExtensionFactory(GenomeSpaceMessageExtensionFactory.class); } catch (MessageException e) { throw new ServletException("Unable to register GenomeSpaceMessageExtensionFactory", e); } try { manager = new ConsumerManager(); } catch (Throwable e) { throw new ServletException(e); } manager.setAssociations(new InMemoryConsumerAssociationStore()); manager.setNonceVerifier(new InMemoryNonceVerifier(5000)); manager.setMinAssocSessEnc(AssociationSessionType.DH_SHA256); } @Override protected boolean canAcceptRequest(HttpServletRequest request, RequestType requestType) { return (requestType == RequestType.GET || requestType == RequestType.POST); } @Override protected void doRequest(HttpServletRequest request, HttpServletResponse response, RequestType requestType) throws ServletException, IOException { logRequest(logger, request, requestType); GenomeSpace gs = (GenomeSpace) getComponent("GenomeSpace"); if ("true".equals(request.getParameter("is_cancel"))) { // User clicked "cancel" on the openID login page. displayResult(response, getCookie(request, GS_RETURN_URL_COOKIE), null, null, "GenomeSpace login was cancelled"); } else if ("true".equals(request.getParameter("is_return"))) { // Handles the auth response callback from OpenID provider // OpenID protocol requires the response be verified, but that's ignored // since GenomeSpace actually uses a 2nd layer of authentication, beyond OpenID // server claiming that the user is authenticated. The token is all we care about String token = null; String username = null; String returnURL = getCookie(request, GS_RETURN_URL_COOKIE); boolean isTempPasswordLogin = false; // indicates login using temporary password // First we look for token in the queryParameters for (String pair : request.getQueryString().split("&")) { String[] parts = pair.split("="); if (parts[0].endsWith(GenomeSpaceMessageExtension.TOKEN_ALIAS) && parts.length == 2) { token = parts[1]; } else if (parts[0].endsWith(GenomeSpaceMessageExtension.USERNAME_ALIAS) && parts.length == 2) { username = parts[1]; } else if (parts[0].endsWith(GenomeSpaceMessageExtension.TEMP_LOGIN_ALIAS) && parts.length == 2) { isTempPasswordLogin = Boolean.valueOf(parts[1]); } } // If not found we look for token in the OpenId message extension. // Should only ever get here if bad login is given. if (token == null || token.length() == 0 || username == null || username.length() == 0) { // Does the OpenID verification try { ParameterList paramList = verifyProviderResponse(request); if (paramList != null) { if (paramList.hasParameter(GenomeSpaceMessageExtension.TOKEN_ALIAS) && paramList.hasParameter(GenomeSpaceMessageExtension.USERNAME_ALIAS) && paramList.hasParameter(GenomeSpaceMessageExtension.EMAIL_ALIAS) && paramList.hasParameter(GenomeSpaceMessageExtension.TEMP_LOGIN_ALIAS)) { token = paramList.getParameterValue(GenomeSpaceMessageExtension.TOKEN_ALIAS); username = paramList.getParameterValue(GenomeSpaceMessageExtension.USERNAME_ALIAS); isTempPasswordLogin = Boolean.valueOf( paramList.getParameterValue(GenomeSpaceMessageExtension.TEMP_LOGIN_ALIAS)); } } } catch (Exception e) { logger.error("Error while doing openID verification: ", e); } } if (token == null || token.length() == 0 || username == null || username.length() == 0) { logger.info("OpenID login failed"); displayResult(response, returnURL, null, null, "OpenID login failed"); } else { logger.info("OpenID login succeeded" + (isTempPasswordLogin ? ", used temp password" : "")); displayResult(response, returnURL, username, token, null); } } else { String returnURL = request.getHeader(REFERER_HEADER); if (null != returnURL) { setCookie(response, GS_RETURN_URL_COOKIE, returnURL); } authRequest(gs.getPropertyValue("prod.openIdUrl"), returnURL, request, response); } } private void displayResult(HttpServletResponse response, String returnURL, String username, String token, String message) throws ServletException, IOException { setCookie(response, GS_TOKEN_COOKIE, token); setCookie(response, GS_USERNAME_COOKIE, username); setCookie(response, GS_AUTH_MESSAGE_COOKIE, message); setCookie(response, GS_RETURN_URL_COOKIE, null); if (null != returnURL) { response.sendRedirect(returnURL); } else { try (PrintWriter out = response.getWriter()) { URL resource = Application.getInstance() .getResource("/WEB-INF/server-assets/templates/gs-auth-result.txt"); String template = StringTools.streamToString(resource.openStream(), "ISO-8859-1"); Map<String, String> params = new HashMap<>(); params.put("gs.token", null != token ? token : "<null>"); params.put("gs.username", null != username ? username : "<null>"); params.put("gs.message", null != message ? message : "<null>"); StrSubstitutor sub = new StrSubstitutor(params); out.print(sub.replace(template)); } catch (Exception x) { logger.error("Caught an exception:", x); } } } //@SuppressWarnings("rawtypes") private void authRequest(String claimedID, String returnURL, HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException, ServletException { try { // "return_to URL" needs to come back to this servlet // in order to verify the OP response. String returnToUrl = httpRequest.getRequestURL().toString() + "?is_return=true"; // Performs openId discovery, puts association into in-memory // store, and creates the auth request. List discoveries = manager.discover(claimedID); DiscoveryInformation discovered = manager.associate(discoveries); httpRequest.getSession().setAttribute("openid-disc", discovered); AuthRequest authReq = manager.authenticate(discovered, returnToUrl); // This block of code is needed only when you choose OpenId Attribute Exchange // to get the gs-token, gs-username, and email. It is included here to illustrate // AX usage, and also to test the GenomeSpace OpenId Provider. if (USE_AX) { // Add Attribute Exchange request for gs token and username FetchRequest fetch = FetchRequest.createFetchRequest(); fetch.addAttribute(GenomeSpaceMessageExtension.TOKEN_ALIAS, GenomeSpaceMessageExtension.URI + GenomeSpaceMessageExtension.TOKEN_ALIAS, true); fetch.addAttribute(GenomeSpaceMessageExtension.USERNAME_ALIAS, GenomeSpaceMessageExtension.URI + GenomeSpaceMessageExtension.USERNAME_ALIAS, true); fetch.addAttribute(GenomeSpaceMessageExtension.EMAIL_ALIAS, GenomeSpaceMessageExtension.URI + GenomeSpaceMessageExtension.EMAIL_ALIAS, true); authReq.addExtension(fetch); } // This block of code is needed only when you choose OpenId Simple Registration // to get the gs-token, gs-username, and email. It is included here to illustrate // SReg usage, and also to test the GenomeSpace OpenId Provider. if (USE_SREG != null) { // Add Simple Registration request for gs token and username. SReg implementation // disallows these names, so fudge it by using "gender" and "nickname" in place of // "gs-token" and "gs-username" respectively. GenomeSpace OpenId provider will // play along with this little fiction and return the expected values. SRegRequest sregReq = SRegRequest.createFetchRequest(); sregReq.addAttribute("gender", true); sregReq.addAttribute("nickname", true); sregReq.addAttribute("email", true); if (USE_SREG.equals("1.1")) { sregReq.setTypeUri(SRegMessage.OPENID_NS_SREG11); } authReq.addExtension(sregReq); } httpResponse.sendRedirect(authReq.getDestinationUrl(true)); } catch (org.openid4java.discovery.yadis.YadisException e) { logger.error("Error requesting OpenID authentication.", e); displayResult(httpResponse, returnURL, null, null, "OpenID Provider XRD " + claimedID + " is unavailable.<BR/>The internal error is <CODE>" + e.getMessage() + "</CODE><P/>"); } catch (OpenIDException e) { logger.error("Error requesting OpenId authentication.", e); displayResult(httpResponse, returnURL, null, null, "Error requesting OpenId authentication.<BR/>The internal error is <CODE>" + e.getMessage() + "</CODE><P/>"); } } /** * If OP's response verifies OK, returns the ParameterList containing the * GenomeSpace token and username, if they are found in the response, and an * empty ParameterList if not found. If the verify fails, returns null */ private ParameterList verifyProviderResponse(HttpServletRequest httpRequest) throws ServletException { try { // extract the parameters from the authentication response // (which comes in as a HTTP request from the OpenID provider) ParameterList response = new ParameterList(httpRequest.getParameterMap()); // retrieve the previously stored discovery information DiscoveryInformation discovered = (DiscoveryInformation) httpRequest.getSession() .getAttribute("openid-disc"); // extract the receiving URL from the HTTP request StringBuffer receivingURL = httpRequest.getRequestURL(); String queryString = httpRequest.getQueryString(); if (queryString != null && queryString.length() > 0) { receivingURL.append("?").append(queryString); } // Response must match what was used to place the authentication request. VerificationResult verification = manager.verify(receivingURL.toString(), response, discovered); AuthSuccess authSuccess; if (verification.getVerifiedId() == null) { // Client verification can fail despite the server doing a // successful login (possibly related to http->https redirect?) // This means the server will have set token and username // cookies in genomespace.org domain, which means GenomeSpace // will allow access. Ideally the client here should redirect // to the openId logout page to remove those cookies. logger.info("OpenID Client side verification failed"); // Only allow this on localhost for GS developer's convenience. boolean onLocalhost = false; try { URI uri = new URI(receivingURL.toString()); if ("localhost".equals(uri.getHost())) { onLocalhost = true; } } catch (Exception e) { logger.debug("Cannot parse receiving URL:", e); } if (onLocalhost) { String msg = "GenomeSpace login is permitted despite OpenID verification fail. This would be rejected if not on localhost."; logger.info(msg); } else { return null; } } else { logger.info("OpenID Client side verification succeeded"); } authSuccess = (AuthSuccess) verification.getAuthResponse(); return extractGenomeSpaceToken(authSuccess); } catch (OpenIDException e) { // present error to the user throw new ServletException(e); } } /** Extracts the GenomeSpace token from the OpenId message. */ @SuppressWarnings("rawtypes") private ParameterList extractGenomeSpaceToken(AuthSuccess authSuccess) throws MessageException { if (authSuccess == null) { return null; } ParameterList returnList = new ParameterList(); // Only one of these three blocks of code is necessary -- pick one that works // and remove the others. All are included here for illustrative purposes, // and also to test the GenomeSpace OpenId Provider. @SuppressWarnings("unchecked") Set<String> extAliases = authSuccess.getExtensions(); for (String extAlias : extAliases) { logger.debug("Found extension " + extAlias); switch (extAlias) { case SRegMessage.OPENID_NS_SREG: case SRegMessage.OPENID_NS_SREG11: MessageExtension ext = authSuccess.getExtension(extAlias); if (!(ext instanceof SRegResponse)) { logger.error("Cannot process extension " + extAlias); continue; } SRegResponse sregResp = (SRegResponse) ext; for (Object name : sregResp.getAttributeNames()) { String value = sregResp.getParameterValue((String) name); logger.info("Found SReg attribute " + name + ":" + value); // Maps SReg nickname -> GenomeSpace username if (name.equals("nickname")) { returnList.addParams(ParameterList .createFromKeyValueForm(GenomeSpaceMessageExtension.USERNAME_ALIAS + ":" + value)); } // Maps SReg gender -> GenomeSpace token if (name.equals("gender")) { returnList.addParams(ParameterList .createFromKeyValueForm(GenomeSpaceMessageExtension.TOKEN_ALIAS + ":" + value)); } if (name.equals("email")) { returnList.addParams(ParameterList .createFromKeyValueForm(GenomeSpaceMessageExtension.EMAIL_ALIAS + ":" + value)); } } break; case AxMessage.OPENID_NS_AX: FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension(extAlias); List aliases = fetchResp.getAttributeAliases(); for (Object name : aliases) { List values = fetchResp.getAttributeValues((String) name); if (values.size() > 0) { String keyValue = name + ":" + values.get(0); logger.info("Found AX attribute " + keyValue); returnList.addParams(ParameterList.createFromKeyValueForm(keyValue)); } } break; case GenomeSpaceMessageExtension.URI: MessageExtension msgExt = authSuccess.getExtension(extAlias); returnList.addParams(msgExt.getParameters()); break; default: logger.warn("No support for extension " + extAlias); } } return returnList; } private ProxyProperties getProxyProperties() { ProxyProperties proxyProps = null; String proxyHost = System.getProperty("http.proxyHost"); String proxyPort = System.getProperty("http.proxyPort"); logger.info("Checking system properties for proxy configuration: [{}:{}]", proxyHost, proxyPort); if (null != proxyHost && null != proxyPort) { proxyProps = new ProxyProperties(); proxyProps.setProxyHostName(proxyHost); proxyProps.setProxyPort(Integer.parseInt(proxyPort)); } return proxyProps; } private String getCookie(HttpServletRequest request, String name) { CookieMap cookies = new CookieMap(request.getCookies()); return cookies.containsKey(name) ? cookies.get(name).getValue() : null; } private void setCookie(HttpServletResponse response, String name, String value) { Cookie cookie = new Cookie(name, null != value ? value : ""); // if the value is null - delete cookie by expiring it cookie.setPath(getServletContext().getContextPath()); if (null == value) { cookie.setMaxAge(0); } response.addCookie(cookie); } }