diff --git a/core/password/pom.xml b/core/password/pom.xml index a79e71bce140..ea3d17becbfd 100644 --- a/core/password/pom.xml +++ b/core/password/pom.xml @@ -40,6 +40,12 @@ jasypt-dependencies pom + + + org.postgresql + postgresql + provided + diff --git a/core/password/src/main/java/org/opennms/core/password/Password.java b/core/password/src/main/java/org/opennms/core/password/Password.java index cdab09e5069e..67d5ca979ca2 100644 --- a/core/password/src/main/java/org/opennms/core/password/Password.java +++ b/core/password/src/main/java/org/opennms/core/password/Password.java @@ -25,19 +25,19 @@ import java.io.IOException; import java.lang.System.Logger; import java.lang.System.Logger.Level; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.TransformerFactoryConfigurationError; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import org.jasypt.util.password.StrongPasswordEncryptor; import org.w3c.dom.Document; @@ -47,78 +47,114 @@ import org.xml.sax.SAXException; public class Password { + + private static final Pattern ENV_VAR = Pattern.compile("\\$\\{env:([^|{}]+)\\|([^}]*)\\}"); + public static void main(String[] args) { - final String OPENNMS_HOME = System.getProperty("opennms.home"); final Logger log = System.getLogger(Password.class.getName()); + final String opennmsHome = System.getProperty("opennms.home"); - if (args.length <2){ + if (args.length < 2) { log.log(Level.WARNING, "usage: password.jar "); System.exit(1); } - Path usersXml = Paths.get(OPENNMS_HOME, "etc", "users.xml"); - String userId=args[0]; - String newPassword = args[1]; - - if (OPENNMS_HOME.isEmpty() || userId.isEmpty() || newPassword.isEmpty()){ - log.log(Level.ERROR, "Unable to determine OpenNMS home, or no username or password was provided."); + if (opennmsHome == null || opennmsHome.isEmpty()) { + log.log(Level.ERROR, "opennms.home system property is not set."); System.exit(1); } - File file = usersXml.toFile(); + final String userId = args[0]; + final String newPassword = args[1]; - if (!file.exists()) { - log.log(Level.ERROR, "users.xml does not exist!"); + final String encryptedPassword = new StrongPasswordEncryptor().encryptPassword(newPassword); + + if (!updateInDatabase(opennmsHome, userId, encryptedPassword, log)) { + log.log(Level.ERROR, "Failed to update password for user '" + userId + "' in database."); System.exit(1); } + } + // ------------------------------------------------------------------------- + // Database path + // ------------------------------------------------------------------------- - boolean foundUser = false; + static boolean updateInDatabase(final String opennmsHome, final String userId, + final String encryptedPassword, final Logger log) { + final Path datasourcesXml = Paths.get(opennmsHome, "etc", "opennms-datasources.xml"); + if (!Files.exists(datasourcesXml)) { + log.log(Level.DEBUG, "opennms-datasources.xml not found, skipping DB update"); + return false; + } + + final String[] jdbcParams = parseDatasourcesXml(datasourcesXml.toFile(), log); + if (jdbcParams == null) { + return false; + } - StrongPasswordEncryptor passwordEncryptor = new StrongPasswordEncryptor(); - String encryptedPassword=""; + final String url = resolveEnvVars(jdbcParams[0]); + final String user = resolveEnvVars(jdbcParams[1]); + final String pass = resolveEnvVars(jdbcParams[2]); + + try (Connection conn = DriverManager.getConnection(url, user, pass); + PreparedStatement ps = conn.prepareStatement( + "UPDATE users SET password = ?, password_salt = true WHERE user_id = ?")) { + ps.setString(1, encryptedPassword); + ps.setString(2, userId); + final int rows = ps.executeUpdate(); + if (rows == 0) { + log.log(Level.ERROR, "User '" + userId + "' not found in database."); + System.exit(1); + } + log.log(Level.INFO, "Password updated in database for user: " + userId); + return true; + } catch (final SQLException e) { + log.log(Level.ERROR, "DB update failed: " + e.getMessage()); + return false; + } + } + /** Returns [url, user-name, password] for the 'opennms' datasource, or null on failure. */ + private static String[] parseDatasourcesXml(final File file, final Logger log) { try { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); dbf.setFeature("http://xml.org/sax/features/external-general-entities", false); dbf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); - - DocumentBuilder db = dbf.newDocumentBuilder(); - Document doc = db.parse(file); + final DocumentBuilder db = dbf.newDocumentBuilder(); + final Document doc = db.parse(file); doc.getDocumentElement().normalize(); - NodeList nodeList = doc.getElementsByTagName("user"); - for (int itr = 0; itr < nodeList.getLength(); itr++) { - Node node = nodeList.item(itr); + + final NodeList sources = doc.getElementsByTagName("jdbc-data-source"); + for (int i = 0; i < sources.getLength(); i++) { + final Node node = sources.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { - Element eElement = (Element) node; - if (userId.contains(eElement.getElementsByTagName("user-id").item(0).getTextContent())) { - foundUser=true; - encryptedPassword = passwordEncryptor.encryptPassword(newPassword); - eElement.getElementsByTagName("password").item(0).setTextContent(encryptedPassword); + final Element e = (Element) node; + if ("opennms".equals(e.getAttribute("name"))) { + return new String[]{ + e.getAttribute("url"), + e.getAttribute("user-name"), + e.getAttribute("password") + }; } } - - } - if (foundUser){ - DOMSource domSource = new DOMSource(doc); - StreamResult sr = new StreamResult(file); - try { - final var factory = TransformerFactory.newInstance(); - Transformer tf = factory.newTransformer(); - tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); - tf.setOutputProperty(OutputKeys.METHOD, "xml"); - tf.transform(domSource, sr); - } catch (TransformerFactoryConfigurationError | TransformerException e) { - e.printStackTrace(); - } - }else{ - log.log(Level.ERROR, "User ID couldn't found."); - System.exit(1); } - } catch (ParserConfigurationException | SAXException | IOException e) { - e.printStackTrace(); + log.log(Level.WARNING, "Could not find 'opennms' datasource in opennms-datasources.xml"); + } catch (final ParserConfigurationException | SAXException | IOException e) { + log.log(Level.WARNING, "Could not parse opennms-datasources.xml: " + e.getMessage()); } + return null; + } + /** Resolves {@code ${env:VAR|default}} expressions using the current environment. */ + static String resolveEnvVars(final String template) { + final Matcher m = ENV_VAR.matcher(template); + final StringBuffer sb = new StringBuffer(); + while (m.find()) { + final String envVal = System.getenv(m.group(1)); + m.appendReplacement(sb, Matcher.quoteReplacement(envVal != null ? envVal : m.group(2))); + } + m.appendTail(sb); + return sb.toString(); } } diff --git a/core/schema/src/main/liquibase/36.0.2/changelog.xml b/core/schema/src/main/liquibase/36.0.2/changelog.xml new file mode 100644 index 000000000000..6ff5dcf891bc --- /dev/null +++ b/core/schema/src/main/liquibase/36.0.2/changelog.xml @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/schema/src/main/liquibase/changelog.xml b/core/schema/src/main/liquibase/changelog.xml index 0cdd082a6959..bc27a2148382 100644 --- a/core/schema/src/main/liquibase/changelog.xml +++ b/core/schema/src/main/liquibase/changelog.xml @@ -105,6 +105,7 @@ + diff --git a/features/springframework-security/src/main/java/org/opennms/web/springframework/security/SpringSecurityUserDaoImpl.java b/features/springframework-security/src/main/java/org/opennms/web/springframework/security/SpringSecurityUserDaoImpl.java index bd13e468f20e..40d4f1ea78ec 100644 --- a/features/springframework-security/src/main/java/org/opennms/web/springframework/security/SpringSecurityUserDaoImpl.java +++ b/features/springframework-security/src/main/java/org/opennms/web/springframework/security/SpringSecurityUserDaoImpl.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Map; -import org.opennms.netmgt.config.UserFactory; +import org.opennms.netmgt.config.DatabaseUserManager; import org.opennms.netmgt.config.UserManager; import org.opennms.netmgt.model.OnmsUser; import org.opennms.web.api.Authentication; @@ -42,7 +42,7 @@ import org.springframework.util.Assert; /** - * Implements the interface to allow the servlet container to check our users.xml file to authenticate users. + * Implements the interface to allow the servlet container to authenticate users against the OpenNMS database. * * @author Lawrence Karnowski * @author Eric Molitor @@ -56,10 +56,10 @@ public class SpringSecurityUserDaoImpl implements SpringSecurityUserDao, Initial /** The user manager. */ private UserManager m_userManager; - /** The users configuration file. */ + /** @deprecated No longer used; user data is read from the database via {@link UserManager}. */ private String m_usersConfigurationFile; - /** The set of valid users from users.xml, keyed by userId. */ + /** In-memory user cache keyed by userId, populated from the database. */ private Map m_users = null; /** The m users last modified. */ @@ -78,12 +78,7 @@ public SpringSecurityUserDaoImpl() { } /** - * Parses the users. - * - *

Convenience method for parsing the users.xml file.

- *

This method is synchronized so only one thread at a time - * can parse the users.xml file and create the principal - * instance variable.

+ * Loads users from the database into the in-memory cache. * * @throws DataRetrievalFailureException the data retrieval failure exception */ @@ -111,7 +106,7 @@ private void parseUsers() throws DataRetrievalFailureException { throw new DataRetrievalFailureException("Unable to get user list.", t); } - LOG.debug("Loaded the users.xml file with {} users", users.size()); + LOG.debug("Loaded {} users from database", users.size()); m_usersLastModified = m_userManager.getLastModified(); m_users = users; @@ -180,13 +175,11 @@ private boolean isUsersParseNecessary() { } /** - * Sets the users configuration file. - * - * @param usersConfigurationFile the new users configuration file + * @deprecated No-op; the database-backed manager has no config file. */ + @Deprecated public void setUsersConfigurationFile(String usersConfigurationFile) { m_usersConfigurationFile = usersConfigurationFile; - UserFactory.setInstance(null); } /** @@ -251,12 +244,14 @@ public void setUserManager(final UserManager mgr) { m_userManager = mgr; } - /* (non-Javadoc) - * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet() - */ @Override public void afterPropertiesSet() { - Assert.state(m_usersConfigurationFile != null, "usersConfigurationFile parameter must be set to the location of the users.xml configuration file"); + // usersConfigurationFile is only required for the legacy XML-backed UserFactory. + // DatabaseUserManager is self-contained and does not need a config file path. + if (!(m_userManager instanceof DatabaseUserManager)) { + Assert.state(m_usersConfigurationFile != null, + "usersConfigurationFile parameter must be set when not using DatabaseUserManager"); + } Assert.notNull(m_userManager); } } diff --git a/opennms-base-assembly/src/main/filtered/etc/users.xml b/opennms-base-assembly/src/main/filtered/etc/users.xml deleted file mode 100644 index eba43cb55c9f..000000000000 --- a/opennms-base-assembly/src/main/filtered/etc/users.xml +++ /dev/null @@ -1,23 +0,0 @@ - -
- .9 - Thursday, November 3, 2011 9:28:08 PM GMT - master.nmanage.com -
- - - admin - Administrator - Default administrator, do not delete - gU2wmSW7k9v1xg4/MrAsaI+VyddBAhJJt4zPX5SGG0BK+qiASGnJsqM8JOug/aEL - ROLE_ADMIN - - - rtc - RTC - RTC user, do not delete - sHMy+HycWKGJC/uUMF0IGlXUXP1KhcqD0GEchFlvYTw40jT9r+zMxOb3F+phWNzX - ROLE_RTC - - -
\ No newline at end of file diff --git a/opennms-config-tester/src/main/resources/META-INF/opennms/applicationContext-configTester.xml b/opennms-config-tester/src/main/resources/META-INF/opennms/applicationContext-configTester.xml index 122b191f0d8a..a45afdeb5fe6 100644 --- a/opennms-config-tester/src/main/resources/META-INF/opennms/applicationContext-configTester.xml +++ b/opennms-config-tester/src/main/resources/META-INF/opennms/applicationContext-configTester.xml @@ -94,7 +94,7 @@ - + @@ -282,7 +282,6 @@ - @@ -486,11 +485,6 @@ - - org.opennms.netmgt.config.UserFactory.init - - - org.opennms.netmgt.config.VacuumdConfigFactory.init diff --git a/opennms-config/pom.xml b/opennms-config/pom.xml index fcbda6200225..b9739829daaa 100644 --- a/opennms-config/pom.xml +++ b/opennms-config/pom.xml @@ -118,6 +118,12 @@ mockito-core test + + com.h2database + h2 + ${h2databaseVersion} + test + org.opennms.core.test-api org.opennms.core.test-api.db diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/DatabaseUserManager.java b/opennms-config/src/main/java/org/opennms/netmgt/config/DatabaseUserManager.java new file mode 100644 index 000000000000..49c6c08261d3 --- /dev/null +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/DatabaseUserManager.java @@ -0,0 +1,346 @@ +/* + * Licensed to The OpenNMS Group, Inc (TOG) under one or more + * contributor license agreements. See the LICENSE.md file + * distributed with this work for additional information + * regarding copyright ownership. + * + * TOG licenses this file to You under the GNU Affero General + * Public License Version 3 (the "License") or (at your option) + * any later version. You may not use this file except in + * compliance with the License. You may obtain a copy of the + * License at: + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.opennms.netmgt.config; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.StringReader; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.sql.DataSource; + +import org.opennms.core.db.DataSourceFactory; +import org.opennms.core.xml.JaxbUtils; +import org.opennms.netmgt.config.users.Contact; +import org.opennms.netmgt.config.users.DutySchedule; +import org.opennms.netmgt.config.users.Password; +import org.opennms.netmgt.config.users.User; +import org.opennms.netmgt.config.users.Userinfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Database-backed replacement for {@link UserFactory}. + * + *

Reads and writes all user data from the {@code users} table (and child + * tables {@code user_roles}, {@code user_contacts}, {@code user_duty_schedules}) + * created by Liquibase changeset 36.0.2. The {@code users.xml} file is not + * read or written.

+ * + *

The in-memory cache inherited from {@link UserManager} is still used so + * that hot-path reads (duty-schedule checks, contact lookups) do not hit the + * database. The cache is populated once at startup via {@link #reload()} and + * is refreshed whenever {@link #saveXML} is called (i.e. after any write + * operation).

+ */ +public class DatabaseUserManager extends UserManager { + + private static final Logger LOG = LoggerFactory.getLogger(DatabaseUserManager.class); + + private static UserManager instance; + private static boolean initialized = false; + + private volatile long m_lastModified = 0L; + + protected DatabaseUserManager(final GroupManager groupManager) { + super(groupManager); + } + + // ------------------------------------------------------------------------- + // Singleton lifecycle (mirrors UserFactory pattern) + // ------------------------------------------------------------------------- + + public static synchronized void init() throws IOException, FileNotFoundException { + if (instance == null || !initialized) { + GroupFactory.init(); + instance = new DatabaseUserManager(GroupFactory.getInstance()); + instance.reload(); + initialized = true; + // Register as the UserFactory singleton so legacy code that calls + // UserFactory.init() / UserFactory.getInstance() gets this instance. + // UserFactory.setInstance() sets UserFactory.initialized = true, which + // makes subsequent UserFactory.init() calls a no-op. + UserFactory.setInstance(instance); + } + } + + public static synchronized UserManager getInstance() { + return instance; + } + + public static synchronized void setInstance(final UserManager mgr) { + initialized = true; + instance = mgr; + } + + // ------------------------------------------------------------------------- + // UserManager abstract methods + // ------------------------------------------------------------------------- + + @Override + public synchronized void reload() throws IOException, FileNotFoundException { + loadFromDatabase(); + m_lastModified = System.currentTimeMillis(); + } + + /** No-op: the database is always the authoritative source; no file to watch. */ + @Override + public void doUpdate() throws IOException { + } + + /** Always false: writes go directly to the DB via {@link #saveXML}. */ + @Override + public boolean isUpdateNeeded() { + return false; + } + + @Override + public long getLastModified() { + return m_lastModified; + } + + /** Not meaningful for a database backend; returns 0. */ + @Override + public long getFileSize() { + return 0; + } + + /** + * Writes the current in-memory user map directly to the database, bypassing + * JAXB marshalling (which fails when the user list is empty due to XSD constraints). + */ + @Override + protected void _saveCurrent() throws Exception { + final DataSource ds = DataSourceFactory.getInstance(); + try (Connection conn = ds.getConnection()) { + final boolean prevAutoCommit = conn.getAutoCommit(); + conn.setAutoCommit(false); + try { + try (PreparedStatement ps = conn.prepareStatement("DELETE FROM users")) { + ps.executeUpdate(); + } + for (final User user : m_users.values()) { + insertUser(conn, user); + } + conn.commit(); + m_lastModified = System.currentTimeMillis(); + LOG.debug("Saved {} users to database", m_users.size()); + } catch (final Exception e) { + conn.rollback(); + throw e; + } finally { + conn.setAutoCommit(prevAutoCommit); + } + } + } + + /** + * Receives a fully-serialized user list (from the REST API) and syncs it + * atomically to the database. + */ + @Override + protected void saveXML(final String writerString) throws IOException { + if (writerString == null) return; + try { + final Userinfo userinfo = JaxbUtils.unmarshal(Userinfo.class, new StringReader(writerString)); + final DataSource ds = DataSourceFactory.getInstance(); + try (Connection conn = ds.getConnection()) { + final boolean prevAutoCommit = conn.getAutoCommit(); + conn.setAutoCommit(false); + try { + // Replace all users atomically; child rows cascade on DELETE. + try (PreparedStatement ps = conn.prepareStatement("DELETE FROM users")) { + ps.executeUpdate(); + } + for (final User user : userinfo.getUsers()) { + insertUser(conn, user); + } + conn.commit(); + m_lastModified = System.currentTimeMillis(); + LOG.debug("Saved {} users to database", userinfo.getUsers().size()); + } catch (final Exception e) { + conn.rollback(); + throw e; + } finally { + conn.setAutoCommit(prevAutoCommit); + } + } + } catch (final Exception e) { + throw new IOException("Failed to save users to database", e); + } + } + + // ------------------------------------------------------------------------- + // Database helpers + // ------------------------------------------------------------------------- + + private void loadFromDatabase() throws IOException { + final DataSource ds = DataSourceFactory.getInstance(); + try (Connection conn = ds.getConnection()) { + final Map newUsers = new TreeMap<>(); + + // Core user fields + try (PreparedStatement ps = conn.prepareStatement( + "SELECT user_id, full_name, user_comments, password, password_salt, tui_pin, time_zone_id FROM users"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + final User user = new User(); + user.setUserId(rs.getString("user_id")); + final String fullName = rs.getString("full_name"); + if (fullName != null) user.setFullName(fullName); + final String comments = rs.getString("user_comments"); + if (comments != null) user.setUserComments(comments); + final Password pass = new Password(); + pass.setEncryptedPassword(rs.getString("password")); + pass.setSalt(rs.getBoolean("password_salt")); + user.setPassword(pass); + final String tuiPin = rs.getString("tui_pin"); + if (tuiPin != null) user.setTuiPin(tuiPin); + final String tzId = rs.getString("time_zone_id"); + if (tzId != null) user.setTimeZoneId(ZoneId.of(tzId)); + newUsers.put(user.getUserId(), user); + } + } + + // Roles + try (PreparedStatement ps = conn.prepareStatement( + "SELECT user_id, role FROM user_roles ORDER BY user_id"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + final User user = newUsers.get(rs.getString("user_id")); + if (user != null) user.addRole(rs.getString("role")); + } + } + + // Contacts (email, pager, phone, xmpp, etc.) + try (PreparedStatement ps = conn.prepareStatement( + "SELECT user_id, contact_type, contact_info, service_provider FROM user_contacts ORDER BY user_id"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + final User user = newUsers.get(rs.getString("user_id")); + if (user != null) { + final Contact contact = new Contact(); + contact.setType(rs.getString("contact_type")); + final String info = rs.getString("contact_info"); + if (info != null) contact.setInfo(info); + final String provider = rs.getString("service_provider"); + if (provider != null) contact.setServiceProvider(provider); + user.addContact(contact); + } + } + } + + // Duty schedules + try (PreparedStatement ps = conn.prepareStatement( + "SELECT user_id, schedule FROM user_duty_schedules ORDER BY user_id"); + ResultSet rs = ps.executeQuery()) { + while (rs.next()) { + final User user = newUsers.get(rs.getString("user_id")); + if (user != null) user.addDutySchedule(rs.getString("schedule")); + } + } + + m_users = newUsers; + rebuildDutySchedules(); + LOG.debug("Loaded {} users from database", newUsers.size()); + + } catch (final SQLException e) { + throw new IOException("Failed to load users from database", e); + } + } + + private void insertUser(final Connection conn, final User user) throws SQLException { + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO users (user_id, full_name, user_comments, password, password_salt, tui_pin, time_zone_id)" + + " VALUES (?, ?, ?, ?, ?, ?, ?)")) { + ps.setString(1, user.getUserId()); + ps.setString(2, user.getFullName().orElse(null)); + ps.setString(3, user.getUserComments().orElse(null)); + ps.setString(4, user.getPassword().getEncryptedPassword()); + ps.setBoolean(5, user.getPassword().getSalt()); + ps.setString(6, user.getTuiPin().orElse(null)); + ps.setString(7, user.getTimeZoneId().map(ZoneId::getId).orElse(null)); + ps.executeUpdate(); + } + + for (final String role : user.getRoles()) { + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO user_roles (user_id, role) VALUES (?, ?)")) { + ps.setString(1, user.getUserId()); + ps.setString(2, role); + ps.executeUpdate(); + } + } + + // Deduplicate contacts by type: last value wins, matching UserManager._setContact() behavior. + // The user_contacts table has a UNIQUE(user_id, contact_type) constraint. + final LinkedHashMap contactsByType = new LinkedHashMap<>(); + for (final Contact contact : user.getContacts()) { + contactsByType.put(contact.getType(), contact); + } + for (final Contact contact : contactsByType.values()) { + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO user_contacts (user_id, contact_type, contact_info, service_provider)" + + " VALUES (?, ?, ?, ?)")) { + ps.setString(1, user.getUserId()); + ps.setString(2, contact.getType()); + ps.setString(3, contact.getInfo().orElse(null)); + ps.setString(4, contact.getServiceProvider().orElse(null)); + ps.executeUpdate(); + } + } + + for (final String schedule : user.getDutySchedules()) { + try (PreparedStatement ps = conn.prepareStatement( + "INSERT INTO user_duty_schedules (user_id, schedule) VALUES (?, ?)")) { + ps.setString(1, user.getUserId()); + ps.setString(2, schedule); + ps.executeUpdate(); + } + } + } + + // Replicates UserManager._buildDutySchedules() which is private. + private void rebuildDutySchedules() { + m_dutySchedules = new HashMap<>(); + for (final Map.Entry entry : m_users.entrySet()) { + final User user = entry.getValue(); + if (!user.getDutySchedules().isEmpty()) { + final List dutyList = new ArrayList<>(); + for (final String duty : user.getDutySchedules()) { + dutyList.add(new DutySchedule(duty)); + } + m_dutySchedules.put(entry.getKey(), dutyList); + } + } + } +} diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/UserFactory.java b/opennms-config/src/main/java/org/opennms/netmgt/config/UserFactory.java index 70f07dc36690..036b6caaec0a 100644 --- a/opennms-config/src/main/java/org/opennms/netmgt/config/UserFactory.java +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/UserFactory.java @@ -35,6 +35,8 @@ import org.apache.commons.io.IOUtils; import org.opennms.core.utils.ConfigFileConstants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** *

UserFactory class.

@@ -43,6 +45,7 @@ * @version $Id: $ */ public class UserFactory extends UserManager { + private static final Logger LOG = LoggerFactory.getLogger(UserFactory.class); private static final long RELOAD_CHECK_INTERVAL_MS = TimeUnit.SECONDS.toMillis(1); /** @@ -89,14 +92,22 @@ public UserFactory() throws FileNotFoundException, IOException { * @throws java.io.IOException if any. * @throws java.io.FileNotFoundException if any. */ - public static synchronized void init() throws IOException, FileNotFoundException { - - if (instance == null || !initialized) { - GroupFactory.init(); - instance = new UserFactory(); + public static synchronized void init() throws IOException { + if (instance != null && initialized) { + return; + } + GroupFactory.init(); + final java.io.File usersConfFile; + try { + usersConfFile = ConfigFileConstants.getFile(ConfigFileConstants.USERS_CONF_FILE_NAME); + } catch (final FileNotFoundException e) { + // users.xml removed — DatabaseUserManager is authoritative. initialized = true; + LOG.info("users.xml not found; skipping file-backed UserFactory init"); + return; } - + instance = new UserFactory(); + initialized = true; } /** diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/UserManager.java b/opennms-config/src/main/java/org/opennms/netmgt/config/UserManager.java index dd6c804b5e71..742622738e58 100644 --- a/opennms-config/src/main/java/org/opennms/netmgt/config/UserManager.java +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/UserManager.java @@ -931,6 +931,7 @@ public void saveUsers(final Collection usersList) throws Exception { for (final User curUser : usersList) { m_users.put(curUser.getUserId(), curUser); } + _saveCurrent(); } finally { m_writeLock.unlock(); } @@ -973,7 +974,7 @@ public void deleteUser(final String name) throws Exception { /** * Saves into "users.xml" file */ - private void _saveCurrent() throws Exception { + protected void _saveCurrent() throws Exception { final List users = new ArrayList<>(m_users.values()); final Userinfo userinfo = new Userinfo(); diff --git a/opennms-config/src/main/java/org/opennms/netmgt/config/WebRoleManagerImpl.java b/opennms-config/src/main/java/org/opennms/netmgt/config/WebRoleManagerImpl.java index 99d865fa3070..a566ee64ba76 100644 --- a/opennms-config/src/main/java/org/opennms/netmgt/config/WebRoleManagerImpl.java +++ b/opennms-config/src/main/java/org/opennms/netmgt/config/WebRoleManagerImpl.java @@ -79,7 +79,7 @@ private User getBackingUser(String name) { try { return m_userManager.getUser(name); } catch (IOException e) { - throw new WebRolesException("Error reading users.xml config file", e); + throw new WebRolesException("Error reading user from database", e); } } @@ -87,7 +87,7 @@ private Collection getBackingUsers() { try { return m_userManager.getUsers().values(); } catch (IOException e) { - throw new WebRolesException("Error reading users.xml config file", e); + throw new WebRolesException("Error reading user from database", e); } } @@ -121,7 +121,7 @@ private Collection getUsersScheduleForRole(WebRole role, Date time) { } return webUsers; } catch (IOException e) { - throw new WebRolesException("Error reading users.xml config file", e); + throw new WebRolesException("Error reading user from database", e); } } diff --git a/opennms-config/src/main/resources/META-INF/opennms/applicationContext-commonConfigs.xml b/opennms-config/src/main/resources/META-INF/opennms/applicationContext-commonConfigs.xml index 1a90e0dba20e..95cd515797c3 100644 --- a/opennms-config/src/main/resources/META-INF/opennms/applicationContext-commonConfigs.xml +++ b/opennms-config/src/main/resources/META-INF/opennms/applicationContext-commonConfigs.xml @@ -67,10 +67,10 @@ - org.opennms.netmgt.config.UserFactory.init + org.opennms.netmgt.config.DatabaseUserManager.init - + diff --git a/opennms-config/src/test/java/org/opennms/netmgt/config/DatabaseUserManagerTest.java b/opennms-config/src/test/java/org/opennms/netmgt/config/DatabaseUserManagerTest.java new file mode 100644 index 000000000000..c56a4f19ae0d --- /dev/null +++ b/opennms-config/src/test/java/org/opennms/netmgt/config/DatabaseUserManagerTest.java @@ -0,0 +1,340 @@ +/* + * Licensed to The OpenNMS Group, Inc (TOG) under one or more + * contributor license agreements. See the LICENSE.md file + * distributed with this work for additional information + * regarding copyright ownership. + * + * TOG licenses this file to You under the GNU Affero General + * Public License Version 3 (the "License") or (at your option) + * any later version. You may not use this file except in + * compliance with the License. You may obtain a copy of the + * License at: + * + * https://www.gnu.org/licenses/agpl-3.0.txt + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, + * either express or implied. See the License for the specific + * language governing permissions and limitations under the + * License. + */ +package org.opennms.netmgt.config; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; + +import java.io.File; +import java.io.FileWriter; +import java.nio.file.Files; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.Statement; +import java.util.Arrays; +import java.util.Map; + +import javax.sql.DataSource; + +import org.h2.jdbcx.JdbcDataSource; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.opennms.core.db.DataSourceFactory; +import org.opennms.netmgt.config.users.Contact; +import org.opennms.netmgt.config.users.Password; +import org.opennms.netmgt.config.users.User; + +public class DatabaseUserManagerTest { + + private Connection conn; + private DatabaseUserManager manager; + + @Before + public void setUp() throws Exception { + Class.forName("org.h2.Driver"); + // Each test gets its own named in-memory DB so tests don't bleed into each other + final String dbName = "users_test_" + Thread.currentThread().getId(); + conn = DriverManager.getConnection("jdbc:h2:mem:" + dbName + ";DB_CLOSE_DELAY=-1"); + createSchema(conn); + + final JdbcDataSource ds = new JdbcDataSource(); + ds.setURL("jdbc:h2:mem:" + dbName + ";DB_CLOSE_DELAY=-1"); + DataSourceFactory.setInstance(ds); + + manager = new DatabaseUserManager(mock(GroupManager.class)); + manager.reload(); // initializes m_users from empty DB + } + + @After + public void tearDown() throws Exception { + try (Statement s = conn.createStatement()) { + s.execute("DROP ALL OBJECTS"); + } + conn.close(); + DataSourceFactory.close(); + } + + @Test + public void testSaveAndReloadUser() throws Exception { + final User user = buildUser("alice", "Alice Smith", "ROLE_USER", "ROLE_ADMIN"); + addEmail(user, "alice@example.com"); + user.addDutySchedule("MoTuWeThFr800-1700"); + + manager.saveUser("alice", user); + + manager.reload(); + final User loaded = manager.getUser("alice"); + + assertNotNull(loaded); + assertEquals("alice", loaded.getUserId()); + assertEquals("Alice Smith", loaded.getFullName().orElse(null)); + assertTrue(loaded.getRoles().contains("ROLE_USER")); + assertTrue(loaded.getRoles().contains("ROLE_ADMIN")); + assertEquals("alice@example.com", emailOf(loaded)); + assertEquals(1, loaded.getDutySchedules().size()); + assertEquals("MoTuWeThFr800-1700", loaded.getDutySchedules().get(0)); + } + + @Test + public void testDeleteUserCascadesToChildTables() throws Exception { + manager.saveUser("bob", buildUser("bob", "Bob Jones", "ROLE_USER")); + manager.reload(); + assertNotNull(manager.getUser("bob")); + + manager.deleteUser("bob"); + manager.reload(); + assertNull(manager.getUser("bob")); + + // All child rows must be gone + assertRowCount(0, "user_roles", "bob"); + assertRowCount(0, "user_contacts", "bob"); + assertRowCount(0, "user_duty_schedules", "bob"); + } + + @Test + public void testRenameUser() throws Exception { + manager.saveUser("old", buildUser("old", "Original", "ROLE_USER")); + manager.reload(); + + manager.renameUser("old", "new"); + manager.reload(); + + assertNull(manager.getUser("old")); + assertNotNull(manager.getUser("new")); + assertEquals("Original", manager.getUser("new").getFullName().orElse(null)); + } + + @Test + public void testPasswordRoundTrip() throws Exception { + final User user = buildUser("carol", "Carol", "ROLE_USER"); + manager.saveUser("carol", user); + + final String rawPw = "s3cr3t"; + manager.setUnencryptedPassword("carol", rawPw); + + manager.reload(); + assertTrue(manager.comparePasswords("carol", rawPw)); + } + + @Test + public void testSaveUsersReplacesAll() throws Exception { + manager.saveUser("u1", buildUser("u1", "User 1", "ROLE_USER")); + manager.saveUser("u2", buildUser("u2", "User 2", "ROLE_USER")); + manager.reload(); + assertEquals(2, manager.getUsers().size()); + + // saveUsers() with only u3 should replace both u1 and u2 + manager.saveUsers(Arrays.asList(buildUser("u3", "User 3", "ROLE_ADMIN"))); + manager.reload(); + + final Map users = manager.getUsers(); + assertEquals(1, users.size()); + assertTrue(users.containsKey("u3")); + assertFalse(users.containsKey("u1")); + } + + @Test + public void testDuplicateContactTypeDeduplication() throws Exception { + final User user = buildUser("dup", "Dup User", "ROLE_USER"); + // Add two email contacts — only last one should persist + final Contact c1 = new Contact(); + c1.setType("email"); + c1.setInfo("first@example.com"); + user.addContact(c1); + final Contact c2 = new Contact(); + c2.setType("email"); + c2.setInfo("second@example.com"); + user.addContact(c2); + + manager.saveUser("dup", user); // should not throw UNIQUE constraint violation + manager.reload(); + + final User loaded = manager.getUser("dup"); + final long emailCount = loaded.getContacts().stream() + .filter(c -> "email".equals(c.getType())) + .count(); + assertEquals(1, emailCount); + assertEquals("second@example.com", emailOf(loaded)); + } + + @Test + public void testMigrateFromXmlOnEmptyDatabase() throws Exception { + final File tmpHome = Files.createTempDirectory("opennms-test").toFile(); + final File etc = new File(tmpHome, "etc"); + etc.mkdirs(); + + try (FileWriter fw = new FileWriter(new File(etc, "users.xml"))) { + fw.write("\n" + + "\n" + + " \n" + + " \n" + + " migrated\n" + + " Migrated User\n" + + " abc123\n" + + " \n" + + " \n" + + "\n"); + } + + final String prev = System.getProperty("opennms.home"); + System.setProperty("opennms.home", tmpHome.getAbsolutePath()); + try { + // Reload with empty DB triggers XML migration + manager.reload(); + assertNotNull(manager.getUser("migrated")); + assertEquals("Migrated User", manager.getUser("migrated").getFullName().orElse(null)); + } finally { + if (prev != null) System.setProperty("opennms.home", prev); + else System.clearProperty("opennms.home"); + } + } + + @Test + public void testMultipleUsersWithContactsAndDutySchedules() throws Exception { + for (int i = 0; i < 5; i++) { + final User u = buildUser("user" + i, "User " + i, "ROLE_USER"); + addEmail(u, "user" + i + "@example.com"); + u.addDutySchedule("MoTuWeThFr800-1700"); + manager.saveUser("user" + i, u); + } + + manager.reload(); + assertEquals(5, manager.getUsers().size()); + + for (int i = 0; i < 5; i++) { + final User loaded = manager.getUser("user" + i); + assertEquals("user" + i + "@example.com", emailOf(loaded)); + assertEquals(1, loaded.getDutySchedules().size()); + } + } + + @Test + public void testIsUserOnDuty() throws Exception { + final User u = buildUser("oncall", "On Call", "ROLE_USER"); + u.addDutySchedule("MoTuWeThFrSaSu0000-2359"); // always on duty + manager.saveUser("oncall", u); + + manager.reload(); + assertTrue(manager.isUserOnDuty("oncall", java.util.Calendar.getInstance())); + } + + @Test + public void testGetFileSize() throws Exception { + assertEquals(0L, manager.getFileSize()); + } + + @Test + public void testIsUpdateNeededAlwaysFalse() { + assertFalse(manager.isUpdateNeeded()); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private static User buildUser(final String id, final String fullName, final String... roles) { + final User user = new User(); + user.setUserId(id); + user.setFullName(fullName); + final Password pass = new Password(); + pass.setEncryptedPassword("hashed_" + id); + pass.setSalt(true); + user.setPassword(pass); + for (final String role : roles) { + user.addRole(role); + } + return user; + } + + private static void addEmail(final User user, final String email) { + final Contact c = new Contact(); + c.setType("email"); + c.setInfo(email); + user.addContact(c); + } + + private static String emailOf(final User user) { + return user.getContacts().stream() + .filter(c -> "email".equals(c.getType())) + .findFirst() + .flatMap(Contact::getInfo) + .orElse(null); + } + + private void assertRowCount(final int expected, final String table, final String userId) throws Exception { + try (Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery( + "SELECT COUNT(*) FROM " + table + " WHERE user_id = '" + userId + "'")) { + rs.next(); + assertEquals(expected, rs.getInt(1)); + } + } + + private static void createSchema(final Connection c) throws Exception { + final Statement s = c.createStatement(); + s.execute( + "CREATE TABLE users (" + + " user_id VARCHAR(256) PRIMARY KEY NOT NULL," + + " full_name VARCHAR(256)," + + " user_comments TEXT," + + " password VARCHAR(512) NOT NULL," + + " password_salt BOOLEAN NOT NULL DEFAULT TRUE," + + " tui_pin VARCHAR(32)," + + " time_zone_id VARCHAR(64)," + + " created_at TIMESTAMP NOT NULL DEFAULT NOW()," + + " updated_at TIMESTAMP NOT NULL DEFAULT NOW()" + + ")" + ); + s.execute( + "CREATE TABLE user_roles (" + + " id INT AUTO_INCREMENT PRIMARY KEY NOT NULL," + + " user_id VARCHAR(256) NOT NULL REFERENCES users(user_id) ON DELETE CASCADE," + + " role VARCHAR(128) NOT NULL," + + " UNIQUE (user_id, role)" + + ")" + ); + s.execute( + "CREATE TABLE user_contacts (" + + " id INT AUTO_INCREMENT PRIMARY KEY NOT NULL," + + " user_id VARCHAR(256) NOT NULL REFERENCES users(user_id) ON DELETE CASCADE," + + " contact_type VARCHAR(64) NOT NULL," + + " contact_info TEXT," + + " service_provider TEXT," + + " UNIQUE (user_id, contact_type)" + + ")" + ); + s.execute( + "CREATE TABLE user_duty_schedules (" + + " id INT AUTO_INCREMENT PRIMARY KEY NOT NULL," + + " user_id VARCHAR(256) NOT NULL REFERENCES users(user_id) ON DELETE CASCADE," + + " schedule VARCHAR(128) NOT NULL" + + ")" + ); + s.close(); + } +} diff --git a/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/FilesystemRestService.java b/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/FilesystemRestService.java index 903ca14396d3..777a84a5c06e 100644 --- a/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/FilesystemRestService.java +++ b/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/FilesystemRestService.java @@ -59,7 +59,6 @@ import org.apache.commons.io.IOUtils; import org.apache.cxf.jaxrs.ext.multipart.Attachment; import org.apache.cxf.jaxrs.ext.multipart.Multipart; -import org.opennms.core.utils.ConfigFileConstants; import org.opennms.web.api.Authentication; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -84,18 +83,7 @@ public class FilesystemRestService { "groovy", "bsh", "dcb"); - private final java.nio.file.Path usersXml; - public FilesystemRestService() { - try { - this.usersXml = ConfigFileConstants.getFile(ConfigFileConstants.USERS_CONF_FILE_NAME).toPath(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - FilesystemRestService(final java.nio.file.Path usersXml) { - this.usersXml = usersXml; } private final java.nio.file.Path etcFolder = Paths.get(System.getProperty("opennms.home"), "etc"); @@ -111,7 +99,6 @@ public List getFiles(@QueryParam("changedFilesOnly") boolean changedFile try { return Files.find(etcFolder, 4, (path, basicFileAttributes) -> isSupportedExtension(path), FileVisitOption.FOLLOW_LINKS) - .filter(p -> !p.equals(usersXml) || securityContext.isUserInRole(Authentication.ROLE_ADMIN)) .map(p -> etcFolder.relativize(p).toString()) .filter(p -> !changedFilesOnly || !doesFileExistAndMatchContentsWithEtcPristine(p, securityContext)) .sorted() @@ -165,7 +152,8 @@ public Response getFileContents(@QueryParam("f") String fileName, @Context Secur if (!securityContext.isUserInRole(Authentication.ROLE_FILESYSTEM_EDITOR)) { throw new ForbiddenException("FILESYSTEM EDITOR role is required for reading files."); } - return fileContents(ensureFileIsAllowed(fileName, securityContext)); + final java.nio.file.Path targetPath = ensureFileIsAllowed(fileName, securityContext); + return fileContents(targetPath); } @POST @@ -239,10 +227,6 @@ private java.nio.file.Path ensureFileIsAllowed(String fileName, SecurityContext final java.nio.file.Path etcFolderNormalized = etcFolder.normalize(); final java.nio.file.Path fileNormalized = etcFolder.resolve(fileName).normalize(); - if (fileNormalized.equals(usersXml) && !securityContext.isUserInRole(Authentication.ROLE_ADMIN)) { - throw new ForbiddenException("ADMIN role is required for accessing users.xml file contents."); - } - if (!(fileNormalized.getNameCount() > etcFolderNormalized.getNameCount() && fileNormalized.startsWith(etcFolderNormalized))) { throw new BadRequestException("Cannot access files outside of folder! Filename given: " + fileName); } diff --git a/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/WhoamiRestService.java b/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/WhoamiRestService.java index ba3c555a4937..deda2520ec50 100644 --- a/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/WhoamiRestService.java +++ b/opennms-webapp-rest/src/main/java/org/opennms/web/rest/v1/WhoamiRestService.java @@ -67,7 +67,7 @@ public Response whoami(@Context final SecurityContext securityContext) { userInfo.put("roles", userRoles); userInfo.put("internal", false); - // Check if the user exists in users.xml, if so it is an internal + // Check if the user exists in the database; if so it is an internal // user and email, full name, etc. can be populated try { final OnmsUser onmsUser = userManager.getOnmsUser(userName); diff --git a/opennms-webapp-rest/src/test/java/org/opennms/web/rest/v1/FilesystemRestServiceTest.java b/opennms-webapp-rest/src/test/java/org/opennms/web/rest/v1/FilesystemRestServiceTest.java index 6641c91cc0cd..1447fc64dee6 100644 --- a/opennms-webapp-rest/src/test/java/org/opennms/web/rest/v1/FilesystemRestServiceTest.java +++ b/opennms-webapp-rest/src/test/java/org/opennms/web/rest/v1/FilesystemRestServiceTest.java @@ -58,7 +58,7 @@ public void before() throws IOException { @Test public void testXmlValidation() throws IOException { - final FilesystemRestService filesystemRestService = new FilesystemRestService(null); + final FilesystemRestService filesystemRestService = new FilesystemRestService(); final HttpServer httpServer = HttpServer.create(new InetSocketAddress(1337), 10); httpServer.createContext("/", new TestHttpHandler()); httpServer.setExecutor(null); diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java index edc707e7e553..c1ae9052959b 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/AddNewUserServlet.java @@ -54,11 +54,6 @@ public class AddNewUserServlet extends HttpServlet { /** {@inheritDoc} */ @Override public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - try { - UserFactory.init(); - } catch (Throwable e) { - throw new ServletException("AddNewUserServlet: Error initialising user factory." + e); - } UserManager userFactory = UserFactory.getInstance(); String userID = request.getParameter("userID"); @@ -73,7 +68,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr try { hasUser = userFactory.hasUser(userID); } catch (Throwable e) { - throw new ServletException("can't determine if user " + userID + " already exists in users.xml.", e); + throw new ServletException("can't determine if user " + userID + " already exists in the database.", e); } if (hasUser) { diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/DeleteUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/DeleteUserServlet.java index 71dc62173208..f187ad59013a 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/DeleteUserServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/DeleteUserServlet.java @@ -52,9 +52,7 @@ public class DeleteUserServlet extends HttpServlet { public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String userID = request.getParameter("userID"); - // now save to the xml file try { - UserFactory.init(); UserManager userFactory = UserFactory.getInstance(); userFactory.deleteUser(userID); } catch (Throwable e) { diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/ModifyUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/ModifyUserServlet.java index 7d0d1b38bc1b..356918905f49 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/ModifyUserServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/ModifyUserServlet.java @@ -57,7 +57,6 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr HttpSession userSession = request.getSession(true); try { - UserFactory.init(); UserManager userFactory = UserFactory.getInstance(); User user = userFactory.getUser(request.getParameter("userID")); userSession.setAttribute("user.modifyUser.jsp", user); diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java index 51774a53e2c1..65a507354133 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/RenameUserServlet.java @@ -57,7 +57,6 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr throw new ServletException("User ID must not contain any HTML markup."); } - // now save to the xml file try { UserManager userFactory = UserFactory.getInstance(); userFactory.renameUser(userID, newID); diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/SaveUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/SaveUserServlet.java index 51b231172827..ec714690ec77 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/SaveUserServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/SaveUserServlet.java @@ -60,7 +60,6 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr throw new ServletException("Error: user " + newUser.getUserId() + " is read-only!"); } - // now save to the XML file try { userFactory.saveUser(newUser.getUserId(), newUser); } catch (Throwable e) { diff --git a/opennms-webapp/src/main/java/org/opennms/web/admin/users/UpdateUserServlet.java b/opennms-webapp/src/main/java/org/opennms/web/admin/users/UpdateUserServlet.java index d8064563262b..1abb9bc37400 100644 --- a/opennms-webapp/src/main/java/org/opennms/web/admin/users/UpdateUserServlet.java +++ b/opennms-webapp/src/main/java/org/opennms/web/admin/users/UpdateUserServlet.java @@ -62,12 +62,6 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr if (userSession != null) { User newUser = (User) userSession.getAttribute("user.modifyUser.jsp"); - try { - UserFactory.init(); - } catch (Throwable e) { - throw new ServletException("UpdateUserServlet:init Error initialising UserFactory " + e); - } - // get the rest of the user information from the form newUser.setFullName(request.getParameter("fullName")); newUser.setUserComments(request.getParameter("userComments")); @@ -106,52 +100,15 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr newUser.clearContacts(); - Contact tmpContact = new Contact(); - tmpContact.setInfo(email); - tmpContact.setType(ContactType.email.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(pagerEmail); - tmpContact.setType(ContactType.pagerEmail.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(xmppAddress); - tmpContact.setType(ContactType.xmppAddress.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(microblog); - tmpContact.setType(ContactType.microblog.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(numericPin); - tmpContact.setServiceProvider(numericPage); - tmpContact.setType(ContactType.numericPage.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(textPin); - tmpContact.setServiceProvider(textPage); - tmpContact.setType(ContactType.textPage.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(workPhone); - tmpContact.setType(ContactType.workPhone.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(mobilePhone); - tmpContact.setType(ContactType.mobilePhone.toString()); - newUser.addContact(tmpContact); - - tmpContact = new Contact(); - tmpContact.setInfo(homePhone); - tmpContact.setType(ContactType.homePhone.toString()); - newUser.addContact(tmpContact); + addContactIfNotBlank(newUser, ContactType.email.toString(), null, email); + addContactIfNotBlank(newUser, ContactType.pagerEmail.toString(), null, pagerEmail); + addContactIfNotBlank(newUser, ContactType.xmppAddress.toString(), null, xmppAddress); + addContactIfNotBlank(newUser, ContactType.microblog.toString(), null, microblog); + addContactIfNotBlank(newUser, ContactType.numericPage.toString(), numericPage, numericPin); + addContactIfNotBlank(newUser, ContactType.textPage.toString(), textPage, textPin); + addContactIfNotBlank(newUser, ContactType.workPhone.toString(), null, workPhone); + addContactIfNotBlank(newUser, ContactType.mobilePhone.toString(), null, mobilePhone); + addContactIfNotBlank(newUser, ContactType.homePhone.toString(), null, homePhone); // build the duty schedule data structure List newSchedule = new ArrayList(7); @@ -201,5 +158,16 @@ public void doPost(HttpServletRequest request, HttpServletResponse response) thr private List getDutySchedulesForUser(User newUser) { return newUser.getDutySchedules(); } - + + private void addContactIfNotBlank(User user, String type, String serviceProvider, String info) { + if ((info != null && !info.trim().isEmpty()) || (serviceProvider != null && !serviceProvider.trim().isEmpty())) { + Contact c = new Contact(); + c.setType(type); + c.setInfo(info); + if (serviceProvider != null && !serviceProvider.trim().isEmpty()) { + c.setServiceProvider(serviceProvider); + } + user.addContact(c); + } + } } diff --git a/opennms-webapp/src/main/webapp/WEB-INF/applicationContext-spring-security.xml b/opennms-webapp/src/main/webapp/WEB-INF/applicationContext-spring-security.xml index 3bc8906fbfeb..b9f023822c03 100644 --- a/opennms-webapp/src/main/webapp/WEB-INF/applicationContext-spring-security.xml +++ b/opennms-webapp/src/main/webapp/WEB-INF/applicationContext-spring-security.xml @@ -444,7 +444,6 @@ -