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}