Skip to content

Commit d287c0c

Browse files
committed
Generate password on first boot
Signed-off-by: Mitch Gaffigan <mitch.gaffigan@comcast.net>
1 parent e765919 commit d287c0c

8 files changed

Lines changed: 75 additions & 26 deletions

File tree

server/conf/mirth.properties

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ server.api.accesscontrolallowheaders = Content-Type
5454
server.api.accesscontrolexposeheaders =
5555
server.api.accesscontrolmaxage =
5656

57+
# Password for the admin user, only applied when the database is first created.
58+
# If blank, a random password is generated and written to the log once on first startup.
59+
server.initialadminpassword =
60+
5761
# Determines whether or not channels are deployed on server startup.
5862
server.startupdeploy = true
5963

server/dbconf/derby/derby-database.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,6 @@ CREATE TABLE DEBUGGER_USAGE
105105

106106
INSERT INTO PERSON (USERNAME, LOGGED_IN) VALUES('admin', 0);
107107

108-
INSERT INTO PERSON_PASSWORD (PERSON_ID, PASSWORD) VALUES(1, 'b8cA3mDkavInMc2JBYa6/C3EGxDp7ppqh7FsoXx0x8+3LWK3Ed3ELg==');
109-
110108
INSERT INTO SCHEMA_INFO (VERSION) VALUES ('4.6.0');
111109

112110
INSERT INTO CONFIGURATION (CATEGORY, NAME, VALUE) VALUES ('core', 'stats.enabled', '1');

server/dbconf/mysql/mysql-database.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,8 +111,6 @@ CREATE TABLE DEBUGGER_USAGE
111111

112112
INSERT INTO PERSON (USERNAME, LOGGED_IN) VALUES('admin', FALSE);
113113

114-
INSERT INTO PERSON_PASSWORD (PERSON_ID, PASSWORD) VALUES(1, 'b8cA3mDkavInMc2JBYa6/C3EGxDp7ppqh7FsoXx0x8+3LWK3Ed3ELg==');
115-
116114
INSERT INTO SCHEMA_INFO (VERSION) VALUES ('4.6.0');
117115

118116
INSERT INTO CONFIGURATION (CATEGORY, NAME, VALUE) VALUES ('core', 'stats.enabled', '1');

server/dbconf/oracle/oracle-database.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,6 @@ CREATE TABLE DEBUGGER_USAGE
108108

109109
INSERT INTO PERSON (ID, USERNAME, LOGGED_IN, USERCONSENT) VALUES (PERSON_SEQUENCE.NEXTVAL, 'admin', '0', '0');
110110

111-
INSERT INTO PERSON_PASSWORD (PERSON_ID, PASSWORD) VALUES(PERSON_SEQUENCE.CURRVAL, 'b8cA3mDkavInMc2JBYa6/C3EGxDp7ppqh7FsoXx0x8+3LWK3Ed3ELg==');
112-
113111
INSERT INTO SCHEMA_INFO (VERSION) VALUES ('4.6.0');
114112

115113
INSERT INTO CONFIGURATION (CATEGORY, NAME, VALUE) VALUES ('core', 'stats.enabled', '1');

server/dbconf/postgres/postgres-database.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,6 @@ CREATE TABLE DEBUGGER_USAGE
107107

108108
INSERT INTO PERSON (USERNAME, LOGGED_IN) VALUES('admin', FALSE);
109109

110-
INSERT INTO PERSON_PASSWORD (PERSON_ID, PASSWORD) VALUES(1, 'b8cA3mDkavInMc2JBYa6/C3EGxDp7ppqh7FsoXx0x8+3LWK3Ed3ELg==');
111-
112110
INSERT INTO SCHEMA_INFO (VERSION) VALUES ('4.6.0');
113111

114112
INSERT INTO CONFIGURATION (CATEGORY, NAME, VALUE) VALUES ('core', 'stats.enabled', '1');

server/dbconf/sqlserver/sqlserver-database.sql

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,8 +114,6 @@ CREATE TABLE DEBUGGER_USAGE
114114

115115
INSERT INTO PERSON (USERNAME, LOGGED_IN, USERCONSENT) VALUES('admin', 0, 0);
116116

117-
INSERT INTO PERSON_PASSWORD (PERSON_ID, PASSWORD) VALUES(1, 'b8cA3mDkavInMc2JBYa6/C3EGxDp7ppqh7FsoXx0x8+3LWK3Ed3ELg==');
118-
119117
INSERT INTO SCHEMA_INFO (VERSION) VALUES ('4.6.0');
120118

121119
INSERT INTO CONFIGURATION (CATEGORY, NAME, VALUE) VALUES ('core', 'stats.enabled', '1');

server/src/main/java/com/mirth/connect/client/core/api/servlets/UserServletInterface.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ public interface UserServletInterface extends BaseServletInterface {
6363
@MirthOperation(name = "login", display = "Login")
6464
public LoginStatus login(// @formatter:off
6565
@Param("username") @Parameter(description = "The username to login with.", required = true, schema = @Schema(defaultValue = "admin")) @FormParam("username") String username,
66-
@Param(value = "password", excludeFromAudit = true) @Parameter(description = "The password to login with.", required = true, schema = @Schema(defaultValue = "admin")) @FormParam("password") String password) throws ClientException;
66+
@Param(value = "password", excludeFromAudit = true) @Parameter(description = "The password to login with.", required = true) @FormParam("password") String password) throws ClientException;
6767
// @formatter:on
6868

6969
@POST

server/src/main/java/com/mirth/connect/server/migration/ServerMigrator.java

Lines changed: 70 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
package com.mirth.connect.server.migration;
1111

