From 794b15d3de26b14d4c758fbab3420c409c255c70 Mon Sep 17 00:00:00 2001 From: Marcus Fihlon Date: Sat, 13 Apr 2024 22:27:56 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=92=EF=B8=8F=20Hash=20admin=20password?= =?UTF-8?q?=20closes=20#84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 9 +++ .../java/swiss/fihlon/apus/Application.java | 23 ++++++- .../swiss/fihlon/apus/ui/view/SocialView.java | 3 +- .../swiss/fihlon/apus/util/PasswordUtil.java | 39 +++++++++++ .../fihlon/apus/util/PasswordUtilTest.java | 68 +++++++++++++++++++ 5 files changed, 138 insertions(+), 4 deletions(-) create mode 100644 src/main/java/swiss/fihlon/apus/util/PasswordUtil.java create mode 100644 src/test/java/swiss/fihlon/apus/util/PasswordUtilTest.java diff --git a/pom.xml b/pom.xml index a194b1a..aa9d503 100644 --- a/pom.xml +++ b/pom.xml @@ -159,6 +159,10 @@ spring-boot-devtools true + + org.springframework.security + spring-security-core + org.jetbrains annotations @@ -196,6 +200,11 @@ owasp-java-html-sanitizer 20240325.1 + + commons-cli + commons-cli + 1.5.0 + org.springframework.boot spring-boot-starter-test diff --git a/src/main/java/swiss/fihlon/apus/Application.java b/src/main/java/swiss/fihlon/apus/Application.java index 9dd0e2e..747a589 100644 --- a/src/main/java/swiss/fihlon/apus/Application.java +++ b/src/main/java/swiss/fihlon/apus/Application.java @@ -22,9 +22,15 @@ import com.vaadin.flow.router.PageTitle; import com.vaadin.flow.server.PWA; import com.vaadin.flow.theme.Theme; +import org.apache.commons.cli.DefaultParser; +import org.apache.commons.cli.Option; +import org.apache.commons.cli.Options; +import org.apache.commons.cli.ParseException; +import org.jetbrains.annotations.NotNull; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; +import swiss.fihlon.apus.util.PasswordUtil; /** * The entry point of the Spring Boot application. @@ -39,11 +45,22 @@ @PageTitle("Apus – Social Media Wall with Conference Agenda") @PWA(name = "Apus", shortName = "Apus") @Theme("apus") -@SuppressWarnings({"FinalClass", "HideUtilityClassConstructor"}) +@SuppressWarnings({"FinalClass", "HideUtilityClassConstructor", "RegexpSingleline"}) public class Application implements AppShellConfigurator { - public static void main(final String[] args) { - SpringApplication.run(Application.class, args); + private static final Option HASH_PASSWORD_OPTION = new Option("p", "password", true, "Password to hash"); + + public static void main(@NotNull final String[] args) throws ParseException { + final var options = new Options(); + options.addOption(HASH_PASSWORD_OPTION); + + final var cmd = new DefaultParser().parse(options, args); + if (cmd.hasOption(HASH_PASSWORD_OPTION)) { + final var password = cmd.getOptionValue(HASH_PASSWORD_OPTION); + System.out.println(PasswordUtil.hashPassword(password)); + } else { + SpringApplication.run(Application.class, args); + } } } diff --git a/src/main/java/swiss/fihlon/apus/ui/view/SocialView.java b/src/main/java/swiss/fihlon/apus/ui/view/SocialView.java index 949a639..7f9a6ea 100644 --- a/src/main/java/swiss/fihlon/apus/ui/view/SocialView.java +++ b/src/main/java/swiss/fihlon/apus/ui/view/SocialView.java @@ -33,6 +33,7 @@ import swiss.fihlon.apus.configuration.Configuration; import swiss.fihlon.apus.service.SocialService; import swiss.fihlon.apus.social.Message; +import swiss.fihlon.apus.util.PasswordUtil; import java.time.Duration; import java.time.Instant; @@ -112,7 +113,7 @@ private void showLoginDialog() { } private void handleLogin(@NotNull final String password) { - if (configuration.getAdmin().password().equals(password)) { + if (PasswordUtil.matches(password, configuration.getAdmin().password())) { adminModeEnabled = true; contextMenu.setTarget(null); updateMessages(); diff --git a/src/main/java/swiss/fihlon/apus/util/PasswordUtil.java b/src/main/java/swiss/fihlon/apus/util/PasswordUtil.java new file mode 100644 index 0000000..95b27d5 --- /dev/null +++ b/src/main/java/swiss/fihlon/apus/util/PasswordUtil.java @@ -0,0 +1,39 @@ +/* + * Apus - A social wall for conferences with additional features. + * Copyright (C) Marcus Fihlon and the individual contributors to Apus. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package swiss.fihlon.apus.util; + +import org.jetbrains.annotations.NotNull; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +public final class PasswordUtil { + + private static final BCryptPasswordEncoder PASSWORD_ENCODER = new BCryptPasswordEncoder(); + + public static String hashPassword(@NotNull final String password) { + return PASSWORD_ENCODER.encode(password); + } + + public static boolean matches(@NotNull final String password, @NotNull final String hashedPassword) { + return PASSWORD_ENCODER.matches(password, hashedPassword); + } + + private PasswordUtil() { + throw new IllegalStateException("Utility class"); + } + +} diff --git a/src/test/java/swiss/fihlon/apus/util/PasswordUtilTest.java b/src/test/java/swiss/fihlon/apus/util/PasswordUtilTest.java new file mode 100644 index 0000000..ead4296 --- /dev/null +++ b/src/test/java/swiss/fihlon/apus/util/PasswordUtilTest.java @@ -0,0 +1,68 @@ +/* + * Apus - A social wall for conferences with additional features. + * Copyright (C) Marcus Fihlon and the individual contributors to Apus. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package swiss.fihlon.apus.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PasswordUtilTest { + + @Test + void positiveTests() { + final String rawPassword = "password"; + + // let's try it five times + final String hashedPassword1 = PasswordUtil.hashPassword(rawPassword); + final String hashedPassword2 = PasswordUtil.hashPassword(rawPassword); + final String hashedPassword3 = PasswordUtil.hashPassword(rawPassword); + final String hashedPassword4 = PasswordUtil.hashPassword(rawPassword); + final String hashedPassword5 = PasswordUtil.hashPassword(rawPassword); + + // hashing the same password multiple times produces different hashes + assertNotEquals(hashedPassword1, hashedPassword2); + assertNotEquals(hashedPassword1, hashedPassword3); + assertNotEquals(hashedPassword1, hashedPassword4); + assertNotEquals(hashedPassword1, hashedPassword5); + assertNotEquals(hashedPassword2, hashedPassword1); + assertNotEquals(hashedPassword2, hashedPassword3); + assertNotEquals(hashedPassword2, hashedPassword4); + assertNotEquals(hashedPassword2, hashedPassword5); + assertNotEquals(hashedPassword3, hashedPassword1); + assertNotEquals(hashedPassword3, hashedPassword2); + assertNotEquals(hashedPassword3, hashedPassword4); + assertNotEquals(hashedPassword3, hashedPassword5); + assertNotEquals(hashedPassword4, hashedPassword1); + assertNotEquals(hashedPassword4, hashedPassword2); + assertNotEquals(hashedPassword4, hashedPassword3); + assertNotEquals(hashedPassword4, hashedPassword5); + assertNotEquals(hashedPassword5, hashedPassword1); + assertNotEquals(hashedPassword5, hashedPassword2); + assertNotEquals(hashedPassword5, hashedPassword3); + assertNotEquals(hashedPassword5, hashedPassword4); + + // every hash should match the password + assertTrue(PasswordUtil.matches(rawPassword, hashedPassword1)); + assertTrue(PasswordUtil.matches(rawPassword, hashedPassword2)); + assertTrue(PasswordUtil.matches(rawPassword, hashedPassword3)); + assertTrue(PasswordUtil.matches(rawPassword, hashedPassword4)); + assertTrue(PasswordUtil.matches(rawPassword, hashedPassword5)); + } + +}