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 * 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 protected String types; 050 protected String indices; 051 protected NuxeoPrincipal principal; 052 protected String url; 053 protected String filteredPayload; 054 055 public AbstractSearchRequestFilterImpl() { 056 057 } 058 059 public void init(CoreSession session, String indices, String types, String rawQuery, String payload) { 060 RequestValidator validator = new RequestValidator(); 061 this.indices = validator.getIndices(indices); 062 this.types = validator.getTypes(this.indices, types); 063 this.principal = (NuxeoPrincipal) session.getPrincipal(); 064 this.rawQuery = rawQuery; 065 this.payload = payload; 066 if (payload == null && !principal.isAdministrator()) { 067 // here we turn the UriSearch query_string into a body search 068 extractPayloadFromQuery(); 069 } 070 } 071 072 public String getTypes() { 073 return types; 074 } 075 076 public String getIndices() { 077 return indices; 078 } 079 080 @Override 081 public String toString() { 082 if (payload == null || payload.isEmpty()) { 083 return "Uri Search: " + getUrl() + " user: " + principal; 084 } 085 try { 086 return "Body Search: " + getUrl() + " user: " + principal + " payload: " + getPayload(); 087 } catch (JSONException e) { 088 return "Body Search: " + getUrl() + " user: " + principal + " invalid JSON payload: " + e.getMessage(); 089 } 090 } 091 092 public @NotNull String getUrl() { 093 if (url == null) { 094 url = "/" + indices + "/" + types + "/_search"; 095 if (rawQuery != null) { 096 url += "?" + rawQuery; 097 } 098 } 099 return url; 100 } 101 102 public abstract String getPayload() throws JSONException; 103 104 protected Map<String, String> getQueryMap() { 105 String[] params = rawQuery.split("&"); 106 Map<String, String> map = new HashMap<>(); 107 for (String param : params) { 108 String name = param.split("=")[0]; 109 if (param.contains("=")) { 110 map.put(name, param.split("=")[1]); 111 } else { 112 map.put(name, ""); 113 } 114 } 115 return map; 116 } 117 118 protected void setRawQuery(Map<String, String> map) { 119 StringBuilder sb = new StringBuilder(); 120 for (Map.Entry<String, String> entry : map.entrySet()) { 121 if (sb.length() > 0) { 122 sb.append("&"); 123 } 124 if (entry.getValue().isEmpty()) { 125 sb.append(entry.getKey()); 126 } else { 127 sb.append(String.format("%s=%s", entry.getKey(), entry.getValue())); 128 } 129 } 130 rawQuery = sb.toString(); 131 } 132 133 protected void extractPayloadFromQuery() { 134 Map<String, String> qm = getQueryMap(); 135 String queryString = qm.remove("q"); 136 if (queryString == null) { 137 payload = MATCH_ALL; 138 return; 139 } 140 try { 141 queryString = URLDecoder.decode(queryString, "UTF-8"); 142 } catch (UnsupportedEncodingException e) { 143 throw new IllegalArgumentException("Invalid URI Search query_string encoding: " + e.getMessage()); 144 } 145 String defaultField = qm.remove("df"); 146 if (defaultField == null) { 147 defaultField = "_all"; 148 } 149 String defaultOperator = qm.remove("default_operator"); 150 if (defaultOperator == null) { 151 defaultOperator = "OR"; 152 } 153 payload = String.format(QUERY_STRING, queryString, defaultField, defaultOperator); 154 setRawQuery(qm); 155 } 156}