Skip to content

Commit

Permalink
Merge pull request #567 from cardano-foundation/fix/resolve-oobi-afte…
Browse files Browse the repository at this point in the history
…r-sms

feat: add missing oobi registration when voting with IDW-SMS
  • Loading branch information
jimcase authored Aug 14, 2024
2 parents 6a66b86 + 28d0b2f commit fcefc07
Show file tree
Hide file tree
Showing 14 changed files with 209 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ max.pending.verification.attempts=${MAX_PENDING_VERIFICATION_ATTEMPTS:5}
spring.h2.console.enabled=${H2_CONSOLE_ENABLED:true}

phone.number.salt=${SALT:67274569c9671a4ae3f753b9647ca719}
discord.bot.eventId.binding=${DISCORD_BOT_EVENT_ID_BINDING:CF_SUMMIT_2024_9BCC}
discord.bot.eventId.binding=${DISCORD_BOT_EVENT_ID_BINDING:CF_SUMMIT_2024_10BCC}

discord.bot.username=${DISCORD_BOT_USERNAME:discord_bot}
discord.bot.password=${DISCORD_BOT_PASSWORD:test}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,12 @@ public String createCFSummit2024Event() {

log.info("Creating CF-Summit 2024 on a PRE-PROD network...");

long startSlot = 64935671;
long startSlot = 67868350;
long endSlot = startSlot + (604800*2); // 2 weeks later

var createEventCommand = CreateEventCommand.builder()
//CF_SUMMIT_2024_7BCC
.id(EVENT_NAME + "_" + "8BCC")
.id(EVENT_NAME + "_" + "10BCC")
.startSlot(Optional.of(startSlot))
.endSlot(Optional.of(endSlot))
.votingPowerAsset(Optional.empty())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
import java.util.HashMap;
import java.util.Map;

import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpStatus.BAD_REQUEST;

@RequiredArgsConstructor
@Component
Expand Down Expand Up @@ -66,4 +68,85 @@ public Either<Problem, Boolean> verifySignature(String aid,
}
}

public Either<Problem, Boolean> registerOOBI(String oobi) {
val url = String.format("%s/oobi", keriVerifierBaseUrl);

val headers = new HttpHeaders();
headers.add("Content-Type", "application/json");

val requestBody = new HashMap<String, String>();
requestBody.put("oobi", oobi);

val entity = new HttpEntity<Map<String, String>>(requestBody, headers);
try {
val response = restTemplate.exchange(url, POST, entity, String.class);

if (response.getStatusCode().is2xxSuccessful()) {
return Either.right(true);
}

return Either.left(Problem.builder()
.withTitle("OOBI_REGISTRATION_FAILED")
.withDetail("Failed to register OOBI.")
.withStatus(new HttpStatusAdapter(response.getStatusCode()))
.build());
} catch (HttpClientErrorException e) {
return Either.left(Problem.builder()
.withTitle("OOBI_REGISTRATION_ERROR")
.withDetail("Unable to register OOBI, reason: " + e.getMessage())
.withStatus(new HttpStatusAdapter(e.getStatusCode()))
.build());
}
}

public Either<Problem, String> getOOBI(String oobi, Integer maxAttempts) {
val url = String.format("%s/oobi?url=%s", keriVerifierBaseUrl, oobi);

val headers = new HttpHeaders();
headers.add("Content-Type", "application/json");

val entity = new HttpEntity<Void>(headers);

int attempts = (maxAttempts == null) ? 1 : maxAttempts;
int attempt = 0;

while (attempt < attempts) {
try {
val response = restTemplate.exchange(url, GET, entity, String.class);

if (response.getStatusCode().is2xxSuccessful()) {
return Either.right(response.getBody());
}
} catch (HttpClientErrorException e) {
if (e.getStatusCode() != BAD_REQUEST) {
return Either.left(Problem.builder()
.withTitle("OOBI_FETCH_ERROR")
.withDetail("Unable to fetch OOBI, reason: " + e.getMessage())
.withStatus(new HttpStatusAdapter(e.getStatusCode()))
.build());
}
}

attempt++;
if (attempt < attempts) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return Either.left(Problem.builder()
.withTitle("INTERRUPTED_ERROR")
.withDetail("Thread was interrupted while waiting to retry.")
.withStatus(new HttpStatusAdapter(BAD_REQUEST))
.build());
}
}
}

return Either.left(Problem.builder()
.withTitle("OOBI_NOT_FOUND")
.withDetail("The OOBI was not found after " + attempts + " attempts.")
.withStatus(new HttpStatusAdapter(BAD_REQUEST))
.build());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
.requestMatchers(new AntPathRequestMatcher("/api/vote/receipt", GET.name())).authenticated()
.requestMatchers(new AntPathRequestMatcher("/api/vote/receipt", HEAD.name())).authenticated()
.requestMatchers(new AntPathRequestMatcher("/api/vote/receipts", GET.name())).authenticated()
.requestMatchers(new AntPathRequestMatcher("/api/vote/receipts", HEAD.name())).authenticated()
// SECURED by Web3 auth
.requestMatchers(new AntPathRequestMatcher("/api/auth/login", GET.name())).authenticated()
// SECURED by JWT auth
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ public class SignedKERI {
@NotBlank
protected String aid;

@NotBlank
protected String oobi;

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ public final class Headers {
public final static String X_Ballot_Signature = "X-Ballot-Signature";
public final static String X_Ballot_Payload = "X-Ballot-Payload";
public final static String X_Ballot_PublicKey = "X-Ballot-Public-Key";
public final static String X_Ballot_Oobi = "X-Ballot-Oobi";

public final static String X_Ballot_Wallet_Type = "X-Ballot-Wallet-Type";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ public ResponseEntity<?> getVoteReceipt(Authentication authentication) {
});
}

