Skip to content

Commit

Permalink
user view page
Browse files Browse the repository at this point in the history
  • Loading branch information
burdoto committed Dec 10, 2023
1 parent 667d8f7 commit 4281188
Show file tree
Hide file tree
Showing 12 changed files with 122 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;

Expand Down
4 changes: 2 additions & 2 deletions src/core/main/java/org/comroid/mcsd/core/MCSD.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import org.comroid.mcsd.api.dto.McsdConfig;
import org.comroid.mcsd.core.entity.*;
import org.comroid.mcsd.core.exception.EntityNotFoundException;
import org.comroid.mcsd.core.exception.InvalidRequestException;
import org.comroid.mcsd.core.exception.BadRequestException;
import org.comroid.mcsd.core.module.discord.DiscordAdapter;
import org.comroid.mcsd.core.repo.*;
import org.comroid.util.Debug;
Expand Down Expand Up @@ -137,7 +137,7 @@ public AbstractEntity findEntity(String type, UUID id) {
case "server" -> servers.findById(id).orElseThrow(()->new EntityNotFoundException(Server.class,id));
case "sh" -> shRepo.findById(id).orElseThrow(()->new EntityNotFoundException(ShConnection.class,id));
case "user" -> users.findById(id).orElseThrow(()->new EntityNotFoundException(User.class,id));
default -> throw new InvalidRequestException("unknown type: " + type);
default -> throw new BadRequestException("unknown type: " + type);
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,12 @@ public enum Permission implements BitmaskAttribute<Permission> {
Modify,

View(0x0100_0000, Status),
Moderate(0x0200_0000, View, Whitelist, Kick, Mute),
Manage(0x0400_0000, Moderate, Ban, Start, Stop, Backup, Update, Maintenance, Enable),
Administrate(0x0800_0000, Manage, Console, Execute, Files, ForceOP, TriggerCron),
Delete(0x1000_0000, Administrate),
Moderate(0x0200_0000, Whitelist, Kick, Mute),
Manage(0x0400_0000, Ban, Start, Stop, Backup, Update, Maintenance, Enable),
Administrate(0x0800_0000, Console, Execute, Files, ForceOP, TriggerCron),
Delete(0x1000_0000),

ManageUsers(0x2000_0000),

Any(0xffff_ffff);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.BAD_REQUEST)
public class InvalidRequestException extends CommandStatusError {
public InvalidRequestException(String message) {
public class BadRequestException extends CommandStatusError {
public BadRequestException(String message) {
super(message);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import org.comroid.mcsd.core.entity.*;
import org.comroid.mcsd.core.exception.EntityNotFoundException;
import org.comroid.mcsd.core.exception.InsufficientPermissionsException;
import org.comroid.mcsd.core.exception.InvalidRequestException;
import org.comroid.mcsd.core.exception.BadRequestException;
import org.comroid.mcsd.core.exception.StatusCode;
import org.comroid.mcsd.core.module.player.PlayerEventModule;
import org.comroid.mcsd.core.repo.*;
Expand All @@ -26,8 +26,6 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.*;

import java.time.Instant;
Expand Down Expand Up @@ -112,7 +110,7 @@ public String editServer(HttpSession session, @RequestParam Map<String, String>
if (data.containsKey("consoleChannelPrefix")) server.setConsoleChannelPrefix(data.get("consolePrefix"));
if (data.containsKey("fancyConsole")) server.setFancyConsole(Boolean.parseBoolean(data.get("fancyConsole")));
} catch (NumberFormatException nfe) {
throw new InvalidRequestException(nfe.getMessage());
throw new BadRequestException(nfe.getMessage());
}
return "redirect:/server/view/"+servers.save(server).getId();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,32 @@
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.comroid.api.IntegerAttribute;
import org.comroid.mcsd.core.BasicController;
import org.comroid.mcsd.core.MCSD;
import org.comroid.mcsd.core.entity.AbstractEntity;
import org.comroid.mcsd.core.entity.Server;
import org.comroid.mcsd.core.entity.User;
import org.comroid.mcsd.core.entity.*;
import org.comroid.mcsd.core.exception.EntityNotFoundException;
import org.comroid.mcsd.core.exception.InsufficientPermissionsException;
import org.comroid.mcsd.core.exception.InvalidRequestException;
import org.comroid.mcsd.core.exception.BadRequestException;
import org.comroid.mcsd.core.repo.*;
import org.comroid.util.Bitmask;
import org.comroid.util.Constraint;
import org.comroid.util.FormData;
import org.comroid.util.Streams;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.data.repository.CrudRepository;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Map;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;
import java.util.UUID;
import java.util.function.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@Controller
Expand Down Expand Up @@ -95,6 +93,18 @@ public String serverView(Model model, HttpSession session, @PathVariable("id") U
return "server/view";
}

@GetMapping("/user/view/{id}")
public String userView(Model model, HttpSession session, @PathVariable("id") UUID userId) {
var user = userRepo.get(session).assertion();
var subject = userRepo.findById(userId).orElseThrow(() -> new EntityNotFoundException(User.class, userId));
model.addAttribute("user", user)
.addAttribute("subject", subject)
.addAttribute("canManageUsers", user.hasPermission(user, AbstractEntity.Permission.ManageUsers))
.addAttribute("edit", false)
.addAttribute("editKey", null);
return "user/view";
}

@GetMapping("/{type}/edit/{id}")
public String entityEdit(HttpSession session, Model model,
@PathVariable("type") String type,
Expand All @@ -114,30 +124,54 @@ public String entityEdit(HttpSession session, Model model,
return type + "/view";
}

@RequestMapping(value = "/{type}/permissions/{target}/{user}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@RequestMapping(value = "/{type}/permissions/{target}/{user}")
public String entityPermissions(Model model, HttpSession session, HttpMethod method,
@PathVariable("type") String type,
@PathVariable("target") UUID targetId,
@PathVariable("user") UUID userId,
@Nullable @RequestParam(value = "auth_code", required = false) String code,
@Nullable @RequestBody(required = false) Map<String, String> data
) {
HttpServletRequest request
) throws IOException {
Constraint.anyOf(method, "method", HttpMethod.GET, HttpMethod.POST).run();
int permissions = 0;
FormData.@Nullable Object data = null;
if (method == HttpMethod.POST) {
data = FormData.Parser.parse(request.getReader().lines().collect(Collectors.joining("")));
for (int perm : data.keySet()
.stream()
.filter(str -> str.startsWith("perm_"))
.map(str -> str.substring("perm_".length()))
.mapToInt(Integer::parseInt)
.toArray()) {
permissions|=perm;
}
}
var user = userRepo.get(session).assertion();
var subject = userRepo.findById(userId).orElseThrow(() -> new EntityNotFoundException(User.class, userId));
var target = core.findEntity(type, targetId);
model.addAttribute("user", user)
.addAttribute("subject", subject)
.addAttribute("permissions", basicController.permissions())
.addAttribute("permissions", Arrays.stream(AbstractEntity.Permission.values())
.filter(perm -> Stream.of(AbstractEntity.Permission.None, AbstractEntity.Permission.Any).noneMatch(perm::equals))
.sorted(Comparator.comparingInt(IntegerAttribute::getAsInt))
.toList())
.addAttribute("mask", Objects.requireNonNullElse(target.getPermissions().get(subject), 0))
.addAttribute("target", target)
.addAttribute("type", type)
.addAttribute(type, target);
var verify = target.verifyPermission(user, AbstractEntity.Permission.Administrate);
if (method == HttpMethod.POST)
verify = verify.or(authorizationLinkRepo.validate(user, targetId, code, AbstractEntity.Permission.Administrate).cast());
if (method == HttpMethod.POST && data.containsKey("auth_code"))
verify = verify.or(authorizationLinkRepo.validate(user, targetId, data.get("auth_code").asString(), AbstractEntity.Permission.Administrate).cast());
verify.orElseThrow(() -> new InsufficientPermissionsException(user, target, AbstractEntity.Permission.Administrate));
if (method == HttpMethod.POST) {
target.getPermissions().put(subject, permissions);
switch(type){
case"agent"->agentRepo.save((Agent)target);
case"discordBot"->discordBotRepo.save((DiscordBot)target);
case"server"->serverRepo.save((Server)target);
case"sh"->shRepo.save((ShConnection)target);
case"user"->userRepo.save((User)target);
default -> throw new BadRequestException("invalid type: "+type);
}
}
return "entity/permissions";
}
Expand Down Expand Up @@ -167,7 +201,7 @@ public String entityDelete(HttpSession session, Model model, HttpMethod method,
case "server" -> serverRepo;
case "sh" -> shRepo;
case "user" -> userRepo;
default -> throw new InvalidRequestException("invalid type: " + type);
default -> throw new BadRequestException("invalid type: " + type);
}).deleteById(id);
return "redirect:/";
}
Expand Down
9 changes: 4 additions & 5 deletions src/hub/main/resources/templates/dashboard.html
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="/head">
<script type="text/javascript" th:src="@{/static/dashboard.js}"></script>
</head>
<head th:insert="~{/head}"></head>
<script type="text/javascript" th:src="@{/static/dashboard.js}"></script>
<body onload="load()" onbeforeunload="unload()">
<div class="ui-menubar" th:include="/menubar"></div>
<div class="ui-menubar" th:insert="~{/menubar}"></div>
<h3>Hello <b th:text="${user.getBestName()}"></b></h3>
<div class="ui-container-page">
<div class="ui-content">
Expand Down Expand Up @@ -35,6 +34,6 @@ <h2>Servers</h2>
</table>
</div>
</div>
<div class="ui-footer" th:include="/footer"></div>
<div class="ui-footer" th:insert="~{/footer}"></div>
</body>
</html>
6 changes: 3 additions & 3 deletions src/hub/main/resources/templates/entity/confirm_delete.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!DOCTYPE html>
<!--suppress HtmlFormInputWithoutLabel -->
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="/head"></head>
<head th:insert="~{/head}"></head>
<body onbeforeunload="unload()" onload="load()">
<div class="ui-menubar" th:include="/menubar"></div>
<div class="ui-menubar" th:insert="~{/menubar}"></div>
<div class="ui-container-page">
<div class="ui-content">
<h1>You are about to delete <b th:text="${target.toString()}"></b>!</h1>
Expand All @@ -14,6 +14,6 @@ <h3>Do you really want to do this?</h3>
</form>
</div>
</div>
<div class="ui-footer" th:include="/footer"></div>
<div class="ui-footer" th:insert="~{/footer}"></div>
</body>
</html>
22 changes: 12 additions & 10 deletions src/hub/main/resources/templates/entity/permissions.html
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
<!DOCTYPE html>
<!--suppress HtmlFormInputWithoutLabel -->
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="/head"></head>
<head th:insert="~{/head}"></head>
<body onbeforeunload="unload()" onload="load()">
<div class="ui-menubar" th:include="/menubar"></div>
<div class="ui-menubar" th:insert="~{/menubar}"></div>
<div class="ui-container-page">
<div class="ui-content">
<h2>Permissions for User <b th:text="${subject.bestName}"></b> for <span th:text="${type}"></span> <b
th:text="${target.bestName}"></b></h2>
<h4 class="error-text" th:if="${target.owner.id == user.id}">User is owner of the service; changes will have no effect</h4>
<form th:action="@{'/'+${type}+'/permissions/'+${target.id}+'/'+${subject.id}}" method="post">
<h4 class="error-text" th:if="${target.owner.id == user.id}">User is owner of the service; changes will have no
effect</h4>
<form method="post" th:action="@{'/'+${type}+'/permissions/'+${target.id}+'/'+${subject.id}}">
<table>
<thead>
<tr>
Expand All @@ -20,17 +21,18 @@ <h4 class="error-text" th:if="${target.owner.id == user.id}">User is owner of th
</thead>
<tbody>
<tr th:each="permission: ${permissions}">
<td><input th:name="'perm_'+${permission.key}"
th:value="${T(org.comroid.util.Bitmask).isFlagSet(mask, permission.key)}"
<td><input th:checked="${permission.isFlagSet(mask)}"
th:name="'perm_'+${permission.asInt}"
type="checkbox"></td>
<td th:text="${permission.value}"></td>
<td th:text="${T(java.lang.String).format('0x%08X', permission.key)}"></td>
<td th:text="${permission.name}"></td>
<td th:text="${T(java.lang.String).format('0x%08X', permission.asInt)}"></td>
</tr>
</tbody>
</table>
<input type="submit" value="submit"></form>
<input type="submit" value="submit">
</form>
</div>
</div>
<div class="ui-footer" th:include="/footer"></div>
<div class="ui-footer" th:insert="~{/footer}"></div>
</body>
</html>
6 changes: 3 additions & 3 deletions src/hub/main/resources/templates/error.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="/head"></head>
<head th:insert="~{/head}"></head>
<body onload="load()" onbeforeunload="unload()">
<div class="ui-menubar" th:include="/menubar"></div>
<div class="ui-menubar" th:insert="~{/menubar}"></div>
<h2 class="error-text">Well, that didn't work :/</h2>
<div class="ui-footer" th:include="/footer"></div>
<div class="ui-footer" th:insert="~{/footer}"></div>
</body>
</html>
10 changes: 5 additions & 5 deletions src/hub/main/resources/templates/server/view.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<!DOCTYPE html>
<!--suppress HtmlFormInputWithoutLabel -->
<html xmlns:th="http://www.thymeleaf.org">
<head th:include="/head"></head>
<head th:insert="~{/head}"></head>
<body onbeforeunload="unload()" onload="load()">
<div class="ui-menubar" th:include="/menubar"></div>
<div class="ui-menubar" th:insert="~{/menubar}"></div>
<div class="ui-container-page">
<div class="ui-content">
<form action="/api/webapp/server/edit" method="post">
Expand Down Expand Up @@ -33,7 +33,7 @@ <h3>General</h3>
<td><input name="id" readonly th:value="${server.id}" type="text"></td>
</tr>
<tr>
<td>Minecraft_Version</td>
<td>Minecraft Version</td>
<td><input name="mcVersion" th:readonly="${!edit}" th:value="${server.mcVersion}" type="text"></td>
</tr>
<tr>
Expand Down Expand Up @@ -139,11 +139,11 @@ <h5 class="error-text" th:unless="${server.getDiscordBot() != null}">Not configu
</tr>
</tbody>
</table>
<input th:if="${editKey!=null}" type="hidden" name="editKey" th:value="${editKey}">
<input th:if="${editKey!=null}" type="hidden" name="auth_key" th:value="${editKey}">
<input th:if="${edit}" type="submit" value="Apply">
</form>
</div>
</div>
<div class="ui-footer" th:include="/footer"></div>
<div class="ui-footer" th:insert="~{/footer}"></div>
</body>
</html>
29 changes: 29 additions & 0 deletions src/hub/main/resources/templates/user/view.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<!DOCTYPE html>
<!--suppress HtmlFormInputWithoutLabel -->
<html xmlns:th="http://www.thymeleaf.org">
<head th:insert="~{/head}"></head>
<body onbeforeunload="unload()" onload="load()">
<div class="ui-menubar" th:insert="~{/menubar}"></div>
<div class="ui-container-page">
<div class="ui-content">
<form action="/api/webapp/subject/edit" method="post">
<h2>User <b th:text="${subject.getBestName()}"></b>
- <a class="ui-button" th:if="${canManageUsers}" th:href="@{'/user/edit/'+${id}}">Edit</a>
<h3>General</h3>
<table>
<tbody>
<tr><td>Name</td><td><input name="name" th:readonly="${!edit}" th:value="${subject.name}" type="text"></td></tr>
<tr><td>Displayname</td><td><input name="name" th:readonly="${!edit}" th:value="${subject.displayName}" type="text"></td></tr>
<tr><td>Email</td><td><input email="email" th:readonly="${!edit}" th:value="${subject.email}" type="email"></td></tr>
<tr><td>HubId</td><td><input hubId="hubId" th:readonly="${!edit}" th:value="${subject.hubId}" type="text"></td></tr>
<tr><td>MinecraftId</td><td><input minecraftId="minecraftId" th:readonly="${!edit}" th:value="${subject.minecraftId}" type="text"></td></tr>
<tr><td>DiscordId</td><td><input discordId="discordId" th:readonly="${!edit}" th:value="${subject.discordId}" type="number"></td></tr>
</tbody>
<input th:if="${editKey!=null}" type="hidden" name="auth_key" th:value="${editKey}">
<input th:if="${edit}" type="submit" value="Apply">
</form>
</div>
</div>
<div class="ui-footer" th:insert="~{/footer}"></div>
</body>
</html>

0 comments on commit 4281188

Please sign in to comment.