Browse Source

working on legacy api:

Teile des Codes aus funktionierendem Projekt kpiert und angepasst, aber noch nicht getestet
feature/document
Stephan Richter 4 months ago
parent
commit
e48ddfdb2c
  1. 1
      backend/build.gradle.kts
  2. 22
      backend/src/main/java/de/srsoftware/umbrella/backend/Application.java
  3. 3
      core/build.gradle.kts
  4. 65
      core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java
  5. 4
      core/src/main/java/de/srsoftware/umbrella/core/Token.java
  6. 5
      legacy/Readme.md
  7. 126
      legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java
  8. 1
      settings.gradle.kts
  9. 39
      user/src/main/java/de/srsoftware/umbrella/user/UserModule.java
  10. 2
      user/src/main/java/de/srsoftware/umbrella/user/api/UserDb.java
  11. 2
      user/src/main/java/de/srsoftware/umbrella/user/model/Session.java
  12. 2
      user/src/main/java/de/srsoftware/umbrella/user/sqlite/SqliteDB.java

1
backend/build.gradle.kts

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

22
backend/src/main/java/de/srsoftware/umbrella/backend/Application.java

@ -10,6 +10,8 @@ import com.sun.net.httpserver.HttpServer; @@ -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 { @@ -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);
}

3
core/build.gradle.kts

@ -11,10 +11,11 @@ repositories { @@ -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 {

65
core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java

@ -0,0 +1,65 @@ @@ -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());
}
}

4
user/src/main/java/de/srsoftware/umbrella/user/model/Token.java → core/src/main/java/de/srsoftware/umbrella/core/Token.java

@ -1,10 +1,10 @@ @@ -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

@ -0,0 +1,5 @@ @@ -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.

126
legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java

@ -0,0 +1,126 @@ @@ -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);
}
}

1
settings.gradle.kts

@ -6,3 +6,4 @@ include("user") @@ -6,3 +6,4 @@ include("user")
include("web")
include("core")
include("legacy")

39
user/src/main/java/de/srsoftware/umbrella/user/UserModule.java

@ -20,12 +20,13 @@ import static java.net.HttpURLConnection.*; @@ -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; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();

2
user/src/main/java/de/srsoftware/umbrella/user/api/UserDb.java

@ -5,7 +5,7 @@ import de.srsoftware.umbrella.core.UmbrellaException; @@ -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;

2
user/src/main/java/de/srsoftware/umbrella/user/model/Session.java

@ -2,6 +2,8 @@ @@ -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 */

2
user/src/main/java/de/srsoftware/umbrella/user/sqlite/SqliteDB.java

@ -16,7 +16,7 @@ import de.srsoftware.umbrella.user.api.LoginServiceDb; @@ -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;

Loading…
Cancel
Save