Skip to content

Commit

Permalink
feat: backend ssh public key validation
Browse files Browse the repository at this point in the history
* replaced regexp in SSH key validators with call to backend validation method
* this unifies the process and offers a more strict validation
* removed unused validators
  • Loading branch information
xflord committed Nov 7, 2023
1 parent 585f94f commit debc73e
Show file tree
Hide file tree
Showing 10 changed files with 185 additions and 240 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,8 @@ public interface PerunRegistrarTranslation extends PerunTranslation {
@DefaultMessage("Unable to check login availability! Please check your internet connection and try again later.")
public String checkingLoginFailed();

@DefaultMessage("Unable to check SSH key validity! Please check your internet connection and try again later.")
public String checkingSSHFailed();
@DefaultMessage("Login contains invalid character(s) or is not allowed!")
public String loginNotAllowed();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -30,6 +33,7 @@ public class ListBox extends WidgetBox {

private final ListBoxValidator validator;
List<ExtendedTextBox> inputList;
Map<ExtendedTextBox, BlurHandler> handlers;

public ListBox(PerunForm form, ApplicationFormItemData item, String lang) {
super(form, item, lang);
Expand Down Expand Up @@ -78,6 +82,7 @@ public boolean validateLocal() {
@Override
protected Widget initWidget() {
inputList = new ArrayList<>();
handlers = new HashMap<>();
return super.initWidget();
}

Expand All @@ -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<Boolean> nothingEvent = new Events<Boolean>() {
@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();
}
});
}
}
}

