Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/password/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@
<artifactId>jasypt-dependencies</artifactId>
<type>pom</type>
</dependency>
<!-- Available at runtime via the OpenNMS lib directory; declared provided so the fat-jar stays thin. -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>

</project>
140 changes: 88 additions & 52 deletions core/password/src/main/java/org/opennms/core/password/Password.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 <username> <password>");
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();
}

}
148 changes: 148 additions & 0 deletions core/schema/src/main/liquibase/36.0.2/changelog.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLog
xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd">

<!-- Primary user store: replaces users.xml -->
<changeSet author="emaki" id="36.0.2-users-table">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="users"/>
</not>
</preConditions>

<createTable tableName="users">
<column name="user_id" type="varchar(256)">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="full_name" type="varchar(256)"/>
<column name="user_comments" type="text"/>
<column name="password" type="varchar(512)">
<constraints nullable="false"/>
</column>
<column name="password_salt" type="boolean" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="tui_pin" type="varchar(32)"/>
<column name="time_zone_id" type="varchar(64)"/>
<column name="email" type="varchar(256)"/>
Comment on lines +16 to +30

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<column name="enabled" type="boolean" defaultValueBoolean="true">
<constraints nullable="false"/>
</column>
<column name="token_salt_count" type="integer" defaultValueNumeric="0">
<constraints nullable="false"/>
</column>
<column name="created_at" type="timestamp with time zone" defaultValueComputed="NOW()">
<constraints nullable="false"/>
</column>
<column name="updated_at" type="timestamp with time zone" defaultValueComputed="NOW()">
<constraints nullable="false"/>
</column>
</createTable>

<rollback>
<dropTable tableName="users"/>
</rollback>
</changeSet>

<!-- One-to-many: user ROLE_ADMIN, ROLE_USER, ROLE_RTC, etc. -->
<changeSet author="emaki" id="36.0.2-user-roles-table">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="user_roles"/>
</not>
</preConditions>

<createTable tableName="user_roles">
<column name="id" type="serial">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="user_id" type="varchar(256)">
<constraints nullable="false"/>
</column>
<column name="role" type="varchar(128)">
<constraints nullable="false"/>
</column>
</createTable>

<addForeignKeyConstraint constraintName="fk_user_roles_user_id"
baseTableName="user_roles" baseColumnNames="user_id"
referencedTableName="users" referencedColumnNames="user_id"
onDelete="CASCADE"/>

<addUniqueConstraint tableName="user_roles" columnNames="user_id, role"
constraintName="uq_user_roles_user_id_role"/>

<rollback>
<dropTable tableName="user_roles"/>
</rollback>
</changeSet>

<!-- One-to-many: email, pagerEmail, workPhone, mobilePhone, xmppAddress, etc. -->
<changeSet author="emaki" id="36.0.2-user-contacts-table">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="user_contacts"/>
</not>
</preConditions>

<createTable tableName="user_contacts">
<column name="id" type="serial">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="user_id" type="varchar(256)">
<constraints nullable="false"/>
</column>
<column name="contact_type" type="varchar(64)">
<constraints nullable="false"/>
</column>
<column name="contact_info" type="text"/>
<column name="service_provider" type="text"/>
</createTable>

<addForeignKeyConstraint constraintName="fk_user_contacts_user_id"
baseTableName="user_contacts" baseColumnNames="user_id"
referencedTableName="users" referencedColumnNames="user_id"
onDelete="CASCADE"/>

<addUniqueConstraint tableName="user_contacts" columnNames="user_id, contact_type"
constraintName="uq_user_contacts_user_id_type"/>

<rollback>
<dropTable tableName="user_contacts"/>
</rollback>
</changeSet>

<!-- One-to-many: notification duty schedules (e.g. "MoTuWeThFrSaSu800-2300") -->
<changeSet author="emaki" id="36.0.2-user-duty-schedules-table">
<preConditions onFail="MARK_RAN">
<not>
<tableExists tableName="user_duty_schedules"/>
</not>
</preConditions>

<createTable tableName="user_duty_schedules">
<column name="id" type="serial">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="user_id" type="varchar(256)">
<constraints nullable="false"/>
</column>
<column name="schedule" type="varchar(128)">
<constraints nullable="false"/>
</column>
</createTable>

<addForeignKeyConstraint constraintName="fk_user_duty_schedules_user_id"
baseTableName="user_duty_schedules" baseColumnNames="user_id"
referencedTableName="users" referencedColumnNames="user_id"
onDelete="CASCADE"/>

<rollback>
<dropTable tableName="user_duty_schedules"/>
</rollback>
</changeSet>

</databaseChangeLog>
1 change: 1 addition & 0 deletions core/schema/src/main/liquibase/changelog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<include file="35.0.0/changelog.xml"/>
<include file="36.0.0/changelog.xml"/>
<include file="36.0.1/changelog.xml"/>
<include file="36.0.2/changelog.xml"/>

<include file="stored-procedures/getManagePercentAvailIntfWindow.xml" />
<include file="stored-procedures/getManagePercentAvailNodeWindow.xml" />
Expand Down
Loading
Loading