12+
import java.security.SecureRandom;
1213
import java.sql.Connection;
1314
import java.sql.PreparedStatement;
1415
import java.sql.ResultSet;
@@ -30,6 +31,7 @@
3031
import org.apache.logging.log4j.LogManager;
3132
import org.apache.logging.log4j.Logger;
3233

34+
import com.mirth.connect.client.core.PropertiesConfigurationUtil;
3335
import com.mirth.connect.client.core.Version;
3436
import com.mirth.connect.model.Channel;
3537
import com.mirth.connect.model.ExportClearable;
@@ -38,9 +40,16 @@
3840
import com.mirth.connect.model.codetemplates.CodeTemplateLibrary;
3941
import com.mirth.connect.model.converters.ObjectXMLSerializer;
4042
import com.mirth.connect.model.util.MigrationException;
43+
import com.mirth.connect.server.Mirth;
44+
import com.mirth.connect.server.controllers.ConfigurationController;
45+
import com.mirth.connect.server.controllers.ControllerFactory;
4146
import com.mirth.connect.server.util.DatabaseUtil;
4247

4348
public class ServerMigrator extends Migrator {
49+
private static final String INITIAL_ADMIN_USERNAME = "admin";
50+
private static final String INITIAL_ADMIN_PASSWORD_PROPERTY = "server.initialadminpassword";
51+
private static final int GENERATED_PASSWORD_LENGTH = 20;
52+
4453
private Logger logger = LogManager.getLogger(getClass());
4554

4655
public ServerMigrator() {
@@ -250,25 +259,71 @@ private void initDatabase(Connection connection) throws MigrationException {
250259
if (!DatabaseUtil.tableExists(connection, "CONFIGURATION")) {
251260
executeScript("/" + getDatabaseType() + "/" + getDatabaseType() + "-database.sql");
252261

253-
/*
254-
* We must update the password date for the initial user. Previously we let the database
255-
* set this via CURRENT_TIMESTAMP, however this could create problems if the database is
256-
* running on a separate machine in a different timezone. (MIRTH-2902)
257-
*/
258-
PreparedStatement statement = null;
262+
initializeAdminPassword();
259263

260-
try {
261-
statement = getConnection().prepareStatement("UPDATE PERSON_PASSWORD SET PASSWORD_DATE = ?");
262-
statement.setTimestamp(1, new Timestamp(System.currentTimeMillis()));
263-
statement.executeUpdate();
264-
} catch (SQLException e) {
265-
throw new MigrationException(e);
266-
} finally {
267-
DbUtils.closeQuietly(statement);
264+
updateVersion(Version.getLatest());
265+
}
266+
}
267+
268+
/**
269+
* Sets the password for the initial administrator account. The password is taken from the
270+
* server.initialadminpassword property if it is set, otherwise a random password is generated
271+
* and logged once so that it can be used to log in for the first time.
272+
*
273+
* The password date is set here rather than letting the database default it via
274+
* CURRENT_TIMESTAMP, since that could create problems if the database is running on a separate
275+
* machine in a different timezone. (MIRTH-2902)
276+
*/
277+
private void initializeAdminPassword() throws MigrationException {
278+
ConfigurationController configurationController = ControllerFactory.getFactory().createConfigurationController();
279+
PropertiesConfiguration mirthProperties = PropertiesConfigurationUtil.create();
280+
configurationController.updatePropertiesConfiguration(mirthProperties);
281+
// Log through the central Mirth.class logger to hit log4j filter configs
282+
Logger startupLogger = LogManager.getLogger(Mirth.class);
283+
284+
String password = mirthProperties.getString(INITIAL_ADMIN_PASSWORD_PROPERTY);
285+
if (StringUtils.isBlank(password)) {
286+
password = generatePassword();
287+
startupLogger.warn(
288+
"Generated an initial password for the \"{}\" user: {}\n"
289+
+ "This password is not logged again, so store it somewhere safe "
290+
+ "and change it after logging in. Set {} in mirth.properties "
291+
+ "before the first startup to choose the password instead.",
292+
INITIAL_ADMIN_USERNAME, password, INITIAL_ADMIN_PASSWORD_PROPERTY);
293+
}
294+
295+
PreparedStatement statement = null;
296+
try {
297+
String digestedPassword = configurationController.getDigester().digest(password);
298+
299+
statement = getConnection().prepareStatement(
300+
"INSERT INTO PERSON_PASSWORD (PERSON_ID, PASSWORD, PASSWORD_DATE) "
301+
+ "SELECT ID, ?, ? FROM PERSON WHERE USERNAME = ?");
302+
statement.setString(1, digestedPassword);
303+
statement.setTimestamp(2, new Timestamp(System.currentTimeMillis()));
304+
statement.setString(3, INITIAL_ADMIN_USERNAME);
305+
306+
if (statement.executeUpdate() != 1) {
307+
throw new MigrationException("Could not set the initial password for the \"" + INITIAL_ADMIN_USERNAME + "\" user.");
268308
}
309+
} catch (SQLException e) {
310+
throw new MigrationException(e);
311+
} finally {
312+
DbUtils.closeQuietly(statement);
313+
}
314+
}
269315

270-
updateVersion(Version.getLatest());
316+
private String generatePassword() {
317+
String characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
318+
SecureRandom random = new SecureRandom();
319+
StringBuilder builder = new StringBuilder();
320+
// Ensure the password contains at least one lowercase letter,
321+
// one uppercase letter, one digit, and one special character
322+
builder.append("Aa1!");
323+
for (int i = 0; i < GENERATED_PASSWORD_LENGTH; i++) {
324+
builder.append(characters.charAt(random.nextInt(characters.length())));
271325
}
326+
return builder.toString();
272327
}
273328

274329
/**

0 commit comments

Comments
 (0)