View Javadoc
1   /*
2    * This file is part of ***  M y C o R e  ***
3    * See http://www.mycore.de/ for details.
4    *
5    * MyCoRe is free software: you can redistribute it and/or modify
6    * it under the terms of the GNU General Public License as published by
7    * the Free Software Foundation, either version 3 of the License, or
8    * (at your option) any later version.
9    *
10   * MyCoRe is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13   * GNU General Public License for more details.
14   *
15   * You should have received a copy of the GNU General Public License
16   * along with MyCoRe.  If not, see <http://www.gnu.org/licenses/>.
17   */
18  
19  package org.mycore.frontend.filter;
20  
21  import java.io.IOException;
22  import java.net.URISyntaxException;
23  import java.util.regex.Pattern;
24  
25  import org.apache.logging.log4j.LogManager;
26  import org.apache.logging.log4j.Logger;
27  import org.mycore.datamodel.ifs.MCRFileNodeServlet;
28  import org.mycore.frontend.MCRFrontendUtil;
29  import org.mycore.frontend.support.MCRSecureTokenV2;
30  
31  import jakarta.servlet.Filter;
32  import jakarta.servlet.FilterChain;
33  import jakarta.servlet.FilterConfig;
34  import jakarta.servlet.ServletException;
35  import jakarta.servlet.ServletRequest;
36  import jakarta.servlet.ServletResponse;
37  import jakarta.servlet.http.HttpServletRequest;
38  import jakarta.servlet.http.HttpServletResponse;
39  
40  /**
41   * Filter for {@link MCRFileNodeServlet} that uses {@link MCRSecureTokenV2} to check access to specific file types.
42   * <p>
43   * used properties:
44   * </p>
45   * <dl>
46   * <dt>MCR.SecureTokenV2.Extensions=mp4,mpeg4</dt>
47   * <dd>List of file extension. If empty, disables this filter</dd>
48   * <dt>MCR.SecureTokenV2.HashParameter=securetoken</dt>
49   * <dd>Name of request parameter that holds hash value</dd>
50   * <dt>MCR.SecureTokenV2.SharedSecret=mySharedSecret</dt>
51   * <dd>shared secret used to calculate secure token</dd>
52   * </dl>
53   * <code>contentPath</code> used for {@link MCRSecureTokenV2} is the {@link HttpServletRequest#getPathInfo() path info}
54   * without leading '/'.
55   *
56   * @author Thomas Scheffler (yagee)
57   */
58  public class MCRSecureTokenV2Filter implements Filter {
59  
60      private static final Logger LOGGER = LogManager.getLogger();
61  
62      private boolean filterEnabled = true;
63  
64      private String hashParameter;
65  
66      private String sharedSecret;
67  
68      /* (non-Javadoc)
69       * @see jakarta.servlet.Filter#init(jakarta.servlet.FilterConfig)
70       */
71      @Override
72      public void init(FilterConfig filterConfig) throws ServletException {
73          filterEnabled = MCRSecureTokenV2FilterConfig.isFilterEnabled();
74          hashParameter = MCRSecureTokenV2FilterConfig.getHashParameterName();
75          sharedSecret = MCRSecureTokenV2FilterConfig.getSharedSecret();
76      }
77  
78      @Override
79      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
80          throws IOException, ServletException {
81          if (filterEnabled) {
82              HttpServletRequest httpServletRequest = (HttpServletRequest) request;
83              String pathInfo = httpServletRequest.getPathInfo();
84              if (pathInfo != null && MCRSecureTokenV2FilterConfig.requireHash(pathInfo)) {
85                  if (!validateSecureToken(httpServletRequest)) {
86                      ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN);
87                      LOGGER.warn("Access to {} forbidden by secure token check.", pathInfo);
88                      return;
89                  }
90              }
91          }
92          chain.doFilter(request, response);
93      }
94  
95      private boolean validateSecureToken(HttpServletRequest httpServletRequest) throws ServletException {
96          String queryString = httpServletRequest.getQueryString();
97          if (queryString == null) {
98              LOGGER.warn("Request contains no parameters {}.", httpServletRequest.getRequestURL());
99          }
100         String hashValue = httpServletRequest.getParameter(hashParameter);
101         if (hashValue == null) {
102             LOGGER.warn("Could not find parameter '{}' in request {}.", hashParameter,
103                 httpServletRequest.getRequestURL().append('?').append(queryString));
104             return false;
105         }
106         String[] origParams = Pattern.compile("&").split(queryString);
107         String[] stripParams = new String[origParams.length - 1];
108         for (int i = origParams.length - 1; i > -1; i--) {
109             if (origParams[i].startsWith(hashParameter + "=")) {
110                 removeElement(origParams, stripParams, i);
111             }
112         }
113         MCRSecureTokenV2 token = new MCRSecureTokenV2(httpServletRequest.getPathInfo().substring(1),
114             MCRFrontendUtil.getRemoteAddr(httpServletRequest), sharedSecret, stripParams);
115         try {
116             LOGGER.info(token.toURI(MCRFrontendUtil.getBaseURL() + "servlets/MCRFileNodeServlet/", hashParameter));
117         } catch (URISyntaxException e) {
118             throw new ServletException(e);
119         }
120         return hashValue.equals(token.getHash());
121     }
122 
123     private static void removeElement(String[] src, String[] dest, int i) {
124         if (i == 0) {
125             return;
126         }
127         System.arraycopy(src, 0, dest, 0, i - 1);
128         if (i < src.length - 1) {
129             System.arraycopy(src, i + 1, dest, i, src.length - 1 - i);
130         }
131     }
132 
133     @Override
134     public void destroy() {
135     }
136 
137 }