diff --git a/build.gradle b/build.gradle index 740ca4b..200faef 100644 --- a/build.gradle +++ b/build.gradle @@ -11,7 +11,24 @@ repositories { mavenCentral() } -version = '1.0.1' +version = '1.1.0' + +sourceSets { + + main { + + java { + srcDir 'src/main/java' + } + } + + test { + + java { + srcDir 'src/test/java' + } + } +} dependencies { @@ -37,11 +54,11 @@ dependencies { // MISC compile 'org.apache.commons:commons-lang3:3.7' - + compile 'org.bouncycastle:bcprov-jdk15on:1.61' // Unit Testing testCompile 'junit:junit:4.11' - testCompile 'org.mockito:mockito-all:1.10.19' + testCompile 'org.mockito:mockito-core:1.10.19' } jar { diff --git a/config/example.fleet.properties b/config/example.fleet.properties index 016c0f3..3796ad7 100644 --- a/config/example.fleet.properties +++ b/config/example.fleet.properties @@ -5,6 +5,9 @@ fleet.app.port=8080 fleet.refresh.interval=60 +# If set to DATABASE, fleet.admin.username and fleet.admin.password are not used. They can be omitted. +fleet.admin.authentication.type=PROPERTIES|DATABASE + # User for management of images and repositories # CHANGE THESE!! fleet.admin.username=test diff --git a/src/main/java/io/linuxserver/fleet/auth/UserCredentials.java b/src/main/java/io/linuxserver/fleet/auth/UserCredentials.java new file mode 100644 index 0000000..2f4546c --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/UserCredentials.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth; + +public class UserCredentials { + + private final String username; + private final String password; + + public UserCredentials(String username, String password) { + + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java b/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java new file mode 100644 index 0000000..4404a4e --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.authenticator; + +import io.linuxserver.fleet.auth.security.PKCS5S2PasswordEncoder; +import io.linuxserver.fleet.core.FleetBeans; +import io.linuxserver.fleet.core.FleetProperties; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AuthenticatorFactory { + + private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticatorFactory.class); + + public static UserAuthenticator getAuthenticator(FleetBeans beans) { + + FleetProperties properties = beans.getProperties(); + + AuthenticationType authType = AuthenticationType.valueOf(properties.getAuthenticationType().toUpperCase()); + switch (authType) { + + case PROPERTIES: + + LOGGER.info("Configuring new authenticator: PropertyLoadedUserAuthenticator"); + return new PropertyLoadedUserAuthenticator(properties.getAppUsername(), properties.getAppPassword()); + + case DATABASE: + + LOGGER.info("Configuring new authenticator: DatabaseStoredUserAuthenticator"); + return new DatabaseStoredUserAuthenticator(beans.getPasswordEncoder(), beans.getUserDelegate()); + } + + throw new RuntimeException("Provided authentication type is not supported"); + } + + public enum AuthenticationType { + PROPERTIES, DATABASE + } +} diff --git a/src/main/java/io/linuxserver/fleet/auth/authenticator/DatabaseStoredUserAuthenticator.java b/src/main/java/io/linuxserver/fleet/auth/authenticator/DatabaseStoredUserAuthenticator.java new file mode 100644 index 0000000..e409c3f --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/authenticator/DatabaseStoredUserAuthenticator.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.authenticator; + +import io.linuxserver.fleet.auth.AuthenticatedUser; +import io.linuxserver.fleet.auth.AuthenticationResult; +import io.linuxserver.fleet.auth.UserCredentials; +import io.linuxserver.fleet.auth.security.PasswordEncoder; +import io.linuxserver.fleet.delegate.UserDelegate; +import io.linuxserver.fleet.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DatabaseStoredUserAuthenticator implements UserAuthenticator { + + private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseStoredUserAuthenticator.class); + + private final PasswordEncoder passwordEncoder; + private final UserDelegate userDelegate; + + public DatabaseStoredUserAuthenticator(PasswordEncoder passwordEncoder, UserDelegate userDelegate) { + + this.passwordEncoder = passwordEncoder; + this.userDelegate = userDelegate; + } + + @Override + public AuthenticationResult authenticate(UserCredentials userCredentials) { + + User user = userDelegate.fetchUserByUsername(userCredentials.getUsername()); + if (null == user) { + + LOGGER.warn("Attempt to log in with user '{}' failed. Not found.", userCredentials.getUsername()); + return AuthenticationResult.notAuthenticated(); + } + + boolean authenticated = passwordEncoder.matches(userCredentials.getPassword(), user.getPassword()); + if (authenticated) { + return new AuthenticationResult(authenticated, new AuthenticatedUser(user.getUsername())); + } + + LOGGER.warn("Unable to verify user credentials for user {}", userCredentials.getUsername()); + return AuthenticationResult.notAuthenticated(); + } +} diff --git a/src/main/java/io/linuxserver/fleet/delegate/PropertiesAuthenticationDelegate.java b/src/main/java/io/linuxserver/fleet/auth/authenticator/PropertyLoadedUserAuthenticator.java similarity index 63% rename from src/main/java/io/linuxserver/fleet/delegate/PropertiesAuthenticationDelegate.java rename to src/main/java/io/linuxserver/fleet/auth/authenticator/PropertyLoadedUserAuthenticator.java index f35a345..3795e8f 100644 --- a/src/main/java/io/linuxserver/fleet/delegate/PropertiesAuthenticationDelegate.java +++ b/src/main/java/io/linuxserver/fleet/auth/authenticator/PropertyLoadedUserAuthenticator.java @@ -15,27 +15,28 @@ * along with this program. If not, see . */ -package io.linuxserver.fleet.delegate; +package io.linuxserver.fleet.auth.authenticator; import io.linuxserver.fleet.auth.AuthenticatedUser; import io.linuxserver.fleet.auth.AuthenticationResult; +import io.linuxserver.fleet.auth.UserCredentials; -public class PropertiesAuthenticationDelegate implements AuthenticationDelegate { +public class PropertyLoadedUserAuthenticator implements UserAuthenticator { private final String adminUsername; private final String adminPassword; - public PropertiesAuthenticationDelegate(String username, String password) { + public PropertyLoadedUserAuthenticator(String adminUsername, String adminPassword) { - this.adminUsername = username; - this.adminPassword = password; + this.adminUsername = adminUsername; + this.adminPassword = adminPassword; } @Override - public AuthenticationResult authenticate(String username, String password) { + public AuthenticationResult authenticate(UserCredentials userCredentials) { - if (adminUsername.equals(username) && adminPassword.equals(password)) - return new AuthenticationResult(true, new AuthenticatedUser(username)); + if (adminUsername.equals(userCredentials.getUsername()) && adminPassword.equals(userCredentials.getPassword())) + return new AuthenticationResult(true, new AuthenticatedUser(userCredentials.getUsername())); return AuthenticationResult.notAuthenticated(); } diff --git a/src/main/java/io/linuxserver/fleet/auth/authenticator/UserAuthenticator.java b/src/main/java/io/linuxserver/fleet/auth/authenticator/UserAuthenticator.java new file mode 100644 index 0000000..bec0401 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/authenticator/UserAuthenticator.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.authenticator; + +import io.linuxserver.fleet.auth.AuthenticationResult; +import io.linuxserver.fleet.auth.UserCredentials; + +/** + *

+ * Provides a mechanism for the application to authenticate a login request + * from a user. + *

+ */ +public interface UserAuthenticator { + + /** + *

+ * Performs an authentication check against the provided credentials and the repository + * of currently stored users. + *

+ */ + AuthenticationResult authenticate(UserCredentials userCredentials); +} diff --git a/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java b/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java new file mode 100644 index 0000000..44051aa --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.security; + +import io.linuxserver.fleet.auth.security.util.SaltGenerator; +import org.bouncycastle.crypto.PBEParametersGenerator; +import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator; +import org.bouncycastle.crypto.params.KeyParameter; + +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Base64; + +/** + *

+ * Uses the PKCS5S2 crypto algorithm to encode and verify hashed passwords. + *

+ */ +public class PKCS5S2PasswordEncoder implements PasswordEncoder { + + private static final int DEFAULT_HASH_WIDTH = 512; + private static final int DEFAULT_ITERATIONS = 150051; + + private final SaltGenerator saltGenerator = new SaltGenerator(); + + private final byte[] secret; + private final int hashWidth; + private final int iterations; + + public PKCS5S2PasswordEncoder(String secret) { + this(secret, DEFAULT_HASH_WIDTH, DEFAULT_ITERATIONS); + } + + public PKCS5S2PasswordEncoder(String secret, int hashWidth, int iterations) { + + this.secret = secret.getBytes(StandardCharsets.UTF_8); + this.hashWidth = hashWidth; + this.iterations = iterations; + } + + @Override + public String encode(String rawPassword) { + + if (null == rawPassword) { + throw new IllegalArgumentException("Password must not be null"); + } + + return toBase64(encode(rawPassword, saltGenerator.generateSalt())); + } + + @Override + public boolean matches(String rawPassword, String encodedPassword) { + + byte[] decodedHash = fromBase64(encodedPassword); + byte[] saltInHash = extractSalt(decodedHash); + byte[] hashToVerify = encode(rawPassword, saltInHash); + + return passwordsMatch(decodedHash, hashToVerify); + } + + /** + *

+ * Compares the two byte arrays by performing a bitwise equality check against each individual + * element of both arrays. + *

+ * + * @implNote I looked at a couple of implementations for doing this, and I preferred how Spring had implemented it. + */ + private boolean passwordsMatch(byte[] originalPassword, byte[] providedPassword) { + + if (originalPassword.length != providedPassword.length) { + return false; + } + + int result = 0; + for (int i = 0; i < originalPassword.length; i++) { + result |= originalPassword[i] ^ providedPassword[i]; + } + + return result == 0; + } + + /** + *

+ * Performs the cryptographic hash against the raw password and the randomly generated salt value. This + * also concatenates the provided secret into the salt. + *

+ */ + private byte[] encode(String rawPassword, byte[] salt) { + + PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(); + generator.init( + PBEParametersGenerator.PKCS5PasswordToBytes(rawPassword.toCharArray()), + joinArrays(salt, secret), + iterations + ); + + return joinArrays(salt, ((KeyParameter) generator.generateDerivedMacParameters(hashWidth)).getKey()); + } + + /** + *

+ * Converts a byte array into a base-64 encoded string. + *

+ */ + private String toBase64(byte[] bytes) { + return Base64.getEncoder().encodeToString(bytes); + } + + /** + *

+ * Converts a base64-encoded string into its raw byte value + *

+ */ + private byte[] fromBase64(String input) { + return Base64.getDecoder().decode(input); + } + + /** + *

+ * Obtains the specific bytes which represent the salt used in a previous hashed password. This is to + * enable the comparision between the existing and new password. + *

+ */ + private byte[] extractSalt(byte[] decodedHash) { + return extractFromArray(decodedHash, 0, saltGenerator.getKeyLength()); + } + + /** + *

+ * Combines two byte arrays together in order. + *

+ */ + private byte[] joinArrays(byte[] first, byte[] second) { + + byte[] result = Arrays.copyOf(first, first.length + second.length); + System.arraycopy(second, 0, result, first.length, second.length); + return result; + } + + /** + *

+ * Extracts a sub-array from the provided array. + *

+ */ + private byte[] extractFromArray(byte[] array, int begin, int end) { + + int length = end - begin; + byte[] subarray = new byte[length]; + System.arraycopy(array, begin, subarray, 0, length); + return subarray; + } +} diff --git a/src/main/java/io/linuxserver/fleet/auth/security/PasswordEncoder.java b/src/main/java/io/linuxserver/fleet/auth/security/PasswordEncoder.java new file mode 100644 index 0000000..a275dd2 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/security/PasswordEncoder.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.security; + +/** + *

+ * Provides a mechanism for a password to be encoded using a strong cryptographic algorithm. + * The general idea of this interface has been taken from Spring's own implementation of this. + * PasswordEncoder + *

+ */ +public interface PasswordEncoder { + + /** + *

+ * Encodes the raw password into a one-way encrypted hash. The result of which should be stored. + *

+ * + * @param rawPassword + * The raw unencrypted password. + * + * @return + * The hashed result. + */ + String encode(String rawPassword); + + /** + *

+ * Determines if the provided raw password, when encoded, matches the stored encoded password. + *

+ * + * @param rawPassword + * The raw password to check + * @param encodedPassword + * The originally encoded and stored password + * @return + * true if the passwords match, false if not. + */ + boolean matches(String rawPassword, String encodedPassword); +} diff --git a/src/main/java/io/linuxserver/fleet/auth/security/util/SaltGenerator.java b/src/main/java/io/linuxserver/fleet/auth/security/util/SaltGenerator.java new file mode 100644 index 0000000..4f8ee30 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/auth/security/util/SaltGenerator.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.security.util; + +import java.security.SecureRandom; + +public class SaltGenerator { + + private static final int KEY_LENGTH = 16; + + public byte[] generateSalt() { + + SecureRandom sr = new SecureRandom(); + + byte[] salt = new byte[KEY_LENGTH]; + sr.nextBytes(salt); + + return salt; + } + + public int getKeyLength() { + return KEY_LENGTH; + } +} diff --git a/src/main/java/io/linuxserver/fleet/core/FleetApp.java b/src/main/java/io/linuxserver/fleet/core/FleetApp.java index 3a0bc56..3cacdb1 100644 --- a/src/main/java/io/linuxserver/fleet/core/FleetApp.java +++ b/src/main/java/io/linuxserver/fleet/core/FleetApp.java @@ -20,6 +20,7 @@ package io.linuxserver.fleet.core; import io.linuxserver.fleet.web.pages.HomePage; import io.linuxserver.fleet.web.pages.LoginPage; import io.linuxserver.fleet.web.pages.ManageRepositoriesPage; +import io.linuxserver.fleet.web.pages.SetupPage; import io.linuxserver.fleet.web.routes.*; import io.linuxserver.fleet.web.websocket.SynchronisationWebSocket; @@ -56,11 +57,14 @@ class FleetApp { beans.getSynchronisationDelegate().registerListener(synchronisationWebSocket); beans.getWebServer().addWebSocket("/admin/ws/sync", synchronisationWebSocket); + beans.getWebServer().addFilter( "*", new InitialUserFilterRoute(beans.getProperties().getAuthenticationType(), beans.getUserDelegate())); beans.getWebServer().start(); beans.getWebServer().addPage( "/", new HomePage(beans.getRepositoryDelegate(), beans.getImageDelegate())); beans.getWebServer().addGetApi( "/api/v1/images", new AllImagesApi(beans.getRepositoryDelegate(), beans.getImageDelegate())); beans.getWebServer().addPage( "/admin", new ManageRepositoriesPage(beans.getRepositoryDelegate())); + beans.getWebServer().addPage( "/admin/setup", new SetupPage()); + beans.getWebServer().addPostRoute( "/admin/setup", new RegisterInitialUserRoute(beans.getUserDelegate())); beans.getWebServer().addPage( "/admin/login", new LoginPage()); beans.getWebServer().addPostRoute( "/admin/login", new LoginRoute(beans.getAuthenticationDelegate())); beans.getWebServer().addPostRoute( "/admin/logout", new LogoutRoute()); diff --git a/src/main/java/io/linuxserver/fleet/core/FleetBeans.java b/src/main/java/io/linuxserver/fleet/core/FleetBeans.java index de6d705..032f112 100644 --- a/src/main/java/io/linuxserver/fleet/core/FleetBeans.java +++ b/src/main/java/io/linuxserver/fleet/core/FleetBeans.java @@ -17,9 +17,13 @@ package io.linuxserver.fleet.core; +import io.linuxserver.fleet.auth.authenticator.AuthenticatorFactory; +import io.linuxserver.fleet.auth.security.PKCS5S2PasswordEncoder; +import io.linuxserver.fleet.auth.security.PasswordEncoder; import io.linuxserver.fleet.db.DefaultDatabaseConnection; import io.linuxserver.fleet.db.dao.DefaultImageDAO; import io.linuxserver.fleet.db.dao.DefaultRepositoryDAO; +import io.linuxserver.fleet.db.dao.DefaultUserDAO; import io.linuxserver.fleet.db.migration.DatabaseVersion; import io.linuxserver.fleet.delegate.*; import io.linuxserver.fleet.dockerhub.DockerHubV2Client; @@ -42,6 +46,8 @@ public class FleetBeans { private final WebServer webServer; private final TaskManager taskManager; private final TaskDelegate taskDelegate; + private final UserDelegate userDelegate; + private final PasswordEncoder passwordEncoder; /** * Ensures the database is kept up to date. @@ -54,16 +60,17 @@ public class FleetBeans { final DefaultDatabaseConnection databaseConnection = new DefaultDatabaseConnection(properties); + passwordEncoder = new PKCS5S2PasswordEncoder(properties.getAppSecret()); databaseVersion = new DatabaseVersion(databaseConnection); imageDelegate = new ImageDelegate(new DefaultImageDAO(databaseConnection)); repositoryDelegate = new RepositoryDelegate(new DefaultRepositoryDAO(databaseConnection)); dockerHubDelegate = new DockerHubDelegate(new DockerHubV2Client(properties.getDockerHubCredentials())); - authenticationDelegate = new PropertiesAuthenticationDelegate(properties.getAppUsername(), properties.getAppPassword()); webServer = new WebServer(properties.getAppPort()); taskManager = new TaskManager(); synchronisationDelegate = new SynchronisationDelegate(imageDelegate, repositoryDelegate, dockerHubDelegate); - + userDelegate = new UserDelegate(passwordEncoder, new DefaultUserDAO(databaseConnection)); taskDelegate = new TaskDelegate(this); + authenticationDelegate = new DefaultAuthenticationDelegate(AuthenticatorFactory.getAuthenticator(this)); } public FleetProperties getProperties() { @@ -105,4 +112,12 @@ public class FleetBeans { public TaskDelegate getTaskDelegate() { return taskDelegate; } + + public UserDelegate getUserDelegate() { + return userDelegate; + } + + public PasswordEncoder getPasswordEncoder() { + return passwordEncoder; + } } diff --git a/src/main/java/io/linuxserver/fleet/core/FleetProperties.java b/src/main/java/io/linuxserver/fleet/core/FleetProperties.java index b7f604c..6f5b2b4 100644 --- a/src/main/java/io/linuxserver/fleet/core/FleetProperties.java +++ b/src/main/java/io/linuxserver/fleet/core/FleetProperties.java @@ -45,6 +45,16 @@ public class FleetProperties { return getStringProperty("fleet.database.password"); } + public String getAuthenticationType() { + return getStringProperty("fleet.admin.authentication.type"); + } + + public String getAppSecret() { + + String secret = getStringProperty("fleet.admin.secret"); + return null == secret ? "" : secret; + } + public String getAppUsername() { return getStringProperty("fleet.admin.username"); } @@ -82,7 +92,7 @@ public class FleetProperties { property = System.getProperty(propertyKey); if (null == property) { - property = System.getenv(propertyKey); + property = System.getenv(propertyKey.replace(".", "_")); } } diff --git a/src/main/java/io/linuxserver/fleet/db/dao/DefaultUserDAO.java b/src/main/java/io/linuxserver/fleet/db/dao/DefaultUserDAO.java new file mode 100644 index 0000000..b9c3efe --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/db/dao/DefaultUserDAO.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.db.dao; + +import io.linuxserver.fleet.db.PoolingDatabaseConnection; +import io.linuxserver.fleet.db.query.InsertUpdateResult; +import io.linuxserver.fleet.db.query.InsertUpdateStatus; +import io.linuxserver.fleet.model.User; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.sql.*; +import java.util.ArrayList; +import java.util.List; + +import static io.linuxserver.fleet.db.dao.Utils.setNullableInt; +import static io.linuxserver.fleet.db.dao.Utils.setNullableString; + +public class DefaultUserDAO implements UserDAO { + + private static final Logger LOGGER = LoggerFactory.getLogger(DefaultUserDAO.class); + + private final PoolingDatabaseConnection databaseConnection; + + public DefaultUserDAO(PoolingDatabaseConnection databaseConnection) { + this.databaseConnection = databaseConnection; + } + + @Override + public User fetchUser(int id) { + + try (Connection connection = databaseConnection.getConnection()) { + + CallableStatement call = connection.prepareCall("{CALL User_Get(?)}"); + call.setInt(1, id); + + ResultSet results = call.executeQuery(); + if (results.next()) + return parseUserFromResultSet(results); + + } catch (SQLException e) { + LOGGER.error("Unable to retrieve user", e); + } + + return null; + } + + @Override + public User fetchUserByUsername(String username) { + + try (Connection connection = databaseConnection.getConnection()) { + + CallableStatement call = connection.prepareCall("{CALL User_GetByName(?)}"); + call.setString(1, username); + + ResultSet results = call.executeQuery(); + if (results.next()) + return parseUserFromResultSet(results); + + } catch (SQLException e) { + LOGGER.error("Unable to retrieve user", e); + } + + return null; + } + + @Override + public InsertUpdateResult saveUser(User user) { + + try (Connection connection = databaseConnection.getConnection()) { + + CallableStatement call = connection.prepareCall("{CALL User_Save(?,?,?,?,?,?)}"); + setNullableInt(call, 1, user.getId()); + call.setString(2, user.getUsername()); + setNullableString(call, 3, user.getPassword()); + + call.registerOutParameter(4, Types.INTEGER); + call.registerOutParameter(5, Types.INTEGER); + call.registerOutParameter(6, Types.VARCHAR); + + call.executeUpdate(); + + int userId = call.getInt(4); + int status = call.getInt(5); + String statusMessage = call.getString(6); + + if (InsertUpdateStatus.OK == status) + return new InsertUpdateResult<>(fetchUser(userId), status, statusMessage); + + return new InsertUpdateResult<>(status, statusMessage); + + } catch (SQLException e) { + + LOGGER.error("Unable to save user", e); + return new InsertUpdateResult<>(null, InsertUpdateStatus.OK, "Unable to save user"); + } + } + + @Override + public List fetchAllUsers() { + + List repositories = new ArrayList<>(); + + try (Connection connection = databaseConnection.getConnection()) { + + CallableStatement call = connection.prepareCall("{CALL User_GetAll()}"); + + ResultSet results = call.executeQuery(); + while (results.next()) + repositories.add(parseUserFromResultSet(results)); + + } catch (SQLException e) { + LOGGER.error("Unable to get all users", e); + } + + return repositories; + } + + @Override + public void removeUser(User user) { + + try (Connection connection = databaseConnection.getConnection()) { + + CallableStatement call = connection.prepareCall("{CALL User_Delete(?)}"); + call.setInt(1, user.getId()); + + call.executeUpdate(); + + } catch (SQLException e) { + LOGGER.error("Error when removing user", e); + } + } + + private User parseUserFromResultSet(ResultSet results) throws SQLException { + + return new User( + results.getInt("UserId"), + results.getString("UserName"), + results.getString("UserPassword") + ).withModifiedTime(results.getTimestamp("ModifiedTime").toLocalDateTime()); + } +} diff --git a/src/main/java/io/linuxserver/fleet/db/dao/UserDAO.java b/src/main/java/io/linuxserver/fleet/db/dao/UserDAO.java new file mode 100644 index 0000000..7b255a8 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/db/dao/UserDAO.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.db.dao; + +import io.linuxserver.fleet.db.query.InsertUpdateResult; +import io.linuxserver.fleet.model.User; + +import java.util.List; + +public interface UserDAO { + + User fetchUser(int id); + + User fetchUserByUsername(String username); + + InsertUpdateResult saveUser(User user); + + List fetchAllUsers(); + + void removeUser(User user); +} diff --git a/src/main/java/io/linuxserver/fleet/delegate/DefaultAuthenticationDelegate.java b/src/main/java/io/linuxserver/fleet/delegate/DefaultAuthenticationDelegate.java new file mode 100644 index 0000000..775dca6 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/delegate/DefaultAuthenticationDelegate.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.delegate; + +import io.linuxserver.fleet.auth.AuthenticationResult; +import io.linuxserver.fleet.auth.UserCredentials; +import io.linuxserver.fleet.auth.authenticator.UserAuthenticator; + +public class DefaultAuthenticationDelegate implements AuthenticationDelegate { + + private final UserAuthenticator authenticator; + + public DefaultAuthenticationDelegate(UserAuthenticator authenticator) { + this.authenticator = authenticator; + } + + @Override + public AuthenticationResult authenticate(String username, String password) { + return authenticator.authenticate(new UserCredentials(username, password)); + } +} diff --git a/src/main/java/io/linuxserver/fleet/delegate/UserDelegate.java b/src/main/java/io/linuxserver/fleet/delegate/UserDelegate.java new file mode 100644 index 0000000..ea59721 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/delegate/UserDelegate.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.delegate; + +import io.linuxserver.fleet.auth.security.PasswordEncoder; +import io.linuxserver.fleet.db.dao.UserDAO; +import io.linuxserver.fleet.db.query.InsertUpdateResult; +import io.linuxserver.fleet.db.query.InsertUpdateStatus; +import io.linuxserver.fleet.exception.SaveException; +import io.linuxserver.fleet.model.User; + +public class UserDelegate { + + private final PasswordEncoder passwordEncoder; + private final UserDAO userDAO; + + public UserDelegate(PasswordEncoder passwordEncoder, UserDAO userDAO) { + + this.passwordEncoder = passwordEncoder; + this.userDAO = userDAO; + } + + public User fetchUser(int id) { + return userDAO.fetchUser(id); + } + + public User fetchUserByUsername(String username) { + return userDAO.fetchUserByUsername(username); + } + + public boolean isUserRepositoryEmpty() { + return userDAO.fetchAllUsers().isEmpty(); + } + + public User createNewUser(String username, String password) throws SaveException { + + InsertUpdateResult result = userDAO.saveUser(new User(username, passwordEncoder.encode(password))); + + if (result.getStatus() == InsertUpdateStatus.OK) + return result.getResult(); + + throw new SaveException(result.getStatusMessage()); + } + + public void removeUser(User user) { + userDAO.removeUser(user); + } +} diff --git a/src/main/java/io/linuxserver/fleet/model/User.java b/src/main/java/io/linuxserver/fleet/model/User.java new file mode 100644 index 0000000..864a111 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/model/User.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.model; + +public class User extends PersistableItem { + + private final String username; + private final String password; + + public User(String username, String password) { + super(); + + this.username = username; + this.password = password; + } + + public User(Integer id, String username, String password) { + super(id); + + this.username = username; + this.password = password; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } +} diff --git a/src/main/java/io/linuxserver/fleet/web/WebServer.java b/src/main/java/io/linuxserver/fleet/web/WebServer.java index 5673ae2..1908997 100644 --- a/src/main/java/io/linuxserver/fleet/web/WebServer.java +++ b/src/main/java/io/linuxserver/fleet/web/WebServer.java @@ -20,10 +20,7 @@ package io.linuxserver.fleet.web; import io.linuxserver.fleet.auth.AuthenticatedUser; import io.linuxserver.fleet.model.api.ApiResponse; import io.linuxserver.fleet.model.api.FleetApiException; -import spark.Route; -import spark.RouteGroup; -import spark.Session; -import spark.TemplateViewRoute; +import spark.*; import spark.template.freemarker.FreeMarkerEngine; import static spark.Spark.*; @@ -45,7 +42,6 @@ public class WebServer { started = true; path("/admin", configureAuthorisationRoute("")); - path("/admin", configureAuthorisationRoute("/repositories")); path("/admin", configureAuthorisationRoute("/images")); path("/admin", configureAuthorisationRoute("/manageImage")); path("/admin", configureAuthorisationRoute("/manageRepository")); @@ -71,6 +67,10 @@ public class WebServer { }); } + public void addFilter(String path, Filter route) { + before(path, route); + } + public void addWebSocket(String path, Object object) { if (started) { diff --git a/src/main/java/io/linuxserver/fleet/web/pages/SetupPage.java b/src/main/java/io/linuxserver/fleet/web/pages/SetupPage.java new file mode 100644 index 0000000..e6d1140 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/web/pages/SetupPage.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.web.pages; + +import spark.ModelAndView; +import spark.Request; + +import java.util.HashMap; + +public class SetupPage extends WebPage { + + @Override + protected ModelAndView handle(Request request) { + return new ModelAndView(new HashMap<>(), "setup.ftl"); + } +} diff --git a/src/main/java/io/linuxserver/fleet/web/routes/InitialUserFilterRoute.java b/src/main/java/io/linuxserver/fleet/web/routes/InitialUserFilterRoute.java new file mode 100644 index 0000000..faf5e7f --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/web/routes/InitialUserFilterRoute.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.web.routes; + +import io.linuxserver.fleet.auth.authenticator.AuthenticatorFactory.AuthenticationType; +import io.linuxserver.fleet.delegate.UserDelegate; +import spark.Filter; +import spark.Request; +import spark.Response; + +/** + *

+ * Forces the maintainer of Fleet to set up an initial user before running Fleet. + *

+ */ +public class InitialUserFilterRoute implements Filter { + + private static final String FLEET_USER_UNDEFINED = "fleet.user.undefined"; + + private static final String[] PATH_EXEMPTIONS = { + "/assets", "/admin/setup" + }; + + private final UserDelegate userDelegate; + private final boolean databaseAuthenticationEnabled; + + public InitialUserFilterRoute(String authenticationType, UserDelegate userDelegate) { + + this.userDelegate = userDelegate; + this.databaseAuthenticationEnabled = AuthenticationType.DATABASE.equals(AuthenticationType.valueOf(authenticationType.toUpperCase())); + } + + @Override + public void handle(Request request, Response response) { + + if (databaseAuthenticationEnabled) { + + if (!initialUserNeedsConfiguring() && "/admin/setup".equals(request.pathInfo())) { + response.redirect("/admin"); + } + + else if (initialUserNeedsConfiguring() && !pathIsExempted(request.pathInfo())) { + response.redirect("/admin/setup"); + } + } + } + + private boolean initialUserNeedsConfiguring() { + + String configured = System.getProperty(FLEET_USER_UNDEFINED); + if (null == configured || "true".equalsIgnoreCase(configured)) { + System.setProperty(FLEET_USER_UNDEFINED, String.valueOf(userDelegate.isUserRepositoryEmpty())); + } + + return "true".equalsIgnoreCase(System.getProperty(FLEET_USER_UNDEFINED)); + } + + private boolean pathIsExempted(String path) { + + for (String exemption : PATH_EXEMPTIONS) { + + if (path.startsWith(exemption)) { + return true; + } + } + + return false; + } +} diff --git a/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java b/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java new file mode 100644 index 0000000..7e09f13 --- /dev/null +++ b/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.web.routes; + +import io.linuxserver.fleet.delegate.UserDelegate; +import io.linuxserver.fleet.exception.SaveException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import spark.Request; +import spark.Response; +import spark.Route; + +public class RegisterInitialUserRoute implements Route { + + private static final Logger LOGGER = LoggerFactory.getLogger(RegisterInitialUserRoute.class); + + private UserDelegate userDelegate; + + public RegisterInitialUserRoute(UserDelegate userDelegate) { + this.userDelegate = userDelegate; + } + + @Override + public Object handle(Request request, Response response) { + + String username = request.queryParams("username"); + String password = request.queryParams("password"); + String verifyPassword = request.queryParams("verify-password"); + + if (!verifyPassword.equals(password)) { + + response.redirect("/admin/setup?passwordMismatch=true"); + return null; + } + + try { + + userDelegate.createNewUser(username, password); + response.redirect("/admin"); + + } catch (SaveException e) { + + response.redirect("/admin/setup?createUserError=true"); + LOGGER.error("Unable to create new user", e); + } + + return null; + } +} diff --git a/src/main/resources/assets/js/fleet.js b/src/main/resources/assets/js/fleet.js index 6559d05..1b37b09 100644 --- a/src/main/resources/assets/js/fleet.js +++ b/src/main/resources/assets/js/fleet.js @@ -310,4 +310,35 @@ var synchronisationManager = (function($) { init: init } +}(jQuery)); + +var passwordValidationManager = (function($) { + + var comparePasswords = function(password, verifyPassword) { + + if (password.val() !== verifyPassword.val()) { + verifyPassword.get(0).setCustomValidity('Mismatch'); + } else { + verifyPassword.get(0).setCustomValidity(''); + } + }; + + var init = function() { + + var password = $('#password'); + var verifyPassword = $('#verify-password'); + + password.on('keyup', function() { + comparePasswords(password, verifyPassword); + }); + + verifyPassword.on('keyup', function() { + comparePasswords(password, verifyPassword); + }); + }; + + return { + init: init + }; + }(jQuery)); \ No newline at end of file diff --git a/src/main/resources/db/migration/V1.2__CreateUserTable.sql b/src/main/resources/db/migration/V1.2__CreateUserTable.sql new file mode 100644 index 0000000..ae01284 --- /dev/null +++ b/src/main/resources/db/migration/V1.2__CreateUserTable.sql @@ -0,0 +1,7 @@ +CREATE TABLE Users ( + `id` INT NOT NULL auto_increment PRIMARY KEY, + `username` VARCHAR(255) NOT NULL, + `password` VARCHAR(255) DEFAULT NULL, + `modified` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + UNIQUE KEY (`username`) +) ENGINE=InnoDB; \ No newline at end of file diff --git a/src/main/resources/db/migration/V1.3__CreateUserSprocs.sql b/src/main/resources/db/migration/V1.3__CreateUserSprocs.sql new file mode 100644 index 0000000..62919d5 --- /dev/null +++ b/src/main/resources/db/migration/V1.3__CreateUserSprocs.sql @@ -0,0 +1,111 @@ +DELIMITER // + +CREATE PROCEDURE `User_Save` +( + in_id INT, + in_username VARCHAR(255), + in_password VARCHAR(255), + + OUT out_id INT, + OUT out_status INT, + OUT out_message VARCHAR(100) +) +BEGIN + + IF in_id IS NULL THEN + + INSERT INTO Users + ( + `username`, + `password` + ) + VALUES + ( + in_username, + in_password + ); + + SET out_id = LAST_INSERT_ID(); + + ELSE + + UPDATE Repositories + SET + `username` = in_username, + `password` = in_password + WHERE + `id` = in_id; + + SET out_id = in_id; + + END IF; + + SET out_status = 0; + SET out_message = 'OK'; + +END; +// + +CREATE PROCEDURE `User_GetAll` () +BEGIN + + SELECT + `id` AS `UserId`, + `username` AS `UserName`, + `password` AS `UserPassword`, + `modified` AS `ModifiedTime` + FROM + Users; + +END; +// + +CREATE PROCEDURE `User_Get` +( + in_id INT +) +BEGIN + + SELECT + `id` AS `UserId`, + `username` AS `UserName`, + `password` AS `UserPassword`, + `modified` AS `ModifiedTime` + FROM + Users + WHERE + `id` = in_id; + +END; +// + +CREATE PROCEDURE `User_GetByName` +( + in_username VARCHAR(255) +) +BEGIN + + SELECT + `id` AS `UserId`, + `username` AS `UserName`, + `password` AS `UserPassword`, + `modified` AS `ModifiedTime` + FROM + Users + WHERE + `username` = in_username; +END; +// + +CREATE PROCEDURE `User_Delete` +( + in_id INT +) +BEGIN + + DELETE FROM Users WHERE `id` = in_id; + +END; +// + +DELIMITER ; diff --git a/src/main/resources/spark/template/freemarker/base.ftl b/src/main/resources/spark/template/freemarker/base.ftl index 40bd0e2..da4557b 100644 --- a/src/main/resources/spark/template/freemarker/base.ftl +++ b/src/main/resources/spark/template/freemarker/base.ftl @@ -69,15 +69,37 @@ $("table.table--sortable").tablesorter(); }); - <#if context='admin'> + <#if context="admin"> repositoryManager.init(); synchronisationManager.init(); - <#if context='home'> + <#if context="home"> imageListManager.init(); + <#if context="setup" || context="login"> + (function() { + 'use strict'; + window.addEventListener('load', function() { + + var forms = document.getElementsByClassName('needs-validation'); + + var validation = Array.prototype.filter.call(forms, function(form) { + form.addEventListener('submit', function(event) { + if (form.checkValidity() === false) { + event.preventDefault(); + event.stopPropagation(); + } + form.classList.add('was-validated'); + }, false); + }); + }, false); + })(); + + passwordValidationManager.init(); + + diff --git a/src/main/resources/spark/template/freemarker/setup.ftl b/src/main/resources/spark/template/freemarker/setup.ftl new file mode 100644 index 0000000..3e4c045 --- /dev/null +++ b/src/main/resources/spark/template/freemarker/setup.ftl @@ -0,0 +1,46 @@ +<#import "./base.ftl" as base> + +<@base.base title="Set Up" context="setup"> + +
+ +
+
+

Set Up Fleet

+ +

+ It looks like this is the first time you're running Fleet. In order to get started, you need to create + and initial user which will have access to the management pages of the application. +

+ +

Create a user

+ +
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+
+
+
+ +
+ + \ No newline at end of file diff --git a/src/test/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoderTest.java b/src/test/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoderTest.java new file mode 100644 index 0000000..9a0df75 --- /dev/null +++ b/src/test/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoderTest.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019 LinuxServer.io + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package io.linuxserver.fleet.auth.security; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class PKCS5S2PasswordEncoderTest { + + private static final String HASH_FOR_PASSWORD = "8SLrEokFZQQrRk1MeDrpgINOHNXKXPMs2r56DMWBCjLXs9oTHsLzEmnwb68oQAc9+1YdKPTdWahAjjqtvO9M8FtQxCC+8yd71+J1VoWizow="; + + private PKCS5S2PasswordEncoder encoder = new PKCS5S2PasswordEncoder("superSecret"); + + @Test + public void shouldGenerateHash() { + assertThat(encoder.matches("password", HASH_FOR_PASSWORD), is(equalTo(true))); + } + + @Test + public void test() { + System.out.println(encoder.encode("password").length()); + } +}