From bee5dd259a4438d45ecd1bcc26dfba12875236d6 Mon Sep 17 00:00:00 2001 From: Thomas Lenz Date: Tue, 26 Jun 2018 11:03:48 +0200 Subject: initial commit --- .../idp/auth/modules/AbstractAuthServletTask.java | 220 +++++++++++++++++++++ .../impl/idp/auth/modules/ModuleRegistration.java | 151 ++++++++++++++ 2 files changed, 371 insertions(+) create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java create mode 100644 eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java (limited to 'eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules') diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java new file mode 100644 index 00000000..a421ff67 --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/AbstractAuthServletTask.java @@ -0,0 +1,220 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.modules; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.fileupload.FileItem; +import org.apache.commons.fileupload.FileItemFactory; +import org.apache.commons.fileupload.FileUploadException; +import org.apache.commons.fileupload.disk.DiskFileItemFactory; +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.apache.commons.lang3.ArrayUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; + +import at.gv.egiz.eaaf.core.api.IRequest; +import at.gv.egiz.eaaf.core.api.IRequestStorage; +import at.gv.egiz.eaaf.core.api.idp.IConfiguration; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.logging.IRevisionLogger; +import at.gv.egiz.eaaf.core.exceptions.TaskExecutionException; +import at.gv.egiz.eaaf.core.impl.idp.controller.AbstractAuthProtocolModulController; +import at.gv.egiz.eaaf.core.impl.idp.process.springweb.AbstractTask; +import at.gv.egiz.eaaf.core.impl.utils.DataURLBuilder; + +/** + * Task based counterpart to {@link AuthServlet}, providing the same utility methods (error handling, parameter parsing + * etc.).

The code has been taken from {@link AuthServlet}. + */ +public abstract class AbstractAuthServletTask extends AbstractTask { + private static final Logger log = LoggerFactory.getLogger(AbstractAuthServletTask.class); + + @Autowired(required=true) protected IRequestStorage requestStoreage; + @Autowired(required=true) protected IConfiguration authConfig; + + @Autowired protected IRevisionLogger revisionsLogger; + + protected static final String ERROR_CODE_PARAM = "errorid"; + + protected IRequest pendingReq = null; + + public abstract void execute(ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) throws TaskExecutionException; + + + protected final IRequest internalExecute(IRequest pendingReq, ExecutionContext executionContext, HttpServletRequest request, + HttpServletResponse response) throws TaskExecutionException { + //set pending-request object + this.pendingReq = pendingReq; + + //execute task specific action + execute(executionContext, request, response); + + //return pending-request object + return this.pendingReq; + } + + /** + * Redirect the authentication process to protocol specific finalization endpoint. + * + * @param pendingReq Actually processed protocol specific authentication request + * @param httpResp + */ + protected void performRedirectToProtocolFinialization(IRequest pendingReq, HttpServletResponse httpResp) { + performRedirectToItself(pendingReq, httpResp, AbstractAuthProtocolModulController.ENDPOINT_FINALIZEPROTOCOL); + + } + + /** + * Redirect the authentication process to IDP itself + * + * @param pendingReq Actually processed protocol specific authentication request + * @param httpResp + * @param idpEndPoint Servlet EndPoint that should receive the redirect + */ + protected void performRedirectToItself(IRequest pendingReq, HttpServletResponse httpResp, String idpEndPoint) { + String redirectURL = new DataURLBuilder().buildDataURL(pendingReq.getAuthURL(), + idpEndPoint, pendingReq.getPendingRequestId()); + + httpResp.setContentType("text/html"); + httpResp.setStatus(302); + httpResp.addHeader("Location", redirectURL); + log.debug("REDIRECT TO: " + redirectURL); + + } + + + /** + * Parses the request input stream for parameters, assuming parameters are + * encoded UTF-8 (no standard exists how browsers should encode them). + * + * @param req + * servlet request + * + * @return mapping parameter name -> value + * + * @throws IOException + * if parsing request parameters fails. + * + * @throws FileUploadException + * if parsing request parameters fails. + */ + protected Map getParameters(HttpServletRequest req) throws IOException, + FileUploadException { + + Map parameters = new HashMap(); + + if (ServletFileUpload.isMultipartContent(req)) { + // request is encoded as mulitpart/form-data + FileItemFactory factory = new DiskFileItemFactory(); + ServletFileUpload upload = null; + upload = new ServletFileUpload(factory); + List items = null; + items = upload.parseRequest(req); + for (int i = 0; i < items.size(); i++) { + FileItem item = (FileItem) items.get(i); + if (item.isFormField()) { + // Process only form fields - no file upload items + parameters.put(item.getFieldName(), item.getString("UTF-8")); + + //log requests on trace + if (log.isTraceEnabled()) { + String logString = item.getString("UTF-8"); + + // TODO use RegExp + String startS = ""; + String endS = "urn:publicid:gv.at:baseid"; + String logWithMaskedBaseid = logString; + int start = logString.indexOf(startS); + if (start > -1) { + int end = logString.indexOf(endS); + if (end > -1) { + logWithMaskedBaseid = logString.substring(0, start); + logWithMaskedBaseid += startS; + logWithMaskedBaseid += "xxxxxxxxxxxxxxxxxxxxxxxx"; + logWithMaskedBaseid += logString.substring(end, + logString.length()); + } + } + + log.debug("Processed multipart/form-data request parameter: \nName: " + + item.getFieldName() + + "\nValue: " + + logWithMaskedBaseid); + } + + } + } + } + + else { + Iterator> requestParamIt = req.getParameterMap().entrySet().iterator(); + while (requestParamIt.hasNext()) { + Entry entry = requestParamIt.next(); + String key = entry.getKey(); + String[] values = entry.getValue(); + // take the last value from the value array since the legacy code above also does it this way + parameters.put(key, ArrayUtils.isEmpty(values) ? null : values[values.length-1]); + } + + } + + return parameters; + } + + /** + * Reads bytes up to a delimiter, consuming the delimiter. + * + * @param in + * input stream + * @param delimiter + * delimiter character + * @return String constructed from the read bytes + * @throws IOException + */ + protected String readBytesUpTo(InputStream in, char delimiter) + throws IOException { + ByteArrayOutputStream bout = new ByteArrayOutputStream(); + boolean done = false; + int b; + while (!done && (b = in.read()) >= 0) { + if (b == delimiter) + done = true; + else + bout.write(b); + } + return bout.toString(); + } + + /** + * Adds a parameter to a URL. + * + * @param url + * the URL + * @param paramname + * parameter name + * @param paramvalue + * parameter value + * @return the URL with parameter added + */ + protected static String addURLParameter(String url, String paramname, + String paramvalue) { + String param = paramname + "=" + paramvalue; + if (url.indexOf("?") < 0) + return url + "?" + param; + else + return url + "&" + param; + } +} diff --git a/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java new file mode 100644 index 00000000..cb4a055a --- /dev/null +++ b/eaaf_core/src/main/java/at/gv/egiz/eaaf/core/impl/idp/auth/modules/ModuleRegistration.java @@ -0,0 +1,151 @@ +/******************************************************************************* + *******************************************************************************/ +package at.gv.egiz.eaaf.core.impl.idp.auth.modules; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.ServiceLoader; + +import javax.annotation.PostConstruct; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.core.io.Resource; + +import at.gv.egiz.eaaf.core.api.idp.auth.modules.AuthModule; +import at.gv.egiz.eaaf.core.api.idp.process.ExecutionContext; +import at.gv.egiz.eaaf.core.api.idp.process.ProcessEngine; +import at.gv.egiz.eaaf.core.impl.idp.process.ProcessDefinitionParserException; + +/** + * This class handles registering modules. The modules are detected either with + * the ServiceLoader mechanism or via Spring. All detected modules are ranked + * according to their priority. + */ +public class ModuleRegistration { + + private static ModuleRegistration instance = new ModuleRegistration(); + + private List priorizedModules = new ArrayList<>(); + + @Autowired + private ApplicationContext ctx; + + @Autowired + private ProcessEngine processEngine; + + private Logger log = LoggerFactory.getLogger(getClass()); + + public static ModuleRegistration getInstance() { + return instance; + } + + private ModuleRegistration() { + } + + @PostConstruct + private void init() { + // load modules via the ServiceLoader + initServiceLoaderModules(); + + // load modules via Spring + initSpringModules(); + + // order modules according to their priority + sortModules(); + } + + /** + * Discovers modules which use the ServiceLoader mechanism. + */ + private void initServiceLoaderModules() { + log.info("Looking for auth modules."); + ServiceLoader loader = ServiceLoader.load(AuthModule.class); + Iterator modules = loader.iterator(); + while (modules.hasNext()) { + AuthModule module = modules.next(); + log.info("Detected module {}", module.getClass().getName()); + registerModuleProcessDefinitions(module); + priorizedModules.add(module); + } + } + + /** + * Discovers modules which use Spring. + */ + private void initSpringModules() { + log.debug("Discovering Spring modules."); + Map modules = ctx.getBeansOfType(AuthModule.class); + for (AuthModule module : modules.values()) { + registerModuleProcessDefinitions(module); + priorizedModules.add(module); + } + } + + /** + * Registers the resource uris for the module. + * + * @param module + * the module. + */ + private void registerModuleProcessDefinitions(AuthModule module) { + for (String uri : module.getProcessDefinitions()) { + Resource resource = ctx.getResource(uri); + if (resource.isReadable()) { + log.info("Registering process definition '{}'.", uri); + try (InputStream processDefinitionInputStream = resource.getInputStream()) { + processEngine.registerProcessDefinition(processDefinitionInputStream); + } catch (IOException e) { + log.error("Process definition '{}' could NOT be read.", uri, e); + } catch (ProcessDefinitionParserException e) { + log.error("Error while parsing process definition '{}'", uri, e); + } + } else { + log.error("Process definition '{}' cannot be read.", uri); + } + } + } + + /** + * Order the modules in descending order according to their priority. + */ + private void sortModules() { + Collections.sort(priorizedModules, new Comparator() { + @Override + public int compare(AuthModule thisAuthModule, AuthModule otherAuthModule) { + int thisOrder = thisAuthModule.getPriority(); + int otherOrder = otherAuthModule.getPriority(); + return (thisOrder < otherOrder ? 1 : (thisOrder == otherOrder ? 0 : -1)); + } + }); + } + + /** + * Returns the process description id of the first process, in the highest ranked + * module, which is able to work with the given execution context. + * + * @param context + * the {@link ExecutionContext}. + * @return the process id or {@code null} + */ + public String selectProcess(ExecutionContext context) { + for (AuthModule module : priorizedModules) { + String id = module.selectProcess(context); + if (StringUtils.isNotEmpty(id)) { + log.debug("Process with id '{}' selected, for context '{}'.", id, context); + return id; + } + } + log.info("No process is able to handle context '{}'.", context); + return null; + } +} -- cgit v1.2.3