diff --git a/server/basedir-includes/configure-from-env b/server/basedir-includes/configure-from-env index 3ab0ebbb7..583d56d11 100755 --- a/server/basedir-includes/configure-from-env +++ b/server/basedir-includes/configure-from-env @@ -114,8 +114,10 @@ while read -r keyvalue; do ACTUAL_KEY="${ACTUAL_KEY//_/.}" # lower case - # example: database.max-connections - ACTUAL_KEY="${ACTUAL_KEY,,}" + # example: database.max-connections (but not _MP_user_pref_default_showTags) + if [[ ! ${ACTUAL_KEY} =~ [a-z] ]]; then + ACTUAL_KEY="${ACTUAL_KEY,,}" + fi # if key does not exist in mirth.properties append it at bottom LINE_COUNT=`grep "^${ACTUAL_KEY}" "$APP_DIR/conf/mirth.properties" | wc -l` diff --git a/server/src/com/mirth/connect/model/AdminUserPreference.java b/server/src/com/mirth/connect/model/AdminUserPreference.java new file mode 100644 index 000000000..692f3d570 --- /dev/null +++ b/server/src/com/mirth/connect/model/AdminUserPreference.java @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: MPL-2.0 +// SPDX-FileCopyrightText: 2026 Mitch Gaffigan + +package com.mirth.connect.model; + +/** Administrative policy for a user preference */ +public record AdminUserPreference(String value, boolean locked) {} diff --git a/server/src/com/mirth/connect/server/controllers/ConfigurationController.java b/server/src/com/mirth/connect/server/controllers/ConfigurationController.java index e4a9ce88c..50380cc63 100644 --- a/server/src/com/mirth/connect/server/controllers/ConfigurationController.java +++ b/server/src/com/mirth/connect/server/controllers/ConfigurationController.java @@ -21,6 +21,7 @@ import com.mirth.commons.encryption.Digester; import com.mirth.commons.encryption.Encryptor; import com.mirth.connect.client.core.ControllerException; +import com.mirth.connect.model.AdminUserPreference; import com.mirth.connect.model.ChannelDependency; import com.mirth.connect.model.ChannelMetadata; import com.mirth.connect.model.ChannelTag; @@ -335,6 +336,9 @@ public Properties getPropertiesForGroup(String group) { public abstract Properties getPropertiesForGroup(String group, Set propertyKeys); + /** Get administratively set user preferences */ + public abstract Map getUserPreferenceProperties(); + public abstract void removePropertiesForGroup(String group); public abstract String getProperty(String group, String name); diff --git a/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java b/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java index bfee2eddb..552c300c2 100644 --- a/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java +++ b/server/src/com/mirth/connect/server/controllers/DefaultConfigurationController.java @@ -99,6 +99,7 @@ import com.mirth.connect.donkey.model.DatabaseConstants; import com.mirth.connect.donkey.server.data.DonkeyStatisticsUpdater; import com.mirth.connect.donkey.util.DonkeyElement; +import com.mirth.connect.model.AdminUserPreference; import com.mirth.connect.model.Channel; import com.mirth.connect.model.ChannelDependency; import com.mirth.connect.model.ChannelMetadata; @@ -144,6 +145,8 @@ public class DefaultConfigurationController extends ConfigurationController { public static final String PROPERTIES_CHANNEL_METADATA = "channelMetadata"; public static final String PROPERTIES_CHANNEL_TAGS = "channelTags"; public static final String PROPERTIES_DATABASE_DRIVERS = "databaseDrivers"; + private static final String USER_PREFERENCE_DEFAULT_PREFIX = "user.pref.default."; + private static final String USER_PREFERENCE_LOCK_PREFIX = "user.pref.lock."; public static final String SECRET_KEY_ALIAS = "encryption"; public static final String VACUUM_LOCK_STATEMENT_ID = "Configuration.vacuumConfigurationTable"; @@ -1002,6 +1005,26 @@ public Properties getPropertiesForGroup(String category, Set propertyKey return properties; } + @Override + public Map getUserPreferenceProperties() { + Properties coreProperties = getPropertiesForGroup(PROPERTIES_CORE); + Map policies = new HashMap(); + + for (String key : coreProperties.stringPropertyNames()) { + if (!key.startsWith(USER_PREFERENCE_DEFAULT_PREFIX)) { + continue; + } + + String preferenceName = key.substring(USER_PREFERENCE_DEFAULT_PREFIX.length()); + String value = coreProperties.getProperty(key); + boolean locked = Boolean.parseBoolean(coreProperties.getProperty(USER_PREFERENCE_LOCK_PREFIX + preferenceName)); + + policies.put(preferenceName, new AdminUserPreference(value, locked)); + } + + return policies; + } + public void removePropertiesForGroup(String category) { logger.debug("deleting all properties: category=" + category); diff --git a/server/src/com/mirth/connect/server/controllers/DefaultUserController.java b/server/src/com/mirth/connect/server/controllers/DefaultUserController.java index dbd9a8949..224677d71 100644 --- a/server/src/com/mirth/connect/server/controllers/DefaultUserController.java +++ b/server/src/com/mirth/connect/server/controllers/DefaultUserController.java @@ -25,6 +25,7 @@ import com.mirth.commons.encryption.Digester; import com.mirth.connect.client.core.ControllerException; +import com.mirth.connect.model.AdminUserPreference; import com.mirth.connect.model.Credentials; import com.mirth.connect.model.LoginStatus; import com.mirth.connect.model.LoginStatus.Status; @@ -560,11 +561,23 @@ public Properties getUserPreferences(Integer userId, Set names) { logger.debug("retrieving preferences: user id=" + userId); Properties properties = new Properties(); + var policies = ControllerFactory.getFactory().createConfigurationController().getUserPreferenceProperties(); + for (var entry : policies.entrySet()) { + if (CollectionUtils.isEmpty(names) || names.contains(entry.getKey())) { + properties.setProperty(entry.getKey(), StringUtils.defaultString(entry.getValue().value())); + } + } + StatementLock.getInstance(VACUUM_LOCK_PREFERENCES_STATEMENT_ID).readLock(); try { List result = SqlConfig.getInstance().getReadOnlySqlSessionManager().selectList("User.selectPreferencesForUser", userId); for (KeyValuePair pair : result) { + var policy = policies.get(pair.getKey()); + if (policy != null && policy.locked()) { + continue; + } + if (CollectionUtils.isEmpty(names) || names.contains(pair.getKey())) { properties.setProperty(pair.getKey(), StringUtils.defaultString(pair.getValue())); } @@ -582,6 +595,14 @@ public Properties getUserPreferences(Integer userId, Set names) { public String getUserPreference(Integer userId, String name) { logger.debug("retrieving preference: user id=" + userId + ", name=" + name); + var policy = ControllerFactory.getFactory().createConfigurationController().getUserPreferenceProperties().get(name); + policy = policy != null ? policy : new AdminUserPreference(null, false); + + String value = policy.value(); + if (policy.locked()) { + return value; + } + StatementLock.getInstance(VACUUM_LOCK_PREFERENCES_STATEMENT_ID).readLock(); try { Map parameterMap = new HashMap();