From 9fe54cdfa59ac84449a840e01267af08d2194a51 Mon Sep 17 00:00:00 2001 From: Jakob Heher Date: Fri, 7 Oct 2022 12:57:31 +0200 Subject: expiry interstitial + waiting for fingerprint screen --- .../asit/pdfover/gui/bku/MobileBKUConnector.java | 19 ++- .../asit/pdfover/gui/bku/mobile/ATrustParser.java | 32 +++++ .../mobilebku/MobileBKUFingerprintComposite.java | 143 ++++----------------- .../mobilebku/WaitingForAppComposite.java | 7 - .../gui/workflow/states/MobileBKUState.java | 116 ++++++++--------- 5 files changed, 130 insertions(+), 187 deletions(-) diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java index 2e301a2e..3efecb4c 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java @@ -291,6 +291,10 @@ public class MobileBKUConnector implements BkuSlConnector { state.rememberCredentialsIfNecessary(this.credentials); } + if (html.interstitialBlock != null) { + this.state.showInformationMessage(html.interstitialBlock.interstitialMessage); + return buildFormSubmit(html, html.interstitialBlock.submitButton); + } if (html.errorBlock != null) { try { this.credentials.password = null; @@ -342,7 +346,7 @@ public class MobileBKUConnector implements BkuSlConnector { } if (html.waitingForAppBlock != null) { try (LongPollThread longPollThread = new LongPollThread(html.waitingForAppBlock.pollingURI, () -> { this.state.signalAppOpened(); })) { - this.state.showWaitingForApp(html.waitingForAppBlock.referenceValue, html.signatureDataLink, html.smsTanLink != null, html.fido2Link != null); + this.state.showWaitingForAppOpen(html.waitingForAppBlock.referenceValue, html.signatureDataLink, html.smsTanLink != null, html.fido2Link != null); longPollThread.start(); var result = this.state.waitForAppOpen(); switch (result) { @@ -353,6 +357,19 @@ public class MobileBKUConnector implements BkuSlConnector { return new HttpGet(html.htmlDocument.baseUri()); } } + if (html.waitingForBiometryBlock != null) { + try (LongPollThread longPollThread = new LongPollThread(html.waitingForBiometryBlock.pollingURI, () -> { this.state.signalAppBiometryDone(); })) { + this.state.showWaitingForAppBiometry(html.waitingForBiometryBlock.referenceValue, html.signatureDataLink, html.smsTanLink != null, html.fido2Link != null); + longPollThread.start(); + var result = this.state.waitForAppBiometry(); + switch (result) { + case UPDATE: break; + case TO_FIDO2: if (html.fido2Link != null) return new HttpGet(html.fido2Link); break; + case TO_SMS: if (html.smsTanLink != null) return new HttpGet(html.smsTanLink); break; + } + return new HttpGet(html.htmlDocument.baseUri()); + } + } if (html.fido2Block != null) { var fido2Result = this.state.promptUserForFIDO2Auth(html.fido2Block.fidoOptions, html.signatureDataLink, html.smsTanLink != null); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java index f7bd45bf..1d0b6406 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java @@ -74,6 +74,19 @@ public class ATrustParser { } } + public static class InterstitialBlock extends TopLevelFormBlock { + public final @Nonnull String submitButton; + public final @Nonnull String interstitialMessage; + + private InterstitialBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull Map formOptions) throws ComponentParseFailed { + super(htmlDocument, formOptions); + if (htmlDocument.baseUri().contains("/ExpiresInfo.aspx")) { + this.interstitialMessage = ISNOTNULL(getElementEnsureNotNull("#Label2").ownText()); + this.submitButton = "#Button_Next"; + } else { throw new ComponentParseFailed(); } + } + } + public static class ErrorBlock extends TopLevelFormBlock { public final boolean isRecoverable; public final @Nonnull String errorText; @@ -159,6 +172,19 @@ public class ATrustParser { } } + public static class WaitingForBiometryBlock extends TopLevelFormBlock { + public final @Nonnull String referenceValue; + public final @Nonnull URI pollingURI; + + private WaitingForBiometryBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull Map formOptions) throws ComponentParseFailed { + super(htmlDocument, formOptions); + abortIfElementMissing("#biometricimage"); + + this.referenceValue = ISNOTNULL(getElementEnsureNotNull("#vergleichswert").ownText()); + this.pollingURI = getLongPollURI(); + } + } + public static class Fido2Block extends TopLevelFormBlock { public final @Nonnull String fidoOptions; private final @Nonnull String credentialResultKey; @@ -186,21 +212,25 @@ public class ATrustParser { public final @CheckForNull URI fido2Link; /* top-level blocks (exactly one is not null) */ + public final @CheckForNull InterstitialBlock interstitialBlock; public final @CheckForNull ErrorBlock errorBlock; public final @CheckForNull UsernamePasswordBlock usernamePasswordBlock; public final @CheckForNull SMSTanBlock smsTanBlock; public final @CheckForNull QRCodeBlock qrCodeBlock; public final @CheckForNull WaitingForAppBlock waitingForAppBlock; + public final @CheckForNull WaitingForBiometryBlock waitingForBiometryBlock; public final @CheckForNull Fido2Block fido2Block; private void validate() { Set populated = new HashSet<>(); + if (interstitialBlock != null) populated.add("interstitialBlock"); if (errorBlock != null) populated.add("errorBlock"); if (usernamePasswordBlock != null) populated.add("usernamePasswordBlock"); if (smsTanBlock != null) populated.add("smsTanBlock"); if (qrCodeBlock != null) populated.add("qrCodeBlock"); if (waitingForAppBlock != null) populated.add("waitingForAppBlock"); + if (waitingForBiometryBlock != null) populated.add("waitingForBiometryBlock"); if (fido2Block != null) populated.add("fido2Block"); switch (populated.size()) { @@ -280,11 +310,13 @@ public class ATrustParser { this.smsTanLink = getHrefIfExists("#SmsButton"); this.fido2Link = getHrefIfExists("#FidoButton"); + this.interstitialBlock = TryParseMainBlock(InterstitialBlock.class); this.errorBlock = TryParseMainBlock(ErrorBlock.class); this.usernamePasswordBlock = TryParseMainBlock(UsernamePasswordBlock.class); this.smsTanBlock = TryParseMainBlock(SMSTanBlock.class); this.qrCodeBlock = TryParseMainBlock(QRCodeBlock.class); this.waitingForAppBlock = TryParseMainBlock(WaitingForAppBlock.class); + this.waitingForBiometryBlock = TryParseMainBlock(WaitingForBiometryBlock.class); this.fido2Block = TryParseMainBlock(Fido2Block.class); validate(); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/MobileBKUFingerprintComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/MobileBKUFingerprintComposite.java index 14ca1dc7..dfde1d1e 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/MobileBKUFingerprintComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/MobileBKUFingerprintComposite.java @@ -15,12 +15,12 @@ */ package at.asit.pdfover.gui.composites.mobilebku; +import java.net.URI; + // Imports import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Rectangle; @@ -29,8 +29,6 @@ import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Link; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import at.asit.pdfover.commons.Constants; import at.asit.pdfover.commons.Messages; @@ -42,102 +40,33 @@ import at.asit.pdfover.gui.workflow.states.State; * Composite for displaying the QR code for the mobile BKU */ public class MobileBKUFingerprintComposite extends StateComposite { - - /** - * - */ - private final class SMSSelectionListener extends SelectionAdapter { - /** - * Empty constructor - */ - public SMSSelectionListener() { - } - - @Override - public void widgetSelected(SelectionEvent e) { - if(!MobileBKUFingerprintComposite.this.btn_sms.getEnabled()) { - return; - } - - MobileBKUFingerprintComposite.this.setUserSMS(true); - MobileBKUFingerprintComposite.this.btn_sms.setEnabled(false); - } - } - - /** - * - */ - private final class CancelSelectionListener extends SelectionAdapter { - /** - * Empty constructor - */ - public CancelSelectionListener() { - } - - @Override - public void widgetSelected(SelectionEvent e) { - MobileBKUFingerprintComposite.this.setUserCancel(true); - } - } - - /** - * SLF4J Logger instance - **/ - static final Logger log = LoggerFactory.getLogger(MobileBKUFingerprintComposite.class); - - boolean userCancel = false; - boolean userSMS = false; - boolean done = false; - - private Label lblRefVal; - - String refVal; - - String signatureData; - - /** - * @return the signatureData - */ - public String getSignatureData() { - return this.signatureData; - } - - /** - * @param signatureData - * the signatureData to set - */ - public void setSignatureData(String signatureData) { - this.signatureData = signatureData; - } - private Label lblError; private Label lblRefValLabel; private Label lblFPLabel; + private Label lblRefVal; + private Button btn_sms; + private Button btn_cancel; + private Link lnk_sig_data; + public URI signatureDataURI; + private String refVal; - Button btn_sms; - Button btn_cancel; + private boolean userCancelClicked = false; + private boolean userSMSClicked = false; + private boolean pollingDone = false; - Link lnk_sig_data; + public void signalPollingDone() { this.pollingDone = true; getDisplay().wake(); } + public boolean isDone() { return (this.userCancelClicked || this.userSMSClicked || this.pollingDone); } + public boolean wasCancelClicked() { return this.userCancelClicked; } + public boolean wasSMSClicked() { return this.userSMSClicked; } + public boolean wasFIDO2Clicked() { return false; } // TODO + public void reset() { this.userCancelClicked = this.userSMSClicked = this.pollingDone = false; } - /** - * @return the userCancel - */ - public boolean isUserCancel() { - return this.userCancel; - } - - /** - * @return the userSMS - */ - public boolean isUserSMS() { - return this.userSMS; + public void setSMSEnabled(boolean state) { + this.btn_sms.setEnabled(state); } - /** - * @return the done - */ - public boolean isDone() { - return this.done; + public void setFIDO2Enabled(boolean state) { + // TODO } /** @@ -152,30 +81,6 @@ public class MobileBKUFingerprintComposite extends StateComposite { Messages.getString("error.Title") + ": " + errorMessage); } - /** - * @param userCancel - * the userCancel to set - */ - public void setUserCancel(boolean userCancel) { - this.userCancel = userCancel; - } - - /** - * @param userSMS - * the userSMS to set - */ - public void setUserSMS(boolean userSMS) { - this.userSMS = userSMS; - } - - /** - * @param done - * the done to set - */ - public void setDone(boolean done) { - this.done = done; - } - /** * @return the reference value */ @@ -245,15 +150,15 @@ public class MobileBKUFingerprintComposite extends StateComposite { this.lnk_sig_data = new Link(containerComposite, SWT.NATIVE | SWT.RESIZE); SWTUtils.anchor(lnk_sig_data).right(100, -20).top(0, 20); this.lnk_sig_data.setEnabled(true); - SWTUtils.addSelectionListener(lnk_sig_data, (e) -> { SWTUtils.openURL(getSignatureData()); }); + SWTUtils.addSelectionListener(lnk_sig_data, (e) -> { SWTUtils.openURL(this.signatureDataURI); }); this.btn_cancel = new Button(containerComposite, SWT.NATIVE); SWTUtils.anchor(btn_cancel).right(100, -20).bottom(100, -20); - this.btn_cancel.addSelectionListener(new CancelSelectionListener()); + SWTUtils.addSelectionListener(btn_cancel, () -> { userCancelClicked = true; }); this.btn_sms = new Button(containerComposite, SWT.NATIVE); SWTUtils.anchor(btn_sms).right(btn_cancel, -20).bottom(100, -20); - this.btn_sms.addSelectionListener(new SMSSelectionListener()); + SWTUtils.addSelectionListener(btn_sms, () -> { userSMSClicked = true; }); this.lblError = new Label(containerComposite, SWT.WRAP | SWT.NATIVE); SWTUtils.anchor(lblError).right(btn_sms, -10).bottom(100, -20); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/WaitingForAppComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/WaitingForAppComposite.java index ed311957..b1e676f0 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/WaitingForAppComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/mobilebku/WaitingForAppComposite.java @@ -41,13 +41,6 @@ public class WaitingForAppComposite extends StateComposite { private boolean userFIDO2Clicked = false; private boolean pollingDone = false; - /** - * @return the isDone - */ - public Boolean getIsDone() { - return this.pollingDone; - } - public void signalPollingDone() { this.pollingDone = true; getDisplay().wake(); } public boolean isDone() { return (this.userCancelClicked || this.userSMSClicked || this.userFIDO2Clicked || this.pollingDone); } public boolean wasCancelClicked() { return this.userCancelClicked; } 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 15348218..4ddededf 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 @@ -51,6 +51,8 @@ import at.asit.pdfover.gui.composites.mobilebku.MobileBKUFingerprintComposite; import at.asit.pdfover.gui.composites.mobilebku.MobileBKUQRComposite; import at.asit.pdfover.gui.composites.mobilebku.WaitingForAppComposite; import at.asit.pdfover.gui.controls.Dialog.BUTTONS; +import at.asit.pdfover.gui.controls.Dialog.ICON; +import at.asit.pdfover.gui.controls.Dialog; import at.asit.pdfover.gui.controls.ErrorDialog; import at.asit.pdfover.commons.Messages; import at.asit.pdfover.gui.workflow.StateMachine; @@ -188,6 +190,16 @@ public class MobileBKUState extends State { }); } + public void showInformationMessage(final @Nonnull String message) throws UserCancelledException { + Display.getDefault().syncCall(() -> { + Dialog dialog = new Dialog(getStateMachine().getMainShell(), Messages.getString("common.info"), message, BUTTONS.OK, ICON.INFORMATION); + int result = dialog.open(); + if (result == SWT.CANCEL) + throw new UserCancelledException(); + return true; /* dummy return to keep java happy */ + }); + } + /** * Show an error message to the user with "retry" or "cancel" as options * returns normally on "retry", throws UserCancelledException on "cancel" @@ -417,7 +429,7 @@ public class MobileBKUState extends State { /** * start showing the "waiting for app" screen * this method will return immediately */ - public void showWaitingForApp(final @Nonnull String referenceValue, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2) { + public void showWaitingForAppOpen(final @Nonnull String referenceValue, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2) { Display.getDefault().syncExec(() -> { WaitingForAppComposite wfa = getWaitingForAppComposite(); wfa.reset(); @@ -473,76 +485,60 @@ public class MobileBKUState extends State { getWaitingForAppComposite().signalPollingDone(); } - /** - * when fingerprint or faceid is selected in the app - * this information is shown - */ - /*public void showFingerPrintInformation() { - final ATrustStatus status = this.status; - final ATrustHandler handler = this.handler; - - 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); - getMobileBKUFingerprintComposite().setDone(true); - Display display = getStateMachine().getMainShell().getDisplay(); - display.wake(); - checkDone.cancel(); - } - Display.getDefault().wake(); - } catch (Exception e) { - log.error("Error getting signature page", e); - } - } - }, 0, 5000); + public void showWaitingForAppBiometry(final @Nonnull String referenceValue, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2) { Display.getDefault().syncExec(() -> { - MobileBKUFingerprintComposite fingerprintComposite = getMobileBKUFingerprintComposite(); + MobileBKUFingerprintComposite bio = getMobileBKUFingerprintComposite(); + bio.reset(); + + bio.setRefVal(referenceValue); + bio.signatureDataURI = signatureDataURI; + bio.setErrorMessage(null); // TODO + bio.setSMSEnabled(showSmsTan); + bio.setFIDO2Enabled(showFido2); + getStateMachine().display(bio); + }); + } - fingerprintComposite.setRefVal(status.refVal); - fingerprintComposite.setSignatureData(status.signatureDataURL); - fingerprintComposite.setErrorMessage(status.errorMessage); - getStateMachine().display(fingerprintComposite); + // TODO can we maybe deduplicate the various waiting screens' logic? - Display display = getStateMachine().getMainShell().getDisplay(); - while (!fingerprintComposite.isUserCancel() && !fingerprintComposite.isUserSMS() && !fingerprintComposite.isDone()) { - if (!display.readAndDispatch()) { + public enum AppBiometryResult { + /* the user has pressed the FIDO2 button */ + TO_FIDO2, + /* the user has pressed the SMS button */ + TO_SMS, + /* signalAppBiometryDone has been called; this indicates that we should refresh the page */ + UPDATE + }; + + public @Nonnull AppBiometryResult waitForAppBiometry() throws UserCancelledException { + return ISNOTNULL(Display.getDefault().syncCall(() -> { + MobileBKUFingerprintComposite bio = getMobileBKUFingerprintComposite(); + + Display display = bio.getDisplay(); + while (!bio.isDone()) { + if (!display.readAndDispatch()) display.sleep(); - } } - checkDone.cancel(); - if (fingerprintComposite.isUserCancel()) { - fingerprintComposite.setUserCancel(false); - clearRememberedPassword(); - status.errorMessage = "cancel"; - return; - } + getStateMachine().display(this.getWaitingComposite()); - if (fingerprintComposite.isUserSMS()) { -// fingerprintComposite.setUserSMS(false); - status.qrCodeURL = null; + if (bio.wasCancelClicked()) { + clearRememberedPassword(); + throw new UserCancelledException(); } - if (fingerprintComposite.isDone()) - fingerprintComposite.setDone(false); + if (bio.wasSMSClicked()) + return AppBiometryResult.TO_SMS; + + if (bio.wasFIDO2Clicked()) + return AppBiometryResult.TO_FIDO2; - // show waiting composite - getStateMachine().display(this.getWaitingComposite()); - }); - }*/ + return AppBiometryResult.UPDATE; + })); + } - /** - * @return a boolean true if the user has pressed the sms tan button - */ - public boolean getSMSStatus() { - return this.getMobileBKUFingerprintComposite().isUserSMS(); + public void signalAppBiometryDone() { + getMobileBKUFingerprintComposite().signalPollingDone(); } public static class FIDO2Result { -- cgit v1.2.3