working on legacy api:

Teile des Codes aus funktionierendem Projekt kpiert und angepasst, aber noch nicht getestet
This commit is contained in:
2025-07-04 00:26:01 +02:00
parent c96e7d73ba
commit e48ddfdb2c
12 changed files with 239 additions and 35 deletions

View File

@@ -13,6 +13,7 @@ application{
dependencies{
implementation(project(":core"))
implementation(project(":legacy"))
implementation(project(":translations"))
implementation(project(":user"))
implementation(project(":web"))

View File

@@ -10,6 +10,8 @@ import com.sun.net.httpserver.HttpServer;
import de.srsoftware.configuration.JsonConfig;
import de.srsoftware.tools.ColorLogger;
import de.srsoftware.umbrella.core.ConnectionProvider;
import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.legacy.LegacyApi;
import de.srsoftware.umbrella.translations.Translations;
import de.srsoftware.umbrella.user.UserModule;
import de.srsoftware.umbrella.user.sqlite.SqliteDB;
@@ -36,22 +38,24 @@ public class Application {
}
}
public static void main(String[] args) throws IOException {
public static void main(String[] args) throws IOException, UmbrellaException {
LOG.log(INFO, "Starting Umbrella:");
var jsonConfig = new JsonConfig(UMBRELLA);
configureLogging(jsonConfig);
var port = jsonConfig.get("umbrella.http.port", 8080);
var threads = jsonConfig.get("umbrella.threads", 16);
var userDB = jsonConfig.get("umbrella.database.user", jsonConfig.file().getParent()+"/umbrella.db");
var loginDB = jsonConfig.get("umbrella.database.login_services",jsonConfig.file().getParent()+"/umbrella.db");
var config = new JsonConfig(UMBRELLA);
configureLogging(config);
var port = config.get("umbrella.http.port", 8080);
var threads = config.get("umbrella.threads", 16);
var userDB = config.get("umbrella.database.user", config.file().getParent()+"/umbrella.db");
var loginDB = config.get("umbrella.database.login_services",config.file().getParent()+"/umbrella.db");
var connectionProvider = new ConnectionProvider();
var userDb = new SqliteDB(connectionProvider.get(userDB));
var loginServicedb = new SqliteDB(connectionProvider.get(loginDB));
var server = HttpServer.create(new InetSocketAddress(port), 0);
server.setExecutor(Executors.newFixedThreadPool(threads));
new WebHandler().bindPath("/").on(server);
new UserModule(userDb,loginServicedb).bindPath("/api/user").on(server);
new LegacyApi(userDb,config).bindPath("/legacy").on(server);
new Translations().bindPath("/api/translations").on(server);
new UserModule(userDb,loginServicedb).bindPath("/api/user").on(server);
new WebHandler().bindPath("/").on(server);
server.start();
LOG.log(INFO,"Started web server at {0}",port);
}

View File

@@ -11,10 +11,11 @@ repositories {
dependencies {
implementation("de.srsoftware:tools.mime:1.1.2")
implementation("de.srsoftware:tools.optionals:1.0.0")
implementation("org.json:json:20240303")
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
testImplementation(platform("org.junit:junit-bom:5.10.0"))
testImplementation("org.junit.jupiter:junit-jupiter")
implementation("org.xerial:sqlite-jdbc:3.49.0.0")
}
tasks.test {

View File

@@ -0,0 +1,65 @@
package de.srsoftware.umbrella.core;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.PathHandler;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;
import static de.srsoftware.tools.MimeType.MIME_JSON;
import static de.srsoftware.tools.Optionals.nullable;
import static java.lang.System.Logger.Level.DEBUG;
import static java.lang.System.Logger.Level.WARNING;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
public abstract class BaseHandler extends PathHandler {
private static HttpExchange addCors(HttpExchange ex){
var headers = ex.getRequestHeaders();
var origin = nullable(headers.get("Origin")).orElse(List.of()).stream().filter(url -> url.contains("://localhost")||url.contains("://127.0.0.1")).findAny();
if (origin.isPresent()) {
var url = origin.get();
headers = ex.getResponseHeaders();
headers.add("Allow-Origin", url);
headers.add("Access-Control-Allow-Origin", url);
headers.add("Access-Control-Allow-Headers", "Content-Type");
headers.add("Access-Control-Allow-Credentials", "true");
headers.add("Access-Control-Allow-Methods","GET, POST, PATCH");
}
return ex;
}
public record Document(String mime, byte[] bytes){}
public boolean load(Path path, HttpExchange ex) throws IOException {
try {
var doc = load(path.toString());
ex.getResponseHeaders().add(CONTENT_TYPE, doc.mime);
return sendContent(ex,doc.bytes);
} catch (UmbrellaException e) {
throw new RuntimeException(e);
}
}
public Document load(String resourcePath) throws IOException, UmbrellaException {
var url = getClass().getClassLoader().getResource(resourcePath);
if (url == null) throw new UmbrellaException(HTTP_NOT_FOUND,"{0} not found!",resourcePath);
LOG.log(DEBUG,"Trying to load {0}",url);
var bos = new ByteArrayOutputStream();
var conn = url.openConnection();
var mime = conn.getContentType();
try (var stream = conn.getInputStream()){
stream.transferTo(bos);
return new Document(mime,bos.toByteArray());
} catch (Exception e) {
LOG.log(WARNING,"Failed to load {0}",url);
throw new UmbrellaException(HTTP_NOT_FOUND,"Failed to load {0}",url);
}
}
public boolean send(HttpExchange ex, UmbrellaException e) throws IOException {
return sendContent(ex,e.statusCode(),e.getMessage());
}
}

View File

@@ -1,10 +1,10 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.user.model;
package de.srsoftware.umbrella.core;
import static de.srsoftware.umbrella.core.Constants.TOKEN;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.AddableMap;
import java.util.UUID;
public class Token implements CharSequence{

5
legacy/Readme.md Normal file
View File

@@ -0,0 +1,5 @@
# Legacy
This module aims to provide an API for legacy modules to communicate with.
As soon as all "old" PHP modules are replaced, this should no longer be required.

View File

@@ -0,0 +1,126 @@
package de.srsoftware.umbrella.legacy;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.configuration.Configuration;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.user.sqlite.SqliteDB;
import static de.srsoftware.tools.MimeType.MIME_FORM_URL;
import static de.srsoftware.umbrella.core.ResponseCode.HTTP_SERVER_ERROR;
import static de.srsoftware.umbrella.core.Constants.*;
import static de.srsoftware.umbrella.core.Constants.KEY;
import static de.srsoftware.umbrella.core.Paths.LOGOUT;
import static de.srsoftware.umbrella.core.Paths.SEARCH;
import static de.srsoftware.umbrella.core.Util.request;
import static java.net.HttpURLConnection.*;
import static java.time.temporal.ChronoUnit.DAYS;
import java.io.IOException;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
public class LegacyApi extends BaseHandler {
private final SqliteDB users;
private final Configuration config;
public LegacyApi(SqliteDB userDb, Configuration config) {
users = userDb;
this.config = config.subset("umbrella.modules").orElseThrow(() -> new RuntimeException("Missing configuration: umbrella.modules"));
}
@Override
public boolean doGet(Path path, HttpExchange ex) throws IOException {
var head = path.pop();
return switch (head){
case null -> sendRedirect(ex, url(ex).replaceAll("/api/.*",""));
case "common_templates" -> {
allowOrigin(ex, "*"); // add CORS header
yield load(path,ex);
}
case LOGIN -> getLegacyLogin(ex);
case LOGOUT-> logout(ex);
case SEARCH -> {
try {
yield legacySearch(ex);
} catch (UmbrellaException e){
yield send(ex,e);
}
}
default -> super.doGet(path,ex);
};
}
private boolean legacySearch(HttpExchange ex) throws IOException, UmbrellaException {
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
var token = optToken.get();
var params = queryParam(ex);
var key = params.get(KEY) instanceof String s ? s : null;
if (key == null) throw new UmbrellaException(HTTP_BAD_REQUEST,"Missing search key");
var fulltext = key.contains("+") || "on".equals(params.get("fulltext"));
StringBuilder searchResult = new StringBuilder();
if (fulltext){
for (var module : config.keys()){
var baseUrl = config.get(module + ".baseUrl");
if (baseUrl.isEmpty()) continue;
var res = request(baseUrl.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,token.asBearer());
if (!(res instanceof String content) || content.isBlank()) continue;
searchResult.append("<fieldset><label>").append(module).append("</label>");
searchResult.append(content).append("</fieldset>\n");
}
} else {
var bookmark = config.get("bookmark.baseUrl");
if (bookmark.isEmpty()) throw new UmbrellaException(500,"Tag search not available: Bookmark module not configured!");
var res = request(bookmark.get()+"/search",token.asMap().plus(KEY,key),MIME_FORM_URL,null);
if (!(res instanceof String content)) throw new UmbrellaException(500,"Search did not return html content!");
searchResult.append(content);
}
return sendContent(ex,searchResult.toString());
};
private boolean logout(HttpExchange ex) throws IOException {
var returnTo = queryParam(ex).get("returnTo");
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isPresent()) try{
var token = optToken.get();
users.dropSession(token);
var expiredToken = new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true);
expiredToken.addTo(ex);
if (returnTo instanceof String location) return sendRedirect(ex,location);
return sendContent(ex, Map.of(REDIRECT,"home"));
} catch (UmbrellaException e) {
return send(ex,e);
}
if (returnTo instanceof String location) return sendRedirect(ex,location);
return sendContent(ex,Map.of(REDIRECT,"home"));
}
private boolean getLegacyLogin(HttpExchange ex) throws IOException {
var returnTo = queryParam(ex).get("returnTo");
if (returnTo instanceof String url) {
var token = SessionToken.from(ex).map(SessionToken::sessionId)
.or(() -> getBearer(ex))
.map(Token::of);
if (token.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
return sendRedirect(ex, url + (url.contains("?") ? "&" : "?") + "token=" + token.get());
}
var location = url(ex);
location = location.replaceAll("/api/.*","/login");
if (returnTo != null) location += "?returnTo="+returnTo;
return sendRedirect(ex,location);
}
@Override
public boolean doPost(Path path, HttpExchange ex) throws IOException {
return super.doPost(path, ex);
}
}

View File

@@ -5,4 +5,5 @@ include("translations")
include("user")
include("web")
include("core")
include("core")
include("legacy")

View File

@@ -20,12 +20,13 @@ import static java.net.HttpURLConnection.*;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.text.MessageFormat.format;
import static java.time.temporal.ChronoUnit.DAYS;
import static javax.security.auth.callback.ConfirmationCallback.OK;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
import de.srsoftware.tools.PathHandler;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.BaseHandler;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.user.api.LoginServiceDb;
import de.srsoftware.umbrella.user.api.UserDb;
@@ -45,7 +46,7 @@ import org.jose4j.keys.resolvers.HttpsJwksVerificationKeyResolver;
import org.json.JSONObject;
public class UserModule extends PathHandler {
public class UserModule extends BaseHandler {
private record State(LoginService loginService, JSONObject config){
public static State of(LoginService loginService, JSONObject config) {
@@ -102,7 +103,7 @@ public class UserModule extends PathHandler {
logins.delete(serviceName);
return sendEmptyResponse(HTTP_OK,ex);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -171,7 +172,7 @@ public class UserModule extends PathHandler {
}
return sendContent(ex,users.load(userId));
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
} catch (NumberFormatException ignored) {}
return super.doGet(path, ex);
@@ -193,7 +194,7 @@ public class UserModule extends PathHandler {
Session session = users.load(Token.of(sessionToken.get()));
requestingUser = users.load(session);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
var head = path.pop();
@@ -211,7 +212,7 @@ public class UserModule extends PathHandler {
try {
editedUser = (DbUser) users.load(userId);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
if (requestingUser.id() != userId && (!(requestingUser instanceof DbUser dbUser) || !dbUser.permissions().contains(UPDATE_USERS))){
@@ -230,7 +231,7 @@ public class UserModule extends PathHandler {
try {
return update(ex, editedUser,json);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -320,7 +321,7 @@ public class UserModule extends PathHandler {
var connections = logins.listAssignments(user.id()).stream().map(ForeignLogin::toMap);
return sendContent(ex,connections);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -342,7 +343,7 @@ public class UserModule extends PathHandler {
try {
return sendContent(ex,logins.loadLoginService(serviceId).toMap());
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
} catch (IOException e) {
return sendContent(ex,HTTP_SERVER_ERROR,e.getMessage());
}
@@ -391,7 +392,7 @@ public class UserModule extends PathHandler {
var services = logins.listLoginServices().stream().map(LoginService::name);
return sendContent(ex,services);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
} catch (IOException e) {
return sendContent(ex,HTTP_SERVER_ERROR,e.getMessage());
}
@@ -403,7 +404,7 @@ public class UserModule extends PathHandler {
var services = logins.listLoginServices().stream().map(LoginService::toMap);
return sendContent(ex,services);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
} catch (IOException e) {
return sendContent(ex,HTTP_SERVER_ERROR,e.getMessage());
}
@@ -420,7 +421,7 @@ public class UserModule extends PathHandler {
var list = users.list(0, null).stream().map(UmbrellaUser::toMap).toList();
return sendContent(ex,list);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -435,7 +436,7 @@ public class UserModule extends PathHandler {
users.getSession(targetUser).cookie().addTo(ex);
return sendContent(ex,targetUser.toMap());
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -480,7 +481,7 @@ public class UserModule extends PathHandler {
var updated = users.save(new DbUser(user.id(), user.name(), user.email(), pass, user.theme(), user.language(), user.permissions(), null));
return sendContent(ex, updated);
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -495,7 +496,7 @@ public class UserModule extends PathHandler {
var service = logins.save(new LoginService(name,url,clientId,secret, DEFAULT_FIELD));
return sendContent(ex,service.toMap());
} catch (UmbrellaException e) {
return sendContent(ex,e.statusCode(),e.getMessage());
return send(ex,e);
}
}
@@ -509,8 +510,8 @@ public class UserModule extends PathHandler {
var user = users.load(username, hashedPass);
users.getSession(user).cookie().addTo(ex);
return sendContent(ex,user);
} catch (UmbrellaException ue){
return sendContent(ex,ue.statusCode(),ue.getMessage());
} catch (UmbrellaException e){
return send(ex,e);
}
}
@@ -526,9 +527,7 @@ public class UserModule extends PathHandler {
return score;
}
private boolean send(HttpExchange ex, UmbrellaException e) throws IOException {
return sendContent(ex,e.statusCode(),e.getMessage());
}
private boolean update(HttpExchange ex, DbUser user, JSONObject json) throws UmbrellaException, IOException {
var id = user.id();

View File

@@ -5,7 +5,7 @@ import de.srsoftware.umbrella.core.UmbrellaException;
import de.srsoftware.umbrella.user.model.DbUser;
import de.srsoftware.umbrella.user.model.Password;
import de.srsoftware.umbrella.user.model.Session;
import de.srsoftware.umbrella.user.model.Token;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.user.model.UmbrellaUser;
import java.util.List;

View File

@@ -2,6 +2,8 @@
package de.srsoftware.umbrella.user.model;
import de.srsoftware.tools.SessionToken;
import de.srsoftware.umbrella.core.Token;
import java.time.Instant;
/* © SRSoftware 2025 */

View File

@@ -16,7 +16,7 @@ import de.srsoftware.umbrella.user.api.LoginServiceDb;
import de.srsoftware.umbrella.user.api.UserDb;
import de.srsoftware.umbrella.user.model.*;
import de.srsoftware.umbrella.user.model.Session;
import de.srsoftware.umbrella.user.model.Token;
import de.srsoftware.umbrella.core.Token;
import de.srsoftware.umbrella.user.model.UmbrellaUser;
import java.sql.Connection;
import java.sql.ResultSet;