diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java index 3969e18c6..64a2c6746 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmClient.java @@ -71,6 +71,12 @@ public class KsmClient { @Inject private KsmConfigurationService confService; + /** + * Service for retrieving data from records. + */ + @Inject + private KsmRecordService recordService; + /** * The publicly-accessible URL for Keeper's documentation covering Keeper * notation. @@ -226,28 +232,17 @@ public class KsmClient { cachedRecordsByUsername.clear(); // Store all records, sorting each into host-based and login-based - // buckets (note that a single record may be associated with - // multiple hosts and logins) + // buckets records.forEach(record -> { // Store based on UID ... cachedRecordsByUid.put(record.getRecordUid(), record); - // ... and standard fields ... - KeeperRecordData data = record.getData(); - addRecordForHosts(record, (Hosts) data.getField(Hosts.class)); - addRecordForLogin(record, (Login) data.getField(Login.class)); + // ... and hostname/address ... + addRecordForHost(record, recordService.getHostname(record)); - // ... and custom fields - List custom = data.getCustom(); - if (custom != null) { - custom.forEach(field -> { - if (field instanceof Hosts) - addRecordForHosts(record, (Hosts) field); - else if (field instanceof Login) - addRecordForLogin(record, (Login) field); - }); - } + // ... and username + addRecordForLogin(record, recordService.getUsername(record)); }); @@ -262,62 +257,51 @@ public class KsmClient { } /** - * Associates the given record with each of the hosts in the given Hosts - * field. The given Hosts field may be null. Both {@link #cachedRecordsByHost} - * and {@link #cachedAmbiguousHosts} are updated appropriately. The write - * lock of {@link #cacheLock} must already be acquired before invoking this - * function. - * - * @param record - * The record to associate with the hosts in the given field. - * - * @param hosts - * The Hosts field containing the hosts that the given record should be - * associated with. This may be null. - */ - private void addRecordForHosts(KeeperRecord record, Hosts hosts) { - - if (hosts == null) - return; - - hosts.getValue().stream().map(host -> host.getHostName()) - .forEachOrdered(hostname -> { - - KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record); - if (existing != null && record != existing) - cachedAmbiguousHosts.add(hostname); - - }); - - } - - /** - * Associates the given record with each of the usernames in the given - * Login field. The given Hosts field may be null. Both - * {@link #cachedRecordsByUsername} and {@link #cachedAmbiguousUsernames} + * Associates the given record with the given hostname. The hostname may be + * null. Both {@link #cachedRecordsByHost} and {@link #cachedAmbiguousHosts} * are updated appropriately. The write lock of {@link #cacheLock} must * already be acquired before invoking this function. * * @param record * The record to associate with the hosts in the given field. * - * @param login - * The Login field containing the usernames that the given record - * should be associated with. This may be null. + * @param hostname + * The hostname/address that the given record should be associated + * with. This may be null. */ - private void addRecordForLogin(KeeperRecord record, Login login) { + private void addRecordForHost(KeeperRecord record, String hostname) { - if (login == null) + if (hostname == null) return; - login.getValue().stream() - .forEachOrdered(username -> { + KeeperRecord existing = cachedRecordsByHost.putIfAbsent(hostname, record); + if (existing != null && record != existing) + cachedAmbiguousHosts.add(hostname); - KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record); - if (existing != null && record != existing) - cachedAmbiguousUsernames.add(username); + } - }); + /** + * Associates the given record with the given username. The given username + * may be null. Both {@link #cachedRecordsByUsername} and + * {@link #cachedAmbiguousUsernames} are updated appropriately. The write + * lock of {@link #cacheLock} must already be acquired before invoking this + * function. + * + * @param record + * The record to associate with the given username. + * + * @param username + * The username that the given record should be associated with. This + * may be null. + */ + private void addRecordForLogin(KeeperRecord record, String username) { + + if (username == null) + return; + + KeeperRecord existing = cachedRecordsByUsername.putIfAbsent(username, record); + if (existing != null && record != existing) + cachedAmbiguousUsernames.add(username); } diff --git a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java index 5eef05ffd..8b14eea59 100644 --- a/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java +++ b/extensions/guacamole-vault/modules/guacamole-vault-ksm/src/main/java/org/apache/guacamole/vault/ksm/secret/KsmRecordService.java @@ -21,6 +21,8 @@ package org.apache.guacamole.vault.ksm.secret; import com.google.inject.Singleton; import com.keepersecurity.secretsManager.core.HiddenField; +import com.keepersecurity.secretsManager.core.Host; +import com.keepersecurity.secretsManager.core.Hosts; import com.keepersecurity.secretsManager.core.KeeperRecord; import com.keepersecurity.secretsManager.core.KeeperRecordData; import com.keepersecurity.secretsManager.core.KeeperRecordField; @@ -40,6 +42,13 @@ import java.util.regex.Pattern; @Singleton public class KsmRecordService { + /** + * Regular expression which matches the labels of custom fields containing + * hostnames/addresses. + */ + private static final Pattern HOSTNAME_LABEL_PATTERN = + Pattern.compile("hostname|(ip\\s*)?address", Pattern.CASE_INSENSITIVE); + /** * Regular expression which matches the labels of custom fields containing * usernames. @@ -225,6 +234,44 @@ public class KsmRecordService { } + /** + * Returns the single hostname (or address) associated with the given + * record. If the record has no associated hostname, or multiple hostnames, + * null is returned. Hostnames are retrieved from "Hosts" fields, as well + * as "Text" and "Hidden" fields that have the label "hostname", "address", + * or "ip address" (case-insensitive, space optional). + * + * @param record + * The record to retrieve the hostname from. + * + * @return + * The hostname associated with the given record, or null if the record + * has no associated hostname or multiple hostnames. + */ + public String getHostname(KeeperRecord record) { + + // Prefer standard login field + Hosts hostsField = getField(record, Hosts.class, null); + if (hostsField != null) + return getSingleValue(hostsField.getValue(), Host::getHostName); + + KeeperRecordData data = record.getData(); + List custom = data.getCustom(); + + // Use text "hostname" custom field as fallback ... + Text textField = getField(custom, Text.class, HOSTNAME_LABEL_PATTERN); + if (textField != null) + return getSingleValue(textField.getValue()); + + // ... or hidden "hostname" custom field + HiddenField hiddenField = getField(custom, HiddenField.class, HOSTNAME_LABEL_PATTERN); + if (hiddenField != null) + return getSingleValue(hiddenField.getValue()); + + return null; + + } + /** * Returns the single username associated with the given record. If the * record has no associated username, or multiple usernames, null is