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 * Benoit Delbosc 018 */ 019package org.nuxeo.elasticsearch.http.readonly; 020 021import java.io.UnsupportedEncodingException; 022import java.net.URLDecoder; 023import java.util.HashMap; 024import java.util.Map; 025 026import javax.validation.constraints.NotNull; 027 028import org.json.JSONException; 029import org.nuxeo.ecm.core.api.CoreSession; 030import org.nuxeo.ecm.core.api.NuxeoPrincipal; 031import org.nuxeo.elasticsearch.http.readonly.filter.RequestValidator; 032import org.nuxeo.elasticsearch.http.readonly.filter.SearchRequestFilter; 033 034/** 035 * Rewrite an Elsaticsearch search request to add security filter. 036 * 037 * URI Search are turned into Request body search. 038 * 039 * @since 7.3 040 */ 041public abstract class AbstractSearchRequestFilterImpl implements SearchRequestFilter { 042 043 protected static final String MATCH_ALL = "{\"query\": {\"match_all\": {}}}"; 044 protected static final String QUERY_STRING = "{\"query\":{\"query_string\":{\"query\":\"%s\",\"default_field\":\"%s\",\"default_operator\":\"%s\"}}}"; 045 protected static final String BACKSLASH_MARKER = "_@@_"; 046 047 protected String payload; 048 protected String rawQuery; 049 /** @deprecated since 11.4, types have been removed since Elasticsearch 7.x */ 050 @Deprecated(since = "11.4", forRemoval = true) 051 protected String types; 052 protected String indices; 053 protected NuxeoPrincipal principal; 054 protected String url; 055 protected String filteredPayload; 056 057 public AbstractSearchRequestFilterImpl() { 058 059 } 060 061 @Override 062 public void init(CoreSession session, String indices, String rawQuery, String payload) { 063 RequestValidator validator = new RequestValidator(); 064 this.indices = validator.getIndices(indices); 065 this.principal = session.getPrincipal(); 066 this.rawQuery = rawQuery; 067 this.payload = payload; 068 if (payload == null && !principal.isAdministrator()) { 069 // here we turn the UriSearch query_string into a body search 070 extractPayloadFromQuery(); 071 } 072 } 073 074 @Override 075 public String getTypes() { 076 return types; 077 } 078 079 @Override 080 public String getIndices() { 081 return indices; 082 } 083 084 @Override 085 public String toString() { 086 if (payload == null || payload.isEmpty()) { 087 return "Uri Search: " + getUrl() + " user: " + principal; 088 } 089 try { 090 return "Body Search: " + getUrl() + " user: " + principal + " payload: " + getPayload(); 091 } catch (JSONException e) { 092 return "Body Search: " + getUrl() + " user: " + principal + " invalid JSON payload: " + e.getMessage(); 093 } 094 } 095 096 @Override 097 public @NotNull String getUrl() { 098 if (url == null) { 099 url = "/" + indices + "/_search"; 100 if (rawQuery != null) { 101 url += "?" + rawQuery; 102 } 103 } 104 return url; 105 } 106 107 @Override 108 public abstract String getPayload() throws JSONException; 109 110 protected Map<String, String> getQueryMap() { 111 String[] params = rawQuery.split("&"); 112 Map<String, String> map = new HashMap<>(); 113 for (String param : params) { 114 String name = param.split("=")[0]; 115 if (param.contains("=")) { 116 map.put(name, param.split("=")[1]); 117 } else { 118 map.put(name, ""); 119 } 120 } 121 return map; 122 } 123 124 protected void setRawQuery(Map<String, String> map) { 125 StringBuilder sb = new StringBuilder(); 126 for (Map.Entry<String, String> entry : map.entrySet()) { 127 if (sb.length() > 0) { 128 sb.append("&"); 129 } 130 if (entry.getValue().isEmpty()) { 131 sb.append(entry.getKey()); 132 } else { 133 sb.append(String.format("%s=%s", entry.getKey(), entry.getValue())); 134 } 135 } 136 rawQuery = sb.toString(); 137 } 138 139 protected void extractPayloadFromQuery() { 140 Map<String, String> qm = getQueryMap(); 141 String queryString = qm.remove("q"); 142 if (queryString == null) { 143 payload = MATCH_ALL; 144 return; 145 } 146 try { 147 queryString = URLDecoder.decode(queryString, "UTF-8"); 148 } catch (UnsupportedEncodingException e) { 149 throw new IllegalArgumentException("Invalid URI Search query_string encoding: " + e.getMessage()); 150 } 151 String defaultField = qm.remove("df"); 152 if (defaultField == null) { 153 defaultField = "_all"; 154 } 155 String defaultOperator = qm.remove("default_operator"); 156 if (defaultOperator == null) { 157 defaultOperator = "OR"; 158 } 159 payload = String.format(QUERY_STRING, queryString, defaultField, defaultOperator); 160 setRawQuery(qm); 161 } 162}