From d6f4b34eae2e977cdd0339fb17302976fdae0574 Mon Sep 17 00:00:00 2001 From: Jakob Heher Date: Tue, 4 Oct 2022 15:02:43 +0200 Subject: QR code handling --- .../gui/workflow/states/MobileBKUState.java | 154 ++++++++++++++------- .../pdfover/gui/workflow/states/SigningState.java | 12 +- 2 files changed, 109 insertions(+), 57 deletions(-) (limited to 'pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow') diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java index 26be4626..9c3fc807 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java @@ -15,8 +15,11 @@ */ package at.asit.pdfover.gui.workflow.states; +import java.io.IOException; import java.io.InputStream; import java.net.ConnectException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.UnknownHostException; import java.util.Timer; import java.util.TimerTask; @@ -31,6 +34,12 @@ import at.asit.pdfover.signer.SignatureException; import at.asit.pdfover.signer.UserCancelledException; import at.asit.pdfover.signer.pdfas.PdfAs4SigningState; +import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; +import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; +import org.apache.hc.client5.http.impl.classic.HttpClients; +import org.apache.hc.core5.http.ClassicHttpRequest; +import org.apache.hc.core5.http.io.entity.EntityUtils; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Display; import org.slf4j.Logger; @@ -220,8 +229,7 @@ public class MobileBKUState extends State { } public void getCredentialsFromUserTo(@Nonnull UsernameAndPassword credentials, @Nullable String errorMessage) throws UserCancelledException { - boolean[] cancelState = new boolean[1]; - Display.getDefault().syncExec(() -> { + Display.getDefault().syncCall(() -> { MobileBKUEnterNumberComposite ui = this.getMobileBKUEnterNumberComposite(); if (!ui.userAck) { // We need number and password => show UI! @@ -252,10 +260,10 @@ public class MobileBKUState extends State { if (!(ui.userCancel && ui.isRememberPassword())) /* don't allow "remember" to be enabled via cancel button */ getStateMachine().configProvider.setRememberMobilePasswordPersistent(ui.isRememberPassword()); - cancelState[0] = ui.userCancel; - ui.userCancel = false; - if (cancelState[0]) - return; + if (ui.userCancel) { + ui.userCancel = false; + throw new UserCancelledException(); + } // user hit ok ui.userAck = false; @@ -266,9 +274,9 @@ public class MobileBKUState extends State { // show waiting composite getStateMachine().display(this.getWaitingComposite()); + + return true; /* dummy return for lambda type deduction */ }); - if (cancelState[0]) - throw new UserCancelledException(); } /** @@ -342,46 +350,45 @@ public class MobileBKUState extends State { }); } + public enum QRResult { + /* the user has pressed the FIDO2 button */ + TO_FIDO2, + /* the user has pressed the SMS button */ + TO_SMS, + /* signalQRScanned has been called; this indicates that we should refresh the page */ + UPDATE + }; + /** - * Show QR code + * start showing the QR code at the indicated URI + * this method will block until the QR code state completes + * (due to QR code being scanned, or the user pressing a button) + *

+ * it is the responsibility of the caller to perform AJAX long polling + * @return */ - public void showQR() { - final ATrustStatus status = this.status; - final ATrustHandler handler = this.handler; - - final Timer checkDone = new Timer(); - checkDone.scheduleAtFixedRate(new TimerTask() { - @Override - public void run() { - // ping signature page to see if code has been scanned - try { - String resp = handler.getSignaturePage(); - if (handler.handleQRResponse(resp)) { - log.debug("Signature page response: " + resp); - getMobileBKUQRComposite().setDone(true); - Display display = getStateMachine(). - getMainShell().getDisplay(); - display.wake(); - checkDone.cancel(); - } - Display.getDefault().wake(); - } catch (Exception e) { - log.error("Error getting signature page", e); - } + public QRResult showQRCode(@Nonnull String referenceValue, @Nonnull URI qrCodeURI, @Nullable String errorMessage) throws UserCancelledException { + byte[] qrCode; + try (final CloseableHttpClient httpClient = HttpClients.createDefault()) { + try (final CloseableHttpResponse response = httpClient.execute(new HttpGet(qrCodeURI))) { + qrCode = EntityUtils.toByteArray(response.getEntity()); } - }, 0, 5000); - - Display.getDefault().syncExec(() -> { + } catch (IOException e) { + log.warn("Failed to load QR code."); + qrCode = null; + } + + final byte[] qrCodeCopy = qrCode; /* because java is silly */ + return Display.getDefault().syncCall(() -> { MobileBKUQRComposite qr = getMobileBKUQRComposite(); + qr.setUserCancel(false); + qr.setUserSMS(false); + qr.setDone(false); qr.setRefVal(status.refVal); qr.setSignatureData(status.signatureDataURL); qr.setErrorMessage(status.errorMessage); - InputStream qrcode = handler.getQRCode(); - if (qrcode == null) { - this.threadException = new Exception(Messages.getString("error.FailedToLoadQRCode")); - } - qr.setQR(qrcode); + qr.setQR(qrCodeCopy); getStateMachine().display(qr); Display display = getStateMachine().getMainShell().getDisplay(); @@ -391,28 +398,69 @@ public class MobileBKUState extends State { } } - checkDone.cancel(); + getStateMachine().display(this.getWaitingComposite()); if (qr.isUserCancel()) { - qr.setUserCancel(false); clearRememberedPassword(); - status.errorMessage = "cancel"; - return; + throw new UserCancelledException(); } - if (qr.isUserSMS()) { - qr.setUserSMS(false); - status.qrCodeURL = null; - } + if (qr.isUserSMS()) + return QRResult.TO_SMS; if (qr.isDone()) - qr.setDone(false); - - // show waiting composite - getStateMachine().display(this.getWaitingComposite()); + return QRResult.UPDATE; + + throw new RuntimeException("unexpected display wake"); }); } + /** + * indicate that the long polling operation completed + * (any ongoing showQRCode call will then return) + */ + public void signalQRScanned() { + getMobileBKUQRComposite().setDone(true); + Display display = getStateMachine(). + getMainShell().getDisplay(); + display.wake(); + } + + /** + * Show QR code + * @throws URISyntaxException + * @throws UserCancelledException + * @throws IOException + */ + public void OLDshowQR() throws IOException, UserCancelledException, URISyntaxException { + final ATrustStatus status = this.status; + final ATrustHandler handler = this.handler; + + final Timer checkDone = new Timer(); + checkDone.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + // ping signature page to see if code has been scanned + try { + String resp = handler.getSignaturePage(); + if (handler.handleQRResponse(resp)) { + log.debug("Signature page response: " + resp); + checkDone.cancel(); + signalQRScanned(); + } + Display.getDefault().wake(); + } catch (Exception e) { + log.error("Error getting signature page", e); + } + } + }, 0, 5000); + + QRResult result = showQRCode(status.refVal, new URI(status.baseURL).resolve(status.qrCodeURL), status.errorMessage); + checkDone.cancel(); + if (result == QRResult.TO_SMS) + status.qrCodeURL = null; + } + /** * This composite notifies the user to open the signature-app */ @@ -569,7 +617,7 @@ public class MobileBKUState extends State { public void run() { this.signingState = getStateMachine().status.signingState; - this.signingState.bkuConnector = new OLDMobileBKUConnector(this); + this.signingState.bkuConnector = new MobileBKUConnector(this); this.signingState.useBase64Request = false; if (this.threadException != null) { diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java index b9bdc917..169aefb8 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/SigningState.java @@ -30,6 +30,7 @@ import at.asit.pdfover.commons.Messages; import at.asit.pdfover.gui.workflow.StateMachine; import at.asit.pdfover.gui.workflow.Status; import at.asit.pdfover.signer.SignatureException; +import at.asit.pdfover.signer.UserCancelledException; import at.asit.pdfover.signer.pdfas.PdfAs4Signer; /** @@ -108,11 +109,14 @@ public class SigningState extends State { if (cause instanceof ConnectException) message += ": " + cause.getMessage(); if (cause instanceof IllegalStateException) { - // Dummy exception - don't display error, go back to BKU Selection - this.setNextState(new BKUSelectionState(getStateMachine())); - return; + // TODO legacy hack + this.threadException = new UserCancelledException(); } - + } + if (this.threadException instanceof UserCancelledException) { + // don't display error, go back to BKU Selection + this.setNextState(new BKUSelectionState(getStateMachine())); + return; } // if we have gotten to this point, this is an actual exception -- cgit v1.2.3