From 87bd502390a628def3ee0568d6038b56adad33e9 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 20 Dec 2024 11:31:04 -0700 Subject: [PATCH 1/6] effectively disable querySanitize() --- src/main/java/org/ecocean/OpenSearch.java | 31 ++--------------------- 1 file changed, 2 insertions(+), 29 deletions(-) diff --git a/src/main/java/org/ecocean/OpenSearch.java b/src/main/java/org/ecocean/OpenSearch.java index e20c100950..b82f2fc9cb 100644 --- a/src/main/java/org/ecocean/OpenSearch.java +++ b/src/main/java/org/ecocean/OpenSearch.java @@ -653,35 +653,8 @@ public static boolean getPermissionsNeeded(Shepherd myShepherd) { public static JSONObject querySanitize(JSONObject query, User user, Shepherd myShepherd) throws IOException { if ((query == null) || (user == null)) throw new IOException("empty query or user"); - // do not add permissions clause when we are admin, as user has no restriction - if (user.isAdmin(myShepherd)) return query; - // if (!Collaboration.securityEnabled("context0")) TODO do we want to allow everything searchable? -/* - JSONObject permClause = new JSONObject("{\"bool\": {\"should\": [] }}"); - "{\"bool\": {\"should\": [{\"term\": {\"publiclyReadable\": true}}, {\"term\": {\"viewUsers\": \"" - + user.getId() + "\"}} ] }}"); - */ - JSONArray shouldArr = new JSONArray(); - shouldArr.put(new JSONObject("{\"term\": {\"publiclyReadable\": true}}")); - shouldArr.put(new JSONObject("{\"term\": {\"submitterUserId\": \"" + user.getId() + - "\"}}")); - shouldArr.put(new JSONObject("{\"term\": {\"viewUsers\": \"" + user.getId() + "\"}}")); - JSONObject pshould = new JSONObject(); - pshould.put("should", shouldArr); - JSONObject permClause = new JSONObject(); - permClause.put("bool", pshould); - JSONObject newQuery = new JSONObject(query.toString()); - try { - JSONArray filter = newQuery.getJSONObject("query").getJSONObject("bool").getJSONArray( - "filter"); - filter.put(permClause); - } catch (Exception ex) { - System.out.println( - "OpenSearch.querySanitize() failed to find placement for permissions in query=" + - query + "; cause: " + ex); - throw new IOException("unable to find placement for permissions clause in query"); - } - return newQuery; + // see issue 958 - now we let query pass as-is for anyone, results are scrubbed later XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX FIXME add method ref + return query; } public static boolean indexingActive() { From 193aaef3afc37f08eb681ab8f66042a448b7a007 Mon Sep 17 00:00:00 2001 From: erinz2020 Date: Fri, 20 Dec 2024 19:15:29 +0000 Subject: [PATCH 2/6] update encounters' background color based on filed 'access' --- frontend/src/components/DataTable.jsx | 44 ++++++++++++++------------ frontend/src/pages/EncounterSearch.jsx | 14 +++++++- 2 files changed, 36 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/DataTable.jsx b/frontend/src/components/DataTable.jsx index dc2cc2219b..41508087f1 100644 --- a/frontend/src/components/DataTable.jsx +++ b/frontend/src/components/DataTable.jsx @@ -17,27 +17,6 @@ const customStyles = { }, }; -const conditionalRowStyles = (theme) => [ - { - when: (row) => row.tableID % 2 === 0, - style: { - backgroundColor: "#ffffff", // Light gray color - "&:hover": { - backgroundColor: theme?.primaryColors?.primary300 || "#e0f7fa", - }, - }, - }, - { - when: (row) => row.tableID % 2 !== 0, - style: { - backgroundColor: "#f2f2f2", // White color - "&:hover": { - backgroundColor: theme?.primaryColors?.primary300 || "#e0f7fa", - }, - }, - }, -]; - const MyDataTable = ({ title = "", columnNames = [], @@ -52,6 +31,7 @@ const MyDataTable = ({ style = {}, tabs = [], isLoading = false, + extraStyles = [], onSelectedRowsChange = () => {}, onRowClicked = () => {}, }) => { @@ -61,6 +41,28 @@ const MyDataTable = ({ const perPageOptions = [10, 20, 30, 40, 50]; const intl = useIntl(); + const conditionalRowStyles = (theme) => + [ + { + when: (row) => row.tableID % 2 === 0, + style: { + backgroundColor: "#ffffff", // Light gray color + "&:hover": { + backgroundColor: theme?.primaryColors?.primary300 || "#e0f7fa", + }, + }, + }, + { + when: (row) => row.tableID % 2 !== 0, + style: { + backgroundColor: "#f2f2f2", // White color + "&:hover": { + backgroundColor: theme?.primaryColors?.primary300 || "#e0f7fa", + }, + }, + }, + ].concat(extraStyles); + const wrappedColumns = useMemo( () => columnNames.map((col) => { diff --git a/frontend/src/pages/EncounterSearch.jsx b/frontend/src/pages/EncounterSearch.jsx index d5bb7c2603..36ee7cc0eb 100644 --- a/frontend/src/pages/EncounterSearch.jsx +++ b/frontend/src/pages/EncounterSearch.jsx @@ -9,6 +9,7 @@ import { useSearchParams } from "react-router-dom"; import { useIntl } from "react-intl"; import axios from "axios"; import { get } from "lodash"; +import ThemeColorContext from "../ThemeColorProvider"; const columns = [ { name: "INDIVIDUAL_ID", selector: "individualDisplayName" }, @@ -31,6 +32,7 @@ export default function EncounterSearch() { const [paramsFormFilters, setParamsFormFilters] = useState([]); const paramsObject = Object.fromEntries(searchParams.entries()) || {}; const [formFilters, setFormFilters] = useState([]); + const theme = React.useContext(ThemeColorContext); const regularQuery = searchParams.get("regularQuery"); @@ -272,10 +274,20 @@ export default function EncounterSearch() { onPerPageChange={queryID ? setSearchIdResultPerPage1 : setPerPage} setSort={setSort} loading={false} + extraStyles={[ + { + when: (row) => row.access === "none", + style: { + backgroundColor: theme?.statusColors?.yellow100 || "#fff3cd", + "&:hover": { + backgroundColor: theme?.primaryColors?.primary300 || "#e0f7fa", + }, + }, + }, + ]} onRowClicked={(row) => { const url = `/encounters/encounter.jsp?number=${row.id}`; window.open(url, "_blank"); - // window.location.href = url; }} onSelectedRowsChange={(selectedRows) => { console.log("Selected Rows: ", selectedRows); From 36c395d5174501e0cc946dfb45588ef0d01d9033 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 20 Dec 2024 12:24:03 -0700 Subject: [PATCH 3/6] opensearchAccess() --- src/main/java/org/ecocean/Encounter.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/main/java/org/ecocean/Encounter.java b/src/main/java/org/ecocean/Encounter.java index ce762ce11c..584e9d70cf 100644 --- a/src/main/java/org/ecocean/Encounter.java +++ b/src/main/java/org/ecocean/Encounter.java @@ -4311,6 +4311,21 @@ public void opensearchDocumentSerializer(JsonGenerator jgen, Shepherd myShepherd } } + // given a doc from opensearch, can user access it? + public static boolean opensearchAccess(org.json.JSONObject doc, User user, + Shepherd myShepherd) { + if ((doc == null) || (user == null)) return false; + if (doc.optBoolean("publiclyReadable", false)) return true; + if (doc.optString("submitterUserId", "__FAIL__").equals(user.getId())) return true; + if (user.isAdmin(myShepherd)) return true; + org.json.JSONArray viewUsers = doc.optJSONArray("viewUsers"); + if (viewUsers == null) return false; + for (int i = 0; i < viewUsers.length(); i++) { + if (viewUsers.optString(i, "__FAIL__").equals(user.getId())) return true; + } + return false; + } + @Override public long getVersion() { return Util.getVersionFromModified(modified); } From a88e42601e0ce6a771d15861fb04f21f015b8070 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 20 Dec 2024 12:40:30 -0700 Subject: [PATCH 4/6] pass1 of sanitizeDoc() --- src/main/java/org/ecocean/OpenSearch.java | 29 +++++++++++++++++++- src/main/java/org/ecocean/api/SearchApi.java | 6 ++-- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/ecocean/OpenSearch.java b/src/main/java/org/ecocean/OpenSearch.java index b82f2fc9cb..d27f880e36 100644 --- a/src/main/java/org/ecocean/OpenSearch.java +++ b/src/main/java/org/ecocean/OpenSearch.java @@ -653,10 +653,37 @@ public static boolean getPermissionsNeeded(Shepherd myShepherd) { public static JSONObject querySanitize(JSONObject query, User user, Shepherd myShepherd) throws IOException { if ((query == null) || (user == null)) throw new IOException("empty query or user"); - // see issue 958 - now we let query pass as-is for anyone, results are scrubbed later XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX FIXME add method ref + // see issue 958 - now we let query pass as-is for anyone, results are scrubbed later e.g. sanitizeDoc() below return query; } + // takes raw search result doc and presents only data user should see + public static JSONObject sanitizeDoc(final JSONObject sourceDoc, String indexName, + Shepherd myShepherd, User user) + throws IOException { + if ((user == null) || (sourceDoc == null)) throw new IOException("null user or sourceDoc"); + JSONObject clean = new JSONObject(); + // this is just punting future classes to later development (should never happen) + if (!"encounter".equals(indexName)) return clean; + boolean hasAccess = Encounter.opensearchAccess(sourceDoc, user, myShepherd); + if (hasAccess) { + clean = new JSONObject(sourceDoc.toString()); + clean.remove("viewUsers"); + clean.put("access", "full"); + return clean; + } + clean.put("access", "none"); + String[] okFields = new String[] { + "id", "version", "indexTimestamp", "version", "individualId", "individualDisplayName", + "occurrenceId", "otherCatalogNumbers", "dateSubmitted", "date", "locationId", + "locationName", "taxonomy", "assignedUsername", "numberAnnotations" + }; + for (String fieldName : okFields) { + if (sourceDoc.has(fieldName)) clean.put(fieldName, sourceDoc.get(fieldName)); + } + return clean; + } + public static boolean indexingActive() { return indexingActiveBackground() || indexingActiveForeground(); } diff --git a/src/main/java/org/ecocean/api/SearchApi.java b/src/main/java/org/ecocean/api/SearchApi.java index 42df780548..de1b75db5c 100644 --- a/src/main/java/org/ecocean/api/SearchApi.java +++ b/src/main/java/org/ecocean/api/SearchApi.java @@ -101,10 +101,8 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response) JSONObject doc = h.optJSONObject("_source"); if (doc == null) throw new IOException("failed to parse doc in hits[" + i + "]"); - // these are kind of noisy - doc.remove("viewUsers"); - doc.remove("editUsers"); - hitsArr.put(doc); + hitsArr.put(OpenSearch.sanitizeDoc(doc, indexName, myShepherd, + currentUser)); } response.setHeader("X-Wildbook-Total-Hits", Integer.toString(totalHits)); response.setHeader("X-Wildbook-Search-Query-Id", searchQueryId); From 089329c960dec2e837d9514c3569a8c3101a9a83 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 20 Dec 2024 15:21:23 -0700 Subject: [PATCH 5/6] skip encounters with no access (unsanitized query now) --- src/main/java/org/ecocean/EncounterQueryProcessor.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/ecocean/EncounterQueryProcessor.java b/src/main/java/org/ecocean/EncounterQueryProcessor.java index 04d3ba505e..bceb5748d4 100644 --- a/src/main/java/org/ecocean/EncounterQueryProcessor.java +++ b/src/main/java/org/ecocean/EncounterQueryProcessor.java @@ -105,7 +105,8 @@ public static String queryStringBuilder(HttpServletRequest request, StringBuffer return failed; } // Encounter enc = myShepherd.getEncounter(hId); - encIds.add(hId); + boolean hasAccess = Encounter.opensearchAccess(h, user, myShepherd); + if (hasAccess) encIds.add(hId); } } catch (Exception ex) { ex.printStackTrace(); @@ -1597,8 +1598,11 @@ public static EncounterQueryResult processQuery(Shepherd myShepherd, HttpServlet return new EncounterQueryResult(rEncounters, searchQuery.toString(), "OpenSearch id " + searchQueryId); } - Encounter enc = myShepherd.getEncounter(hId); - if (enc != null) rEncounters.add(enc); + boolean hasAccess = Encounter.opensearchAccess(h, user, myShepherd); + if (hasAccess) { + Encounter enc = myShepherd.getEncounter(hId); + if (enc != null) rEncounters.add(enc); + } } } catch (Exception ex) { ex.printStackTrace(); From 317a81e865e4216f61069a181730b17518d76645 Mon Sep 17 00:00:00 2001 From: Jon Van Oast Date: Fri, 20 Dec 2024 16:23:08 -0700 Subject: [PATCH 6/6] bugfix --- src/main/java/org/ecocean/EncounterQueryProcessor.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/ecocean/EncounterQueryProcessor.java b/src/main/java/org/ecocean/EncounterQueryProcessor.java index bceb5748d4..5b020bd3b2 100644 --- a/src/main/java/org/ecocean/EncounterQueryProcessor.java +++ b/src/main/java/org/ecocean/EncounterQueryProcessor.java @@ -105,7 +105,8 @@ public static String queryStringBuilder(HttpServletRequest request, StringBuffer return failed; } // Encounter enc = myShepherd.getEncounter(hId); - boolean hasAccess = Encounter.opensearchAccess(h, user, myShepherd); + boolean hasAccess = Encounter.opensearchAccess(h.optJSONObject("_source"), user, + myShepherd); if (hasAccess) encIds.add(hId); } } catch (Exception ex) { @@ -1598,7 +1599,8 @@ public static EncounterQueryResult processQuery(Shepherd myShepherd, HttpServlet return new EncounterQueryResult(rEncounters, searchQuery.toString(), "OpenSearch id " + searchQueryId); } - boolean hasAccess = Encounter.opensearchAccess(h, user, myShepherd); + boolean hasAccess = Encounter.opensearchAccess(h.optJSONObject("_source"), user, + myShepherd); if (hasAccess) { Encounter enc = myShepherd.getEncounter(hId); if (enc != null) rEncounters.add(enc);