Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

search result changes to allow inaccessible results #959

Merged
merged 7 commits into from
Dec 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 23 additions & 21 deletions frontend/src/components/DataTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [],
Expand All @@ -52,6 +31,7 @@ const MyDataTable = ({
style = {},
tabs = [],
isLoading = false,
extraStyles = [],
onSelectedRowsChange = () => {},
onRowClicked = () => {},
}) => {
Expand All @@ -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) => {
Expand Down
14 changes: 13 additions & 1 deletion frontend/src/pages/EncounterSearch.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand All @@ -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");

Expand Down Expand Up @@ -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);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/org/ecocean/Encounter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
12 changes: 9 additions & 3 deletions src/main/java/org/ecocean/EncounterQueryProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ public static String queryStringBuilder(HttpServletRequest request, StringBuffer
return failed;
}
// Encounter enc = myShepherd.getEncounter(hId);
encIds.add(hId);
boolean hasAccess = Encounter.opensearchAccess(h.optJSONObject("_source"), user,
myShepherd);
if (hasAccess) encIds.add(hId);
}
} catch (Exception ex) {
ex.printStackTrace();
Expand Down Expand Up @@ -1597,8 +1599,12 @@ 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.optJSONObject("_source"), user,
myShepherd);
if (hasAccess) {
Encounter enc = myShepherd.getEncounter(hId);
if (enc != null) rEncounters.add(enc);
}
}
} catch (Exception ex) {
ex.printStackTrace();
Expand Down
56 changes: 28 additions & 28 deletions src/main/java/org/ecocean/OpenSearch.java
Original file line number Diff line number Diff line change
Expand Up @@ -653,35 +653,35 @@ 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");
// 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 newQuery;
return clean;
}

public static boolean indexingActive() {
Expand Down
6 changes: 2 additions & 4 deletions src/main/java/org/ecocean/api/SearchApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading