Browse Source

unified loading of user in UserModule

Signed-off-by: Stephan Richter <s.richter@srsoftware.de>
feature/document
Stephan Richter 4 months ago
parent
commit
4ba113daf5
  1. 14
      core/src/main/java/de/srsoftware/umbrella/core/BaseHandler.java
  2. 5
      core/src/main/java/de/srsoftware/umbrella/core/model/EmailAddress.java
  3. 6
      legacy/src/main/java/de/srsoftware/umbrella/legacy/LegacyApi.java
  4. 1
      messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java
  5. 2
      messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java
  6. 171
      user/src/main/java/de/srsoftware/umbrella/user/UserModule.java

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

@ -4,7 +4,7 @@ package de.srsoftware.umbrella.core; @@ -4,7 +4,7 @@ package de.srsoftware.umbrella.core;
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;
import static java.net.HttpURLConnection.*;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
@ -30,6 +30,10 @@ public abstract class BaseHandler extends PathHandler { @@ -30,6 +30,10 @@ public abstract class BaseHandler extends PathHandler {
return ex;
}
public boolean forbidden(HttpExchange ex) throws IOException {
return sendEmptyResponse(HTTP_FORBIDDEN,ex);
}
public record Document(String mime, byte[] bytes){}
public boolean load(Path path, HttpExchange ex) throws IOException {
@ -58,7 +62,15 @@ public abstract class BaseHandler extends PathHandler { @@ -58,7 +62,15 @@ public abstract class BaseHandler extends PathHandler {
}
}
public boolean ok(HttpExchange ex) throws IOException {
return sendEmptyResponse(HTTP_OK,ex);
}
public boolean send(HttpExchange ex, UmbrellaException e) throws IOException {
return sendContent(ex,e.statusCode(),e.getMessage());
}
public boolean unauthorized(HttpExchange ex) throws IOException {
return sendEmptyResponse(HTTP_FORBIDDEN,ex);
}
}

5
core/src/main/java/de/srsoftware/umbrella/core/model/EmailAddress.java

@ -1,11 +1,10 @@ @@ -1,11 +1,10 @@
/* © SRSoftware 2025 */
package de.srsoftware.umbrella.core.model;
import de.srsoftware.umbrella.core.UmbrellaException;
import static de.srsoftware.tools.Optionals.allSet;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.text.MessageFormat.format;
import de.srsoftware.umbrella.core.UmbrellaException;
public class EmailAddress {
private final String email;

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

@ -92,7 +92,7 @@ public class LegacyApi extends BaseHandler { @@ -92,7 +92,7 @@ public class LegacyApi extends BaseHandler {
public boolean getModules(HttpExchange ex) throws IOException {
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
if (optToken.isEmpty()) return unauthorized(ex);
var list = new ArrayList<Map<String,String>>();
for (var module : config.keys()){
Optional<String> url = config.get(module+".baseUrl");
@ -202,7 +202,7 @@ public class LegacyApi extends BaseHandler { @@ -202,7 +202,7 @@ public class LegacyApi extends BaseHandler {
if (!(resp instanceof JSONObject json)) throw new UmbrellaException(500,"{0} did not return JSON!",messageUrl);
// TODO: should we return json?
return sendEmptyResponse(HTTP_OK,ex);
return ok(ex);
}
private boolean logout(HttpExchange ex) throws IOException {
@ -224,7 +224,7 @@ public class LegacyApi extends BaseHandler { @@ -224,7 +224,7 @@ public class LegacyApi extends BaseHandler {
private boolean postSearch(HttpExchange ex) throws IOException, UmbrellaException {
var optToken = SessionToken.from(ex).map(Token::of);
if (optToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
if (optToken.isEmpty()) return unauthorized(ex);
var token = optToken.get();
var json = json(ex);
if (!(json.has(KEY) && json.get(KEY) instanceof String key) || key.isBlank()) return sendContent(ex,HTTP_BAD_REQUEST,"No search key given");

1
messages/src/main/java/de/srsoftware/umbrella/message/MessageSystem.java

@ -25,7 +25,6 @@ import jakarta.mail.util.ByteArrayDataSource; @@ -25,7 +25,6 @@ import jakarta.mail.util.ByteArrayDataSource;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.BiFunction;
import java.util.function.Function;
public class MessageSystem implements PostBox {
public static final System.Logger LOG = System.getLogger(MessageSystem.class.getSimpleName());

2
messages/src/main/java/de/srsoftware/umbrella/message/model/CombinedMessage.java

@ -6,10 +6,8 @@ import static java.lang.System.Logger.Level.TRACE; @@ -6,10 +6,8 @@ import static java.lang.System.Logger.Level.TRACE;
import static java.text.MessageFormat.format;
import de.srsoftware.umbrella.core.model.UmbrellaUser;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
public class CombinedMessage {
private static final System.Logger LOG = System.getLogger(CombinedMessage.class.getSimpleName());

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

@ -21,6 +21,7 @@ import static java.net.HttpURLConnection.*; @@ -21,6 +21,7 @@ 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 java.util.Optional.empty;
import com.sun.net.httpserver.HttpExchange;
import de.srsoftware.tools.Path;
@ -94,10 +95,10 @@ public class UserModule extends BaseHandler { @@ -94,10 +95,10 @@ public class UserModule extends BaseHandler {
}
private boolean deleteService(HttpExchange ex, UmbrellaUser user, String serviceName) throws IOException {
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(MANAGE_LOGIN_SERVICES))) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(MANAGE_LOGIN_SERVICES))) return unauthorized(ex);
try {
logins.delete(serviceName);
return sendEmptyResponse(HTTP_OK,ex);
return ok(ex);
} catch (UmbrellaException e) {
return send(ex,e);
}
@ -118,55 +119,55 @@ public class UserModule extends BaseHandler { @@ -118,55 +119,55 @@ public class UserModule extends BaseHandler {
try {
logins.unlink(ForeignLogin.of(serviceId,foreignId,user.id()));
return sendEmptyResponse(HTTP_OK,ex);
return ok(ex);
} catch (UmbrellaException e) {
return send(ex,e);
}
}
private Optional<UmbrellaUser> loadUser(Optional<Token> sessionToken) throws UmbrellaException {
if (sessionToken.isEmpty()) return empty();
var session = users.load(sessionToken.get());
return Optional.of(users.load(session));
}
private Optional<UmbrellaUser> loadUser(HttpExchange ex) throws UmbrellaException {
return loadUser(SessionToken.from(ex).map(Token::of));
}
@Override
public boolean doDelete(Path path, HttpExchange ex) throws IOException {
UmbrellaUser user = null;
var sessionToken = SessionToken.from(ex).map(Token::of);
if (sessionToken.isPresent()) try {
user = users.load(users.load(sessionToken.get()));
} catch (UmbrellaException e) {
LOG.log(WARNING,e);
try {
var user = loadUser(ex);
addCors(ex);
var head = path.pop();
if (OIDC.equals(head)) return user.isEmpty() ? unauthorized(ex) : deleteOIDC(ex, user.get(), path);
} catch (UmbrellaException e){
return send(ex,e);
}
addCors(ex);
var head = path.pop();
switch (head) {
case OIDC: return deleteOIDC(ex,user,path);
};
return super.doDelete(path, ex);
}
@Override
public boolean doGet(Path path, HttpExchange ex) throws IOException {
UmbrellaUser user = null;
var sessionToken = SessionToken.from(ex).map(Token::of);
if (sessionToken.isPresent()) try {
user = users.load(users.load(sessionToken.get()));
} catch (UmbrellaException e) {
LOG.log(WARNING,e);
}
addCors(ex);
var head = path.pop();
switch (head) {
case LIST: return getUserList(ex, user);
case LOGOUT: return logout(ex, sessionToken);
case OIDC: return getOIDC(ex,user,path);
case VALIDATE_OTP: return validateOtpToken(ex,path.pop());
case WHOAMI: return getUser(ex, user);
};
try {
var user = loadUser(sessionToken);
addCors(ex);
var head = path.pop();
switch (head) {
case LIST: return user.isEmpty() ? unauthorized(ex) : getUserList(ex, user.get());
case LOGOUT: return logout(ex, sessionToken);
case OIDC: return getOIDC(ex,user.orElse(null),path);
case VALIDATE_OTP: return validateOtpToken(ex,path.pop());
case WHOAMI: return user.isEmpty() ? unauthorized(ex) : sendContent(ex,user.get());
};
long userId = Long.parseLong(head);
if (!(user instanceof DbUser dbUser && (user.id() == userId || dbUser.permissions().contains(LIST_USERS)))) {
return sendEmptyResponse(HTTP_FORBIDDEN,ex);
}
if (user.isEmpty()) return forbidden(ex);
if (!(user.get() instanceof DbUser dbUser)) return forbidden(ex);
if (dbUser.id() == userId || dbUser.permissions().contains(LIST_USERS)) return forbidden(ex);
return sendContent(ex,users.load(userId));
} catch (UmbrellaException e) {
return send(ex,e);
@ -177,56 +178,36 @@ public class UserModule extends BaseHandler { @@ -177,56 +178,36 @@ public class UserModule extends BaseHandler {
@Override
public boolean doOptions(Path path, HttpExchange ex) throws IOException {
return sendEmptyResponse(200,addCors(ex));
return ok(addCors(ex));
}
public boolean doPatch(Path path, HttpExchange ex) throws IOException {
addCors(ex);
var sessionToken = SessionToken.from(ex);
if (sessionToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
UmbrellaUser requestingUser;
try {
Session session = users.load(Token.of(sessionToken.get()));
requestingUser = users.load(session);
} catch (UmbrellaException e) {
return send(ex,e);
}
var head = path.pop();
long userId;
String head = null;
try {
var requestingUser = loadUser(ex);
head = path.pop();
long userId;
if (head == null || head.isBlank()) return sendContent(ex, HTTP_UNPROCESSABLE,"User id missing!");
if (PASSWORD.equals(head)) return patchPassword(ex,requestingUser);
if (OIDC.equals(head)) return patchService(ex,path.pop(),requestingUser);
if (requestingUser.isEmpty()) return unauthorized(ex);
if (PASSWORD.equals(head)) return patchPassword(ex,requestingUser.get());
if (OIDC.equals(head)) return patchService(ex,path.pop(),requestingUser.get());
userId = Long.parseLong(head);
} catch (NumberFormatException e) {
return sendContent(ex, HTTP_UNPROCESSABLE,"Invalid user id: "+head);
}
DbUser editedUser;
try {
editedUser = (DbUser) users.load(userId);
} catch (UmbrellaException e) {
return send(ex,e);
}
if (requestingUser.id() != userId && (!(requestingUser instanceof DbUser dbUser) || !dbUser.permissions().contains(UPDATE_USERS))){
return sendContent(ex,HTTP_FORBIDDEN,"You are not allowed to update user "+editedUser.name());
}
JSONObject json;
try {
json = json(ex);
} catch (Exception e){
LOG.log(WARNING,"Request does not contain valid JSON",e);
return sendContent(ex,HTTP_BAD_REQUEST,"Body contains no JSON data");
}
DbUser editedUser = (DbUser) users.load(userId);
if (!(requestingUser.get() instanceof DbUser dbUser) || !dbUser.permissions().contains(UPDATE_USERS)) return sendContent(ex,HTTP_FORBIDDEN,"You are not allowed to update user "+editedUser.name());
try {
JSONObject json;
try {
json = json(ex);
} catch (Exception e){
LOG.log(WARNING,"Request does not contain valid JSON",e);
return sendContent(ex,HTTP_BAD_REQUEST,"Body contains no JSON data");
}
return update(ex, editedUser,json);
} catch (NumberFormatException e) {
return sendContent(ex, HTTP_UNPROCESSABLE,"Invalid user id: "+head);
} catch (UmbrellaException e) {
return send(ex,e);
}
@ -273,14 +254,8 @@ public class UserModule extends BaseHandler { @@ -273,14 +254,8 @@ public class UserModule extends BaseHandler {
if (!(resp instanceof JSONObject json)) return sendContent(ex,HTTP_BAD_REQUEST,format("{0} did not return JSON!",location));
if (!json.has(ID_TOKEN)) return sendContent(ex,HTTP_FAILED_DEPENDENCY,"Missing ID token – token exchange failed!");
var idToken = json.getString(ID_TOKEN);
Optional<Token> optToken = SessionToken.from(ex).map(Token::of);
Optional<UmbrellaUser> optUser = Optional.empty();
if (optToken.isPresent()){
Session session = users.load(optToken.get());
optUser = Optional.of(users.load(session));
}
var oidcUserId = verifyAndGetUserId(idToken, state);
var optUser = loadUser(ex);
if (optUser.isPresent()){ // user already logged in – this is the case when the new id gets assigned
var currentUser = optUser.get();
var assignment = new ForeignLogin(state.loginService.name(),oidcUserId,currentUser.id());
@ -298,7 +273,7 @@ public class UserModule extends BaseHandler { @@ -298,7 +273,7 @@ public class UserModule extends BaseHandler {
}
private boolean getConnectedServices(HttpExchange ex, UmbrellaUser user) throws IOException {
if (user == null) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
if (user == null) return unauthorized(ex);
try {
var connections = logins.listAssignments(user.id()).stream().map(ForeignLogin::toMap);
return sendContent(ex,connections);
@ -321,7 +296,7 @@ public class UserModule extends BaseHandler { @@ -321,7 +296,7 @@ public class UserModule extends BaseHandler {
}
private boolean getOIDC(HttpExchange ex, UmbrellaUser user, String serviceId) throws IOException {
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(MANAGE_LOGIN_SERVICES))) return sendEmptyResponse(HTTP_FORBIDDEN,ex);
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(MANAGE_LOGIN_SERVICES))) return forbidden(ex);
try {
return sendContent(ex,logins.loadLoginService(serviceId).toMap());
} catch (UmbrellaException e) {
@ -381,7 +356,7 @@ public class UserModule extends BaseHandler { @@ -381,7 +356,7 @@ public class UserModule extends BaseHandler {
}
private boolean getServiceList(HttpExchange ex, UmbrellaUser user) throws IOException {
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(MANAGE_LOGIN_SERVICES))) return sendEmptyResponse(HTTP_FORBIDDEN,ex);
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(MANAGE_LOGIN_SERVICES))) return forbidden(ex);
try {
var services = logins.listLoginServices().stream().map(LoginService::toMap);
return sendContent(ex,services);
@ -392,11 +367,6 @@ public class UserModule extends BaseHandler { @@ -392,11 +367,6 @@ public class UserModule extends BaseHandler {
}
}
private boolean getUser(HttpExchange ex, UmbrellaUser user) throws IOException {
if (user != null) return sendContent(ex,user);
return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
}
private boolean getUserList(HttpExchange ex, UmbrellaUser user) throws IOException {
if (!(user instanceof DbUser dbUser && dbUser.permissions().contains(LIST_USERS))) return sendContent(ex,HTTP_FORBIDDEN,"You are not allowed to list users!");
try {
@ -408,11 +378,10 @@ public class UserModule extends BaseHandler { @@ -408,11 +378,10 @@ public class UserModule extends BaseHandler {
}
private boolean impersonate(HttpExchange ex, Long targetId) throws IOException {
var sessionToken = SessionToken.from(ex).map(Token::of);
if (sessionToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
try {
var requestingUser = users.load(users.load(sessionToken.get()));
if (!(requestingUser instanceof DbUser dbUser && dbUser.permissions().contains(PERMISSION.IMPERSONATE))) return sendEmptyResponse(HTTP_FORBIDDEN,ex);
var requestingUser = loadUser(ex);
if (!(requestingUser.isPresent() && requestingUser.get() instanceof DbUser dbUser)) return unauthorized(ex);
if (!dbUser.permissions().contains(PERMISSION.IMPERSONATE)) return forbidden(ex);
if (targetId == null) return sendContent(ex,HTTP_UNPROCESSABLE,"user id missing");
var targetUser = users.load(targetId);
users.getSession(targetUser).cookie().addTo(ex);
@ -431,9 +400,9 @@ public class UserModule extends BaseHandler { @@ -431,9 +400,9 @@ public class UserModule extends BaseHandler {
}
new SessionToken(token.toString(),"/", Instant.now().minus(1, DAYS),true).addTo(ex);
return sendEmptyResponse(HTTP_OK,ex);
return ok(ex);
}
return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
return unauthorized(ex);
}
@ -468,11 +437,11 @@ public class UserModule extends BaseHandler { @@ -468,11 +437,11 @@ public class UserModule extends BaseHandler {
}
private boolean postCreate(HttpExchange ex) throws IOException {
var sessionToken = SessionToken.from(ex).map(Token::of);
if (sessionToken.isEmpty()) return sendEmptyResponse(HTTP_UNAUTHORIZED,ex);
try {
var u = users.load(users.load(sessionToken.get()));
if (!(u instanceof DbUser requestingUser && requestingUser.permissions().contains(PERMISSION.CREATE_USERS))) return sendEmptyResponse(HTTP_FORBIDDEN,ex);
var optUser = loadUser(ex);
if (!(optUser.isPresent() && optUser.get() instanceof DbUser dbUser)) return unauthorized(ex);
if (!dbUser.permissions().contains(PERMISSION.CREATE_USERS)) return forbidden(ex);
var json = json(ex);
if (json.has(USER)) json = json.getJSONObject(USER);
@ -511,11 +480,11 @@ public class UserModule extends BaseHandler { @@ -511,11 +480,11 @@ public class UserModule extends BaseHandler {
} catch (UmbrellaException e){
return send(ex,e);
}
return sendEmptyResponse(HTTP_OK,ex);
return ok(ex);
}
private boolean patchService(HttpExchange ex, String serviceName, UmbrellaUser requestingUser) throws IOException {
if (!(requestingUser instanceof DbUser user && user.permissions().contains(MANAGE_LOGIN_SERVICES))) return sendEmptyResponse(HTTP_FORBIDDEN,ex);
if (!(requestingUser instanceof DbUser user && user.permissions().contains(MANAGE_LOGIN_SERVICES))) return forbidden(ex);
try {
var json = json(ex);
if (!json.has(NAME) || !(json.get(NAME) instanceof String name) || name.isBlank()) return sendContent(ex,HTTP_UNPROCESSABLE,format(ERROR_MISSING_FIELD,NAME));

Loading…
Cancel
Save