001/* 002 * (C) Copyright 2015-2020 Nuxeo (http://nuxeo.com/) and others. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 * 016 * Contributors: 017 * bdelbosc 018 */ 019package org.nuxeo.elasticsearch.http.readonly; 020 021import java.io.IOException; 022 023import javax.validation.constraints.NotNull; 024import javax.ws.rs.Consumes; 025import javax.ws.rs.GET; 026import javax.ws.rs.POST; 027import javax.ws.rs.Path; 028import javax.ws.rs.PathParam; 029import javax.ws.rs.Produces; 030import javax.ws.rs.core.Context; 031import javax.ws.rs.core.MediaType; 032import javax.ws.rs.core.MultivaluedMap; 033import javax.ws.rs.core.UriInfo; 034 035import org.apache.logging.log4j.LogManager; 036import org.apache.logging.log4j.Logger; 037import org.json.JSONException; 038import org.nuxeo.ecm.core.api.NuxeoPrincipal; 039import org.nuxeo.ecm.webengine.model.WebObject; 040import org.nuxeo.ecm.webengine.model.impl.ModuleRoot; 041import org.nuxeo.elasticsearch.http.readonly.filter.DefaultSearchRequestFilter; 042import org.nuxeo.elasticsearch.http.readonly.filter.RequestValidator; 043import org.nuxeo.elasticsearch.http.readonly.filter.SearchRequestFilter; 044import org.nuxeo.elasticsearch.http.readonly.service.RequestFilterService; 045import org.nuxeo.runtime.api.Framework; 046 047/** 048 * Exposes a limited set of Read Only Elasticsearch REST API. 049 * 050 * @since 7.3 051 */ 052@Path("/es") 053@WebObject(type = "es") 054public class Main extends ModuleRoot { 055 private static final Logger log = LogManager.getLogger(Main.class); 056 057 private static final String DEFAULT_ES_BASE_URL = "http://localhost:9200/"; 058 059 private static final java.lang.String ES_BASE_URL_PROPERTY = "elasticsearch.httpReadOnly.baseUrl"; 060 061 private String esBaseUrl; 062 063 public Main() { 064 super(); 065 if (getContext() == null) { 066 } 067 log.debug("New instance of ES module"); 068 } 069 070 @GET 071 @Path("_search") 072 @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 073 @Produces(MediaType.APPLICATION_JSON) 074 public String searchWithPayload(@Context UriInfo uriInf, MultivaluedMap<String, String> formParams) 075 throws IOException, JSONException { 076 return doSearchWithPayload("_all", uriInf.getRequestUri().getRawQuery(), formParams.keySet().iterator().next()); 077 } 078 079 @POST 080 @Path("_search") 081 @Consumes(MediaType.APPLICATION_JSON) 082 @Produces(MediaType.APPLICATION_JSON) 083 public String searchWithPost(@Context UriInfo uriInf, String payload) throws IOException, JSONException { 084 return doSearchWithPayload("_all", uriInf.getRequestUri().getRawQuery(), payload); 085 } 086 087 @GET 088 @Path("{indices}/_search") 089 @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 090 @Produces(MediaType.APPLICATION_JSON) 091 public String searchWithPayload(@PathParam("indices") String indices, @Context UriInfo uriInf, 092 MultivaluedMap<String, String> formParams) throws IOException, JSONException { 093 return doSearchWithPayload(indices, uriInf.getRequestUri().getRawQuery(), 094 formParams.keySet().iterator().next()); 095 } 096 097 @POST 098 @Path("{indices}/_search") 099 @Consumes(MediaType.APPLICATION_JSON) 100 @Produces(MediaType.APPLICATION_JSON) 101 public String searchWithPost(@PathParam("indices") String indices, @Context UriInfo uriInf, String payload) 102 throws IOException, JSONException { 103 return doSearchWithPayload(indices, uriInf.getRequestUri().getRawQuery(), payload); 104 } 105 106 /** 107 * @deprecated since 11.4, types have been removed since Elasticsearch 7.x, use 108 * {@link #searchWithPayload(String, UriInfo, MultivaluedMap)} instead 109 */ 110 @GET 111 @Path("{indices}/{types}/_search") 112 @Consumes(MediaType.APPLICATION_FORM_URLENCODED) 113 @Produces(MediaType.APPLICATION_JSON) 114 @Deprecated(since = "11.4", forRemoval = true) 115 public String searchWithPayload(@PathParam("indices") String indices, @PathParam("types") String types, 116 @Context UriInfo uriInf, MultivaluedMap<String, String> formParams) throws IOException, JSONException { 117 return doSearchWithPayload(indices, types, uriInf.getRequestUri().getRawQuery(), 118 formParams.keySet().iterator().next()); 119 } 120 121 /** 122 * @deprecated since 11.4, types have been removed since Elasticsearch 7.x, use 123 * {@link #searchWithPost(String, UriInfo, String)} instead 124 */ 125 @POST 126 @Path("{indices}/{types}/_search") 127 @Consumes(MediaType.APPLICATION_JSON) 128 @Produces(MediaType.APPLICATION_JSON) 129 @Deprecated(since = "11.4", forRemoval = true) 130 public String searchWithPost(@PathParam("indices") String indices, @PathParam("types") String types, 131 @Context UriInfo uriInf, String payload) throws IOException, JSONException { 132 return doSearchWithPayload(indices, types, uriInf.getRequestUri().getRawQuery(), payload); 133 } 134 135 /** 136 * @deprecated since 11.4, types have been removed since Elasticsearch 7.x, use 137 * {@link #doSearchWithPayload(String, String, String)} instead 138 */ 139 @Deprecated(since = "11.4", forRemoval = true) 140 protected String doSearchWithPayload(String indices, String types, String rawQuery, String payload) 141 throws IOException, JSONException { 142 log.warn("Specifying types in search requests is deprecated."); 143 return doSearchWithPayload(indices, rawQuery, payload); 144 } 145 146 protected String doSearchWithPayload(String indices, String rawQuery, String payload) 147 throws IOException, JSONException { 148 RequestFilterService requestFilterService = Framework.getService(RequestFilterService.class); 149 try { 150 SearchRequestFilter req = requestFilterService.getRequestFilters(indices); 151 if (req == null) { 152 req = new DefaultSearchRequestFilter(); 153 } 154 req.init(getContext().getCoreSession(), indices, rawQuery, payload); 155 log.debug(req); 156 return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl(), req.getPayload()); 157 } catch (ReflectiveOperationException e) { 158 log.error("Error when trying to get Search Request Filter for indice {}", indices, e); 159 return null; 160 } 161 } 162 163 /** 164 * @since 11.4 165 */ 166 @GET 167 @Path("{indices}/_search") 168 @Produces(MediaType.APPLICATION_JSON) 169 public String searchWithUri(@PathParam("indices") String indices, @Context UriInfo uriInf) 170 throws IOException, JSONException { 171 DefaultSearchRequestFilter req = new DefaultSearchRequestFilter(); 172 req.init(getContext().getCoreSession(), indices, uriInf.getRequestUri().getRawQuery(), null); 173 log.debug(req); 174 return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl(), req.getPayload()); 175 } 176 177 /** 178 * @deprecated since 11.4, types have been removed since Elasticsearch 7.x, use 179 * {@link #searchWithUri(String, UriInfo)} instead 180 */ 181 @GET 182 @Path("{indices}/{types}/_search") 183 @Produces(MediaType.APPLICATION_JSON) 184 @Deprecated(since = "11.4", forRemoval = true) 185 public String searchWithUri(@PathParam("indices") String indices, @PathParam("types") String types, 186 @Context UriInfo uriInf) throws IOException, JSONException { 187 log.warn("Specifying types in search requests is deprecated."); 188 return searchWithUri(indices, uriInf); 189 } 190 191 @GET 192 @Path("{indices}/{documentId: [a-zA-Z0-9\\-]+}") 193 @Produces(MediaType.APPLICATION_JSON) 194 public String getDocument(@PathParam("indices") String indices, @PathParam("documentId") String documentId, 195 @Context UriInfo uriInf) throws IOException, JSONException { 196 NuxeoPrincipal principal = getPrincipal(); 197 RequestValidator validator = new RequestValidator(); 198 indices = validator.getIndices(indices); 199 validator.checkValidDocumentId(documentId); 200 DocRequestFilter req = new DocRequestFilter(principal, indices, documentId, 201 uriInf.getRequestUri().getRawQuery()); 202 log.debug(req); 203 if (!principal.isAdministrator()) { 204 String docAcl = HttpClient.get(getElasticsearchBaseUrl() + req.getCheckAccessUrl()); 205 validator.checkAccess(principal, docAcl); 206 } 207 return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl()); 208 } 209 210 /** 211 * @deprecated since 11.4, types have been removed since Elasticsearch 7.x, use 212 * {@link #getDocument(String, String, UriInfo)} instead 213 */ 214 @GET 215 @Path("{indices}/{types}/{documentId: [a-zA-Z0-9\\-]+}") 216 @Produces(MediaType.APPLICATION_JSON) 217 @Deprecated(since = "11.4", forRemoval = true) 218 public String getDocument(@PathParam("indices") String indices, @PathParam("types") String types, 219 @PathParam("documentId") String documentId, @Context UriInfo uriInf) throws IOException, JSONException { 220 log.warn("Specifying types in get document is deprecated."); 221 return getDocument(indices, documentId, uriInf); 222 } 223 224 protected String getElasticsearchBaseUrl() { 225 if (esBaseUrl == null) { 226 esBaseUrl = Framework.getProperty(ES_BASE_URL_PROPERTY, DEFAULT_ES_BASE_URL); 227 } 228 return esBaseUrl; 229 } 230 231 public @NotNull NuxeoPrincipal getPrincipal() { 232 NuxeoPrincipal principal = ctx.getPrincipal(); 233 if (principal == null) { 234 throw new IllegalArgumentException("No principal found"); 235 } 236 return principal; 237 } 238 239}