summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Heher <jakob.heher@iaik.tugraz.at>2022-10-05 11:39:07 +0200
committerJakob Heher <jakob.heher@iaik.tugraz.at>2022-10-05 11:39:07 +0200
commit4269338d2e11028a880c99eb906c93a397fd0c1f (patch)
treeaf3ab0f0988fe088e81fc946c38cf47fbaf47e07
parentd6f4b34eae2e977cdd0339fb17302976fdae0574 (diff)
downloadpdf-over-4269338d2e11028a880c99eb906c93a397fd0c1f.tar.gz
pdf-over-4269338d2e11028a880c99eb906c93a397fd0c1f.tar.bz2
pdf-over-4269338d2e11028a880c99eb906c93a397fd0c1f.zip
FIDO2 support once again
-rw-r--r--pdf-over-gui/pom.xml5
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/MobileBKUConnector.java120
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/OLDmobile/ATrustHandler.java15
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/bku/mobile/ATrustParser.java90
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterNumberComposite.java7
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUEnterTANComposite.java68
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUFingerprintComposite.java33
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/MobileBKUQRComposite.java185
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/composites/configuration/AboutComposite.java59
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/SWTUtils.java28
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/utils/UpdateCheckManager.java13
-rw-r--r--pdf-over-gui/src/main/java/at/asit/pdfover/gui/workflow/states/MobileBKUState.java63
-rw-r--r--pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages.properties1
-rw-r--r--pdf-over-gui/src/main/resources/at/asit/pdfover/gui/messages_de.properties1
-rw-r--r--pdf-over-gui/src/main/resources/logback.xml1
-rw-r--r--pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java5
16 files changed, 307 insertions, 387 deletions
diff --git a/pdf-over-gui/pom.xml b/pdf-over-gui/pom.xml
index 4cb083e3..125a3e33 100644
--- a/pdf-over-gui/pom.xml
+++ b/pdf-over-gui/pom.xml
@@ -74,6 +74,11 @@
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
+ <dependency>
+ <groupId>at.a-sit</groupId>
+ <artifactId>webauthn-java</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ </dependency>
</dependencies>
<dependencyManagement>
<dependencies>
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"/>
diff --git a/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java b/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java
index c7be135f..561452a4 100644
--- a/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java
+++ b/pdf-over-signer/src/main/java/at/asit/pdfover/signer/pdfas/PdfAs4Signer.java
@@ -160,8 +160,9 @@ public class PdfAs4Signer {
Throwable rootCause = e;
while (rootCause.getCause() != null)
rootCause = rootCause.getCause();
- try { /* error code 6001 is user cancellation */
- if (((SLPdfAsException)rootCause).getMessage().startsWith("6001 :"))
+ try { /* error code 60xx is user cancellation */
+ int errorCode = Integer.parseInt(((SLPdfAsException)rootCause).getMessage().split(":",2)[0].trim());
+ if ((6000 <= errorCode) && (errorCode <= 6099))
throw new UserCancelledException();
} catch (ClassCastException e2) { /* fall through to wrapped throw */}
}