diff --git a/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java b/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java index 4404a4e..d72e856 100644 --- a/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java +++ b/src/main/java/io/linuxserver/fleet/auth/authenticator/AuthenticatorFactory.java @@ -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 { diff --git a/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java b/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java index 44051aa..1dd6799 100644 --- a/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java +++ b/src/main/java/io/linuxserver/fleet/auth/security/PKCS5S2PasswordEncoder.java @@ -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), diff --git a/src/main/java/io/linuxserver/fleet/core/FleetApp.java b/src/main/java/io/linuxserver/fleet/core/FleetApp.java index 979ed7b..a8143b7 100644 --- a/src/main/java/io/linuxserver/fleet/core/FleetApp.java +++ b/src/main/java/io/linuxserver/fleet/core/FleetApp.java @@ -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.*; + /** *

* 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()); } } diff --git a/src/main/java/io/linuxserver/fleet/core/FleetBeans.java b/src/main/java/io/linuxserver/fleet/core/FleetBeans.java index 032f112..0900980 100644 --- a/src/main/java/io/linuxserver/fleet/core/FleetBeans.java +++ b/src/main/java/io/linuxserver/fleet/core/FleetBeans.java @@ -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; /** *

@@ -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; } diff --git a/src/main/java/io/linuxserver/fleet/web/WebServer.java b/src/main/java/io/linuxserver/fleet/web/WebServer.java deleted file mode 100644 index dbdcb57..0000000 --- a/src/main/java/io/linuxserver/fleet/web/WebServer.java +++ /dev/null @@ -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 . - */ - -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"); - } - }); - } -} diff --git a/src/main/java/io/linuxserver/fleet/web/pages/WebPage.java b/src/main/java/io/linuxserver/fleet/web/pages/WebPage.java index 2c454b9..4f86cf9 100644 --- a/src/main/java/io/linuxserver/fleet/web/pages/WebPage.java +++ b/src/main/java/io/linuxserver/fleet/web/pages/WebPage.java @@ -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) modelAndView.getModel()).put("__AUTHENTICATED_USER", session.attribute(SessionAttribute.USER)); - return modelAndView; + return new FreeMarkerEngine().render(modelAndView); } protected abstract ModelAndView handle(Request request); diff --git a/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java b/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java index 35bddc8..45d3e49 100644 --- a/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java +++ b/src/main/java/io/linuxserver/fleet/web/routes/RegisterInitialUserRoute.java @@ -51,7 +51,7 @@ public class RegisterInitialUserRoute implements Route { try { userDelegate.createNewUser(username, password); - response.redirect("/admin"); + response.redirect("/admin/login"); } catch (SaveException e) {