001/*
002 * (C) Copyright 2015 Nuxeo SA (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;
022import java.security.Principal;
023
024import javax.validation.constraints.NotNull;
025import javax.ws.rs.Consumes;
026import javax.ws.rs.GET;
027import javax.ws.rs.POST;
028import javax.ws.rs.Path;
029import javax.ws.rs.PathParam;
030import javax.ws.rs.Produces;
031import javax.ws.rs.core.Context;
032import javax.ws.rs.core.MediaType;
033import javax.ws.rs.core.MultivaluedMap;
034import javax.ws.rs.core.UriInfo;
035
036import org.apache.commons.logging.Log;
037import org.apache.commons.logging.LogFactory;
038import org.json.JSONException;
039import org.nuxeo.ecm.core.api.NuxeoPrincipal;
040import org.nuxeo.ecm.webengine.model.WebObject;
041import org.nuxeo.ecm.webengine.model.impl.ModuleRoot;
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.elasticsearch.http.readonly.filter.DefaultSearchRequestFilter;
046import org.nuxeo.runtime.api.Framework;
047
048/**
049 * Exposes a limited set of Read Only Elasticsearch REST API.
050 *
051 * @since 7.3
052 */
053@Path("/es")
054@WebObject(type = "es")
055public class Main extends ModuleRoot {
056    private static final Log log = LogFactory.getLog(Main.class);
057    private static final String DEFAULT_ES_BASE_URL = "http://localhost:9200/";
058    private static final java.lang.String ES_BASE_URL_PROPERTY = "elasticsearch.httpReadOnly.baseUrl";
059    private String esBaseUrl;
060
061    public Main() {
062        super();
063        if (getContext() == null) {
064        }
065        if (log.isDebugEnabled()) {
066            log.debug("New instance of ES module");
067        }
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", "_all", uriInf.getRequestUri().getRawQuery(),
077                formParams.keySet().iterator().next());
078    }
079
080    @POST
081    @Path("_search")
082    @Consumes(MediaType.APPLICATION_JSON)
083    @Produces(MediaType.APPLICATION_JSON)
084    public String searchWithPost(@Context UriInfo uriInf, String payload) throws IOException, JSONException {
085        return doSearchWithPayload("_all", "_all", uriInf.getRequestUri().getRawQuery(), payload);
086    }
087
088    @GET
089    @Path("{indices}/_search")
090    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
091    @Produces(MediaType.APPLICATION_JSON)
092    public String searchWithPayload(@PathParam("indices") String indices, @Context UriInfo uriInf,
093            MultivaluedMap<String, String> formParams) throws IOException, JSONException {
094        return doSearchWithPayload(indices, "_all", uriInf.getRequestUri().getRawQuery(),
095                formParams.keySet().iterator().next());
096    }
097
098    @POST
099    @Path("{indices}/_search")
100    @Consumes(MediaType.APPLICATION_JSON)
101    @Produces(MediaType.APPLICATION_JSON)
102    public String searchWithPost(@PathParam("indices") String indices, @Context UriInfo uriInf, String payload)
103            throws IOException, JSONException {
104        return doSearchWithPayload(indices, "_all", uriInf.getRequestUri().getRawQuery(), payload);
105    }
106
107    @GET
108    @Path("{indices}/{types}/_search")
109    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
110    @Produces(MediaType.APPLICATION_JSON)
111    public String searchWithPayload(@PathParam("indices") String indices, @PathParam("types") String types,
112            @Context UriInfo uriInf, MultivaluedMap<String, String> formParams) throws IOException, JSONException {
113        return doSearchWithPayload(indices, types, uriInf.getRequestUri().getRawQuery(),
114                formParams.keySet().iterator().next());
115    }
116
117    @POST
118    @Path("{indices}/{types}/_search")
119    @Consumes(MediaType.APPLICATION_JSON)
120    @Produces(MediaType.APPLICATION_JSON)
121    public String searchWithPost(@PathParam("indices") String indices, @PathParam("types") String types,
122            @Context UriInfo uriInf, String payload) throws IOException, JSONException {
123        return doSearchWithPayload(indices, types, uriInf.getRequestUri().getRawQuery(), payload);
124    }
125
126    protected String doSearchWithPayload(String indices, String types, String rawQuery, String payload)
127            throws IOException, JSONException {
128        RequestFilterService requestFilterService = Framework.getService(RequestFilterService.class);
129        try {
130            SearchRequestFilter req = requestFilterService.getRequestFilters(indices);
131            if (req == null) {
132                req = new DefaultSearchRequestFilter();
133            }
134            req.init(getContext().getCoreSession(), indices, types, rawQuery, payload);
135            log.debug(req);
136            return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl(), req.getPayload());
137        } catch (InstantiationException | IllegalAccessException e) {
138            log.error("Error when trying to get Search Request Filter for indice " + indices, e);
139            return null;
140        }
141    }
142
143    @GET
144    @Path("{indices}/{types}/_search")
145    @Produces(MediaType.APPLICATION_JSON)
146    public String searchWithUri(@PathParam("indices") String indices, @PathParam("types") String types, @Context UriInfo uriInf)
147            throws IOException, JSONException {
148        DefaultSearchRequestFilter req = new DefaultSearchRequestFilter();
149        req.init(getContext().getCoreSession(), indices, types,
150                uriInf.getRequestUri().getRawQuery(), null);
151        log.debug(req);
152        return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl(), req.getPayload());
153    }
154
155    @GET
156    @Path("{indices}/{types}/{documentId: [a-zA-Z0-9\\-]+}")
157    @Produces(MediaType.APPLICATION_JSON)
158    public String getDocument(@PathParam("indices") String indices, @PathParam("types") String types,
159            @PathParam("documentId") String documentId, @Context UriInfo uriInf) throws IOException, JSONException {
160        NuxeoPrincipal principal = getPrincipal();
161        RequestValidator validator = new RequestValidator();
162        indices = validator.getIndices(indices);
163        types = validator.getTypes(indices, types);
164        validator.checkValidDocumentId(documentId);
165        DocRequestFilter req = new DocRequestFilter(principal, indices, types, documentId,
166                uriInf.getRequestUri().getRawQuery());
167        log.debug(req);
168        if (!principal.isAdministrator()) {
169            String docAcl = HttpClient.get(getElasticsearchBaseUrl() + req.getCheckAccessUrl());
170            validator.checkAccess(principal, docAcl);
171        }
172        return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl());
173    }
174
175    protected String getElasticsearchBaseUrl() {
176        if (esBaseUrl == null) {
177            esBaseUrl = Framework.getProperty(ES_BASE_URL_PROPERTY, DEFAULT_ES_BASE_URL);
178        }
179        return esBaseUrl;
180    }
181
182    public @NotNull NuxeoPrincipal getPrincipal() {
183        Principal principal = ctx.getPrincipal();
184        if (principal == null) {
185            throw new IllegalArgumentException("No principal found");
186        }
187        return (NuxeoPrincipal) principal;
188    }
189
190}