@RequestMapping(value = "/receipts", method = { GET, HEAD }, produces = "application/json")
@RequestMapping(value = "/receipts", method = { GET }, produces = "application/json")
@Timed(value = "resource.vote.receipts", histogram = true)
@Operation(
summary = "Retrieve all vote receipts for the authenticated user",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ public Optional<String> getPublicKey() {
return signedCIP30.getPublicKey();
}

public Optional<String> getOobi() {
return Optional.empty();
}

@Override
public String getSignedJson() {
return cip30VerificationResult.getMessage(MessageFormat.TEXT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ public Optional<String> getPayload() {
return Optional.of(signedKERI.getPayload());
}

@Override
public Optional<String> getOobi() {
return Optional.of(signedKERI.getOobi());
}

@Override
public Optional<String> getPublicKey() {
return Optional.of(signedKERI.getAid());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import io.vavr.control.Either;

import static com.bloxbean.cardano.client.util.HexUtil.decodeHexString;
import static org.cardano.foundation.voting.domain.Role.VOTER;
Expand Down Expand Up @@ -73,6 +74,7 @@ protected void doFilterInternal(HttpServletRequest req,
val headerSignatureM = Optional.ofNullable(req.getHeader(X_Ballot_Signature));
val headerPayloadM = Optional.ofNullable(req.getHeader(X_Ballot_Payload));
val headerAidM = Optional.ofNullable(req.getHeader(X_Ballot_PublicKey));
val headerOobiM = Optional.ofNullable(req.getHeader(X_Ballot_Oobi));

if (headerSignatureM.isEmpty()) {
val problem = Problem.builder()
Expand Down Expand Up @@ -104,9 +106,66 @@ protected void doFilterInternal(HttpServletRequest req,
sendBackProblem(objectMapper, res, problem);
return;
}

if (headerOobiM.isEmpty()) {
val problem = Problem.builder()
.withTitle("NO_LOGIN_HTTP_HEADERS_SET")
.withDetail("X-Ballot-Oobi http header must be set.")
.withStatus(BAD_REQUEST)
.build();

sendBackProblem(objectMapper, res, problem);
return;
}

val headerSignature = headerSignatureM.orElseThrow();
val headerSignedJson = new String(decodeHexString(headerPayloadM.orElseThrow()));
val headerAid = headerAidM.orElseThrow();
val headerOobi = headerOobiM.orElseThrow();

// Step 1: Check if OOBI is already registered
Either<Problem, String> oobiCheckResult = keriVerificationClient.getOOBI(headerOobi, 1);
if (oobiCheckResult.isLeft()) {
sendBackProblem(objectMapper, res, oobiCheckResult.getLeft());
return;
}

// Log if OOBI is registered or not
log.info("OOBI status: {}", oobiCheckResult.get());

if (oobiCheckResult.isRight()) {
log.info("OOBI already registered: {}", oobiCheckResult);
Either<Problem, Boolean> verificationResult = keriVerificationClient.verifySignature(headerAid, headerSignature, headerSignedJson);

if (verificationResult.isEmpty()) {
val problem = Problem.builder()
.withTitle("KERI_SIGNATURE_VERIFICATION_FAILED")
.withDetail("Unable to verify KERI header signature, reason: " + verificationResult.swap().get().getDetail())
.withStatus(BAD_REQUEST)
.build();

sendBackProblem(objectMapper, res, problem);
return;
}
}

log.info("OOBI not registered yet: {}", headerOobi);
// Step 2: Register OOBI if not already registered
val oobiRegistrationResultE = keriVerificationClient.registerOOBI(headerOobi);

if (oobiRegistrationResultE.isLeft()) {
sendBackProblem(objectMapper, res, oobiRegistrationResultE.getLeft());
return;
}

log.info("OOBI registered successfully: {}", headerOobi);

// Step 3: Attempt to verify OOBI registration up to 60 times
val oobiFetchResultE = keriVerificationClient.getOOBI(headerOobi, 60);
if (oobiFetchResultE.isLeft()) {
sendBackProblem(objectMapper, res, oobiFetchResultE.getLeft());
return;
}

val keriVerificationResultE = keriVerificationClient.verifySignature(headerAid, headerSignature, headerSignedJson);
if (keriVerificationResultE.isEmpty()) {
Expand Down Expand Up @@ -314,7 +373,7 @@ protected void doFilterInternal(HttpServletRequest req,

val eventDetails = eventDetailsM.orElseThrow();

val signedKERI = new SignedKERI(headerSignature, headerSignedJson, headerAid);
val signedKERI = new SignedKERI(headerSignature, headerSignedJson, headerAid, headerOobi);

val web3Details = Web3CommonDetails.builder()
.event(eventDetails)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,6 @@ public interface Web3ConcreteDetails {

String getSignedJson();

Optional<String> getOobi();

}
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,6 @@ public Either<Problem, Vote> castVote(Web3AuthenticationToken web3Authentication
.build());
}



if (details.getChainTip().isNotSynced()) {
return Either.left(Problem.builder()
.withTitle("CHAIN_FOLLOWER_NOT_SYNCED")
Expand Down
Loading

0 comments on commit fcefc07

Please sign in to comment.