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 2, 2023
1 parent 585f94f commit f849c39
Show file tree
Hide file tree
Showing 8 changed files with 168 additions and 239 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 @@ -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,7 @@ enum Result {
INVALID_FORMAT_EMAIL,
LOGIN_NOT_AVAILABLE,
CHECKING_LOGIN,
CHECKING_SSH,
CANT_CHECK_LOGIN,
EMPTY_PASSWORD,
PASSWORD_TOO_SHORT,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,29 @@
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 <[email protected]>
*/
public class SshKeysListBoxValidator extends ListBoxValidator {

RegExp regExp = RegExp.compile("^(" +
"(ssh-(rsa|dss|ed25519)([email protected])?)|" +
"(sk-(ssh-ed25519|ecdsa-sha2-nistp256)(-cert-v01)[email protected])|" +
"(ecdsa-sha2-nistp(256|384|521)([email protected])?))" +
" (([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;

@Override
public boolean validateLocal(ListBox listBox) {
Expand All @@ -26,34 +32,95 @@ 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();

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(",")) {
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 (ValidationState.ERROR == state) {
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);
}
}

@Override
public void onError(PerunException error) {
validatingCounter--;
checkVals.put(sshKey, ValidationState.ERROR);
// set error immediately
setResult(Result.INVALID_FORMAT);
listBox.setStatus(getTransl().sshKeySeparatorNotAllowed(), ValidationState.ERROR);
return false;
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);
}
}

MatchResult matcher = regExp.exec(sshKey);
if (matcher == null) {
wrongValues += "<br>" + index + ". " + (sshKey.length() > 25 ? sshKey.substring(0, 23) + "..." : sshKey);
@Override
public void onLoadingStart() {
if (validatingCounter == 0) {
listBox.unsetStatus();
events.onLoadingStart();
setResult(Result.CHECKING_SSH);
listBox.setStatus(ValidationState.WARNING);
}
validatingCounter++;
}
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;
}
}
}
Loading

0 comments on commit f849c39

Please sign in to comment.