Expand Down Expand Up @@ -154,8 +194,19 @@ 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();
validate(new Events<Boolean>() {
@Override
public void onFinished(Boolean result) {
}
@Override
public void onError(PerunException error) {
}
@Override
public void onLoadingStart() {
}
});
}
});
setupRemoveButton(removeButton);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
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;
import cz.metacentrum.perun.wui.registrar.widgets.items.validators.SshKeysTextAreaValidator;
import cz.metacentrum.perun.wui.registrar.widgets.items.validators.TextAreaValidator;
import cz.metacentrum.perun.wui.widgets.boxes.ExtendedTextArea;
import org.gwtbootstrap3.client.ui.html.Paragraph;
Expand All @@ -27,12 +26,7 @@ public class TextArea extends PerunFormItemEditable {
public TextArea(PerunForm form, ApplicationFormItemData item, String lang) {
super(form, item, lang);

if ("urn:perun:user:attribute-def:def:sshPublicKey".equals(item.getFormItem().getPerunDestinationAttribute())) {
this.validator = new SshKeysTextAreaValidator();
} else {
this.validator = new TextAreaValidator();
}

this.validator = new TextAreaValidator();
}

protected Widget initWidget() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
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;
import cz.metacentrum.perun.wui.registrar.widgets.items.validators.SshKeysTextFieldValidator;
import cz.metacentrum.perun.wui.registrar.widgets.items.validators.TextFieldValidator;
import cz.metacentrum.perun.wui.widgets.boxes.ExtendedTextBox;
import org.gwtbootstrap3.client.ui.constants.ColumnSize;
Expand All @@ -29,11 +28,7 @@ public class TextField extends PerunFormItemEditable {

public TextField(PerunForm form, ApplicationFormItemData item, String lang) {
super(form, item, lang);
if ("urn:perun:user:attribute-def:def:sshPublicKey".equals(item.getFormItem().getPerunDestinationAttribute())) {
this.validator = new SshKeysTextFieldValidator();
} else {
this.validator = new TextFieldValidator();
}
this.validator = new TextFieldValidator();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ enum Result {
INVALID_FORMAT_EMAIL,
LOGIN_NOT_AVAILABLE,
CHECKING_LOGIN,
CHECKING_SSH,
CANT_CHECK_SSH,
CANT_CHECK_LOGIN,
EMPTY_PASSWORD,
PASSWORD_TOO_SHORT,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
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.HashMap;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

/**
* Validator for ListBox
*
* @author Jakub Hejda <Jakub.Hejda@cesnet.cz>
*/
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]+)?$");
final Map<String, ValidationState> checkVals = new TreeMap<>();
final Map<String, Integer> indexMap = new HashMap<>();
int validatingCounter = 0;
boolean callFailed = false;

@Override
public boolean validateLocal(ListBox listBox) {
Expand All @@ -26,34 +33,106 @@ public boolean validateLocal(ListBox listBox) {
listBox.setRawStatus(getTransl().cantBeEmpty(), ValidationState.ERROR);
return false;
}
// FIXME - but it is probably not necessary as API should check it
for (ExtendedTextBox extendedTextBox : listBox.getListValue()) {
String sshKey = extendedTextBox.getValue();
if (sshKey.contains(",")) {
setResult(Result.INVALID_FORMAT);
listBox.setStatus(getTransl().sshKeySeparatorNotAllowed(), ValidationState.ERROR);
return false;
}
}
listBox.setStatus(ValidationState.SUCCESS);
return true;
}

@Override
public void validate(ListBox listBox, Events<Boolean> events) {

if (!validateLocal(listBox)) {
events.onFinished(false);
return;
}

if (listBox.getValue() == null || listBox.getValue().isEmpty()) {
events.onFinished(true);
return;
}

checkVals.clear();
indexMap.clear();
callFailed = false;

if (listBox.getValue() != null && !listBox.getValue().isEmpty()) {
int counter = 1;
for (ExtendedTextBox extendedTextBox : listBox.getListValue()) {
String sshKey = extendedTextBox.getValue();
indexMap.put(sshKey, counter);
checkVals.put(sshKey, ValidationState.NONE);
counter++;
}

String wrongValues = "";
int index = 1;
for (ExtendedTextBox extendedTextBox : listBox.getListValue()) {
String sshKey = extendedTextBox.getValue();
for (String sshKey : checkVals.keySet()) {

if (sshKey.contains(",")) {
setResult(Result.INVALID_FORMAT);
listBox.setStatus(getTransl().sshKeySeparatorNotAllowed(), ValidationState.ERROR);
return false;
UsersManager.validateSSHKey(sshKey, new JsonEvents() {
@Override
public void onFinished(JavaScriptObject result) {
validatingCounter--;
checkVals.put(sshKey, ValidationState.SUCCESS);
if (validatingCounter == 0) {
// last check trigger events
for (ValidationState state : checkVals.values()) {
// at least one key is invalid -> switch to error if API call didn't fail
if (ValidationState.ERROR == state) {
if (!callFailed) {
setResult(Result.INVALID_FORMAT);
listBox.setRawStatus(getTransl().incorrectFormatItemList() + " <b> <br>" + checkVals.entrySet().stream().filter(entry -> (entry.getValue() == ValidationState.ERROR)).map((entry) -> indexMap.get(entry.getKey()) + ". " +
(entry.getKey().length() > 25 ? entry.getKey().substring(0, 23) + "..." : entry.getKey())).collect(Collectors.joining("<br>")) + "</b>", ValidationState.ERROR);
}
// pass to outer event as fail
events.onFinished(false);
return;
}
}
// all values were OK -> trigger success
listBox.setStatus(ValidationState.SUCCESS);
events.onFinished(true);
}
}

MatchResult matcher = regExp.exec(sshKey);
if (matcher == null) {
wrongValues += "<br>" + index + ". " + (sshKey.length() > 25 ? sshKey.substring(0, 23) + "..." : sshKey);
@Override
public void onError(PerunException error) {
if (!"SSHKeyNotValidException".equalsIgnoreCase(error.getName())) {
callFailed = true;
}
validatingCounter--;
checkVals.put(sshKey, ValidationState.ERROR);
// set error immediately
if (callFailed) {
setResult(Result.CANT_CHECK_SSH);
listBox.setStatus(getTransl().checkingSSHFailed(), ValidationState.ERROR);
} else {
setResult(Result.INVALID_FORMAT);
listBox.setRawStatus(getTransl().incorrectFormatItemList() + " <b> <br>" + checkVals.entrySet().stream().filter(entry -> (entry.getValue() == ValidationState.ERROR)).map((entry) -> indexMap.get(entry.getKey()) + ". " +
(entry.getKey().length() > 25 ? entry.getKey().substring(0, 23) + "..." : entry.getKey())).collect(Collectors.joining("<br>")) + "</b>", ValidationState.ERROR);
}
if (validatingCounter == 0) {
// if last pass to outer event as fail
events.onFinished(false);
}
}
index++;
}
if (!wrongValues.isEmpty()) {
setResult(Result.INVALID_FORMAT);
listBox.setRawStatus(getTransl().incorrectFormatItemList() + " <b>" + wrongValues + "</b>", ValidationState.ERROR);
return false;
}
}

listBox.setStatus(ValidationState.SUCCESS);
return true;
@Override
public void onLoadingStart() {
if (validatingCounter == 0) {
listBox.unsetStatus();
events.onLoadingStart();
setResult(Result.CHECKING_SSH);
listBox.setStatus(ValidationState.WARNING);
}
validatingCounter++;
}
});

}
}
}
Loading

0 comments on commit debc73e

Please sign in to comment.