From 2b582cda9e75aec85f66f6f89bd452de1dc2b9e6 Mon Sep 17 00:00:00 2001 From: xflord <493294@mail.muni.cz> Date: Wed, 25 Oct 2023 13:09:27 +0200 Subject: [PATCH] feat: backend ssh public key validation * replaced regexp in SSH key validators with call to backend validation method * this unifies the process and offers a more strict validation --- .../perun/wui/json/managers/UsersManager.java | 13 +++ .../wui/registrar/widgets/items/ListBox.java | 51 +++++++++- .../wui/registrar/widgets/items/TextArea.java | 39 ++++++-- .../registrar/widgets/items/TextField.java | 39 ++++++-- .../validators/PerunFormItemValidator.java | 1 + .../validators/SshKeysListBoxValidator.java | 92 +++++++++++++------ .../validators/SshKeysTextAreaValidator.java | 71 +++++++++----- .../validators/SshKeysTextFieldValidator.java | 72 ++++++++++----- 8 files changed, 290 insertions(+), 88 deletions(-) diff --git a/perun-wui-core/src/main/java/cz/metacentrum/perun/wui/json/managers/UsersManager.java b/perun-wui-core/src/main/java/cz/metacentrum/perun/wui/json/managers/UsersManager.java index 3f91c4e7..9982caf3 100644 --- a/perun-wui-core/src/main/java/cz/metacentrum/perun/wui/json/managers/UsersManager.java +++ b/perun-wui-core/src/main/java/cz/metacentrum/perun/wui/json/managers/UsersManager.java @@ -252,6 +252,19 @@ public static Request validatePreferredEmailChange(int userId, String token, Jso } + /** + * Validate ssh public key, throws exception if validation fails + * + * @param sshKey ssh public key to verify + * @return Request unique request + */ + public static Request validateSSHKey(String sshKey, JsonEvents events) { + JsonClient client = new JsonClient(events); + client.put("sshKey", sshKey); + + return client.call(USERS_MANAGER + "validateSSHKey"); + } + /** * Change password in selected namespace * diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/ListBox.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/ListBox.java index 81470486..4db932bd 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/ListBox.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/ListBox.java @@ -9,6 +9,7 @@ import com.google.gwt.user.client.ui.Widget; import com.google.gwt.user.client.ui.VerticalPanel; import cz.metacentrum.perun.wui.json.Events; +import cz.metacentrum.perun.wui.model.PerunException; import cz.metacentrum.perun.wui.registrar.client.resources.PerunRegistrarResources; import cz.metacentrum.perun.wui.registrar.widgets.items.validators.ListBoxValidator; import cz.metacentrum.perun.wui.registrar.widgets.items.validators.SshKeysListBoxValidator; @@ -19,7 +20,9 @@ import org.gwtbootstrap3.client.ui.html.Paragraph; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * Represents ListBox form item. @@ -30,6 +33,7 @@ public class ListBox extends WidgetBox { private final ListBoxValidator validator; List inputList; + Map handlers; public ListBox(PerunForm form, ApplicationFormItemData item, String lang) { super(form, item, lang); @@ -78,6 +82,7 @@ public boolean validateLocal() { @Override protected Widget initWidget() { inputList = new ArrayList<>(); + handlers = new HashMap<>(); return super.initWidget(); } @@ -95,13 +100,48 @@ protected void setValidationTriggers() { if (isOnlyPreview()) { return; } - for (ExtendedTextBox input : inputList) { - input.addBlurHandler(new BlurHandler() { + if ("urn:perun:user:attribute-def:def:sshPublicKey".equals(this.getItemData().getFormItem().getPerunDestinationAttribute())) { + final Events nothingEvent = new Events() { @Override - public void onBlur(BlurEvent event) { - validateLocal(); + public void onFinished(Boolean result) { + + } + + @Override + public void onError(PerunException error) { + } - }); + + @Override + public void onLoadingStart() { + + } + }; + + for (ExtendedTextBox input : inputList) { + + if (!handlers.containsKey(input)) { + BlurHandler handler = new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + validate(nothingEvent); + } + }; + input.addBlurHandler(handler); + handlers.put(input, handler); + } + + } + + } else { + for (ExtendedTextBox input : inputList) { + input.addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + validateLocal(); + } + }); + } } } @@ -154,6 +194,7 @@ protected void generateItemWithRemoveButton(VerticalPanel vp) { PerunButton removeButton = new PerunButton("", new ClickHandler() { public void onClick(ClickEvent event) { inputList.remove(input); + handlers.remove(input); vp.remove(hp); validateLocal(); } diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextArea.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextArea.java index 8e7c7062..e18011ab 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextArea.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextArea.java @@ -4,6 +4,7 @@ import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.user.client.ui.Widget; import cz.metacentrum.perun.wui.json.Events; +import cz.metacentrum.perun.wui.model.PerunException; import cz.metacentrum.perun.wui.model.beans.ApplicationFormItemData; import cz.metacentrum.perun.wui.registrar.widgets.PerunForm; import cz.metacentrum.perun.wui.registrar.widgets.items.validators.PerunFormItemValidator; @@ -87,12 +88,38 @@ public void setValidationTriggers() { if (isOnlyPreview()) { return; } - getBox().addBlurHandler(new BlurHandler() { - @Override - public void onBlur(BlurEvent event) { - validateLocal(); - } - }); + if ("urn:perun:user:attribute-def:def:sshPublicKey".equals(this.getItemData().getFormItem().getPerunDestinationAttribute())) { + final Events nothingEvent = new Events() { + @Override + public void onFinished(Boolean result) { + + } + + @Override + public void onError(PerunException error) { + + } + + @Override + public void onLoadingStart() { + + } + }; + + getBox().addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + validate(nothingEvent); + } + }); + } else { + getBox().addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + validateLocal(); + } + }); + } } @Override diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextField.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextField.java index 0a244c8c..389e2598 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextField.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/TextField.java @@ -4,6 +4,7 @@ import com.google.gwt.event.dom.client.BlurHandler; import com.google.gwt.user.client.ui.Widget; import cz.metacentrum.perun.wui.json.Events; +import cz.metacentrum.perun.wui.model.PerunException; import cz.metacentrum.perun.wui.model.beans.ApplicationFormItemData; import cz.metacentrum.perun.wui.registrar.widgets.PerunForm; import cz.metacentrum.perun.wui.registrar.widgets.items.validators.PerunFormItemValidator; @@ -104,12 +105,38 @@ public void setValidationTriggers() { if (isOnlyPreview()) { return; } - getBox().addBlurHandler(new BlurHandler() { - @Override - public void onBlur(BlurEvent event) { - validateLocal(); - } - }); + if ("urn:perun:user:attribute-def:def:sshPublicKey".equals(this.getItemData().getFormItem().getPerunDestinationAttribute())) { + final Events nothingEvent = new Events() { + @Override + public void onFinished(Boolean result) { + + } + + @Override + public void onError(PerunException error) { + + } + + @Override + public void onLoadingStart() { + + } + }; + + getBox().addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + validate(nothingEvent); + } + }); + } else { + getBox().addBlurHandler(new BlurHandler() { + @Override + public void onBlur(BlurEvent event) { + validateLocal(); + } + }); + } } @Override diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/PerunFormItemValidator.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/PerunFormItemValidator.java index d408b90a..ea1f492a 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/PerunFormItemValidator.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/PerunFormItemValidator.java @@ -20,6 +20,7 @@ enum Result { INVALID_FORMAT_EMAIL, LOGIN_NOT_AVAILABLE, CHECKING_LOGIN, + CHECKING_SSH, CANT_CHECK_LOGIN, EMPTY_PASSWORD, PASSWORD_TOO_SHORT, diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysListBoxValidator.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysListBoxValidator.java index d4f4c096..aa4cf09c 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysListBoxValidator.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysListBoxValidator.java @@ -1,11 +1,18 @@ package cz.metacentrum.perun.wui.registrar.widgets.items.validators; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.core.client.JavaScriptObject; +import cz.metacentrum.perun.wui.json.Events; +import cz.metacentrum.perun.wui.json.JsonEvents; +import cz.metacentrum.perun.wui.json.managers.UsersManager; +import cz.metacentrum.perun.wui.model.PerunException; import cz.metacentrum.perun.wui.registrar.widgets.items.ListBox; import cz.metacentrum.perun.wui.widgets.boxes.ExtendedTextBox; import org.gwtbootstrap3.client.ui.constants.ValidationState; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; + /** * Validator for ListBox * @@ -13,11 +20,8 @@ */ public class SshKeysListBoxValidator extends ListBoxValidator { - RegExp regExp = RegExp.compile("^(" + - "(ssh-(rsa|dss|ed25519)(-cert-v01@openssh.com)?)|" + - "(sk-(ssh-ed25519|ecdsa-sha2-nistp256)(-cert-v01)?@openssh.com)|" + - "(ecdsa-sha2-nistp(256|384|521)(-cert-v01@openssh.com)?))" + - " (([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?)( [^,\n]+)?$"); + static String wrongValues = ""; + Map wrongVals = new TreeMap<>(); @Override public boolean validateLocal(ListBox listBox) { @@ -26,34 +30,64 @@ public boolean validateLocal(ListBox listBox) { listBox.setRawStatus(getTransl().cantBeEmpty(), ValidationState.ERROR); return false; } + listBox.setStatus(ValidationState.SUCCESS); + return true; + } - if (listBox.getValue() != null && !listBox.getValue().isEmpty()) { + @Override + public void validate(ListBox listBox, Events events) { + events.onLoadingStart(); - String wrongValues = ""; - int index = 1; - for (ExtendedTextBox extendedTextBox : listBox.getListValue()) { - String sshKey = extendedTextBox.getValue(); + if (!validateLocal(listBox)) { + events.onFinished(false); + return; + } - if (sshKey.contains(",")) { - setResult(Result.INVALID_FORMAT); - listBox.setStatus(getTransl().sshKeySeparatorNotAllowed(), ValidationState.ERROR); - return false; - } + if (listBox.getValue() == null || listBox.getValue().isEmpty()) { + events.onFinished(true); + return; + } - MatchResult matcher = regExp.exec(sshKey); - if (matcher == null) { - wrongValues += "
" + index + ". " + (sshKey.length() > 25 ? sshKey.substring(0, 23) + "..." : sshKey); - } - index++; - } - if (!wrongValues.isEmpty()) { + + wrongValues = ""; + wrongVals.clear(); + int index = 1; + for (ExtendedTextBox extendedTextBox : listBox.getListValue()) { + String sshKey = extendedTextBox.getValue(); + + if (sshKey.contains(",")) { setResult(Result.INVALID_FORMAT); - listBox.setRawStatus(getTransl().incorrectFormatItemList() + " " + wrongValues + "", ValidationState.ERROR); - return false; + listBox.setStatus(getTransl().sshKeySeparatorNotAllowed(), ValidationState.ERROR); + events.onFinished(false); } - } - listBox.setStatus(ValidationState.SUCCESS); - return true; + int currIndex = index; + UsersManager.validateSSHKey(sshKey, new JsonEvents() { + @Override + public void onFinished(JavaScriptObject result) { + if (wrongValues.isEmpty()) { + events.onFinished(true); + listBox.setStatus(ValidationState.SUCCESS); + } + } + + @Override + public void onError(PerunException error) { + wrongVals.put(currIndex, sshKey); + wrongValues += "
" + currIndex + ". " + (sshKey.length() > 25 ? sshKey.substring(0, 23) + "..." : sshKey); + setResult(Result.INVALID_FORMAT); + listBox.setRawStatus(getTransl().incorrectFormatItemList() + " " + wrongVals.entrySet().stream().map((entry) -> entry.getKey() + ". " + + (entry.getValue().length() > 25 ? entry.getValue().substring(0,23) + "..." : entry.getValue())) + "", ValidationState.ERROR); + events.onFinished(false); + } + + @Override + public void onLoadingStart() { + setResult(Result.CHECKING_SSH); + listBox.unsetStatus(); + } + }); + index++; + } } } diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextAreaValidator.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextAreaValidator.java index 5f16d7b1..cee1c09c 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextAreaValidator.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextAreaValidator.java @@ -1,7 +1,10 @@ package cz.metacentrum.perun.wui.registrar.widgets.items.validators; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.core.client.JavaScriptObject; +import cz.metacentrum.perun.wui.json.Events; +import cz.metacentrum.perun.wui.json.JsonEvents; +import cz.metacentrum.perun.wui.json.managers.UsersManager; +import cz.metacentrum.perun.wui.model.PerunException; import cz.metacentrum.perun.wui.registrar.widgets.items.TextArea; import org.gwtbootstrap3.client.ui.constants.ValidationState; @@ -13,12 +16,7 @@ * @author Pavel Zlámal */ public class SshKeysTextAreaValidator extends TextAreaValidator { - - RegExp regExp = RegExp.compile("^(" + - "(ssh-(rsa|dss|ed25519)(-cert-v01@openssh.com)?)|" + - "(sk-(ssh-ed25519|ecdsa-sha2-nistp256)(-cert-v01)?@openssh.com)|" + - "(ecdsa-sha2-nistp(256|384|521)(-cert-v01@openssh.com)?))" + - " (([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?)( [^,\n]+)?$"); + String wrongValues = ""; @Override public boolean validateLocal(TextArea textArea) { @@ -74,26 +72,57 @@ public boolean validateLocal(TextArea textArea) { } } */ + } + + textArea.setStatus(ValidationState.SUCCESS); + return true; - // normalize value just in case - sshKeys = sshKeys.replaceAll("(,)+", ","); - List keys = Arrays.stream(sshKeys.split(",")).collect(Collectors.toList()); + } + @Override + public void validate(TextArea textArea, Events events) { + events.onLoadingStart(); + + if (!validateLocal(textArea)) { + events.onFinished(false); + return; + } + + if (textArea.getValue() == null || textArea.getValue().isEmpty()) { + events.onFinished(true); + return; + } - for (String key : keys) { - MatchResult matcher = regExp.exec(key); - if (matcher == null) { + String sshKeys = textArea.getValue(); + // normalize value just in case + sshKeys = sshKeys.replaceAll("(,)+", ","); + List keys = Arrays.stream(sshKeys.split(",")).collect(Collectors.toList()); + wrongValues = ""; + + for (String key : keys) { + UsersManager.validateSSHKey(key, new JsonEvents() { + @Override + public void onFinished(JavaScriptObject result) { + if (wrongValues.isEmpty()) { + textArea.setStatus(ValidationState.SUCCESS); + events.onFinished(true); + } + } + + @Override + public void onError(PerunException error) { + wrongValues += key; int length = Math.min(key.length(), 30); textArea.setRawStatus(getTransl().sshKeyFormat(key.substring(0, length)+((length == 30) ? "..." : "")), ValidationState.ERROR); setResult(Result.INVALID_FORMAT); - return false; + events.onFinished(false); } - } + @Override + public void onLoadingStart() { + setResult(Result.CHECKING_SSH); + textArea.unsetStatus(); + } + }); } - - textArea.setStatus(ValidationState.SUCCESS); - return true; - } - } diff --git a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextFieldValidator.java b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextFieldValidator.java index fd42724f..9e2fc1fc 100644 --- a/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextFieldValidator.java +++ b/perun-wui-registrar/src/main/java/cz/metacentrum/perun/wui/registrar/widgets/items/validators/SshKeysTextFieldValidator.java @@ -1,7 +1,10 @@ package cz.metacentrum.perun.wui.registrar.widgets.items.validators; -import com.google.gwt.regexp.shared.MatchResult; -import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.core.client.JavaScriptObject; +import cz.metacentrum.perun.wui.json.Events; +import cz.metacentrum.perun.wui.json.JsonEvents; +import cz.metacentrum.perun.wui.json.managers.UsersManager; +import cz.metacentrum.perun.wui.model.PerunException; import cz.metacentrum.perun.wui.registrar.widgets.items.TextField; import org.gwtbootstrap3.client.ui.constants.ValidationState; @@ -13,12 +16,7 @@ * @author Pavel Zlámal */ public class SshKeysTextFieldValidator extends TextFieldValidator { - - RegExp regExp = RegExp.compile("^(" + - "(ssh-(rsa|dss|ed25519)(-cert-v01@openssh.com)?)|" + - "(sk-(ssh-ed25519|ecdsa-sha2-nistp256)(-cert-v01)?@openssh.com)|" + - "(ecdsa-sha2-nistp(256|384|521)(-cert-v01@openssh.com)?))" + - " (([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)?)( [^,\n]+)?$"); + String wrongValues = ""; @Override public boolean validateLocal(TextField textField) { @@ -69,25 +67,57 @@ public boolean validateLocal(TextField textField) { } */ - // normalize value just in case - sshKeys = sshKeys.replaceAll("(,)+", ","); - List keys = Arrays.stream(sshKeys.split(",")).collect(Collectors.toList()); + } + + textField.setStatus(ValidationState.SUCCESS); + return true; + + } + + @Override + public void validate(TextField textField, Events events) { + events.onLoadingStart(); - for (String key : keys) { - MatchResult matcher = regExp.exec(key); - if (matcher == null) { + if (!validateLocal(textField)) { + events.onFinished(false); + return; + } + + if (textField.getValue() == null || textField.getValue().isEmpty()) { + events.onFinished(true); + return; + } + + String sshKeys = textField.getValue(); + // normalize value just in case + sshKeys = sshKeys.replaceAll("(,)+", ","); + List keys = Arrays.stream(sshKeys.split(",")).collect(Collectors.toList()); + wrongValues = ""; + for (String key : keys) { + UsersManager.validateSSHKey(key, new JsonEvents() { + @Override + public void onFinished(JavaScriptObject result) { + if (wrongValues.isEmpty()) { + textField.setStatus(ValidationState.SUCCESS); + events.onFinished(true); + } + } + + @Override + public void onError(PerunException error) { + wrongValues += key; int length = Math.min(key.length(), 30); textField.setRawStatus(getTransl().sshKeyFormat(key.substring(0, length)+((length == 30) ? "..." : "")), ValidationState.ERROR); setResult(Result.INVALID_FORMAT); - return false; + events.onFinished(false); } - } + @Override + public void onLoadingStart() { + setResult(Result.CHECKING_SSH); + textField.unsetStatus(); + } + }); } - - textField.setStatus(ValidationState.SUCCESS); - return true; - } - }