001package org.nuxeo.elasticsearch.http.readonly;/* 002 * (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Benoit Delbosc 016 */ 017 018import java.io.UnsupportedEncodingException; 019import java.net.URLDecoder; 020import java.util.HashMap; 021import java.util.Map; 022 023import javax.validation.constraints.NotNull; 024 025import org.json.JSONException; 026import org.nuxeo.ecm.core.api.CoreSession; 027import org.nuxeo.ecm.core.api.NuxeoPrincipal; 028import org.nuxeo.elasticsearch.http.readonly.filter.RequestValidator; 029import org.nuxeo.elasticsearch.http.readonly.filter.SearchRequestFilter; 030 031/** 032 * Rewrite an Elsaticsearch search request to add security filter. 033 * 034 * URI Search are turned into Request body search. 035 * 036 * @since 7.3 037 */ 038public abstract class AbstractSearchRequestFilterImpl implements SearchRequestFilter { 039 040 protected static final String MATCH_ALL = "{\"query\": {\"match_all\": {}}}"; 041 protected static final String QUERY_STRING = "{\"query\":{\"query_string\":{\"query\":\"%s\",\"default_field\":\"%s\",\"default_operator\":\"%s\"}}}"; 042 protected static final String BACKSLASH_MARKER = "_@@_"; 043 044 protected String payload; 045 protected String rawQuery; 046 protected String types; 047 protected String indices; 048 protected NuxeoPrincipal principal; 049 protected String url; 050 protected String filteredPayload; 051 052 public AbstractSearchRequestFilterImpl() { 053 054 } 055 056 public void init(CoreSession session, String indices, String types, String rawQuery, String payload) { 057 RequestValidator validator = new RequestValidator(); 058 this.indices = validator.getIndices(indices); 059 this.types = validator.getTypes(indices, types); 060 this.principal = (NuxeoPrincipal) session.getPrincipal(); 061 this.rawQuery = rawQuery; 062 this.payload = payload; 063 if (payload == null && !principal.isAdministrator()) { 064 // here we turn the UriSearch query_string into a body search 065 extractPayloadFromQuery(); 066 } 067 } 068 069 public String getTypes() { 070 return types; 071 } 072 073 public String getIndices() { 074 return indices; 075 } 076 077 @Override 078 public String toString() { 079 if (payload == null || payload.isEmpty()) { 080 return "Uri Search: " + getUrl() + " user: " + principal; 081 } 082 try { 083 return "Body Search: " + getUrl() + " user: " + principal + " payload: " + getPayload(); 084 } catch (JSONException e) { 085 return "Body Search: " + getUrl() + " user: " + principal + " invalid JSON payload: " + e.getMessage(); 086 } 087 } 088 089 public @NotNull String getUrl() { 090 if (url == null) { 091 url = "/" + indices + "/" + types + "/_search"; 092 if (rawQuery != null) { 093 url += "?" + rawQuery; 094 } 095 } 096 return url; 097 } 098 099 public abstract String getPayload() throws JSONException; 100 101 protected Map<String, String> getQueryMap() { 102 String[] params = rawQuery.split("&"); 103 Map<String, String> map = new HashMap<>(); 104 for (String param : params) { 105 String name = param.split("=")[0]; 106 if (param.contains("=")) { 107 map.put(name, param.split("=")[1]); 108 } else { 109 map.put(name, ""); 110 } 111 } 112 return map; 113 } 114 115 protected void setRawQuery(Map<String, String> map) { 116 StringBuilder sb = new StringBuilder(); 117 for (Map.Entry<String, String> entry : map.entrySet()) { 118 if (sb.length() > 0) { 119 sb.append("&"); 120 } 121 if (entry.getValue().isEmpty()) { 122 sb.append(entry.getKey()); 123 } else { 124 sb.append(String.format("%s=%s", entry.getKey(), entry.getValue())); 125 } 126 } 127 rawQuery = sb.toString(); 128 } 129 130 protected void extractPayloadFromQuery() { 131 Map<String, String> qm = getQueryMap(); 132 String queryString = qm.remove("q"); 133 if (queryString == null) { 134 payload = MATCH_ALL; 135 return; 136 } 137 try { 138 queryString = URLDecoder.decode(queryString, "UTF-8"); 139 } catch (UnsupportedEncodingException e) { 140 throw new IllegalArgumentException("Invalid URI Search query_string encoding: " + e.getMessage()); 141 } 142 String defaultField = qm.remove("df"); 143 if (defaultField == null) { 144 defaultField = "_all"; 145 } 146 String defaultOperator = qm.remove("default_operator"); 147 if (defaultOperator == null) { 148 defaultOperator = "OR"; 149 } 150 payload = String.format(QUERY_STRING, queryString, defaultField, defaultOperator); 151 setRawQuery(qm); 152 } 153}