/*
* (C) Copyright 2015 Nuxeo SA (http://nuxeo.com/) and others.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* Benoit Delbosc
*/
package org.nuxeo.elasticsearch.http.readonly;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import javax.validation.constraints.NotNull;
import org.json.JSONException;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.NuxeoPrincipal;
import org.nuxeo.elasticsearch.http.readonly.filter.RequestValidator;
import org.nuxeo.elasticsearch.http.readonly.filter.SearchRequestFilter;
/**
* Rewrite an Elsaticsearch search request to add security filter.
*
* URI Search are turned into Request body search.
*
* @since 7.3
*/
public abstract class AbstractSearchRequestFilterImpl implements SearchRequestFilter {
protected static final String MATCH_ALL = "{\"query\": {\"match_all\": {}}}";
protected static final String QUERY_STRING = "{\"query\":{\"query_string\":{\"query\":\"%s\",\"default_field\":\"%s\",\"default_operator\":\"%s\"}}}";
protected static final String BACKSLASH_MARKER = "_@@_";
protected String payload;
protected String rawQuery;
protected String types;
protected String indices;
protected NuxeoPrincipal principal;
protected String url;
protected String filteredPayload;
public AbstractSearchRequestFilterImpl() {
}
public void init(CoreSession session, String indices, String types, String rawQuery, String payload) {
RequestValidator validator = new RequestValidator();
this.indices = validator.getIndices(indices);
this.types = validator.getTypes(this.indices, types);
this.principal = (NuxeoPrincipal) session.getPrincipal();
this.rawQuery = rawQuery;
this.payload = payload;
if (payload == null && !principal.isAdministrator()) {
// here we turn the UriSearch query_string into a body search
extractPayloadFromQuery();
}
}
public String getTypes() {
return types;
}
public String getIndices() {
return indices;
}
@Override
public String toString() {
if (payload == null || payload.isEmpty()) {
return "Uri Search: " + getUrl() + " user: " + principal;
}
try {
return "Body Search: " + getUrl() + " user: " + principal + " payload: " + getPayload();
} catch (JSONException e) {
return "Body Search: " + getUrl() + " user: " + principal + " invalid JSON payload: " + e.getMessage();
}
}
public @NotNull String getUrl() {
if (url == null) {
url = "/" + indices + "/" + types + "/_search";
if (rawQuery != null) {
url += "?" + rawQuery;
}
}
return url;
}
public abstract String getPayload() throws JSONException;
protected Map<String, String> getQueryMap() {
String[] params = rawQuery.split("&");
Map<String, String> map = new HashMap<>();
for (String param : params) {
String name = param.split("=")[0];
if (param.contains("=")) {
map.put(name, param.split("=")[1]);
} else {
map.put(name, "");
}
}
return map;
}
protected void setRawQuery(Map<String, String> map) {
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, String> entry : map.entrySet()) {
if (sb.length() > 0) {
sb.append("&");
}
if (entry.getValue().isEmpty()) {
sb.append(entry.getKey());
} else {
sb.append(String.format("%s=%s", entry.getKey(), entry.getValue()));
}
}
rawQuery = sb.toString();
}
protected void extractPayloadFromQuery() {
Map<String, String> qm = getQueryMap();
String queryString = qm.remove("q");
if (queryString == null) {
payload = MATCH_ALL;
return;
}
try {
queryString = URLDecoder.decode(queryString, "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new IllegalArgumentException("Invalid URI Search query_string encoding: " + e.getMessage());
}
String defaultField = qm.remove("df");
if (defaultField == null) {
defaultField = "_all";
}
String defaultOperator = qm.remove("default_operator");
if (defaultOperator == null) {
defaultOperator = "OR";
}
payload = String.format(QUERY_STRING, queryString, defaultField, defaultOperator);
setRawQuery(qm);
}
}