1
2
3
4
5
6
7
8
9
10
11
12
13
14
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 }