Skip to content

Commit 9850627

Browse files
committed
Deal with some edge cases
1 parent 7b39b0b commit 9850627

18 files changed

Lines changed: 147 additions & 37 deletions

File tree

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/auth/WebPermission.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ public enum WebPermission implements Supplier<String>, Lang {
130130

131131
MANAGE_GROUPS("Allows modifying group permissions & Access to /manage/groups page"),
132132
MANAGE_USERS("Allows modifying what users belong to what group"),
133-
MANAGE_THEMES("Allows editing and saving themes via theme-editor");
133+
MANAGE_THEMES("Allows saving or deleting themes via theme-editor for everyone");
134134

135135
private final String description;
136136
private final boolean deprecated;

Plan/common/src/main/java/com/djrapitops/plan/delivery/domain/datatransfer/ThemeDto.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,20 @@
2424
*/
2525
public class ThemeDto {
2626

27+
private String name;
2728
private Map<String, String> colors;
2829
private Map<String, String> nightColors;
2930
private Map<String, Object> useCases;
3031
private Map<String, Object> nightModeUseCases;
3132

33+
public String getName() {
34+
return name;
35+
}
36+
37+
public void setName(String name) {
38+
this.name = name;
39+
}
40+
3241
public Map<String, String> getColors() {
3342
return colors;
3443
}

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/ResponseFactory.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@
3535
import com.djrapitops.plan.settings.config.PlanConfig;
3636
import com.djrapitops.plan.settings.locale.Locale;
3737
import com.djrapitops.plan.settings.locale.lang.ErrorPageLang;
38-
import com.djrapitops.plan.settings.theme.Theme;
3938
import com.djrapitops.plan.storage.database.DBSystem;
4039
import com.djrapitops.plan.storage.database.Database;
4140
import com.djrapitops.plan.storage.database.queries.containers.ContainerFetchQueries;
@@ -75,7 +74,6 @@ public class ResponseFactory {
7574
private final PageFactory pageFactory;
7675
private final Locale locale;
7776
private final DBSystem dbSystem;
78-
private final Theme theme;
7977
private final Lazy<Addresses> addresses;
8078
private final Lazy<BundleAddressCorrection> bundleAddressCorrection;
8179
private final Formatter<Long> httpLastModifiedFormatter;
@@ -88,7 +86,6 @@ public ResponseFactory(
8886
Locale locale,
8987
DBSystem dbSystem,
9088
Formatters formatters,
91-
Theme theme,
9289
Lazy<Addresses> addresses,
9390
Lazy<BundleAddressCorrection> bundleAddressCorrection
9491
) {
@@ -97,7 +94,6 @@ public ResponseFactory(
9794
this.pageFactory = pageFactory;
9895
this.locale = locale;
9996
this.dbSystem = dbSystem;
100-
this.theme = theme;
10197
this.addresses = addresses;
10298

10399
httpLastModifiedFormatter = formatters.httpLastModifiedLong();

Plan/common/src/main/java/com/djrapitops/plan/delivery/webserver/resolver/json/theme/SaveThemeJSONResolver.java

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,16 +112,21 @@ private Response getResponse(@Untrusted String themeName, Request request) {
112112
if (!themeFilePattern.matcher(themeName).matches()) {
113113
throw new BadRequestException("'theme' parameter was invalid");
114114
}
115+
if (themeName.length() > 100) {
116+
throw new BadRequestException("'theme' name was too long");
117+
}
115118

116119
@Untrusted byte[] requestBody = request.getRequestBody();
117120
try {
118121
@Untrusted ThemeDto result = gson.fromJson(new String(requestBody, StandardCharsets.UTF_8), ThemeDto.class);
119122
if (result == null
123+
|| result.getName() == null || result.getName().isEmpty()
124+
|| !themeFilePattern.matcher(result.getName()).matches()
120125
|| result.getColors() == null || result.getColors().isEmpty()
121126
|| result.getNightColors() == null || result.getNightColors().isEmpty()
122127
|| result.getUseCases() == null || result.getUseCases().isEmpty()
123128
|| result.getNightModeUseCases() == null || result.getNightModeUseCases().isEmpty()) {
124-
throw new BadRequestException("Body needs to be a vaild theme file");
129+
throw new BadRequestException("Body needs to be a valid theme file");
125130
}
126131

127132
List<String> issues = new ArrayList<>();
@@ -135,12 +140,14 @@ private Response getResponse(@Untrusted String themeName, Request request) {
135140

136141
java.nio.file.Path themeDirectory = files.getThemeDirectory();
137142
java.nio.file.Path themeFile = themeDirectory.resolve(themeName + ".json");
138-
if (!themeFile.startsWith(themeFile)) {
143+
if (!themeFile.startsWith(themeDirectory)) {
139144
throw new BadRequestException("'theme' parameter was invalid");
140145
}
141146

142147
Files.write(themeFile, gson.toJson(result).getBytes(StandardCharsets.UTF_8), PlanFiles.replaceIfExists());
143148

149+
request.getQuery().get("originalName")
150+
.ifPresent(originalName -> deleteOriginal(themeName, originalName, themeDirectory));
144151
return responseFactory.successResponse();
145152
} catch (JsonSyntaxException e) {
146153
throw new BadRequestException("Request body was invalid json");
@@ -149,6 +156,32 @@ private Response getResponse(@Untrusted String themeName, Request request) {
149156
}
150157
}
151158

159+
private void deleteOriginal(@Untrusted String themeName, @Untrusted String originalName, java.nio.file.Path themeDirectory) {
160+
if (originalName.equals(themeName)) return; // Theme was not renamed.
161+
162+
// All these checks are against trying to delete other themes than the one they're editing.
163+
if (!themeFilePattern.matcher(originalName).matches()) {
164+
throw new BadRequestException("'originalTheme' parameter was invalid, theme could have been renamed by another user");
165+
}
166+
java.nio.file.Path originalThemeFile = themeDirectory.resolve(originalName + ".json");
167+
if (!originalThemeFile.startsWith(themeDirectory)) {
168+
throw new BadRequestException("'originalTheme' parameter was invalid, theme could have been renamed by another user");
169+
}
170+
if (!Files.exists(originalThemeFile)) {
171+
throw new BadRequestException("'originalTheme' parameter was invalid, theme could have been renamed by another user");
172+
}
173+
try {
174+
ThemeDto original = gson.fromJson(Files.readString(originalThemeFile), ThemeDto.class);
175+
if (!original.getName().equals(originalName)) {
176+
throw new BadRequestException("'originalTheme' parameter was invalid, doesn't match original file");
177+
}
178+
179+
Files.delete(originalThemeFile);
180+
} catch (JsonSyntaxException | IOException e) {
181+
throw new BadRequestException("'originalTheme' parameter was invalid, could not parse or delete original file. Delete manually.");
182+
}
183+
}
184+
152185
private void validateUseCases(String prefix, Map<String, Object> useCases, List<String> issues) {
153186
for (Map.Entry<String, Object> entry : useCases.entrySet()) {
154187
String prefixedKey = prefix + entry.getKey();

Plan/common/src/main/java/com/djrapitops/plan/settings/locale/lang/HtmlLang.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -460,8 +460,7 @@ public enum HtmlLang implements Lang {
460460
THEME_EDITOR_MISSING("html.label.themeEditor.missing", "Missing color"),
461461
THEME_EDITOR_REMOVE_OVERRIDE("html.label.themeEditor.removeOverride", "Remove night mode override"),
462462
THEME_EDITOR_NAME("html.label.themeEditor.themeName", "Theme name"),
463-
THEME_EDITOR_INVALID_NAME("html.label.themeEditor.invalidName", "Name should be alphanumerical. Max 100 characters."),
464-
THEME_EDITOR_EXISTING_NAME("html.label.themeEditor.existingName", "Name should be unique."),
463+
THEME_EDITOR_INVALID_NAME("html.label.themeEditor.invalidName", "Name should be alphanumerical and must be unique. Max 100 characters."),
465464
THEME_EDITOR_DEFAULT_NAME("html.label.themeEditor.defaultThemeNameFeedback", "Default theme can not be renamed. You can create a new theme based on Default instead."),
466465
THEME_EDITOR_UNSAVED_CHANGES("html.label.themeEditor.unsavedChanges", "There are unsaved changes - do you still want to leave the page?"),
467466
THEME_EDITOR_SHOW_HISTORY("html.label.themeEditor.showHistory", "Show history"),
@@ -474,12 +473,14 @@ public enum HtmlLang implements Lang {
474473
THEME_EDITOR_UPLOAD_THEME("html.label.themeEditor.uploadTheme", "or Upload a previously downloaded theme:"),
475474

476475
THEME_EDITOR_DELETE_THEMES("html.label.themeEditor.deleteThemes", "Delete themes"),
476+
THEME_EDITOR_CANNOT_DELETE_BUILT_IN("html.label.themeEditor.canNotDeleteBuiltIn", "Note that you can not delete built-in themes, only the modifications you have made to them."),
477477
THEME_EDITOR_THEME_TO_DELETE("html.label.themeEditor.themeToDelete", "Theme to delete"),
478-
THEME_EDITOR_DOWNLOAD_BEFORE_DELETE("html.label.themeEditor.downloadThemeBeforeDeleting", "Would you like to download the theme before deleting it?"),
479-
THEME_EDITOR_CONFIRM_DELETE("html.label.themeEditor.confirmDelete", "I confirm that I want to delete theme called {{theme}} and that this is an irreversible action."),
478+
THEME_EDITOR_DOWNLOAD_BEFORE_DELETE("html.label.themeEditor.downloadThemeBeforeDeleting", "Would you like to download the theme '{{theme}}' before deleting it?"),
479+
THEME_EDITOR_CONFIRM_DELETE("html.label.themeEditor.confirmDelete", "I confirm that I want to delete theme called '{{theme}}' and that this is an irreversible action."),
480480
THEME_EDITOR_DELETE_THEME("html.label.themeEditor.deleteTheme", "Delete theme"),
481481
THEME_EDITOR_DELETE_LOCAL("html.label.themeEditor.deleteLocalTheme", "Delete theme (Only the locally stored one)"),
482482
THEME_EDITOR_NO_PERMISSION_TO_DELETE("html.label.themeEditor.noPermissionToDelete", "You don't have access rights for deleting non-local themes. You may need to delete them from the plugin folder."),
483+
THEME_EDITOR_FAILED_CLONE("html.label.themeEditor.failedToClone", "Failed to clone the original theme {{error}}"),
483484

484485
THEME_EDITOR_CHANGE_RENAME_COLOR("html.label.themeEditor.changes.renameColor", "Renamed {{previous}} to {{name}}, set color to {{color}}"),
485486
THEME_EDITOR_CHANGE_SET_COLOR("html.label.themeEditor.changes.setColor", "Set {{name}} color to {{color}}"),

Plan/common/src/main/resources/assets/plan/themes/default.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"name": "default",
23
"colors": {
34
"black": "#555555",
45
"white": "#ffffff",

Plan/common/src/main/resources/assets/plan/themes/high-contrast.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
{
2+
"name": "high-contrast",
23
"colors": {
34
"black": "#000",
45
"white": "#ffffff",

Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlTest.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,11 @@ static Stream<Arguments> testCases() {
168168
Arguments.of("/v1/gameAllowlistBounces?server=" + TestConstants.SERVER_UUID_STRING, WebPermission.PAGE_SERVER_ALLOWLIST_BOUNCE, 200, 403),
169169
Arguments.of("/v1/theme?theme=default", WebPermission.ACCESS, 200, 200),
170170
Arguments.of("/v1/saveTheme?theme=default", WebPermission.MANAGE_THEMES, 405, 403),
171-
Arguments.of("/v1/deleteTheme?theme=default", WebPermission.MANAGE_THEMES, 405, 403)
171+
Arguments.of("/v1/deleteTheme?theme=default", WebPermission.MANAGE_THEMES, 405, 403),
172+
Arguments.of("/theme-editor", WebPermission.ACCESS_THEME_EDITOR, 200, 403),
173+
Arguments.of("/theme-editor/new", WebPermission.ACCESS_THEME_EDITOR, 200, 403),
174+
Arguments.of("/theme-editor/delete", WebPermission.ACCESS_THEME_EDITOR, 200, 403),
175+
Arguments.of("/theme-editor/default", WebPermission.ACCESS_THEME_EDITOR, 200, 403)
172176
);
173177
}
174178

Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/AccessControlVisibilityTest.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,9 @@ static Stream<Arguments> pageLevelVisibleCases() {
201201
Arguments.arguments(WebPermission.ACCESS_QUERY, "query-button", "query"),
202202
Arguments.arguments(WebPermission.ACCESS_PLAYERS, "players-table", "players"),
203203
Arguments.arguments(WebPermission.ACCESS_ERRORS, "content", "errors"),
204-
Arguments.arguments(WebPermission.ACCESS_THEME_EDITOR, "theme-editor", "theme-editor/default")
204+
Arguments.arguments(WebPermission.ACCESS_THEME_EDITOR, "theme-editor", "theme-editor/default"),
205+
Arguments.arguments(WebPermission.ACCESS_THEME_EDITOR, "theme-editor", "theme-editor/new"),
206+
Arguments.arguments(WebPermission.ACCESS_THEME_EDITOR, "theme-editor", "theme-editor/delete")
205207
// Arguments.arguments(WebPermission.ACCESS_DOCS, "swagger-ui", "docs")
206208
);
207209
}

Plan/common/src/test/java/com/djrapitops/plan/delivery/webserver/resolver/json/theme/SaveThemeJSONResolverTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ void setUp(PlanFiles files) {
6363

6464
@Test
6565
void defaultThemeIsDeemedValid(PlanFiles files) throws IOException {
66+
when(request.getMethod()).thenReturn("POST");
6667
when(request.getQuery()).thenReturn(new URIQuery(Map.of("theme", "default")));
6768
when(request.getRequestBody()).thenReturn(TestResources.getJarResourceAsBytes("/assets/plan/themes/default.json"));
6869
lenient().when(responseFactory.badRequest(any(), any())).thenReturn(mock(Response.class));

0 commit comments

Comments
 (0)