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.annotation.PostConstruct;
27 import javax.servlet.FilterChain;
28 import javax.servlet.ServletException;
29 import javax.servlet.ServletInputStream;
30 import javax.servlet.ServletOutputStream;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33
34 import org.apache.catalina.connector.ClientAbortException;
35 import org.apache.logging.log4j.LogManager;
36 import org.apache.logging.log4j.Logger;
37 import org.codelibs.core.io.CopyUtil;
38 import org.codelibs.core.lang.StringUtil;
39 import org.codelibs.curl.Curl.Method;
40 import org.codelibs.curl.CurlRequest;
41 import org.codelibs.curl.CurlResponse;
42 import org.codelibs.fess.Constants;
43 import org.codelibs.fess.api.BaseApiManager;
44 import org.codelibs.fess.exception.FessSystemException;
45 import org.codelibs.fess.exception.WebApiException;
46 import org.codelibs.fess.mylasta.action.FessUserBean;
47 import org.codelibs.fess.util.ComponentUtil;
48 import org.codelibs.fess.util.ResourceUtil;
49 import org.lastaflute.web.servlet.request.RequestManager;
50 import org.lastaflute.web.servlet.session.SessionManager;
51
52 public class EsApiManager extends BaseApiManager {
53 private static final String ADMIN_SERVER = "/admin/server_";
54
55 private static final Logger logger = LogManager.getLogger(EsApiManager.class);
56
57 protected String[] acceptedRoles = new String[] { "admin" };
58
59 public EsApiManager() {
60 setPathPrefix(ADMIN_SERVER);
61 }
62
63 @PostConstruct
64 public void register() {
65 if (logger.isInfoEnabled()) {
66 logger.info("Load {}", this.getClass().getSimpleName());
67 }
68 ComponentUtil.getWebApiManagerFactory().add(this);
69 }
70
71 @Override
72 public boolean matches(final HttpServletRequest request) {
73 final String servletPath = request.getServletPath();
74 return servletPath.startsWith(pathPrefix);
75 }
76
77 @Override
78 public void process(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
79 throws IOException, ServletException {
80 final RequestManager requestManager = ComponentUtil.getRequestManager();
81 if (!requestManager.findUserBean(FessUserBean.class).map(user -> user.hasRoles(acceptedRoles)).orElse(Boolean.FALSE)) {
82 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized access: " + request.getServletPath());
83 return;
84 }
85
86 try {
87 getSessionManager().getAttribute(Constants.ES_API_ACCESS_TOKEN, String.class).ifPresent(token -> {
88 final String servletPath = request.getServletPath();
89 final String pathPrefix = ADMIN_SERVER + token;
90 if (!servletPath.startsWith(pathPrefix)) {
91 throw new WebApiException(HttpServletResponse.SC_FORBIDDEN, "Invalid access token.");
92 }
93 final String path;
94 final String value = servletPath.substring(pathPrefix.length());
95 if (!value.startsWith("/")) {
96 path = "/" + value;
97 } else {
98 path = value;
99 }
100 processRequest(request, response, path);
101 }).orElse(() -> {
102 throw new WebApiException(HttpServletResponse.SC_FORBIDDEN, "Invalid session.");
103 });
104 } catch (final WebApiException e) {
105 logger.warn("Web API access error.", e);
106 response.sendError(e.getStatusCode(), "Web API access error.");
107 }
108 }
109
110 protected void processRequest(final HttpServletRequest request, final HttpServletResponse response, final String path) {
111 if (StringUtil.isNotBlank(path)) {
112 final String lowerPath = path.toLowerCase(Locale.ROOT);
113 if (lowerPath.endsWith(".html")) {
114 response.setContentType("text/html;charset=utf-8");
115 } else if (lowerPath.endsWith(".txt")) {
116 response.setContentType("text/plain");
117 } else if (lowerPath.endsWith(".css")) {
118 response.setContentType("text/css");
119 }
120 }
121
122 if ("/_plugin".equals(path) || path.startsWith("/_plugin/")) {
123 processPluginRequest(request, response, path.replaceFirst("^/_plugin", StringUtil.EMPTY));
124 return;
125 }
126
127 final Method httpMethod = Method.valueOf(request.getMethod().toUpperCase(Locale.ROOT));
128 final CurlRequest curlRequest = ComponentUtil.getCurlHelper().request(httpMethod, path);
129
130 final String contentType = request.getHeader("Content-Type");
131 if (StringUtil.isNotEmpty(contentType)) {
132 curlRequest.header("Content-Type", contentType);
133 }
134
135 request.getParameterMap().entrySet().stream().forEach(entry -> {
136 if (entry.getValue().length > 1) {
137 curlRequest.param(entry.getKey(), String.join(",", entry.getValue()));
138 } else if (entry.getValue().length == 1) {
139 curlRequest.param(entry.getKey(), entry.getValue()[0]);
140 }
141 });
142 try (final CurlResponse curlResponse = curlRequest.onConnect((req, con) -> {
143 con.setDoOutput(true);
144 if (httpMethod != Method.GET && request.getContentLength() > 2) {
145 try (ServletInputStream in = request.getInputStream(); OutputStream out = con.getOutputStream()) {
146 CopyUtil.copy(in, out);
147 } catch (final IOException e) {
148 throw new WebApiException(HttpServletResponse.SC_BAD_REQUEST, e);
149 }
150 }
151 }).execute()) {
152
153 try (ServletOutputStream out = response.getOutputStream(); InputStream in = curlResponse.getContentAsStream()) {
154 response.setStatus(curlResponse.getHttpStatusCode());
155 writeHeaders(response);
156 CopyUtil.copy(in, out);
157 } catch (final ClientAbortException e) {
158 logger.debug("Client aborts this request.", e);
159 }
160 } catch (final Exception e) {
161 if (!(e.getCause() instanceof ClientAbortException)) {
162 throw new WebApiException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
163 }
164 logger.debug("Client aborts this request.", e);
165 }
166 }
167
168 protected void processPluginRequest(final HttpServletRequest request, final HttpServletResponse response, final String path) {
169 Path filePath = ResourceUtil.getSitePath(path.replaceAll("\\.\\.+", StringUtil.EMPTY).replaceAll("/+", "/").split("/"));
170 if (Files.isDirectory(filePath)) {
171 filePath = filePath.resolve("index.html");
172 }
173 if (Files.exists(filePath)) {
174 try (InputStream in = Files.newInputStream(filePath); ServletOutputStream out = response.getOutputStream()) {
175 response.setStatus(HttpServletResponse.SC_OK);
176 writeHeaders(response);
177 CopyUtil.copy(in, out);
178 } catch (final ClientAbortException e) {
179 logger.debug("Client aborts this request.", e);
180 } catch (final IOException e) {
181 logger.error("Failed to read {} from {}", path, filePath);
182 throw new WebApiException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
183 }
184 } else {
185 try {
186 writeHeaders(response);
187 response.sendError(HttpServletResponse.SC_NOT_FOUND, path + " is not found.");
188 } catch (final ClientAbortException e) {
189 logger.debug("Client aborts this request.", e);
190 } catch (final IOException e) {
191 logger.error("Failed to read {} from {}", path, filePath);
192 throw new WebApiException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
193 }
194 }
195 }
196
197 public void setAcceptedRoles(final String[] acceptedRoles) {
198 this.acceptedRoles = acceptedRoles;
199 }
200
201 public String getServerPath() {
202 return getSessionManager().getAttribute(Constants.ES_API_ACCESS_TOKEN, String.class).map(token -> ADMIN_SERVER + token)
203 .orElseThrow(() -> new FessSystemException("Cannot create an access token."));
204 }
205
206 public void saveToken() {
207 getSessionManager().setAttribute(Constants.ES_API_ACCESS_TOKEN, UUID.randomUUID().toString().replace("-", ""));
208 }
209
210 private SessionManager getSessionManager() {
211 return ComponentUtil.getComponent(SessionManager.class);
212 }
213
214 @Override
215 protected void writeHeaders(final HttpServletResponse response) {
216 ComponentUtil.getFessConfig().getApiDashboardResponseHeaderList().forEach(e -> response.setHeader(e.getFirst(), e.getSecond()));
217 }
218 }