mirror of
https://github.com/linuxserver/fleet.git
synced 2026-02-20 05:11:08 +08:00
Reworked web server to configure servlets more directly. Still need to sort login filter
This commit is contained in:
parent
52c5ea952d
commit
bddbb06835
@ -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 {
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
|
||||
@ -51,7 +51,7 @@ public class RegisterInitialUserRoute implements Route {
|
||||
try {
|
||||
|
||||
userDelegate.createNewUser(username, password);
|
||||
response.redirect("/admin");
|
||||
response.redirect("/admin/login");
|
||||
|
||||
} catch (SaveException e) {
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user