mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 13:17:41 +00:00
GUACAMOLE-641: Additionally match against KSM custom fields based on labels.
This commit is contained in:
@@ -20,13 +20,19 @@
|
|||||||
package org.apache.guacamole.vault.ksm.secret;
|
package org.apache.guacamole.vault.ksm.secret;
|
||||||
|
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
|
import com.keepersecurity.secretsManager.core.HiddenField;
|
||||||
import com.keepersecurity.secretsManager.core.KeeperRecord;
|
import com.keepersecurity.secretsManager.core.KeeperRecord;
|
||||||
import com.keepersecurity.secretsManager.core.KeeperRecordData;
|
import com.keepersecurity.secretsManager.core.KeeperRecordData;
|
||||||
|
import com.keepersecurity.secretsManager.core.KeeperRecordField;
|
||||||
import com.keepersecurity.secretsManager.core.KeyPair;
|
import com.keepersecurity.secretsManager.core.KeyPair;
|
||||||
import com.keepersecurity.secretsManager.core.KeyPairs;
|
import com.keepersecurity.secretsManager.core.KeyPairs;
|
||||||
import com.keepersecurity.secretsManager.core.Login;
|
import com.keepersecurity.secretsManager.core.Login;
|
||||||
import com.keepersecurity.secretsManager.core.Password;
|
import com.keepersecurity.secretsManager.core.Password;
|
||||||
|
import com.keepersecurity.secretsManager.core.Text;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service for automatically parsing out secrets and data from Keeper records.
|
* Service for automatically parsing out secrets and data from Keeper records.
|
||||||
@@ -34,10 +40,197 @@ import java.util.List;
|
|||||||
@Singleton
|
@Singleton
|
||||||
public class KsmRecordService {
|
public class KsmRecordService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches the labels of custom fields containing
|
||||||
|
* usernames.
|
||||||
|
*/
|
||||||
|
private static final Pattern USERNAME_LABEL_PATTERN =
|
||||||
|
Pattern.compile("username", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches the labels of custom fields containing
|
||||||
|
* passwords.
|
||||||
|
*/
|
||||||
|
private static final Pattern PASSWORD_LABEL_PATTERN =
|
||||||
|
Pattern.compile("password", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches the labels of custom fields containing
|
||||||
|
* passphrases for private keys.
|
||||||
|
*/
|
||||||
|
private static final Pattern PASSPHRASE_LABEL_PATTERN =
|
||||||
|
Pattern.compile("passphrase", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Regular expression which matches the labels of custom fields containing
|
||||||
|
* private keys.
|
||||||
|
*/
|
||||||
|
private static final Pattern PRIVATE_KEY_LABEL_PATTERN =
|
||||||
|
Pattern.compile("private\\s*key", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the single value stored in the given list. If the list is empty
|
||||||
|
* or contains multiple values, null is returned.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* The type of object stored in the list.
|
||||||
|
*
|
||||||
|
* @param values
|
||||||
|
* The list to retrieve a single value from.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The single value stored in the given list, or null if the list is
|
||||||
|
* empty or contains multiple values.
|
||||||
|
*/
|
||||||
|
private <T> T getSingleValue(List<T> values) {
|
||||||
|
|
||||||
|
if (values == null || values.size() != 1)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return values.get(0);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the single value stored in the given list, additionally
|
||||||
|
* performing a mapping transformation on the single value. If the list is
|
||||||
|
* empty or contains multiple values, null is returned.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* The type of object stored in the list.
|
||||||
|
*
|
||||||
|
* @param <R>
|
||||||
|
* The type of object to return.
|
||||||
|
*
|
||||||
|
* @param values
|
||||||
|
* The list to retrieve a single value from.
|
||||||
|
*
|
||||||
|
* @param mapper
|
||||||
|
* The function to use to map the single object of type T to type R.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The single value stored in the given list, transformed using the
|
||||||
|
* provided mapping function, or null if the list is empty or contains
|
||||||
|
* multiple values.
|
||||||
|
*/
|
||||||
|
private <T, R> R getSingleValue(List<T> values, Function<T, R> mapper) {
|
||||||
|
|
||||||
|
T value = getSingleValue(values);
|
||||||
|
if (value == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return mapper.apply(value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the instance of the only field that has the given type and
|
||||||
|
* matches the given label pattern. If there are no such fields, or
|
||||||
|
* multiple such fields, null is returned.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* The type of field to return.
|
||||||
|
*
|
||||||
|
* @param fields
|
||||||
|
* The list of fields to retrieve the field from.
|
||||||
|
*
|
||||||
|
* @param fieldClass
|
||||||
|
* The class representing the type of field to return.
|
||||||
|
*
|
||||||
|
* @param labelPattern
|
||||||
|
* The pattern to match against the desired field's label, or null if
|
||||||
|
* no label pattern match should be performed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The field having the given type and matching the given label
|
||||||
|
* pattern, or null if there is not exactly one such field.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked") // Manually verified with isAssignableFrom()
|
||||||
|
private <T extends KeeperRecordField> T getField(List<KeeperRecordField> fields,
|
||||||
|
Class<T> fieldClass, Pattern labelPattern) {
|
||||||
|
|
||||||
|
T foundField = null;
|
||||||
|
for (KeeperRecordField field : fields) {
|
||||||
|
|
||||||
|
// Ignore fields of wrong class
|
||||||
|
if (!fieldClass.isAssignableFrom(field.getClass()))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Match against provided pattern, if any
|
||||||
|
if (labelPattern != null) {
|
||||||
|
|
||||||
|
// Ignore fields without labels if a label match is requested
|
||||||
|
String label = field.getLabel();
|
||||||
|
if (label == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ignore fields whose labels do not match
|
||||||
|
Matcher labelMatcher = labelPattern.matcher(label);
|
||||||
|
if (!labelMatcher.matches())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore ambiguous fields
|
||||||
|
if (foundField != null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Tentative match found - we can use this as long as no other
|
||||||
|
// field matches the criteria
|
||||||
|
foundField = (T) field;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return foundField;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the instance of the only field that has the given type and
|
||||||
|
* matches the given label pattern. If there are no such fields, or
|
||||||
|
* multiple such fields, null is returned. Both standard and custom fields
|
||||||
|
* are searched. As standard fields do not have labels, any given label
|
||||||
|
* pattern is ignored for standard fields.
|
||||||
|
*
|
||||||
|
* @param <T>
|
||||||
|
* The type of field to return.
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* The Keeper record to retrieve the field from.
|
||||||
|
*
|
||||||
|
* @param fieldClass
|
||||||
|
* The class representing the type of field to return.
|
||||||
|
*
|
||||||
|
* @param labelPattern
|
||||||
|
* The pattern to match against the labels of custom fields, or null if
|
||||||
|
* no label pattern match should be performed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The field having the given type and matching the given label
|
||||||
|
* pattern, or null if there is not exactly one such field.
|
||||||
|
*/
|
||||||
|
private <T extends KeeperRecordField> T getField(KeeperRecord record,
|
||||||
|
Class<T> fieldClass, Pattern labelPattern) {
|
||||||
|
|
||||||
|
KeeperRecordData data = record.getData();
|
||||||
|
|
||||||
|
// Attempt to find standard field first, ignoring custom fields if a
|
||||||
|
// standard field exists (NOTE: standard fields do not have labels)
|
||||||
|
T field = getField(data.getFields(), fieldClass, null);
|
||||||
|
if (field != null)
|
||||||
|
return field;
|
||||||
|
|
||||||
|
// Fall back on custom fields
|
||||||
|
return getField(data.getCustom(), fieldClass, labelPattern);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the single username associated with the given record. If the
|
* Returns the single username associated with the given record. If the
|
||||||
* record has no associated username, or multiple usernames, null is
|
* record has no associated username, or multiple usernames, null is
|
||||||
* returned. Usernames are retrieved from "Login" fields.
|
* returned. Usernames are retrieved from "Login" fields, as well as
|
||||||
|
* "Text" and "Hidden" fields that have the label "username"
|
||||||
|
* (case-insensitive).
|
||||||
*
|
*
|
||||||
* @param record
|
* @param record
|
||||||
* The record to retrieve the username from.
|
* The record to retrieve the username from.
|
||||||
@@ -48,23 +241,65 @@ public class KsmRecordService {
|
|||||||
*/
|
*/
|
||||||
public String getUsername(KeeperRecord record) {
|
public String getUsername(KeeperRecord record) {
|
||||||
|
|
||||||
|
// Prefer standard login field
|
||||||
|
Login loginField = getField(record, Login.class, null);
|
||||||
|
if (loginField != null)
|
||||||
|
return getSingleValue(loginField.getValue());
|
||||||
|
|
||||||
KeeperRecordData data = record.getData();
|
KeeperRecordData data = record.getData();
|
||||||
|
List<KeeperRecordField> custom = data.getCustom();
|
||||||
|
|
||||||
Login loginField = (Login) data.getField(Login.class);
|
// Use text "username" custom field as fallback ...
|
||||||
if (loginField == null)
|
Text textField = getField(custom, Text.class, USERNAME_LABEL_PATTERN);
|
||||||
return null;
|
if (textField != null)
|
||||||
|
return getSingleValue(textField.getValue());
|
||||||
|
|
||||||
List<String> usernames = loginField.getValue();
|
// ... or hidden "username" custom field
|
||||||
if (usernames.size() != 1)
|
HiddenField hiddenField = getField(custom, HiddenField.class, USERNAME_LABEL_PATTERN);
|
||||||
return null;
|
if (hiddenField != null)
|
||||||
|
return getSingleValue(hiddenField.getValue());
|
||||||
|
|
||||||
return usernames.get(0);
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the password associated with the given record, as dictated by
|
* Returns the password associated with the given record and matching the
|
||||||
* the {@link KeeperRecord#getPassword()}.
|
* given label pattern. Both standard and custom fields are searched. As
|
||||||
|
* standard fields do not have labels, the label pattern is ignored for
|
||||||
|
* standard fields. Only "Password" and "Hidden" field types are
|
||||||
|
* considered.
|
||||||
|
*
|
||||||
|
* @param record
|
||||||
|
* The record to retrieve the password from.
|
||||||
|
*
|
||||||
|
* @param labelPattern
|
||||||
|
* The pattern to match against the labels of custom fields, or null if
|
||||||
|
* no label pattern match should be performed.
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
* The password associated with the given record, or null if the record
|
||||||
|
* has no associated password or multiple passwords.
|
||||||
|
*/
|
||||||
|
private String getPassword(KeeperRecord record, Pattern labelPattern) {
|
||||||
|
|
||||||
|
Password passwordField = getField(record, Password.class, labelPattern);
|
||||||
|
if (passwordField != null)
|
||||||
|
return getSingleValue(passwordField.getValue());
|
||||||
|
|
||||||
|
HiddenField hiddenField = getField(record, HiddenField.class, labelPattern);
|
||||||
|
if (hiddenField != null)
|
||||||
|
return getSingleValue(hiddenField.getValue());
|
||||||
|
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the password associated with the given record. Both standard and
|
||||||
|
* custom fields are searched. Only "Password" and "Hidden" field types are
|
||||||
|
* considered. Custom fields must additionally have the label "password"
|
||||||
|
* (case-insensitive).
|
||||||
*
|
*
|
||||||
* @param record
|
* @param record
|
||||||
* The record to retrieve the password from.
|
* The record to retrieve the password from.
|
||||||
@@ -74,25 +309,16 @@ public class KsmRecordService {
|
|||||||
* has no associated password.
|
* has no associated password.
|
||||||
*/
|
*/
|
||||||
public String getPassword(KeeperRecord record) {
|
public String getPassword(KeeperRecord record) {
|
||||||
|
return getPassword(record, PASSWORD_LABEL_PATTERN);
|
||||||
KeeperRecordData data = record.getData();
|
|
||||||
|
|
||||||
Password passwordField = (Password) data.getField(Password.class);
|
|
||||||
if (passwordField == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
List<String> values = passwordField.getValue();
|
|
||||||
if (values.size() != 1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return values.get(0);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the private key associated with the given record. If the record
|
* Returns the private key associated with the given record. If the record
|
||||||
* has no associated private key, or multiple private keys, null is
|
* has no associated private key, or multiple private keys, null is
|
||||||
* returned. Private keys are retrieved from "KeyPairs" fields.
|
* returned. Private keys are retrieved from "KeyPairs" fields.
|
||||||
|
* Alternatively, private keys are retrieved from custom fields with the
|
||||||
|
* label "private key" (case-insensitive, space optional) if they are
|
||||||
|
* "KeyPairs", "Password", or "Hidden" fields.
|
||||||
*
|
*
|
||||||
* @param record
|
* @param record
|
||||||
* The record to retrieve the private key from.
|
* The record to retrieve the private key from.
|
||||||
@@ -103,25 +329,23 @@ public class KsmRecordService {
|
|||||||
*/
|
*/
|
||||||
public String getPrivateKey(KeeperRecord record) {
|
public String getPrivateKey(KeeperRecord record) {
|
||||||
|
|
||||||
KeeperRecordData data = record.getData();
|
// Attempt to find single matching keypair field
|
||||||
|
KeyPairs keyPairsField = getField(record, KeyPairs.class, PRIVATE_KEY_LABEL_PATTERN);
|
||||||
|
if (keyPairsField != null)
|
||||||
|
return getSingleValue(keyPairsField.getValue(), KeyPair::getPrivateKey);
|
||||||
|
|
||||||
KeyPairs keyPairsField = (KeyPairs) data.getField(KeyPairs.class);
|
// Fall back to general password/hidden fields if not found or ambiguous
|
||||||
if (keyPairsField == null)
|
return getPassword(record, PRIVATE_KEY_LABEL_PATTERN);
|
||||||
return null;
|
|
||||||
|
|
||||||
List<KeyPair> keyPairs = keyPairsField.getValue();
|
|
||||||
if (keyPairs.size() != 1)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
return keyPairs.get(0).getPrivateKey();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the passphrase for the private key associated with the given
|
* Returns the passphrase for the private key associated with the given
|
||||||
* record. Currently, this is simply dictated by {@link KeeperRecord#getPassword()},
|
* record. Both standard and custom fields are searched. Only "Password"
|
||||||
* as there is no specific association between private keys and passphrases
|
* and "Hidden" field types are considered. Custom fields must additionally
|
||||||
* in the "KeyPairs" field type.
|
* have the label "passphrase" (case-insensitive). Note that there is no
|
||||||
|
* specific association between private keys and passphrases in the
|
||||||
|
* "KeyPairs" field type.
|
||||||
*
|
*
|
||||||
* @param record
|
* @param record
|
||||||
* The record to retrieve the passphrase from.
|
* The record to retrieve the passphrase from.
|
||||||
@@ -131,7 +355,7 @@ public class KsmRecordService {
|
|||||||
* or null if there is no such passphrase associated with the record.
|
* or null if there is no such passphrase associated with the record.
|
||||||
*/
|
*/
|
||||||
public String getPassphrase(KeeperRecord record) {
|
public String getPassphrase(KeeperRecord record) {
|
||||||
return getPassword(record);
|
return getPassword(record, PASSPHRASE_LABEL_PATTERN);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user