View Javadoc
1   /*
2    * Copyright 2012-2017 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.api.es;
17  
18  import java.io.IOException;
19  import java.io.InputStream;
20  import java.io.OutputStream;
21  import java.nio.file.Files;
22  import java.nio.file.Path;
23  import java.util.Locale;
24  import java.util.UUID;
25  
26  import javax.servlet.FilterChain;
27  import javax.servlet.ServletException;
28  import javax.servlet.ServletInputStream;
29  import javax.servlet.ServletOutputStream;
30  import javax.servlet.http.HttpServletRequest;
31  import javax.servlet.http.HttpServletResponse;
32  
33  import org.apache.catalina.connector.ClientAbortException;
34  import org.codelibs.core.io.CopyUtil;
35  import org.codelibs.core.lang.StringUtil;
36  import org.codelibs.elasticsearch.runner.net.Curl.Method;
37  import org.codelibs.elasticsearch.runner.net.CurlRequest;
38  import org.codelibs.fess.Constants;
39  import org.codelibs.fess.api.BaseApiManager;
40  import org.codelibs.fess.exception.FessSystemException;
41  import org.codelibs.fess.exception.WebApiException;
42  import org.codelibs.fess.mylasta.action.FessUserBean;
43  import org.codelibs.fess.util.ComponentUtil;
44  import org.codelibs.fess.util.ResourceUtil;
45  import org.lastaflute.web.servlet.request.RequestManager;
46  import org.lastaflute.web.servlet.session.SessionManager;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  public class EsApiManager extends BaseApiManager {
51      private static final String ADMIN_SERVER = "/admin/server_";
52  
53      private static final Logger logger = LoggerFactory.getLogger(EsApiManager.class);
54  
55      protected String[] acceptedRoles = new String[] { "admin" };
56  
57      public EsApiManager() {
58          setPathPrefix(ADMIN_SERVER);
59      }
60  
61      @Override
62      public boolean matches(final HttpServletRequest request) {
63          final String servletPath = request.getServletPath();
64          if (servletPath.startsWith(pathPrefix)) {
65              final RequestManager requestManager = ComponentUtil.getRequestManager();
66              return requestManager.findUserBean(FessUserBean.class).map(user -> user.hasRoles(acceptedRoles)).orElse(Boolean.FALSE);
67          }
68          return false;
69      }
70  
71      @Override
72      public void process(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws IOException,
73              ServletException {
74          try {
75              getSessionManager().getAttribute(Constants.ES_API_ACCESS_TOKEN, String.class).ifPresent(token -> {
76                  final String servletPath = request.getServletPath();
77                  final String pathPrefix = ADMIN_SERVER + token;
78                  if (!servletPath.startsWith(pathPrefix)) {
79                      throw new WebApiException(HttpServletResponse.SC_FORBIDDEN, "Invalid access token.");
80                  }
81                  final String path;
82                  final String value = servletPath.substring(pathPrefix.length());
83                  if (!value.startsWith("/")) {
84                      path = "/" + value;
85                  } else {
86                      path = value;
87                  }
88                  processRequest(request, response, path);
89              }).orElse(() -> {
90                  throw new WebApiException(HttpServletResponse.SC_FORBIDDEN, "Invalid session.");
91              });
92          } catch (final WebApiException e) {
93              logger.debug("Web API access error. ", e);
94              e.sendError(response);
95          }
96      }
97  
98      protected void processRequest(final HttpServletRequest request, final HttpServletResponse response, final String path) {
99          if (StringUtil.isNotBlank(path)) {
100             final String lowerPath = path.toLowerCase(Locale.ROOT);
101             if (lowerPath.endsWith(".html")) {
102                 response.setContentType("text/html;charset=utf-8");
103             } else if (lowerPath.endsWith(".txt")) {
104                 response.setContentType("text/plain");
105             } else if (lowerPath.endsWith(".css")) {
106                 response.setContentType("text/css");
107             }
108         }
109 
110         if (path.equals("/_plugin") || path.startsWith("/_plugin/")) {
111             processPluginRequest(request, response, path.replaceFirst("^/_plugin", StringUtil.EMPTY));
112             return;
113         }
114 
115         final Method httpMethod = Method.valueOf(request.getMethod().toUpperCase(Locale.ROOT));
116         final CurlRequest curlRequest = new CurlRequest(httpMethod, ResourceUtil.getElasticsearchHttpUrl() + path);
117 
118         request.getParameterMap().entrySet().stream().forEach(entry -> {
119             if (entry.getValue().length > 1) {
120                 curlRequest.param(entry.getKey(), String.join(",", entry.getValue()));
121             } else if (entry.getValue().length == 1) {
122                 curlRequest.param(entry.getKey(), entry.getValue()[0]);
123             }
124         });
125         curlRequest.onConnect((req, con) -> {
126             con.setDoOutput(true);
127             if (httpMethod != Method.GET) {
128                 try (ServletInputStream in = request.getInputStream(); OutputStream out = con.getOutputStream()) {
129                     CopyUtil.copy(in, out);
130                 } catch (final IOException e) {
131                     throw new WebApiException(HttpServletResponse.SC_BAD_REQUEST, e);
132                 }
133             }
134         }).execute(con -> {
135             try (ServletOutputStream out = response.getOutputStream()) {
136                 try (InputStream in = con.getInputStream()) {
137                     response.setStatus(con.getResponseCode());
138                     CopyUtil.copy(in, out);
139                 } catch (final Exception e) {
140                     response.setStatus(con.getResponseCode());
141                     try (InputStream err = con.getErrorStream()) {
142                         CopyUtil.copy(err, out);
143                     }
144                 }
145             } catch (final ClientAbortException e) {
146                 logger.debug("Client aborts this request.", e);
147             } catch (final Exception e) {
148                 if (e.getCause() instanceof ClientAbortException) {
149                     logger.debug("Client aborts this request.", e);
150                 } else {
151                     throw new WebApiException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
152                 }
153             }
154         });
155     }
156 
157     protected void processPluginRequest(final HttpServletRequest request, final HttpServletResponse response, final String path) {
158         Path filePath = ResourceUtil.getSitePath(path.replaceAll("\\.\\.+", StringUtil.EMPTY).replaceAll("/+", "/").split("/"));
159         if (Files.isDirectory(filePath)) {
160             filePath = filePath.resolve("index.html");
161         }
162         if (Files.exists(filePath)) {
163             try (InputStream in = Files.newInputStream(filePath); ServletOutputStream out = response.getOutputStream()) {
164                 response.setStatus(HttpServletResponse.SC_OK);
165                 CopyUtil.copy(in, out);
166             } catch (final ClientAbortException e) {
167                 logger.debug("Client aborts this request.", e);
168             } catch (final IOException e) {
169                 logger.error("Failed to read " + path + " from " + filePath);
170                 throw new WebApiException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
171             }
172         } else {
173             try {
174                 response.sendError(HttpServletResponse.SC_NOT_FOUND, path + " is not found.");
175             } catch (final ClientAbortException e) {
176                 logger.debug("Client aborts this request.", e);
177             } catch (final IOException e) {
178                 logger.error("Failed to read " + path + " from " + filePath);
179                 throw new WebApiException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
180             }
181         }
182     }
183 
184     public void setAcceptedRoles(final String[] acceptedRoles) {
185         this.acceptedRoles = acceptedRoles;
186     }
187 
188     public String getServerPath() {
189         return getSessionManager().getAttribute(Constants.ES_API_ACCESS_TOKEN, String.class).map(token -> ADMIN_SERVER + token)
190                 .orElseThrow(() -> new FessSystemException("Cannot create an access token."));
191     }
192 
193     public void saveToken() {
194         getSessionManager().setAttribute(Constants.ES_API_ACCESS_TOKEN, UUID.randomUUID().toString().replace("-", ""));
195     }
196 
197     private SessionManager getSessionManager() {
198         return ComponentUtil.getComponent(SessionManager.class);
199     }
200 }