Skip to content
Open
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
47 changes: 47 additions & 0 deletions cwms-data-api/src/main/java/cwms/cda/data/dao/JooqDao.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import cwms.cda.datasource.DelegatingConnectionPreparer;
import cwms.cda.helpers.DatabaseHelpers.SCHEMA_VERSION;
import cwms.cda.security.CwmsAuthException;
import io.javalin.http.BadRequestResponse;
import io.javalin.http.Context;
import io.javalin.http.HandlerType;
import java.math.BigDecimal;
Expand All @@ -50,11 +51,15 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -98,6 +103,7 @@ public abstract class JooqDao<T> extends Dao<T> {
"ORA-12899: value too large for column \".+\"\\.\".+\"\\.\"(.+)\" "
+ "\\(actual: (\\d+), maximum: (\\d+)\\)");
private static final Pattern REGEX_META_CHARS_EXCEPT_DOT = Pattern.compile("[\\\\^$|?+()\\[\\]{}]");
private static final Pattern ORA_CODE = Pattern.compile("^ORA-\\d+: ERROR: .*");

public enum DeleteMethod {
DELETE_ALL(DeleteRule.DELETE_ALL),
Expand Down Expand Up @@ -339,6 +345,8 @@ public static RuntimeException wrapException(RuntimeException input) {
retVal = buildFieldLengthExceededException(input);
} else if (isTSIDInvalidIntervalException(input)) {
retVal = buildInvalidTSIDIntervalException(input);
} else if (isBadRequest(input)) {
retVal = buildBadRequest(input);
}

return retVal;
Expand Down Expand Up @@ -381,6 +389,45 @@ private static boolean hasCodeAndMessage(SQLException sqlException,
// See link for a more complete list of CWMS Error codes:
// https://bitbucket.hecdev.net/projects/CWMS/repos/cwms_database_origin_teamcity_work/browse/src/buildSqlScripts.py#4866

public static boolean isBadRequest(RuntimeException input) {
boolean retVal = false;

Optional<SQLException> optional = getSqlException(input);
if (optional.isPresent()) {
SQLException sqlException = optional.get();
if (!sqlException.getLocalizedMessage().contains("CAN_NOT_DELETE")) {
List<Integer> codes = IntStream.range(20000, 20999).boxed().collect(Collectors.toList());

retVal = hasCodeOrMessage(sqlException, codes, new ArrayList<>());
}
}
return retVal;
}

public static BadRequestResponse buildBadRequest(RuntimeException input) {
Throwable cause = input;
if (input instanceof DataAccessException) {
DataAccessException dae = (DataAccessException) input;
cause = dae.getCause();
}

String localizedMessage = cause.getLocalizedMessage();

if (localizedMessage != null) {
String[] parts = localizedMessage.split("\n");
String errorMessage = parts[0].replace("'", "");
if (ORA_CODE.matcher(errorMessage).matches()) {
errorMessage = errorMessage.substring(errorMessage.indexOf("ERROR: ") + 7);
}
errorMessage = sanitizeOrNull(errorMessage);
Map<String, String> errorDetails = new HashMap<>();
errorDetails.put("message", errorMessage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Message details need to be put through the sanitizer. The input is from the user so we assume it fail because it was an attack.... even if 98% of the time it's just a typo.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Passed message through sanitizer

return new BadRequestResponse("", errorDetails);
}
return new BadRequestResponse();
}


public static boolean isNotFound(RuntimeException input) {
boolean retVal = false;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -766,6 +766,175 @@ void test_create_read_delete_same_names_different_offices(String format) throws
.statusCode(is(HttpServletResponse.SC_NOT_FOUND));
}


@Test
void test_create_read_delete_same_aliases_different_names() throws Exception {
// Create two location groups of the same name with an agency alias category
String officeId = user.getOperatingOffice();
String locationId = "LocGrpTestN1";
String locationId2 = "LocGrpTestN2";
createLocation(locationId, true, officeId);
createLocation(locationId2, true, officeId);
LocationCategory cat = new LocationCategory(officeId, "AliasTestCategory", "AliasIntegrationTesting");
AssignedLocation assignLoc = new AssignedLocation(locationId, officeId, "AliasedId", 1, locationId);
AssignedLocation assignLoc2 = new AssignedLocation(locationId2, officeId, "AliasedId", 1, locationId);
LocationGroup group = new LocationGroup(new LocationGroup(cat, officeId, "LocationGroupTestIT", "IntegrationTesting",
"sharedLocAliasId", locationId, 123), Collections.singletonList(assignLoc));
LocationGroup group2 = new LocationGroup(new LocationGroup(cat, officeId, "LocationGroupTestIT1", "IntegrationTesting1",
"sharedLocAliasId1", locationId, 123), Collections.singletonList(assignLoc2));
ContentType contentType = Formats.parseHeader(Formats.JSON, LocationCategory.class);
String groupXml = Formats.format(contentType, group);
groupsToCleanup.add(group);
String categoryXml = Formats.format(contentType, cat);
categoriesToCleanup.add(cat);
registerCategory(cat);
//Create Category
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.body(categoryXml)
.header("Authorization", user.toHeaderValue())
.when()
.redirects().follow(true)
.redirects().max(3)
.post("/location/category")
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_CREATED));

//Create Group
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.body(groupXml)
.header("Authorization", user.toHeaderValue())
.when()
.redirects().follow(true)
.redirects().max(3)
.post("/location/group")
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_CREATED));
//Create Group 2
groupXml = Formats.format(contentType, group2);
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.body(groupXml)
.header("Authorization", user.toHeaderValue())
.when()
.redirects().follow(true)
.redirects().max(3)
.post("/location/group")
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_BAD_REQUEST))
.body("message", equalTo("Bad Request"))
.body("details.message", equalTo("Alias (AliasedId) would reference multiple locations. " +
"If you want to allow this, set the CWMSDB/Allow_multiple_locations_for_alias " +
"property to T for office id SPK. Note that this action will eliminate the " +
"ability to look up a location using the alias or any others that reference multiple locations."));
//Read
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.queryParam(OFFICE, officeId)
.queryParam(CATEGORY_ID, group.getLocationCategory().getId())
.queryParam(CATEGORY_OFFICE_ID, CWMS_OFFICE)
.queryParam(GROUP_OFFICE_ID, officeId)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/location/group/" + group.getId())
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_OK))
.body("office-id", equalTo(group.getOfficeId()))
.body("id", equalTo(group.getId()))
.body("description", equalTo(group.getDescription()))
.body("assigned-locations[0].location-id", equalTo(locationId))
.body("assigned-locations[0].alias-id", equalTo("AliasedId"))
.body("assigned-locations[0].ref-location-id", equalTo(locationId));
//Read
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.queryParam(OFFICE, officeId)
.queryParam(CATEGORY_ID, group2.getLocationCategory().getId())
.queryParam(CATEGORY_OFFICE_ID, officeId)
.queryParam(GROUP_OFFICE_ID, officeId)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/location/group/" + group2.getId())
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_NOT_FOUND));

