001package org.nuxeo.elasticsearch.http.readonly;
002
003/*
004 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors.
005 *
006 * All rights reserved. This program and the accompanying materials
007 * are made available under the terms of the GNU Lesser General Public License
008 * (LGPL) version 2.1 which accompanies this distribution, and is available at
009 * http://www.gnu.org/licenses/lgpl-2.1.html
010 *
011 * This library is distributed in the hope that it will be useful,
012 * but WITHOUT ANY WARRANTY; without even the implied warranty of
013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014 * Lesser General Public License for more details.
015 *
016 * Contributors:
017 *     bdelbosc
018 */
019
020import java.io.IOException;
021import java.security.Principal;
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.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
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.RequestValidator;
042import org.nuxeo.elasticsearch.http.readonly.filter.SearchRequestFilter;
043import org.nuxeo.elasticsearch.http.readonly.service.RequestFilterService;
044import org.nuxeo.elasticsearch.http.readonly.filter.DefaultSearchRequestFilter;
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 Log log = LogFactory.getLog(Main.class);
056    private static final String DEFAULT_ES_BASE_URL = "http://localhost:9200/";
057    private static final java.lang.String ES_BASE_URL_PROPERTY = "elasticsearch.httpReadOnly.baseUrl";
058    private String esBaseUrl;
059
060    public Main() {
061        super();
062        if (getContext() == null) {
063        }
064        if (log.isDebugEnabled()) {
065            log.debug("New instance of ES module");
066        }
067    }
068
069    @GET
070    @Path("_search")
071    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
072    @Produces(MediaType.APPLICATION_JSON)
073    public String searchWithPayload(@Context UriInfo uriInf, MultivaluedMap<String, String> formParams)
074            throws IOException, JSONException {
075        return doSearchWithPayload("_all", "_all", uriInf.getRequestUri().getRawQuery(),
076                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", "_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, "_all", 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, "_all", uriInf.getRequestUri().getRawQuery(), payload);
104    }
105
106    @GET
107    @Path("{indices}/{types}/_search")
108    @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
109    @Produces(MediaType.APPLICATION_JSON)
110    public String searchWithPayload(@PathParam("indices") String indices, @PathParam("types") String types,
111            @Context UriInfo uriInf, MultivaluedMap<String, String> formParams) throws IOException, JSONException {
112        return doSearchWithPayload(indices, types, uriInf.getRequestUri().getRawQuery(),
113                formParams.keySet().iterator().next());
114    }
115
116    @POST
117    @Path("{indices}/{types}/_search")
118    @Consumes(MediaType.APPLICATION_JSON)
119    @Produces(MediaType.APPLICATION_JSON)
120    public String searchWithPost(@PathParam("indices") String indices, @PathParam("types") String types,
121            @Context UriInfo uriInf, String payload) throws IOException, JSONException {
122        return doSearchWithPayload(indices, types, uriInf.getRequestUri().getRawQuery(), payload);
123    }
124
125    protected String doSearchWithPayload(String indices, String types, String rawQuery, String payload)
126            throws IOException, JSONException {
127        RequestFilterService requestFilterService = Framework.getService(RequestFilterService.class);
128        try {
129            SearchRequestFilter req = requestFilterService.getRequestFilters(indices);
130            if (req == null) {
131                req = new DefaultSearchRequestFilter();
132            }
133            req.init(getContext().getCoreSession(), indices, types, rawQuery, payload);
134            log.debug(req);
135            return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl(), req.getPayload());
136        } catch (InstantiationException | IllegalAccessException e) {
137            log.error("Error when trying to get Search Request Filter for indice " + indices, e);
138            return null;
139        }
140    }
141
142    @GET
143    @Path("{indices}/{types}/_search")
144    @Produces(MediaType.APPLICATION_JSON)
145    public String searchWithUri(@PathParam("indices") String indices, @PathParam("types") String types, @Context UriInfo uriInf)
146            throws IOException, JSONException {
147        DefaultSearchRequestFilter req = new DefaultSearchRequestFilter();
148        req.init(getContext().getCoreSession(), indices, types,
149                uriInf.getRequestUri().getRawQuery(), null);
150        log.debug(req);
151        return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl(), req.getPayload());
152    }
153
154    @GET
155    @Path("{indices}/{types}/{documentId: [a-zA-Z0-9\\-]+}")
156    @Produces(MediaType.APPLICATION_JSON)
157    public String getDocument(@PathParam("indices") String indices, @PathParam("types") String types,
158            @PathParam("documentId") String documentId, @Context UriInfo uriInf) throws IOException, JSONException {
159        NuxeoPrincipal principal = getPrincipal();
160        RequestValidator validator = new RequestValidator();
161        indices = validator.getIndices(indices);
162        types = validator.getTypes(indices, types);
163        validator.checkValidDocumentId(documentId);
164        DocRequestFilter req = new DocRequestFilter(principal, indices, types, documentId,
165                uriInf.getRequestUri().getRawQuery());
166        log.debug(req);
167        if (!principal.isAdministrator()) {
168            String docAcl = HttpClient.get(getElasticsearchBaseUrl() + req.getCheckAccessUrl());
169            validator.checkAccess(principal, docAcl);
170        }
171        return HttpClient.get(getElasticsearchBaseUrl() + req.getUrl());
172    }
173
174    protected String getElasticsearchBaseUrl() {
175        if (esBaseUrl == null) {
176            esBaseUrl = Framework.getProperty(ES_BASE_URL_PROPERTY, DEFAULT_ES_BASE_URL);
177        }
178        return esBaseUrl;
179    }
180
181    public @NotNull NuxeoPrincipal getPrincipal() {
182        Principal principal = ctx.getPrincipal();
183        if (principal == null) {
184            throw new IllegalArgumentException("No principal found");
185        }
186        return (NuxeoPrincipal) principal;
187    }
188
189}