Merge branch 'dev' into feature/document
This commit is contained in:
@@ -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 {
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 {
|
||||
|
||||
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");
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
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 {
|
||||
|
||||
@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);
|
||||
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());
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
return update(ex, editedUser,json);
|
||||
} catch (UmbrellaException e) {
|
||||
return send(ex,e);
|
||||
}
|
||||
@@ -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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
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 {
|
||||
}
|
||||
|
||||
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 {
|
||||
} 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));
|
||||
|
||||
Reference in New Issue
Block a user