diff options
author | Jakob Heher <jakob.heher@iaik.tugraz.at> | 2022-10-05 11:39:07 +0200 |
---|---|---|
committer | Jakob Heher <jakob.heher@iaik.tugraz.at> | 2022-10-05 11:39:07 +0200 |
commit | 4269338d2e11028a880c99eb906c93a397fd0c1f (patch) | |
tree | af3ab0f0988fe088e81fc946c38cf47fbaf47e07 /pdf-over-gui/src | |
parent | d6f4b34eae2e977cdd0339fb17302976fdae0574 (diff) | |
download | pdf-over-4269338d2e11028a880c99eb906c93a397fd0c1f.tar.gz pdf-over-4269338d2e11028a880c99eb906c93a397fd0c1f.tar.bz2 pdf-over-4269338d2e11028a880c99eb906c93a397fd0c1f.zip |
FIDO2 support once again
Diffstat (limited to 'pdf-over-gui/src')
14 files changed, 299 insertions, 385 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 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<String, String> formOptions; + protected final @Nonnull org.jsoup.nodes.Document htmlDocument; + protected final @Nonnull Map<String, String> formOptions; protected TopLevelFormBlock(@Nonnull org.jsoup.nodes.Document d, @Nonnull Map<String,String> 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<String, String> 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<String, String> formOptions) throws ComponentParseFailed { + private UsernamePasswordBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map<String, String> 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<String, String> formOptions) throws ComponentParseFailed { + private QRCodeBlock(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map<String, String> 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<String, String> formOptions) throws ComponentParseFailed { + private Fido2Block(@Nonnull org.jsoup.nodes.Document htmlDocument, @Nonnull URI formTarget, @Nonnull Map<String, String> 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<String, String> 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<String, @CheckForNull NameValuePair> submitButtons = new HashMap<>(); - public @Nonnull Iterable<Map.Entry<String, String>> 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<String> 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 <T extends TopLevelFormBlock> @Nullable T TryParseMainBlock(Class<T> 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;
@@ -268,36 +261,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.
*
* @param parent
@@ -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;
@@ -51,52 +54,16 @@ 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
**/
static final Logger log = LoggerFactory.getLogger(MobileBKUQRComposite.class);
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;
diff --git a/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages.properties b/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages.properties index 65e353a1..2d5d74f1 100644 --- a/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages.properties +++ b/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages.properties @@ -253,6 +253,7 @@ tanEnter.FP=Please open the Handy-Signature app\nand confirm the signature. tanEnter.QR=QR code\: tanEnter.ReferenceValue=Reference value\: tanEnter.SMS=Request &SMS +tanEnter.FIDO2=&FIDO2 tanEnter.TAN=TAN\: tanEnter.tries=%d tries left\! tanEnter.try=Only 1 try left\! diff --git a/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages_de.properties b/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages_de.properties index a2afff03..a75dfbb4 100644 --- a/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages_de.properties +++ b/pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages_de.properties @@ -244,6 +244,7 @@ tanEnter.QR=QR Code\: tanEnter.FP=Bitte öffnen Sie die Handy-Signatur App\nund bestätigen Sie die Signatur. tanEnter.ReferenceValue=Vergleichswert\: tanEnter.SMS=&SMS anfordern +tanEnter.FIDO2=&FIDO2 tanEnter.TAN=TAN\: tanEnter.APPTAN=Bitte öffnen Sie die Handy-Signatur App\nTAN\: tanEnter.tries=%d Versuche übrig\! diff --git a/pdf-over-gui/src/main/resources/logback.xml b/pdf-over-gui/src/main/resources/logback.xml index 866a82f7..4e33264a 100644 --- a/pdf-over-gui/src/main/resources/logback.xml +++ b/pdf-over-gui/src/main/resources/logback.xml @@ -22,6 +22,7 @@ </triggeringPolicy> </appender> <logger name="at.asit.pdfover" level="INFO"/> + <logger name="at.asit.pdfover.gui.bku" level="TRACE"/> <root level="WARN"> <appender-ref ref="STDOUT"/> <appender-ref ref="LOGFILE"/> |