diff options
Diffstat (limited to 'src/main/java/at/gv/util/filter/moaid')
-rw-r--r-- | src/main/java/at/gv/util/filter/moaid/AbstractGenericMOAIDAuthenticationServlet.java | 246 | ||||
-rw-r--r-- | src/main/java/at/gv/util/filter/moaid/MOAIDAuthenticationFilter.java | 259 |
2 files changed, 505 insertions, 0 deletions
diff --git a/src/main/java/at/gv/util/filter/moaid/AbstractGenericMOAIDAuthenticationServlet.java b/src/main/java/at/gv/util/filter/moaid/AbstractGenericMOAIDAuthenticationServlet.java new file mode 100644 index 0000000..f9edb35 --- /dev/null +++ b/src/main/java/at/gv/util/filter/moaid/AbstractGenericMOAIDAuthenticationServlet.java @@ -0,0 +1,246 @@ +/* + * Copyright 2011 Federal Chancellery Austria and + * Graz University of Technology + * + * 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 at.gv.util.filter.moaid; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.time.DateFormatUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.util.MiscUtil; +import at.gv.util.ToStringUtil; +import at.gv.util.WebAppUtil; +import at.gv.util.client.moaid.MOAIDClient; +import at.gv.util.client.moaid.MOAIDClientException; +import at.gv.util.config.EgovUtilConfiguration; +import at.gv.util.xsd.saml.assertion.AssertionType; +import at.gv.util.xsd.saml.protocol.ResponseType; + +/** + * @author <a href="mailto:arne.tauber@egiz.gv.at">Arne Tauber</a> + * @author <a href="mailto:thomas.knall@iaik.tugraz.at">Thomas Knall</a> + */ +public abstract class AbstractGenericMOAIDAuthenticationServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + public static final String REQUEST_ATTRIBUTE_ERROR_MESSAGE = "javax.servlet.error.message"; + //private boolean alreadyLogged = false; + + private HttpServletRequest request = null; + private HttpServletResponse response = null; + + private final Logger log = LoggerFactory.getLogger(AbstractGenericMOAIDAuthenticationServlet.class); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + this.doPost(req, resp); + } + + private void errorPage(HttpServletRequest request, HttpServletResponse response, String... args) { + this.request = request; + this.response = response; + log.debug("Forwarding to error page \"" + this.getErrorPage() + "\"."); + log.error("Messages for Errorpage (saved in request attribute \"" + REQUEST_ATTRIBUTE_ERROR_MESSAGE + "\"): " + ToStringUtil.toString(args)); + request.setAttribute(REQUEST_ATTRIBUTE_ERROR_MESSAGE, ToStringUtil.toString(args)); + try { + request.getRequestDispatcher(this.getErrorPage()).forward(request, response); + return; + } catch (Throwable t) { + log.error("Unexpected error (" + t.getMessage() + ") forwarding to error page \"" + this.getErrorPage() + "\"."); + } + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + log.debug("MOAIDLogin invoked."); + + this.request = request; + this.response = response; + + String artifact = request.getParameter("SAMLArtifact"); + if (MiscUtil.isEmpty(artifact)) { + this.errorPage(request, response, "SAMLArtifact post parameter must not be null or empty."); + return; + } + + String expectedTarget = this.getExpectedTarget(); + String target = request.getParameter("Target"); + if (MiscUtil.isNotEmpty(target)) { + if (MiscUtil.isNotEmpty(expectedTarget)) { + log.debug("Verifying target parameter."); + if (!expectedTarget.equals(target)) { + this.errorPage(request, response, "Transmitted target parameter does not match the expected target parameter value \"" + expectedTarget + "\"."); + return; + } + } + } else { + log.debug("No expected target parameter given. Maybe configured as wbpk application. Skipping target value evaluation."); + } + + log.debug("SAMLArtifact = \"" + artifact + "\"."); + + AssertionType assertion = null; + try { + MOAIDClient client = new MOAIDClient(this.getConfiguration()); + ResponseType moaidResponse = client.sendGetAuthenticationDataRequest(this.getAuthDataURL(), artifact); + if (!"Success".equals(moaidResponse.getStatus().getStatusCode().getValue().getLocalPart())) { + throw new MOAIDClientException("Wrong MOA-ID return code: " + moaidResponse.getStatus().getStatusCode().getValue().toString()); + } + assertion = moaidResponse.getAssertion().get(0); + } catch (MOAIDClientException c) { + String em = "Error retrieving authentication data (" + c.getMessage() + ")."; + log.error(em); + this.errorPage(request, response, em); + return; + } + + HttpSession session = request.getSession(false); + + if (session != null) { + + HashMap<String, Object> attributes = new HashMap<String,Object>(); + + Enumeration<String> enames = session.getAttributeNames(); + while (enames.hasMoreElements()) { + String name = enames.nextElement(); + if (!name.equals("JSESSIONID")) + attributes.put(name, session.getAttribute( name)); + } + +// // alle Notizen kopieren +// HashMap<String, Object> notes = new HashMap<String,Object>(); +// Iterator<String> nameit = session.getNoteN; +// while (nameit.hasNext()) { +// String name = nameit.next(); +// notes.put( name, session.getNote( name)); +// } +// + session.invalidate(); + + + session = request.getSession(true); + // Attribute und Notizen zurückkopieren + for (Entry<String,Object> et : attributes.entrySet()) { + session.setAttribute( et.getKey(), et.getValue()); + } + + } else + session = request.getSession(true); + + + log.debug("Using session " + session.getId() + ", created at " + DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(session.getCreationTime()) + "."); + Object authData = this.getAuthDataObject(assertion, request); + + if (authData == null) { + log.info("No auth data provided from implementing application. Denying access."); + response.sendError(HttpServletResponse.SC_FORBIDDEN, "Access is denied."); + return; + } + + log.debug("Authentication data = " + authData); + + session.setAttribute(this.getAuthDataSessionAttribute(), authData); + + + + String originalURL = null; + String authenticatedPage = StringUtils.trim(this.getAuthenticatedPage(request, originalURL)); + if (MiscUtil.isNotEmpty(authenticatedPage)) { + try { + // check if absolute URL is given + new URL(authenticatedPage); + // if absolute URL is given take it + } catch (MalformedURLException e) { + // if no absolute URL is given, prepend the webapp context + authenticatedPage = WebAppUtil.getBaseURL(request) + MiscUtil.removePrecedingSlash(authenticatedPage); + } + log.debug("User is authenticated. Authenticated page given. Redirecting to \"" + authenticatedPage + "\"."); + response.sendRedirect(response.encodeRedirectURL(authenticatedPage)); + return; + } else { + log.debug("No authenticated page given. Trying to find original url."); + String sessionAttribute = this.getStoredRequestURLSessionAttribute(); + if (MiscUtil.isNotEmpty(sessionAttribute)) { + log.debug("Fetching saved request url from session attribute \"" + sessionAttribute + "\"."); + originalURL = (String) session.getAttribute(sessionAttribute); + session.removeAttribute(sessionAttribute); + } + if (originalURL == null) { + log.warn("Unable to find saved request. Session seems to got lost."); + String sessionLostPage = this.getSessionLostPage(); + if (MiscUtil.isNotEmpty(sessionLostPage)) { + sessionLostPage = WebAppUtil.getBaseURL(request) + MiscUtil.removePrecedingSlash(sessionLostPage); + log.debug("Found failsafe page for redirection in case of session loss. Redirecting to \"" + sessionLostPage + "\"."); + response.sendRedirect(response.encodeRedirectURL(sessionLostPage)); + return; + } + this.errorPage(request, response, "Unable to find saved request."); + return; + } + log.debug("User is authenticated. Redirecting to original location \"" + originalURL + "\"."); + response.sendRedirect(response.encodeRedirectURL(originalURL)); + return; + } + } + + public HttpServletRequest getRequest() { + return request; + } + + public HttpServletResponse getResponse() { + return response; + } + + public abstract String getAuthenticatedPage(HttpServletRequest request, String savedRequestURL); + + public abstract String getAuthDataSessionAttribute(); + + public abstract String getStoredRequestURLSessionAttribute(); + + public abstract String getErrorPage(); + + public abstract Object getAuthDataObject(AssertionType samlAssertion); + + public Object getAuthDataObject(AssertionType samlAssertion, HttpServletRequest request) { + return this.getAuthDataObject(samlAssertion); + } + + public abstract String getAuthDataURL(); + + public abstract String getSessionLostPage(); + + public abstract String getExpectedTarget(); + + public abstract EgovUtilConfiguration getConfiguration(); + + + +} diff --git a/src/main/java/at/gv/util/filter/moaid/MOAIDAuthenticationFilter.java b/src/main/java/at/gv/util/filter/moaid/MOAIDAuthenticationFilter.java new file mode 100644 index 0000000..cb16b8f --- /dev/null +++ b/src/main/java/at/gv/util/filter/moaid/MOAIDAuthenticationFilter.java @@ -0,0 +1,259 @@ +/* + * Copyright 2011 Federal Chancellery Austria and + * Graz University of Technology + * + * 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 at.gv.util.filter.moaid; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.StringTokenizer; +import java.util.regex.Pattern; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.time.DateFormatUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import at.gv.util.MiscUtil; +import at.gv.util.ToStringUtil; +import at.gv.util.WebAppUtil; + +/** + * @author <a href="mailto:arne.tauber@egiz.gv.at">Arne Tauber</a> + * @author <a href="mailto:thomas.knall@iaik.tugraz.at">Thomas Knall</a> + */ +public class MOAIDAuthenticationFilter implements Filter { + + private final Logger log = LoggerFactory.getLogger(MOAIDAuthenticationFilter.class); + + public static final String USER_AUTH_DATA_ID = "AnyAuthDataObject:authenticatedUser"; + public static final String STORED_REQUEST_URL_ID = String.class.getName() + ":" + "storedRequestURL"; + + public static final String WEB_XML_INIT_PARAM_LOGIN_PAGE = "loginPage"; + public static final String WEB_XML_INIT_PARAM_ENABLED = "enabled"; + public static final String WEB_XML_INIT_PARAM_ERROR_PAGE = "errorPage"; + public static final String WEB_XML_INIT_PARAM_AUTHENTICATED_PAGE = "authenticatedPage"; // optional + public static final String WEB_XML_INIT_PARAM_SESSION_LOST_PAGE = "sessionLostPage"; // optional + public static final String WEB_XML_INIT_PARAM_ALLOWED_LIST = "allowedList"; + public static final String WEB_XML_INIT_PARAM_ALLOWED_REGEX = "allowed"; + + private static final String WEB_XML_INIT_PARAM_EXCLUDED_PAGES_DELIMITER = ","; + + private boolean enabled = true; + + private static String loginPage = null; + private boolean loginPageForward = true; + private static String errorPage = null; + private static String authenticatedPage = null; + private static String sessionLostPage = null; + + private static String[] excludedPages = null; + private static Pattern excludedRegEx = null; + + private HttpServletRequest servletRequest = null; + + public void destroy() { + log.trace("Shutting down " + this.getClass().getName() + "..."); + } + + public static String getErrorPage() { + return errorPage; + } + + public static String getAuthenticatedPage() { + return authenticatedPage; + } + + public static String getLoginPage() { + return loginPage; + } + + public static String getSessionLostPage() { + return sessionLostPage; + } + + public HttpServletRequest getHttpServletRequest() { + return this.servletRequest; + } + + private boolean isExcluded(String url) { + boolean excluded = false; + if (MiscUtil.isNotEmpty(excludedPages)) { + for (String candidate : excludedPages) { + if (StringUtils.upperCase(url).endsWith(StringUtils.upperCase(candidate))) { + excluded = true; + break; + } + } + } + if (excludedRegEx != null && !excluded) { + // log.debug("Trying to match regex \"{}\" with \"{}\".", + // excludedRegEx.toString(), url); + if (excludedRegEx.matcher(url).matches()) { + excluded = true; + } + } + log.debug("URL \"" + url + "\" is " + (excluded ? "" : "NOT ") + "excluded from filter."); + return excluded; + } + + public void doFilter(ServletRequest request, ServletResponse response, final FilterChain filterChain) throws IOException, ServletException { + if (this.enabled) { + log.debug("Applying " + this.getClass().getSimpleName() + "..."); + + HttpServletRequest httpServletRequest = (HttpServletRequest) request; + servletRequest = httpServletRequest; + HttpServletResponse httpServletResponse = (HttpServletResponse) response; + HttpSession session = httpServletRequest.getSession(); + log.debug("Using session " + session.getId() + ", created at " + DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT.format(session.getCreationTime()) + "."); + + Object anyObject = session.getAttribute(USER_AUTH_DATA_ID); + String requestURL = WebAppUtil.getRequestURLWithParameters(httpServletRequest, true); + log.trace("Request URL: " + requestURL); + if (anyObject == null && !this.isExcluded(requestURL)) { + Object dummyAuthData = this.provideDummyAuthenticationData(); + if (dummyAuthData != null) { + log.warn("Unable to find regular authentication data but dummy authentication data is provided."); + log.debug("Authentication data = " + dummyAuthData.toString()); + log.warn("Putting dummy authentication data into session."); + session.setAttribute(USER_AUTH_DATA_ID, dummyAuthData); + if (MiscUtil.isNotEmpty(getAuthenticatedPage())) { + if (loginPageForward) { + log.debug("Authenticated page is set. Forwarding to \"" + getAuthenticatedPage() + "\"."); + RequestDispatcher dispatcher = request.getRequestDispatcher(getAuthenticatedPage()); + dispatcher.forward(httpServletRequest, httpServletResponse); + } else { + log.debug("Authenticated page is set. Redirecting to \"" + getAuthenticatedPage() + "\"."); + httpServletResponse.sendRedirect(httpServletResponse.encodeRedirectURL(getAuthenticatedPage())); + } + return; + } + } else { + if (MiscUtil.isNotEmpty(getAuthenticatedPage())) { + log.debug("Unable to find authentication data. Authenticated page is given so there is no need to save original request url. " + (loginPageForward ? "Forwarding" : "Redirecting") + " to login page \"" + loginPage + "\"."); + } else { + log.debug("Unable to find authentication data. Storing request url and " + (loginPageForward ? "forwarding" : "redirecting") + " to login page \"" + loginPage + "\"."); + // TODO: save HttpServletRequest + // log.debug("new CustomHttpServletRequest(request).toString() = + // {}", new + // CustomHttpServletRequest(httpServletRequest).toString()); + session.setAttribute(STORED_REQUEST_URL_ID, requestURL); + } + if (loginPageForward) { + RequestDispatcher dispatcher = request.getRequestDispatcher(loginPage); + dispatcher.forward(httpServletRequest, httpServletResponse); + } else { + httpServletResponse.sendRedirect(httpServletResponse.encodeRedirectURL(loginPage)); + } + return; + } + + } + } + + filterChain.doFilter(request, response); + } + + public void init(FilterConfig filterConfig) throws ServletException { + log.debug("Starting init of " + this.getClass().getName() + "."); + + // enabled? + String enabledValue = StringUtils.trimToNull(filterConfig.getInitParameter(WEB_XML_INIT_PARAM_ENABLED)); + this.enabled = BooleanUtils.isNotFalse(BooleanUtils.toBooleanObject(enabledValue)); + + // login page + loginPage = StringUtils.trim(filterConfig.getInitParameter(WEB_XML_INIT_PARAM_LOGIN_PAGE)); + if (MiscUtil.isEmpty(loginPage)) { + throw new ServletException("ServletInitParameter \"" + WEB_XML_INIT_PARAM_LOGIN_PAGE + "\" must not be empty."); + } + loginPageForward = false; //!WebAppUtil.isFullQualifiedURL(loginPage); + + // error page + errorPage = StringUtils.trim(filterConfig.getInitParameter(WEB_XML_INIT_PARAM_ERROR_PAGE)); + if (MiscUtil.isEmpty(errorPage)) { + throw new ServletException("ServletInitParameter \"" + WEB_XML_INIT_PARAM_ERROR_PAGE + "\" must not be empty."); + } + + // session lost page + sessionLostPage = StringUtils.trim(filterConfig.getInitParameter(WEB_XML_INIT_PARAM_SESSION_LOST_PAGE)); + if (MiscUtil.isEmpty(sessionLostPage)) { + log.warn("ServletInitParameter \"" + WEB_XML_INIT_PARAM_SESSION_LOST_PAGE + + "\" is empty. This parameter defines a failsafe url the browser is redirected to if the original url has been lost due to session timeout."); + } + + // authenticated page + authenticatedPage = StringUtils.trim(filterConfig.getInitParameter(WEB_XML_INIT_PARAM_AUTHENTICATED_PAGE)); + if (MiscUtil.isEmpty(authenticatedPage)) { + log.debug("ServletInitParameter \"" + WEB_XML_INIT_PARAM_AUTHENTICATED_PAGE + + "\" is empty. This parameter defines the url the user is redirected to (instead of the original url) on successful authentication."); + } + String excluded = filterConfig.getInitParameter(WEB_XML_INIT_PARAM_ALLOWED_LIST); + ArrayList<String> excludedList = new ArrayList<String>(); + if (MiscUtil.isNotEmpty(excluded)) { + StringTokenizer tokenizer = new StringTokenizer(excluded, WEB_XML_INIT_PARAM_EXCLUDED_PAGES_DELIMITER); + while (tokenizer.hasMoreTokens()) { + String ex = StringUtils.trim(tokenizer.nextToken()); + if (MiscUtil.isNotEmpty(ex)) { + excludedList.add(ex); + } + } + } + excludedList.add(loginPage); + excludedList.add(errorPage); + excludedPages = new String[excludedList.size()]; + excludedPages = excludedList.toArray(excludedPages); + + String excludedRegExString = StringUtils.trim(filterConfig.getInitParameter(WEB_XML_INIT_PARAM_ALLOWED_REGEX)); + if (MiscUtil.isNotEmpty(excludedRegExString)) { + excludedRegEx = Pattern.compile(excludedRegExString); + } + + if (!this.enabled) { + log.info(this.getClass().getName() + " is DISABLED"); + } else { + log.debug(WEB_XML_INIT_PARAM_ENABLED + " = \"" + this.enabled + "\""); + } + log.debug(WEB_XML_INIT_PARAM_LOGIN_PAGE + " [" + (loginPageForward ? "forward" : "redirect") + "] = \"" + loginPage + "\""); + log.debug(WEB_XML_INIT_PARAM_AUTHENTICATED_PAGE + " = \"" + (MiscUtil.isNotEmpty(authenticatedPage) ? authenticatedPage : "<n/a>") + "\""); + log.debug(WEB_XML_INIT_PARAM_ERROR_PAGE + " = \"" + errorPage + "\""); + log.debug(WEB_XML_INIT_PARAM_SESSION_LOST_PAGE + " = \"" + (MiscUtil.isNotEmpty(sessionLostPage) ? sessionLostPage : "<n/a>") + "\""); + log.debug(WEB_XML_INIT_PARAM_ALLOWED_LIST + " = " + ToStringUtil.toString(excludedPages, ", ", "\"")); + log.debug(WEB_XML_INIT_PARAM_ALLOWED_REGEX + " = \"" + (excludedRegEx != null ? excludedRegEx.pattern() : "<n/a>") + "\""); + } + + /** + * May be overwritten in order to provide static authentication data during + * development process. + * + * @return some kind of dummy authentication data + */ + public Object provideDummyAuthenticationData() { + return null; + } + +} |