Reworked web server to configure servlets more directly. Still need to sort login filter

This commit is contained in:
Josh Stark 2019-03-18 18:41:39 +00:00
parent 52c5ea952d
commit bddbb06835
7 changed files with 134 additions and 167 deletions

View File

@ -34,18 +34,17 @@ public class AuthenticatorFactory {
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");
case PROPERTIES:
default:
LOGGER.info("Configuring new authenticator: PropertyLoadedUserAuthenticator");
return new PropertyLoadedUserAuthenticator(properties.getAppUsername(), properties.getAppPassword());
}
}
public enum AuthenticationType {

View File

@ -19,6 +19,7 @@ package io.linuxserver.fleet.auth.security;
import io.linuxserver.fleet.auth.security.util.SaltGenerator;
import org.bouncycastle.crypto.PBEParametersGenerator;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.bouncycastle.crypto.params.KeyParameter;
@ -103,7 +104,7 @@ public class PKCS5S2PasswordEncoder implements PasswordEncoder {
*/
private byte[] encode(String rawPassword, byte[] salt) {
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator();
PKCS5S2ParametersGenerator generator = new PKCS5S2ParametersGenerator(new SHA256Digest());
generator.init(
PBEParametersGenerator.PKCS5PasswordToBytes(rawPassword.toCharArray()),
joinArrays(salt, secret),

View File

@ -17,16 +17,22 @@
package io.linuxserver.fleet.core;
import io.linuxserver.fleet.web.WebServer;
import io.linuxserver.fleet.auth.AuthenticatedUser;
import io.linuxserver.fleet.auth.authenticator.AuthenticatorFactory.AuthenticationType;
import io.linuxserver.fleet.web.JsonTransformer;
import io.linuxserver.fleet.web.SessionAttribute;
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;
import spark.Session;
import java.util.concurrent.TimeUnit;
import static spark.Spark.*;
/**
* <p>
* Primary entry point for the application. All contexts and resources are loaded
@ -73,31 +79,122 @@ public class FleetApp {
private void configureWeb() {
port(beans.getProperties().getAppPort());
staticFiles.location("/assets");
staticFiles.expireTime(600);
SynchronisationWebSocket synchronisationWebSocket = new SynchronisationWebSocket();
beans.getSynchronisationDelegate().registerListener(synchronisationWebSocket);
WebServer webServer = beans.getWebServer();
webServer.addWebSocket("/admin/ws/sync", synchronisationWebSocket);
webServer.addFilter( "*", new InitialUserFilterRoute(beans.getProperties().getAuthenticationType(), beans.getUserDelegate()));
webServer.start();
webServer.addPage( "/", new HomePage(beans.getRepositoryDelegate(), beans.getImageDelegate()));
webServer.addGetApi( "/api/v1/images", new AllImagesApi(beans.getRepositoryDelegate(), beans.getImageDelegate()));
webServer.addPage( "/admin", new ManageRepositoriesPage(beans.getRepositoryDelegate()));
webServer.addPage( "/admin/login", new LoginPage());
webServer.addPostRoute( "/admin/login", new LoginRoute(beans.getAuthenticationDelegate()));
webServer.addPostRoute( "/admin/logout", new LogoutRoute());
webServer.addPostApi( "/admin/manageImage", new ManageImageApi(beans.getImageDelegate()));
webServer.addGetApi( "/admin/getImage", new GetImageApi(beans.getImageDelegate()));
webServer.addPostApi( "/admin/manageRepository", new ManageRepositoryApi(beans.getRepositoryDelegate()));
webServer.addPostApi( "/admin/forceSync", new ForceSyncApi(beans.getTaskDelegate()));
webSocket("/admin/ws/sync", synchronisationWebSocket);
init();
/*
* -----------------------
* Set Up
* -----------------------
*/
if (initialUserNeedsConfiguring()) {
webServer.addPage( "/setup", new SetupPage());
webServer.addPostRoute( "/setup", new RegisterInitialUserRoute(beans.getUserDelegate()));
path("/setup", () -> {
before("", (request, response) -> {
if (!initialUserNeedsConfiguring()) {
halt(401);
}
});
get("", new SetupPage());
post("", new RegisterInitialUserRoute(beans.getUserDelegate()));
});
}
/*
* -----------------------
* Image List
* -----------------------
*/
get("/", new HomePage(beans.getRepositoryDelegate(), beans.getImageDelegate()));
/*
* -----------------------
* API
* -----------------------
*/
path("/api/v1", () -> {
get("/images", new AllImagesApi(beans.getRepositoryDelegate(), beans.getImageDelegate()), new JsonTransformer());
after("/*", (request, response) -> {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Methods", "GET");
response.header("Content-Type","application/json");
});
});
/*
* -----------------------
* Admin
* -----------------------
*/
path("/admin", () -> {
before("", (request, response) -> {
if (initialUserNeedsConfiguring()) {
response.redirect("/setup");
} else {
Session session = request.session(false);
if (null == session)
response.redirect("/admin/login");
else {
AuthenticatedUser user = session.attribute(SessionAttribute.USER);
if (null == user)
response.redirect("/admin/login");
}
}
});
before("/*", (request, response) -> {
if (initialUserNeedsConfiguring()) {
response.redirect("/setup");
} else {
Session session = request.session(false);
if (null == session)
response.redirect("/admin/login");
else {
AuthenticatedUser user = session.attribute(SessionAttribute.USER);
if (null == user)
response.redirect("/admin/login");
}
}
});
get("", new ManageRepositoriesPage(beans.getRepositoryDelegate()));
get("/admin/login", new LoginPage());
post("/admin/login", new LoginRoute(beans.getAuthenticationDelegate()));
post("/logout", new LogoutRoute());
post("/manageImage", new ManageImageApi(beans.getImageDelegate()));
get("/getImage", new GetImageApi(beans.getImageDelegate()));
post("/manageRepository", new ManageRepositoryApi(beans.getRepositoryDelegate()));
post("/forceSync", new ForceSyncApi(beans.getTaskDelegate()));
});
}
private void scheduleSync() {
@ -111,6 +208,10 @@ public class FleetApp {
System.setProperty(FLEET_USER_UNDEFINED, String.valueOf(beans.getUserDelegate().isUserRepositoryEmpty()));
}
return "true".equalsIgnoreCase(System.getProperty(FLEET_USER_UNDEFINED));
return "true".equalsIgnoreCase(System.getProperty(FLEET_USER_UNDEFINED)) && databaseAuthenticationEnabled();
}
private boolean databaseAuthenticationEnabled() {
return AuthenticationType.DATABASE == AuthenticationType.valueOf(beans.getProperties().getAuthenticationType());
}
}

View File

@ -28,7 +28,6 @@ import io.linuxserver.fleet.db.migration.DatabaseVersion;
import io.linuxserver.fleet.delegate.*;
import io.linuxserver.fleet.dockerhub.DockerHubV2Client;
import io.linuxserver.fleet.thread.TaskManager;
import io.linuxserver.fleet.web.WebServer;
/**
* <p>
@ -43,7 +42,6 @@ public class FleetBeans {
private final AuthenticationDelegate authenticationDelegate;
private final DockerHubDelegate dockerHubDelegate;
private final SynchronisationDelegate synchronisationDelegate;
private final WebServer webServer;
private final TaskManager taskManager;
private final TaskDelegate taskDelegate;
private final UserDelegate userDelegate;
@ -65,7 +63,6 @@ public class FleetBeans {
imageDelegate = new ImageDelegate(new DefaultImageDAO(databaseConnection));
repositoryDelegate = new RepositoryDelegate(new DefaultRepositoryDAO(databaseConnection));
dockerHubDelegate = new DockerHubDelegate(new DockerHubV2Client(properties.getDockerHubCredentials()));
webServer = new WebServer(properties.getAppPort());
taskManager = new TaskManager();
synchronisationDelegate = new SynchronisationDelegate(imageDelegate, repositoryDelegate, dockerHubDelegate);
userDelegate = new UserDelegate(passwordEncoder, new DefaultUserDAO(databaseConnection));
@ -101,10 +98,6 @@ public class FleetBeans {
return databaseVersion;
}
public WebServer getWebServer() {
return webServer;
}
public TaskManager getTaskManager() {
return taskManager;
}

View File

@ -1,128 +0,0 @@
/*
* 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;
import io.linuxserver.fleet.auth.AuthenticatedUser;
import io.linuxserver.fleet.model.api.ApiResponse;
import io.linuxserver.fleet.model.api.FleetApiException;
import spark.*;
import spark.template.freemarker.FreeMarkerEngine;
import static spark.Spark.*;
public class WebServer {
private boolean started;
public WebServer(int appPort) {
port(appPort);
staticFiles.location("/assets");
staticFiles.expireTime(600);
}
public void stopServer() {
stop();
started = false;
}
public void start() {
started = true;
init();
path("/admin", configureAuthorisationRoute(""));
path("/admin", configureAuthorisationRoute("/images"));
path("/admin", configureAuthorisationRoute("/manageImage"));
path("/admin", configureAuthorisationRoute("/manageRepository"));
path("/admin", configureAuthorisationRoute("/forceSync"));
path("/admin", configureAuthorisationRoute("/getImage"));
after("/api/v1/*", (request, response) -> {
response.header("Access-Control-Allow-Origin", "*");
response.header("Access-Control-Allow-Methods", "GET");
response.header("Content-Type", "application/json");
});
after("/admin/getImage", (request, response) -> {
response.header("Content-Type", "application/json");
});
exception(FleetApiException.class, (exception, request, response) -> {
response.body(new JsonTransformer().render(new ApiResponse<>("ERROR", exception.getMessage())));
response.header("Content-Type", "application/json");
response.status(exception.getStatusCode());
});
}
public void addFilter(String path, Filter route) {
before(path, route);
}
public void addWebSocket(String path, Object object) {
if (started) {
throw new IllegalStateException("Server has already started! Add a web socket before starting");
}
webSocket(path, object);
}
public void addPage(String path, TemplateViewRoute page) {
get(path, page, new FreeMarkerEngine());
}
public void addPostRoute(String path, Route route) {
post(path, route);
}
public void addGetApi(String path, Route route) {
get(path, route, new JsonTransformer());
}
public void addPostApi(String path, Route route) {
post(path, route, new JsonTransformer());
}
private RouteGroup configureAuthorisationRoute(String path) {
return () -> before(path, (request, response) -> {
if (response.raw().isCommitted())
return;
Session session = request.session(false);
if (null == session)
response.redirect("/admin/login");
else {
AuthenticatedUser user = session.attribute(SessionAttribute.USER);
if (null == user)
response.redirect("/admin/login");
}
});
}
}

View File

@ -19,14 +19,15 @@ package io.linuxserver.fleet.web.pages;
import io.linuxserver.fleet.web.SessionAttribute;
import spark.*;
import spark.template.freemarker.FreeMarkerEngine;
import java.util.Map;
public abstract class WebPage implements TemplateViewRoute {
public abstract class WebPage implements Route {
@Override
@SuppressWarnings("unchecked")
public ModelAndView handle(Request request, Response response) {
public Object handle(Request request, Response response) {
ModelAndView modelAndView = handle(request);
@ -34,7 +35,7 @@ public abstract class WebPage implements TemplateViewRoute {
if (null != session && null != session.attribute(SessionAttribute.USER))
((Map<String, Object>) modelAndView.getModel()).put("__AUTHENTICATED_USER", session.attribute(SessionAttribute.USER));
return modelAndView;
return new FreeMarkerEngine().render(modelAndView);
}
protected abstract ModelAndView handle(Request request);

View File

@ -51,7 +51,7 @@ public class RegisterInitialUserRoute implements Route {
try {
userDelegate.createNewUser(username, password);
response.redirect("/admin");
response.redirect("/admin/login");
} catch (SaveException e) {