From 4269338d2e11028a880c99eb906c93a397fd0c1f Mon Sep 17 00:00:00 2001 From: Jakob Heher Date: Wed, 5 Oct 2022 11:39:07 +0200 Subject: FIDO2 support once again --- .../asit/pdfover/gui/bku/MobileBKUConnector.java | 120 +++++++++++-- .../pdfover/gui/bku/OLDmobile/ATrustHandler.java | 15 +- .../asit/pdfover/gui/bku/mobile/ATrustParser.java | 90 +++++----- .../composites/MobileBKUEnterNumberComposite.java | 7 +- .../gui/composites/MobileBKUEnterTANComposite.java | 68 ++------ .../composites/MobileBKUFingerprintComposite.java | 33 +--- .../gui/composites/MobileBKUQRComposite.java | 185 +++++---------------- .../composites/configuration/AboutComposite.java | 59 +------ .../java/at/asit/pdfover/gui/utils/SWTUtils.java | 28 ++++ .../asit/pdfover/gui/utils/UpdateCheckManager.java | 13 +- .../gui/workflow/states/MobileBKUState.java | 63 ++++--- 11 files changed, 296 insertions(+), 385 deletions(-) (limited to 'pdf-over-gui/src/main/java/at/asit/pdfover/gui') 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 14971ce1..1c07376c 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 @@ -5,12 +5,16 @@ import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; import java.util.ArrayList; +import java.util.Base64; import java.util.List; import java.util.Optional; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.annotation.CheckForNull; import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.swing.text.html.HTML.Tag; import org.apache.hc.client5.http.classic.methods.HttpGet; import org.apache.hc.client5.http.classic.methods.HttpPost; @@ -20,6 +24,7 @@ import org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder; 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.client5.http.impl.classic.RequestFailedException; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.Header; @@ -46,6 +51,10 @@ import at.asit.pdfover.signer.BkuSlConnector; import at.asit.pdfover.signer.SignatureException; import at.asit.pdfover.signer.UserCancelledException; import at.asit.pdfover.signer.pdfas.PdfAs4SLRequest; +import at.asit.webauthn.PublicKeyCredentialRequestOptions; +import at.asit.webauthn.WebAuthN; +import at.asit.webauthn.exceptions.WebAuthNOperationFailed; +import at.asit.webauthn.exceptions.WebAuthNUserCancelled; import static at.asit.pdfover.commons.Constants.ISNOTNULL; @@ -80,6 +89,9 @@ public class MobileBKUConnector implements BkuSlConnector { } } + /* some anti-infinite-loop safeguards so we don't murder the atrust servers by accident */ + private int loopHTTPRequestCounter = 0; + private Long lastHTTPRequestTime = null; /** * Sends the specified request, following redirects (including meta-tag redirects) recursively * @return The JSOUP document retrieved @@ -89,6 +101,16 @@ public class MobileBKUConnector implements BkuSlConnector { * @throws InterruptedException */ private @Nonnull ATrustParser.Result sendHTTPRequest(CloseableHttpClient httpClient, ClassicHttpRequest request) throws IOException, ProtocolException, URISyntaxException { + long now = System.nanoTime(); + if ((lastHTTPRequestTime != null) && ((now - lastHTTPRequestTime) < 2e+9)) { /* less than 2s since last request */ + ++loopHTTPRequestCounter; + if (loopHTTPRequestCounter > 250) + throw new IOException("Infinite loop protection triggered"); + } else { + loopHTTPRequestCounter = 0; + } + lastHTTPRequestTime = now; + log.debug("Sending request to '{}'...", request.getUri().toString()); try (final CloseableHttpResponse response = httpClient.execute(request)) { int httpStatus = response.getCode(); @@ -176,17 +198,30 @@ public class MobileBKUConnector implements BkuSlConnector { return post; } - private static @Nonnull ClassicHttpRequest buildFormSubmit(@Nonnull ATrustParser.HTMLResult html, @Nonnull String submitButtonId) { + private static @Nonnull ClassicHttpRequest buildFormSubmit(@Nonnull ATrustParser.HTMLResult html, @CheckForNull String submitButton) { HttpPost post = new HttpPost(html.formTarget); var builder = MultipartEntityBuilder.create(); for (var pair : html.iterateFormOptions()) builder.addTextBody(pair.getKey(), pair.getValue()); - if (html.submitButtons.containsKey(submitButtonId)) { - var submitInfo = html.submitButtons.get(submitButtonId); - builder.addTextBody(submitInfo.name, submitInfo.value); - } else { - log.warn("Attempted to use submit button #{} which does not exist. Omitting.", submitButtonId); + + if (submitButton != null) { + var submitButtonElm = html.htmlDocument.selectFirst(submitButton); + if (submitButtonElm != null) { + if ("input".equalsIgnoreCase(submitButtonElm.tagName())) { + if ("submit".equalsIgnoreCase(submitButtonElm.attr("type"))) { + String name = submitButtonElm.attr("name"); + if (!name.isEmpty()) + builder.addTextBody(name, submitButtonElm.attr("value")); + } else { + log.warn("Skipped specified submitButton {}, type is {} (not submit)", submitButton, submitButtonElm.attr("type")); + } + } else { + log.warn("Skipped specified submitButton {}, tag name is {} (not input)", submitButton, submitButtonElm.tagName()); + } + } else { + log.warn("Skipped specified submitButton {}, element not found", submitButton); + } } post.setEntity(builder.build()); @@ -198,26 +233,41 @@ public class MobileBKUConnector implements BkuSlConnector { * @return the next request to make, or null if the current response should be returned */ private @Nonnull ClassicHttpRequest presentResponseToUserAndReturnNextRequest(@Nonnull ATrustParser.HTMLResult html) throws UserCancelledException { + if (html.errorBlock != null) { + try { + this.credentials.password = null; + this.state.clearRememberedPassword(); + + if (html.errorBlock.isRecoverable) + this.state.showRecoverableError(html.errorBlock.errorText); + else + this.state.showUnrecoverableError(html.errorBlock.errorText); + return buildFormSubmit(html, "#Button_Back"); + } catch (UserCancelledException e) { + return buildFormSubmit(html, "#Button_Cancel"); + } + } if (html.usernamePasswordBlock != null) { try { while ((this.credentials.username == null) || (this.credentials.password == null)) { this.state.getCredentialsFromUserTo(this.credentials, null); // TODO error message } html.usernamePasswordBlock.setUsernamePassword(this.credentials.username, this.credentials.password); - return buildFormSubmit(html, "Button_Identification"); + return buildFormSubmit(html, "#Button_Identification"); } catch (UserCancelledException e) { - return buildFormSubmit(html, "Button_Cancel"); + return buildFormSubmit(html, "#Button_Cancel"); } } if (html.qrCodeBlock != null) { try (final CloseableHttpClient httpClient = HttpClients.custom().disableRedirectHandling().build()) { - final HttpGet request = new HttpGet(html.qrCodeBlock.qrCodeURI); + final HttpGet request = new HttpGet(html.qrCodeBlock.pollingURI); boolean[] done = new boolean[1]; done[0] = false; Thread longPollThread = new Thread(() -> { + long timeout = System.nanoTime() + (300l * 1000l * 1000l * 1000l); /* a-trust timeout is 5 minutes */ log.debug("longPollThread hello"); while (!done[0]) { - try (final CloseableHttpResponse response = httpClient.execute(new HttpGet(html.qrCodeBlock.pollingURI))) { + try (final CloseableHttpResponse response = httpClient.execute(request)) { JSONObject jsonResponse = new JSONObject(EntityUtils.toString(response.getEntity())); if (jsonResponse.getBoolean("Fin")) state.signalQRScanned(); @@ -233,8 +283,11 @@ public class MobileBKUConnector implements BkuSlConnector { break; } } catch (NoHttpResponseException e) { - continue; + if (timeout <= System.nanoTime()) + state.signalQRScanned(); /* reload to find the timeout error */ + continue; /* httpclient timeout */ } catch (IOException | ParseException | IllegalStateException e) { + if (done[0]) break; log.warn("QR code long polling exception", e); /* sleep so we don't hammer a-trust too hard in case this goes wrong */ try { Thread.sleep(5000); } catch (InterruptedException e2) {} @@ -244,19 +297,54 @@ public class MobileBKUConnector implements BkuSlConnector { }); try { longPollThread.start(); - MobileBKUState.QRResult result = this.state.showQRCode(html.qrCodeBlock.referenceValue, html.qrCodeBlock.qrCodeURI, null); + MobileBKUState.QRResult result = this.state.showQRCode(html.qrCodeBlock.referenceValue, html.qrCodeBlock.qrCodeURI, html.signatureDataLink, html.smsTanLink != null, html.fido2Link != null, null); switch (result) { - case UPDATE: return new HttpGet(html.htmlDocument.baseUri()); - case TO_FIDO2: throw new IllegalStateException(); - case TO_SMS: throw new IllegalStateException(); + 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()); } finally { done[0] = true; request.abort(); try { longPollThread.join(1000); } catch (InterruptedException e) {} } } catch (IOException e) { - log.warn("closing CloseableHttpClient threw exception", e); + log.warn("closing long-polling HttpClient threw exception", e); + } + } + if (html.fido2Block != null) { + // TODO composite for this + if (WebAuthN.isAvailable()) { + try { + var fido2Assertion = PublicKeyCredentialRequestOptions.FromJSONString(html.fido2Block.fidoOptions).get("https://service.a-trust.at"); + + Base64.Encoder base64 = Base64.getEncoder(); + + JSONObject aTrustAssertion = new JSONObject(); + aTrustAssertion.put("id", fido2Assertion.id); + aTrustAssertion.put("rawId", base64.encodeToString(fido2Assertion.rawId)); + aTrustAssertion.put("type", fido2Assertion.type); + aTrustAssertion.put("extensions", new JSONObject()); // TODO fix extensions in library + + JSONObject aTrustAssertionResponse = new JSONObject(); + aTrustAssertion.put("response", aTrustAssertionResponse); + aTrustAssertionResponse.put("authenticatorData", base64.encodeToString(fido2Assertion.response.authenticatorData)); + aTrustAssertionResponse.put("clientDataJson", base64.encodeToString(fido2Assertion.response.clientDataJSON)); + aTrustAssertionResponse.put("signature", base64.encodeToString(fido2Assertion.response.signature)); + if (fido2Assertion.response.userHandle != null) + aTrustAssertionResponse.put("userHandle", base64.encodeToString(fido2Assertion.response.userHandle)); + else + aTrustAssertionResponse.put("userHandle", JSONObject.NULL); + + html.fido2Block.setFIDOResult(aTrustAssertion.toString()); + return buildFormSubmit(html, "#FidoContinue"); + } catch (WebAuthNUserCancelled e) { + log.debug("WebAuthN authentication cancelled by user"); + throw new UserCancelledException(); + } catch (WebAuthNOperationFailed e) { + log.warn("WebAuthN authentication failed", e); + } } } throw new IllegalStateException("No top-level block is set? Something has gone terribly wrong."); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDmobile/ATrustHandler.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDmobile/ATrustHandler.java index 2e69e779..ab85645b 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDmobile/ATrustHandler.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDmobile/ATrustHandler.java @@ -57,6 +57,7 @@ import at.asit.pdfover.gui.controls.Dialog.BUTTONS; import at.asit.pdfover.gui.controls.Dialog.ICON; import at.asit.pdfover.gui.exceptions.ATrustConnectionException; import at.asit.pdfover.gui.utils.FileUploadSource; +import at.asit.pdfover.gui.utils.SWTUtils; import at.asit.pdfover.commons.Messages; import at.asit.pdfover.gui.workflow.states.LocalBKUState; import at.asit.pdfover.gui.workflow.states.MobileBKUState; @@ -399,25 +400,13 @@ public class ATrustHandler { status.errorMessage = null; - final Document responseDocument = Jsoup.parse(responseData); - if (responseData.contains("ExpiresInfo.aspx?sid=")) { // Certificate expiration interstitial - skip if (!expiryNoticeDisplayed) { Display.getDefault().syncExec(()-> { Dialog d = new Dialog(ATrustHandler.this.shell, Messages.getString("common.info"), Messages.getString("mobileBKU.certExpiresSoon"), BUTTONS.YES_NO, ICON.WARNING); if (d.open() == SWT.YES) { - log.debug("Trying to open " + ACTIVATION_URL); - if (Desktop.isDesktopSupported()) { - try { - Desktop.getDesktop().browse(new URI(ACTIVATION_URL)); - return; - } catch (Exception e) { - log.debug("Error opening URL", e); - } - } - log.info("SWT Desktop is not supported on this platform"); - Program.launch(ACTIVATION_URL); + SWTUtils.openURL(ACTIVATION_URL); } }); expiryNoticeDisplayed = true; 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 890ffad1..16f571a3 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 @@ -24,8 +24,8 @@ public class ATrustParser { private static class ComponentParseFailed extends Exception {} private static class TopLevelFormBlock { - protected @Nonnull org.jsoup.nodes.Document htmlDocument; - protected @Nonnull Map formOptions; + protected final @Nonnull org.jsoup.nodes.Document htmlDocument; + protected final @Nonnull Map formOptions; protected TopLevelFormBlock(@Nonnull org.jsoup.nodes.Document d, @Nonnull Map fO) { this.htmlDocument = d; this.formOptions = fO; } protected void abortIfElementMissing(@Nonnull String selector) throws ComponentParseFailed { @@ -56,30 +56,49 @@ public class ATrustParser { } } + public static class ErrorBlock extends TopLevelFormBlock { + public final boolean isRecoverable; + public final @Nonnull String errorText; + + private ErrorBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map formOptions) throws ComponentParseFailed { + super(htmlDocument, formOptions); + if (!formTarget.getPath().contains("/error.aspx")) + throw new ComponentParseFailed(); + + this.isRecoverable = (htmlDocument.selectFirst("#Button_Back") != null); + + String errorText = getElementEnsureNotNull("#Label1").ownText(); + if (errorText.startsWith("Fehler:")) + errorText = errorText.substring(7); + this.errorText = ISNOTNULL(errorText.trim()); + } + } + public static class UsernamePasswordBlock extends TopLevelFormBlock { - private @Nonnull String usernameKey; - private @Nonnull String passwordKey; - public @CheckForNull String errorMessage; + private final @Nonnull String usernameKey; + private final @Nonnull String passwordKey; + public final @CheckForNull String errorMessage; public void setUsernamePassword(String username, String password) { formOptions.put(usernameKey, username); formOptions.put(passwordKey, password); } - private UsernamePasswordBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull Map formOptions) throws ComponentParseFailed { + private UsernamePasswordBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map formOptions) throws ComponentParseFailed { super(htmlDocument, formOptions); abortIfElementMissing("#handynummer"); this.usernameKey = getAttributeEnsureNotNull("#handynummer", "name"); this.passwordKey = getAttributeEnsureNotNull("#signaturpasswort", "name"); + this.errorMessage = null; } } public static class QRCodeBlock extends TopLevelFormBlock { - public @Nonnull String referenceValue; - public @Nonnull URI qrCodeURI; - public @Nonnull URI pollingURI; - public @Nullable String errorMessage; + public final @Nonnull String referenceValue; + public final @Nonnull URI qrCodeURI; + public final @Nonnull URI pollingURI; + public final @Nullable String errorMessage; - private QRCodeBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull Map formOptions) throws ComponentParseFailed { + private QRCodeBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map formOptions) throws ComponentParseFailed { super(htmlDocument, formOptions); abortIfElementMissing("#qrimage"); @@ -102,17 +121,18 @@ public class ATrustParser { log.warn("URI '{}' could not be parsed", pollingUriString); throw new ComponentParseFailed(); } + + this.errorMessage = null; } } public static class Fido2Block extends TopLevelFormBlock { - private @Nonnull String fidoOptions; - private @Nonnull String credentialResultKey; + public final @Nonnull String fidoOptions; + private final @Nonnull String credentialResultKey; - public @Nonnull String getFIDOOptions() { return fidoOptions; } public void setFIDOResult(String result) { formOptions.put(credentialResultKey, result); } - private Fido2Block(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull Map formOptions) throws ComponentParseFailed { + private Fido2Block(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map formOptions) throws ComponentParseFailed { super(htmlDocument, formOptions); abortIfElementMissing("#fidoBlock"); this.fidoOptions = getAttributeEnsureNotNull("#credentialOptions", "value"); @@ -125,22 +145,15 @@ public class ATrustParser { public final @Nonnull URI formTarget; public final @Nonnull Map formOptions = new HashMap<>(); - public static class NameValuePair { - public final @Nonnull String name; - public final @Nonnull String value; - public NameValuePair(@Nonnull String n, @Nonnull String v) { name = n; value = v; } - } - /** - * map: id -> (name, value) - */ - public final @Nonnull Map submitButtons = new HashMap<>(); - public @Nonnull Iterable> iterateFormOptions() { return ISNOTNULL(formOptions.entrySet()); } - /* optional mode switch links (any number may or may not be null) */ + /* optional links (any number may or may not be null) */ + public final @CheckForNull URI signatureDataLink; + public final @CheckForNull URI smsTanLink; public final @CheckForNull URI fido2Link; /* top-level blocks (exactly one is not null) */ + public final @CheckForNull ErrorBlock errorBlock; public final @CheckForNull UsernamePasswordBlock usernamePasswordBlock; public final @CheckForNull QRCodeBlock qrCodeBlock; public final @CheckForNull Fido2Block fido2Block; @@ -148,6 +161,7 @@ public class ATrustParser { private void validate() { Set populated = new HashSet<>(); + if (errorBlock != null) populated.add("errorBlock"); if (usernamePasswordBlock != null) populated.add("usernamePasswordBlock"); if (qrCodeBlock != null) populated.add("qrCodeBlock"); if (fido2Block != null) populated.add("fido2Block"); @@ -178,7 +192,7 @@ public class ATrustParser { */ private @Nullable T TryParseMainBlock(Class clazz) { try { - return clazz.getDeclaredConstructor(org.jsoup.nodes.Document.class, Map.class).newInstance(this.htmlDocument, this.formOptions); + return clazz.getDeclaredConstructor(org.jsoup.nodes.Document.class, URI.class, Map.class).newInstance(this.htmlDocument, this.formTarget, this.formOptions); } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | IllegalArgumentException | SecurityException e) { log.error("Internal parser error; check your method signatures?", e); return null; @@ -194,6 +208,7 @@ public class ATrustParser { } private HTMLResult(@Nonnull org.jsoup.nodes.Document htmlDocument) { + log.trace("Now parsing:\n{}", htmlDocument.toString()); this.htmlDocument = htmlDocument; var forms = htmlDocument.getElementsByTag("form"); @@ -214,20 +229,21 @@ public class ATrustParser { for (var input : mainForm.select("input")) { String name = input.attr("name"); - /* special handling for submit inputs, they only get sent if they are "clicked" */ - if ("submit".equals(input.attr("type"))) { - if (name.isEmpty()) - this.submitButtons.put(input.attr("id"), null); - else - this.submitButtons.put(input.attr("id"), new NameValuePair(name, ISNOTNULL(input.attr("value")))); - } else { - if (!name.isEmpty()) - this.formOptions.put(name, input.attr("value")); - } + if (name.isEmpty()) + continue; + + /* submit inputs omitted here, they only get sent if they are "clicked", cf. MobileBKUConnector::buildFormSubmit */ + if ("submit".equalsIgnoreCase(input.attr("type"))) + continue; + + this.formOptions.put(name, input.attr("value")); } + this.signatureDataLink = getHrefIfExists("#LinkList a[href*=\"ShowSigobj.aspx\"]"); /* grr, they didn't give it an ID */ + this.smsTanLink = getHrefIfExists("#SmsButton"); this.fido2Link = getHrefIfExists("#FidoButton"); + this.errorBlock = TryParseMainBlock(ErrorBlock.class); this.usernamePasswordBlock = TryParseMainBlock(UsernamePasswordBlock.class); this.qrCodeBlock = TryParseMainBlock(QRCodeBlock.class); this.fido2Block = TryParseMainBlock(Fido2Block.class); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterNumberComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterNumberComposite.java index 5f228bcb..6c75b160 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterNumberComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterNumberComposite.java @@ -72,12 +72,11 @@ public class MobileBKUEnterNumberComposite extends StateComposite { MobileBKUEnterNumberComposite.this.btn_ok.setEnabled(false); } catch(InvalidPasswordException ex) { - log.error("Validating input for Mobile BKU failed!", ex); + log.info("Validating input for Mobile BKU failed!", ex); MobileBKUEnterNumberComposite.this.setErrorMessage(ex.getMessage()); MobileBKUEnterNumberComposite.this.txt_password.setFocus(); - } - catch (Exception ex) { - log.error("Validating input for Mobile BKU failed!", ex); + } catch (Exception ex) { + log.info("Validating input for Mobile BKU failed!", ex); MobileBKUEnterNumberComposite.this.setErrorMessage(Messages.getString("error.InvalidPhoneNumber")); MobileBKUEnterNumberComposite.this.txt_number.setFocus(); return; diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterTANComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterTANComposite.java index 7fe40ffe..ab5f5962 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterTANComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterTANComposite.java @@ -41,6 +41,8 @@ import org.eclipse.swt.widgets.Text; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.beust.jcommander.internal.Nullable; + import at.asit.pdfover.commons.Constants; import at.asit.pdfover.commons.Messages; import at.asit.pdfover.gui.utils.SWTUtils; @@ -55,11 +57,6 @@ public class MobileBKUEnterTANComposite extends StateComposite { * */ private final class OkSelectionListener extends SelectionAdapter { - /** - * Empty constructor - */ - public OkSelectionListener() { - } @Override public void widgetSelected(SelectionEvent e) { @@ -127,25 +124,21 @@ public class MobileBKUEnterTANComposite extends StateComposite { String refVal; - String signatureData; - - /** - * @return the signatureData - */ - public String getSignatureData() { - return this.signatureData; - } + URI signatureDataURI; /** * @param signatureData * the signatureData to set */ - public void setSignatureData(String signatureData) { - this.signatureData = signatureData; + public void setSignatureDataURI(@Nullable URI uri) { + this.signatureDataURI = uri; + this.lnk_sig_data.setEnabled(uri != null); } String tan; + private Link lnk_sig_data; + private Label lblTries; private Label lblRefValLabel; private Label lblTan; @@ -267,36 +260,6 @@ public class MobileBKUEnterTANComposite extends StateComposite { } } - /** - * Selection Listener for open button - */ - private final class ShowSignatureDataListener extends SelectionAdapter { - /** - * Empty constructor - */ - public ShowSignatureDataListener() { - } - - @Override - public void widgetSelected(SelectionEvent e) { - try { - String signatureData = MobileBKUEnterTANComposite.this - .getSignatureData(); - if (signatureData != null && !signatureData.equals("")) { - log.debug("Trying to open " + signatureData); - if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(new URI(signatureData)); - } else { - log.info("SWT Desktop is not supported on this platform"); - Program.launch(signatureData); - } - } - } catch (Exception ex) { - log.error("OpenSelectionListener: ", ex); - } - } - } - /** * Create the composite. * @@ -368,27 +331,22 @@ public class MobileBKUEnterTANComposite extends StateComposite { if (text.length() > 3 && MobileBKUEnterTANComposite.this.getRefVal() .startsWith(text.trim())) { - MobileBKUEnterTANComposite.this.setMessage(Messages - .getString("error.EnteredReferenceValue")); + MobileBKUEnterTANComposite.this.setMessage(Messages.getString("error.EnteredReferenceValue")); } } }); - Link lnk_sig_data = new Link(containerComposite, SWT.NATIVE | SWT.RESIZE); + this.lnk_sig_data = new Link(containerComposite, SWT.NATIVE | SWT.RESIZE); SWTUtils.anchor(lnk_sig_data).right(100,-20).top(0,20); lnk_sig_data.setEnabled(true); - lnk_sig_data.addSelectionListener(new ShowSignatureDataListener()); - SWTUtils.setLocalizedText(lnk_sig_data, "mobileBKU.show"); - SWTUtils.setLocalizedToolTipText(lnk_sig_data, "mobileBKU.show_tooltip"); + SWTUtils.addSelectionListener(lnk_sig_data, (e) -> { SWTUtils.openURL(this.signatureDataURI); }); this.btn_ok = new Button(containerComposite, SWT.NATIVE); SWTUtils.anchor(btn_ok).right(100,-20).bottom(100,-20); - SWTUtils.setLocalizedText(btn_ok, "common.Ok"); this.btn_ok.addSelectionListener(new OkSelectionListener()); this.btn_cancel = new Button(containerComposite, SWT.NATIVE); SWTUtils.anchor(btn_cancel).right(btn_ok, -20).bottom(100, -20); - SWTUtils.setLocalizedText(btn_cancel, "common.Cancel"); this.btn_cancel.addSelectionListener(new CancelSelectionListener()); this.lblTries = new Label(containerComposite, SWT.WRAP | SWT.NATIVE); @@ -417,7 +375,11 @@ public class MobileBKUEnterTANComposite extends StateComposite { */ @Override public void reloadResources() { + SWTUtils.setLocalizedText(lnk_sig_data, "mobileBKU.show"); + SWTUtils.setLocalizedToolTipText(lnk_sig_data, "mobileBKU.show_tooltip"); SWTUtils.setLocalizedText(lblRefValLabel, "tanEnter.ReferenceValue"); SWTUtils.setLocalizedText(lblTan, "tanEnter.TAN"); + SWTUtils.setLocalizedText(btn_cancel, "common.Cancel"); + SWTUtils.setLocalizedText(btn_ok, "common.Ok"); } } diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUFingerprintComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUFingerprintComposite.java index af630b8d..afa71f9f 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUFingerprintComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUFingerprintComposite.java @@ -201,37 +201,6 @@ public class MobileBKUFingerprintComposite extends StateComposite { } - - /** - * Selection Listener for open button - */ - private final class ShowSignatureDataListener extends SelectionAdapter { - /** - * Empty constructor - */ - public ShowSignatureDataListener() { - } - - @Override - public void widgetSelected(SelectionEvent e) { - try { - String signatureData = MobileBKUFingerprintComposite.this - .getSignatureData(); - if (signatureData != null && !signatureData.equals("")) { - log.debug("Trying to open " + signatureData); - if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(new URI(signatureData)); - } else { - log.info("SWT Desktop is not supported on this platform"); - Program.launch(signatureData); - } - } - } catch (Exception ex) { - log.error("OpenSelectionListener: ", ex); - } - } - } - /** * Create the composite. * @@ -279,7 +248,7 @@ 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); - this.lnk_sig_data.addSelectionListener(new ShowSignatureDataListener()); + SWTUtils.addSelectionListener(lnk_sig_data, (e) -> { SWTUtils.openURL(getSignatureData()); }); this.btn_cancel = new Button(containerComposite, SWT.NATIVE); SWTUtils.anchor(btn_cancel).right(100, -20).bottom(100, -20); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java index 3f1aa04d..652baed4 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java @@ -20,6 +20,9 @@ import java.awt.Desktop; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.net.URI; +import java.util.Objects; + +import javax.annotation.Nullable; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; @@ -50,43 +53,6 @@ import at.asit.pdfover.gui.workflow.states.State; */ public class MobileBKUQRComposite extends StateComposite { - /** - * - */ - private final class SMSSelectionListener extends SelectionAdapter { - /** - * Empty constructor - */ - public SMSSelectionListener() { - } - - @Override - public void widgetSelected(SelectionEvent e) { - if(!MobileBKUQRComposite.this.btn_sms.getEnabled()) { - return; - } - - MobileBKUQRComposite.this.setUserSMS(true); - MobileBKUQRComposite.this.btn_sms.setEnabled(false); - } - } - - /** - * - */ - private final class CancelSelectionListener extends SelectionAdapter { - /** - * Empty constructor - */ - public CancelSelectionListener() { - } - - @Override - public void widgetSelected(SelectionEvent e) { - MobileBKUQRComposite.this.setUserCancel(true); - } - } - /** * SLF4J Logger instance **/ @@ -94,9 +60,10 @@ public class MobileBKUQRComposite extends StateComposite { private Label lblQR; - private boolean userCancel = false; - private boolean userSMS = false; - private boolean done = false; + private boolean userCancelClicked = false; + private boolean userSMSClicked = false; + private boolean userFIDO2Clicked = false; + private boolean pollingDone = false; private Label lblRefVal; @@ -104,52 +71,24 @@ public class MobileBKUQRComposite extends StateComposite { private ImageData currentQRImage; - private 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 URI signatureDataURI; private Label lblError; private Label lblRefValLabel; private Label lblQRLabel; + private Button btn_fido2; private Button btn_sms; private Button btn_cancel; private Link lnk_sig_data; - /** - * @return the userCancel - */ - public boolean isUserCancel() { - return this.userCancel; - } - - /** - * @return the userSMS - */ - public boolean isUserSMS() { - return this.userSMS; - } - - /** - * @return the done - */ - public boolean isDone() { - return this.done; - } + 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; } + public boolean wasSMSClicked() { return this.userSMSClicked; } + public boolean wasFIDO2Clicked() { return this.userFIDO2Clicked; } + public void reset() { this.userCancelClicked = this.userSMSClicked = this.userFIDO2Clicked = this.pollingDone = false; } /** * Set an error message @@ -163,48 +102,10 @@ public class MobileBKUQRComposite 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 - */ - public String getRefVal() { - return this.refVal; - } - - /** - * @param refVal - * the reference value to set - */ + public String getRefVal() { return this.refVal; } public void setRefVal(String refVal) { - if (this.refVal != null) { - this.refVal = refVal.trim(); - this.lblRefVal.setText(this.refVal); - } else { - this.lblRefVal.setText(""); - } + this.refVal = (refVal != null) ? refVal.trim() : null; + this.lblRefVal.setText(Objects.requireNonNullElse(this.refVal, "")); } private void updateQRImage() { @@ -238,34 +139,17 @@ public class MobileBKUQRComposite extends StateComposite { updateQRImage(); } - /** - * Selection Listener for open button - */ - private final class ShowSignatureDataListener extends SelectionAdapter { - /** - * Empty constructor - */ - public ShowSignatureDataListener() { - } + public void setSMSEnabled(boolean state) { + this.btn_sms.setEnabled(state); + } - @Override - public void widgetSelected(SelectionEvent e) { - try { - String signatureData = MobileBKUQRComposite.this - .getSignatureData(); - if (signatureData != null && !signatureData.equals("")) { - log.debug("Trying to open " + signatureData); - if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(new URI(signatureData)); - } else { - log.info("SWT Desktop is not supported on this platform"); - Program.launch(signatureData); - } - } - } catch (Exception ex) { - log.error("OpenSelectionListener: ", ex); - } - } + public void setFIDO2Enabled(boolean state) { + this.btn_fido2.setEnabled(state); + } + + public void setSignatureDataURI(@Nullable URI uri) { + this.signatureDataURI = uri; + this.lnk_sig_data.setEnabled(uri != null); } /** @@ -317,16 +201,20 @@ public class MobileBKUQRComposite 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); - this.lnk_sig_data.addSelectionListener(new ShowSignatureDataListener()); + 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, -5); - this.btn_cancel.addSelectionListener(new CancelSelectionListener()); + SWTUtils.addSelectionListener(btn_cancel, (e) -> { this.userCancelClicked = true; }); this.btn_sms = new Button(containerComposite, SWT.NATIVE); SWTUtils.anchor(btn_sms).right(btn_cancel, -20).bottom(100, -5); - this.btn_sms.addSelectionListener(new SMSSelectionListener()); + SWTUtils.addSelectionListener(btn_sms, (e) -> { this.userSMSClicked = true; }); + + this.btn_fido2 = new Button(containerComposite, SWT.NATIVE); + SWTUtils.anchor(btn_fido2).right(btn_sms, -20).bottom(100, -5); + SWTUtils.addSelectionListener(btn_fido2, (e) -> {this.userFIDO2Clicked = true; }); + SWTUtils.anchor(lblQR).left(50, 10).right(100, -20).top(lblRefVal, 10).bottom(btn_sms, -10); @@ -364,5 +252,6 @@ public class MobileBKUQRComposite extends StateComposite { SWTUtils.setLocalizedToolTipText(lnk_sig_data, "mobileBKU.show_tooltip"); SWTUtils.setLocalizedText(btn_cancel, "common.Cancel"); SWTUtils.setLocalizedText(btn_sms, "tanEnter.SMS"); + SWTUtils.setLocalizedText(btn_fido2, "tanEnter.FIDO2"); } } diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/configuration/AboutComposite.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/configuration/AboutComposite.java index 35b73e7b..b4675e2a 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/configuration/AboutComposite.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/configuration/AboutComposite.java @@ -47,6 +47,7 @@ public class AboutComposite extends ConfigurationCompositeBase { this.lnkAbout = new Link(this, SWT.WRAP); SWTUtils.anchor(lnkAbout).top(0,5).right(100,-5).left(0,5); SWTUtils.setFontHeight(lnkAbout, Constants.TEXT_SIZE_NORMAL); + SWTUtils.addSelectionListener(lnkAbout, (e) -> { SWTUtils.openURL(Messages.getString("config.LicenseURL")); }); this.lblDataProtection = new Label(this, SWT.WRAP); SWTUtils.anchor(lblDataProtection).top(lnkAbout, 15).left(0,5).right(100,-5); @@ -56,6 +57,7 @@ public class AboutComposite extends ConfigurationCompositeBase { this.lnkDataProtection = new Link(this, SWT.WRAP); SWTUtils.anchor(lnkDataProtection).top(lblDataProtection,10).left(0,5).right(100,-5); SWTUtils.setFontHeight(lnkDataProtection, Constants.TEXT_SIZE_NORMAL); + SWTUtils.addSelectionListener(lnkDataProtection, (e) -> { SWTUtils.openURL(Messages.getString("config.DataProtectionURL")); }); this.lnkUpdateCheckStatus = new Link(this, SWT.NONE); SWTUtils.anchor(lnkUpdateCheckStatus).bottom(100, -5).left(0,5); @@ -84,56 +86,13 @@ public class AboutComposite extends ConfigurationCompositeBase { SWTUtils.reanchor(lnkDataProtection).bottom(btnUpdateCheck,-5); - this.lnkAbout.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - try { - URI url = new URI(Messages.getString("config.LicenseURL")); - log.debug("Trying to open " + url.toString()); - if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(url); - } else { - log.info("AWT Desktop is not supported on this platform"); - Program.launch(url.toString()); - } - } catch (IOException ex) { - log.error("AboutComposite: ", ex); - } catch (URISyntaxException ex) { - log.error("AboutComposite: ", ex); - } - } - }); - - this.lnkDataProtection.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - try { - URI url = new URI(Messages.getString("config.DataProtectionURL")); - log.debug("Trying to open " + url.toString()); - if (Desktop.isDesktopSupported()) { - Desktop.getDesktop().browse(url); - } else { - log.info("AWT Desktop is not supported on this platform"); - Program.launch(url.toString()); - } - } catch (IOException ex) { - log.error("AboutComposite: ", ex); - } catch (URISyntaxException ex) { - log.error("AboutComposite: ", ex); - } - } - }); - - this.btnOpenLogDirectory.addSelectionListener(new SelectionAdapter() { - @Override - public void widgetSelected(SelectionEvent e) { - try - { - if (Desktop.isDesktopSupported()) - Desktop.getDesktop().open(new File(Constants.CONFIG_DIRECTORY + File.separator + "logs")); - } catch (Exception ex) { - log.warn("Failed to open log directory: ", ex); - } + SWTUtils.addSelectionListener(btnOpenLogDirectory, (e) -> { + try + { + if (Desktop.isDesktopSupported()) + Desktop.getDesktop().open(new File(Constants.CONFIG_DIRECTORY + File.separator + "logs")); + } catch (Exception ex) { + log.warn("Failed to open log directory: ", ex); } }); diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/SWTUtils.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/SWTUtils.java index ef93e3e9..d276ecd6 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/SWTUtils.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/SWTUtils.java @@ -1,9 +1,14 @@ package at.asit.pdfover.gui.utils; +import java.awt.Desktop; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.URI; +import java.net.URISyntaxException; import java.util.function.Consumer; +import javax.annotation.Nullable; + import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ScrolledComposite; import org.eclipse.swt.events.SelectionAdapter; @@ -14,6 +19,7 @@ import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.FormAttachment; import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.program.Program; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; @@ -171,4 +177,26 @@ public final class SWTUtils { log.error("Failed to add selection listener on object of type {}", swtObj.getClass().getSimpleName(), e); } } + + public static void openURL(@Nullable URI uri) { + try { + if (uri == null) return; + if (Desktop.isDesktopSupported()) { + Desktop.getDesktop().browse(uri); + } else { + Program.launch(uri.toURL().toExternalForm()); + } + } catch (Exception e) { + log.warn("Failed to open URI: {}", uri, e); + } + } + + public static void openURL(@Nullable String uri) { + if (uri == null) return; + try { + openURL(new URI(uri)); + } catch (URISyntaxException e) { + log.warn("Failed to open URI: {}", uri, e); + } + } } diff --git a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/UpdateCheckManager.java b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/UpdateCheckManager.java index 92104176..133ecdcb 100644 --- a/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/UpdateCheckManager.java +++ b/pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/UpdateCheckManager.java @@ -70,18 +70,7 @@ public final class UpdateCheckManager { BUTTONS.OK_CANCEL, ICON.INFORMATION); if (info.open() == SWT.OK) - { - if (Desktop.isDesktopSupported()) { - try { - Desktop.getDesktop().browse(new URI(Constants.UPDATE_URL)); - } catch (Exception e) { - log.error("Error opening update location ", e); - } - } else { - log.info("SWT Desktop is not supported on this platform"); - Program.launch(Constants.UPDATE_URL); - } - } + SWTUtils.openURL(Constants.UPDATE_URL); }); } 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 9c3fc807..563b3b88 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 @@ -190,6 +190,32 @@ public class MobileBKUState extends State { }); } + /** + * Show an error message to the user with "retry" or "cancel" as options + * returns normally on "retry", throws UserCancelledException on "cancel" + */ + public void showRecoverableError(final @Nonnull String errorMessage) throws UserCancelledException { + Display.getDefault().syncCall(() -> { + ErrorDialog error = new ErrorDialog(getStateMachine().getMainShell(), errorMessage, BUTTONS.RETRY_CANCEL); + int result = error.open(); + if (result == SWT.CANCEL) + throw new UserCancelledException(); + return true; /* dummy return */ + }); + } + + /** + * Show an error message to the user with only an "ok" option + * throws UserCancelledException afterwards + */ + public void showUnrecoverableError(final @Nonnull String errorMessage) throws UserCancelledException { + Display.getDefault().syncCall(() -> { + ErrorDialog error = new ErrorDialog(getStateMachine().getMainShell(), errorMessage, BUTTONS.OK); + error.open(); + throw new UserCancelledException(); + }); + } + public static class UsernameAndPassword { public @CheckForNull String username; public @CheckForNull String password; @@ -313,7 +339,7 @@ public class MobileBKUState extends State { if (!tan.isUserAck()) { // we need the TAN tan.setRefVal(mobileStatus.refVal); - tan.setSignatureData(mobileStatus.signatureDataURL); + try { tan.setSignatureDataURI(new URI(mobileStatus.signatureDataURL)); } catch (URISyntaxException e) {} tan.setErrorMessage(mobileStatus.errorMessage); if (mobileStatus.tanTries < ATrustStatus.MOBILE_MAX_TAN_TRIES && mobileStatus.tanTries > 0) { @@ -367,7 +393,7 @@ public class MobileBKUState extends State { * it is the responsibility of the caller to perform AJAX long polling * @return */ - public QRResult showQRCode(@Nonnull String referenceValue, @Nonnull URI qrCodeURI, @Nullable String errorMessage) throws UserCancelledException { + public QRResult showQRCode(final @Nonnull String referenceValue, @Nonnull URI qrCodeURI, @Nullable URI signatureDataURI, final boolean showSmsTan, final boolean showFido2, final @Nullable String errorMessage) throws UserCancelledException { byte[] qrCode; try (final CloseableHttpClient httpClient = HttpClients.createDefault()) { try (final CloseableHttpResponse response = httpClient.execute(new HttpGet(qrCodeURI))) { @@ -381,18 +407,18 @@ public class MobileBKUState extends State { 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.reset(); - qr.setRefVal(status.refVal); - qr.setSignatureData(status.signatureDataURL); - qr.setErrorMessage(status.errorMessage); + qr.setRefVal(referenceValue); + qr.setSignatureDataURI(signatureDataURI); + qr.setErrorMessage(errorMessage); qr.setQR(qrCodeCopy); + qr.setSMSEnabled(showSmsTan); + qr.setFIDO2Enabled(showFido2); getStateMachine().display(qr); Display display = getStateMachine().getMainShell().getDisplay(); - while (!qr.isUserCancel() && !qr.isUserSMS() && !qr.isDone()) { + while (!qr.isDone()) { if (!display.readAndDispatch()) { display.sleep(); } @@ -400,18 +426,18 @@ public class MobileBKUState extends State { getStateMachine().display(this.getWaitingComposite()); - if (qr.isUserCancel()) { + if (qr.wasCancelClicked()) { clearRememberedPassword(); throw new UserCancelledException(); } - if (qr.isUserSMS()) + if (qr.wasSMSClicked()) return QRResult.TO_SMS; - - if (qr.isDone()) - return QRResult.UPDATE; - throw new RuntimeException("unexpected display wake"); + if (qr.wasFIDO2Clicked()) + return QRResult.TO_FIDO2; + + return QRResult.UPDATE; }); } @@ -420,10 +446,7 @@ public class MobileBKUState extends State { * (any ongoing showQRCode call will then return) */ public void signalQRScanned() { - getMobileBKUQRComposite().setDone(true); - Display display = getStateMachine(). - getMainShell().getDisplay(); - display.wake(); + getMobileBKUQRComposite().signalPollingDone(); } /** @@ -455,7 +478,7 @@ public class MobileBKUState extends State { } }, 0, 5000); - QRResult result = showQRCode(status.refVal, new URI(status.baseURL).resolve(status.qrCodeURL), status.errorMessage); + QRResult result = showQRCode(status.refVal, new URI(status.baseURL).resolve(status.qrCodeURL), new URI(status.baseURL).resolve(status.signatureDataURL), true, false, status.errorMessage); checkDone.cancel(); if (result == QRResult.TO_SMS) status.qrCodeURL = null; -- cgit v1.2.3