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

Block assertion regeneration after audit starts #187

Merged
merged 4 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,12 @@
import spark.Request;
import spark.Response;
import us.freeandfair.corla.Main;
import us.freeandfair.corla.asm.ASMUtilities;
import us.freeandfair.corla.asm.DoSDashboardASM;
import us.freeandfair.corla.model.Choice;
import us.freeandfair.corla.model.ContestResult;
import us.freeandfair.corla.persistence.Persistence;
import us.freeandfair.corla.query.ComparisonAuditQueries;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
Expand All @@ -46,6 +49,8 @@
import java.net.URL;
import java.util.*;

import static us.freeandfair.corla.asm.ASMState.DoSDashboardState.PARTIAL_AUDIT_INFO_SET;

/**
* The Generate Assertions endpoint. Takes a GenerateAssertionsRequest, and optional parameters
* specifying a contest and time limit.
Expand Down Expand Up @@ -82,7 +87,7 @@ public class GenerateAssertions extends AbstractAllIrvEndpoint {
/**
* Query specifier for contest name.
*/
private static final String CONTEST_NAME = "contestName";
public static final String CONTEST_NAME = "contestName";

/**
* Default time limit for assertion generation.
Expand Down Expand Up @@ -118,6 +123,20 @@ public String endpointBody(final Request the_request, final Response the_respons
final String prefix = "[endpointBody]";
LOGGER.debug(String.format("%s %s.", prefix, "Received Generate Assertions request"));

// Check that the request is valid; abort if not.
if (!validateParameters(the_request)) {
final String msg = "Blank contest name or invalid time limit in Generate Assertions request";
LOGGER.debug(String.format("%s %s %s.", prefix, msg, the_request.body()));
badDataContents(the_response, msg);
}

// Check that the state is OK for assertion generation; abort if not.
if (!assertionGenerationAllowed(the_request)) {
final String msg = "Assertion generation not allowed in current state.";
LOGGER.error(String.format("%s %s.", prefix, msg));
illegalTransition(the_response, msg);
}

final List<GenerateAssertionsResponse> responseData;

final String raireUrl = Main.properties().getProperty(RAIRE_URL, "") + RAIRE_ENDPOINT;
Expand All @@ -133,8 +152,8 @@ public String endpointBody(final Request the_request, final Response the_respons
// Get all the IRV contest results.
final List<ContestResult> IRVContestResults = getIRVContestResults();

// Try to do the work.
try {
if(validateParameters(the_request)) {
if (contestName.isBlank()) {
// No contest was requested - generate for all.

Expand All @@ -151,11 +170,7 @@ public String endpointBody(final Request the_request, final Response the_respons
okJSON(the_response, Main.GSON.toJson(responseData));

LOGGER.debug(String.format("%s %s.", prefix, "Completed Generate Assertions request"));
} else {
final String msg = "Blank contest name or invalid time limit in Generate Assertions request";
LOGGER.debug(String.format("%s %s %s.", prefix, msg, the_request.body()));
badDataContents(the_response, msg);
}

} catch (IllegalArgumentException e) {
LOGGER.debug(String.format("%s %s.", prefix, "Bad Generate Assertions request"));
badDataContents(the_response, e.getMessage());
Expand Down Expand Up @@ -331,5 +346,38 @@ protected boolean validateParameters(final Request the_request) {
final String contestName = the_request.queryParams(CONTEST_NAME);
return contestName == null || !contestName.isEmpty();
}
}

/**
* Assertion generation is allowed to commence if we are in the DOS_INITIAL_STATE or the
* PARTIAL_AUDIT_INFO_SET state, otherwise it is not.
* This function also checks that there are no ComparisonAudits in the database, though this should
* always be true in the required states.
* @param the_request the endpoint request.
* @return true if we are in the right state and there are no ComparisonAudits in the database.
*/
private boolean assertionGenerationAllowed(final Request the_request) {
final String prefix = "[assertionGenerationAllowed]";
final String errorMsg = "Blocked assertion generation when requested for contest";

final DoSDashboardASM dashboardASM = ASMUtilities.asmFor(DoSDashboardASM.class, DoSDashboardASM.IDENTITY);

// Check that we're in either the initial state or the PARTIAL_AUDIT_INFO_SET state.
final boolean allowedState
= (dashboardASM.isInInitialState() || dashboardASM.currentState().equals(PARTIAL_AUDIT_INFO_SET));
if(!allowedState) {
LOGGER.debug(String.format("%s %s %s from illegal state %s.", prefix, errorMsg,
the_request.queryParams(CONTEST_NAME), dashboardASM.currentState()));
}

final boolean noComparisonAudits = ComparisonAuditQueries.count() == 0;

// Check that there are no ComparisonAudits in the database (which should not happen given the state).
if(!noComparisonAudits) {
LOGGER.debug(String.format("%s %s %s %s with %d ComparisonAudits in the database.", prefix, errorMsg,
the_request.queryParams(CONTEST_NAME), dashboardASM.currentState().toString(),
ComparisonAuditQueries.count()));
}

return allowedState && noComparisonAudits;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import spark.Request;

import javax.transaction.Transactional;
import java.util.HashSet;
import java.util.HashMap;
import java.util.List;
import java.util.OptionalLong;

Expand Down Expand Up @@ -425,7 +425,7 @@ void testACVRUploadAndStorage() {
startTheRound();

// We seem to need a dummy request to run before.
final Request request = new SparkRequestStub("", new HashSet<>());
final Request request = new SparkRequestStub("", new HashMap<String,String>());
uploadEndpoint.before(request, response);

// Before the test, there should be 10 UPLOADED and zero AUDITOR_ENTERED cvrs.
Expand Down Expand Up @@ -523,7 +523,7 @@ private void testIRVBallotInterpretations(final long CvrNum, final String imprin
*/
private void testSuccessResponse(final long CvrId, final String expectedImprintedId, final String CvrAsJson,
final List<String> expectedInterpretedChoices, final int expectedACVRs) {
final Request request = new SparkRequestStub(CvrAsJson, new HashSet<>());
final Request request = new SparkRequestStub(CvrAsJson, new HashMap<String,String>());
uploadEndpoint.endpointBody(request, response);

// There should now be expectedACVRs audit cvrs.
Expand Down Expand Up @@ -589,7 +589,7 @@ private void testPreviousAreReaudited(final long CvrId, final String expectedImp
* @param expectedError The expected error message.
*/
private void testErrorResponseAndNoMatchingCvr(final long CvrId, final String CvrAsJson, final String expectedError) {
final Request request = new SparkRequestStub(CvrAsJson, new HashSet<>());
final Request request = new SparkRequestStub(CvrAsJson, new HashMap<String,String>());
String errorBody = "";

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ void basicEstimatedSampleSizesPluralityAndIRV() {
mockedMain.when(Main::authentication).thenReturn(auth);

// We seem to need a dummy request to run before.
final Request request = new SparkRequestStub("", new HashSet<>());
final Request request = new SparkRequestStub("", new HashMap<String,String>());
endpoint.before(request, response);

// // First test: hit the endpoint before defining the risk limit. Should throw an error.
Expand Down
Loading
Loading