mirror of
https://github.com/linuxserver/fleet.git
synced 2026-02-20 05:11:08 +08:00
Reworked authentication for users. Now includes option to use database
This commit is contained in:
parent
44ac92dfcc
commit
6ce1e99438
23
build.gradle
23
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 {
|
||||
|
||||
@ -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
|
||||
|
||||
38
src/main/java/io/linuxserver/fleet/auth/UserCredentials.java
Normal file
38
src/main/java/io/linuxserver/fleet/auth/UserCredentials.java
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
||||
@ -15,27 +15,28 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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();
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.auth.authenticator;
|
||||
|
||||
import io.linuxserver.fleet.auth.AuthenticationResult;
|
||||
import io.linuxserver.fleet.auth.UserCredentials;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Provides a mechanism for the application to authenticate a login request
|
||||
* from a user.
|
||||
* </p>
|
||||
*/
|
||||
public interface UserAuthenticator {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Performs an authentication check against the provided credentials and the repository
|
||||
* of currently stored users.
|
||||
* </p>
|
||||
*/
|
||||
AuthenticationResult authenticate(UserCredentials userCredentials);
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Uses the PKCS5S2 crypto algorithm to encode and verify hashed passwords.
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Compares the two byte arrays by performing a bitwise equality check against each individual
|
||||
* element of both arrays.
|
||||
* </p>
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Performs the cryptographic hash against the raw password and the randomly generated salt value. This
|
||||
* also concatenates the provided secret into the salt.
|
||||
* </p>
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Converts a byte array into a base-64 encoded string.
|
||||
* </p>
|
||||
*/
|
||||
private String toBase64(byte[] bytes) {
|
||||
return Base64.getEncoder().encodeToString(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Converts a base64-encoded string into its raw byte value
|
||||
* </p>
|
||||
*/
|
||||
private byte[] fromBase64(String input) {
|
||||
return Base64.getDecoder().decode(input);
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*/
|
||||
private byte[] extractSalt(byte[] decodedHash) {
|
||||
return extractFromArray(decodedHash, 0, saltGenerator.getKeyLength());
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Combines two byte arrays together in order.
|
||||
* </p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Extracts a sub-array from the provided array.
|
||||
* </p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.auth.security;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* 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.
|
||||
* <a href="https://docs.spring.io/spring-security/site/docs/4.2.4.RELEASE/apidocs/org/springframework/security/crypto/password/PasswordEncoder.html">PasswordEncoder</a>
|
||||
* </p>
|
||||
*/
|
||||
public interface PasswordEncoder {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Encodes the raw password into a one-way encrypted hash. The result of which should be stored.
|
||||
* </p>
|
||||
*
|
||||
* @param rawPassword
|
||||
* The raw unencrypted password.
|
||||
*
|
||||
* @return
|
||||
* The hashed result.
|
||||
*/
|
||||
String encode(String rawPassword);
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Determines if the provided raw password, when encoded, matches the stored encoded password.
|
||||
* </p>
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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());
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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(".", "_"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
157
src/main/java/io/linuxserver/fleet/db/dao/DefaultUserDAO.java
Normal file
157
src/main/java/io/linuxserver/fleet/db/dao/DefaultUserDAO.java
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<User> 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<User> fetchAllUsers() {
|
||||
|
||||
List<User> 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());
|
||||
}
|
||||
}
|
||||
36
src/main/java/io/linuxserver/fleet/db/dao/UserDAO.java
Normal file
36
src/main/java/io/linuxserver/fleet/db/dao/UserDAO.java
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<User> saveUser(User user);
|
||||
|
||||
List<User> fetchAllUsers();
|
||||
|
||||
void removeUser(User user);
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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<User> 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);
|
||||
}
|
||||
}
|
||||
46
src/main/java/io/linuxserver/fleet/model/User.java
Normal file
46
src/main/java/io/linuxserver/fleet/model/User.java
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package io.linuxserver.fleet.model;
|
||||
|
||||
public class User extends PersistableItem<User> {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
31
src/main/java/io/linuxserver/fleet/web/pages/SetupPage.java
Normal file
31
src/main/java/io/linuxserver/fleet/web/pages/SetupPage.java
Normal file
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Forces the maintainer of Fleet to set up an initial user before running Fleet.
|
||||
* </p>
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
@ -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;
|
||||
111
src/main/resources/db/migration/V1.3__CreateUserSprocs.sql
Normal file
111
src/main/resources/db/migration/V1.3__CreateUserSprocs.sql
Normal file
@ -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 ;
|
||||
@ -69,15 +69,37 @@
|
||||
$("table.table--sortable").tablesorter();
|
||||
});
|
||||
|
||||
<#if context='admin'>
|
||||
<#if context="admin">
|
||||
repositoryManager.init();
|
||||
synchronisationManager.init();
|
||||
</#if>
|
||||
|
||||
<#if context='home'>
|
||||
<#if context="home">
|
||||
imageListManager.init();
|
||||
</#if>
|
||||
|
||||
<#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();
|
||||
</#if>
|
||||
|
||||
</script>
|
||||
|
||||
</html>
|
||||
|
||||
46
src/main/resources/spark/template/freemarker/setup.ftl
Normal file
46
src/main/resources/spark/template/freemarker/setup.ftl
Normal file
@ -0,0 +1,46 @@
|
||||
<#import "./base.ftl" as base>
|
||||
|
||||
<@base.base title="Set Up" context="setup">
|
||||
|
||||
<div class="container container--white mt-3">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-12 p-3">
|
||||
<h2>Set Up Fleet</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<h3 class="mb-3">Create a user</h3>
|
||||
|
||||
<form action="/admin/setup" method="POST" class="needs-validation" novalidate>
|
||||
<div class="form-group row">
|
||||
<label for="username" class="col-sm-2 col-form-label">Username</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="text" class="form-control" name="username" id="username" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="password" class="col-sm-2 col-form-label">Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="password" id="password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label for="verify-password" class="col-sm-2 col-form-label">Verify Password</label>
|
||||
<div class="col-sm-10">
|
||||
<input type="password" class="form-control" name="verify-password" id="verify-password" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group text-center">
|
||||
<button type="submit" class="btn btn-primary">Continue</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</@base.base>
|
||||
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user