//Delete Group
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.header("Authorization", user.toHeaderValue())
.queryParam(OFFICE, officeId)
.queryParam(CATEGORY_ID, cat.getId())
.queryParam(CASCADE_DELETE, "true")
.when()
.redirects().follow(true)
.redirects().max(3)
.delete("/location/group/" + group.getId())
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_NO_CONTENT));

//Read Empty
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.queryParam(OFFICE, officeId)
.queryParam(CATEGORY_ID, group.getLocationCategory().getId())
.queryParam(CATEGORY_OFFICE_ID, officeId)
.queryParam(GROUP_OFFICE_ID, officeId)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/location/group/" + group.getId())
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_NOT_FOUND));
//Read Empty
given()
.log().ifValidationFails(LogDetail.ALL,true)
.accept(Formats.JSON)
.contentType(Formats.JSON)
.queryParam(OFFICE, officeId)
.queryParam(CATEGORY_ID, group2.getLocationCategory().getId())
.queryParam(CATEGORY_OFFICE_ID, officeId)
.queryParam(GROUP_OFFICE_ID, officeId)
.when()
.redirects().follow(true)
.redirects().max(3)
.get("/location/group/" + group2.getId())
.then()
.log().ifValidationFails(LogDetail.ALL,true)
.assertThat()
.statusCode(is(HttpServletResponse.SC_NOT_FOUND));
}

@ParameterizedTest
@ValueSource(strings = {Formats.JSON, Formats.DEFAULT})
void test_create_read_delete_office_combinations(String format) throws Exception {
Expand Down
Loading