Sanitize user input in logging statements

This commit is contained in:
2026-01-05 22:12:50 +01:00
parent 3cb33c6f97
commit c10f47c2cb
4 changed files with 105 additions and 43 deletions

View File

@@ -12,6 +12,7 @@ import dev.kruhlmann.imgfloat.model.PlaybackRequest;
import dev.kruhlmann.imgfloat.model.TransformRequest; import dev.kruhlmann.imgfloat.model.TransformRequest;
import dev.kruhlmann.imgfloat.model.TwitchUserProfile; import dev.kruhlmann.imgfloat.model.TwitchUserProfile;
import dev.kruhlmann.imgfloat.model.VisibilityRequest; import dev.kruhlmann.imgfloat.model.VisibilityRequest;
import dev.kruhlmann.imgfloat.util.LogSanitizer;
import dev.kruhlmann.imgfloat.service.AuthorizationService; import dev.kruhlmann.imgfloat.service.AuthorizationService;
import dev.kruhlmann.imgfloat.service.ChannelDirectoryService; import dev.kruhlmann.imgfloat.service.ChannelDirectoryService;
import dev.kruhlmann.imgfloat.service.TwitchUserLookupService; import dev.kruhlmann.imgfloat.service.TwitchUserLookupService;
@@ -76,11 +77,14 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
String logRequestUsername = LogSanitizer.sanitize(request.getUsername());
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
LOG.info("User {} adding admin {} to {}", sessionUsername, request.getUsername(), broadcaster.replaceAll("[\n\r]", "_")); LOG.info("User {} adding admin {} to {}", logSessionUsername, logRequestUsername, logBroadcaster);
boolean added = channelDirectoryService.addAdmin(broadcaster, request.getUsername()); boolean added = channelDirectoryService.addAdmin(broadcaster, request.getUsername());
if (!added) { if (!added) {
LOG.info("User {} already admin for {} or could not be added", request.getUsername(), broadcaster.replaceAll("[\n\r]", "_")); LOG.info("User {} already admin for {} or could not be added", logRequestUsername, logBroadcaster);
} }
return ResponseEntity.ok().body(added); return ResponseEntity.ok().body(added);
} }
@@ -92,8 +96,10 @@ public class ChannelApiController {
HttpServletRequest request HttpServletRequest request
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
LOG.debug("Listing admins for {} by {}", broadcaster, sessionUsername); LOG.debug("Listing admins for {} by {}", logBroadcaster, logSessionUsername);
var channel = channelDirectoryService.getOrCreateChannel(broadcaster); var channel = channelDirectoryService.getOrCreateChannel(broadcaster);
List<String> admins = channel.getAdmins().stream().sorted(Comparator.naturalOrder()).toList(); List<String> admins = channel.getAdmins().stream().sorted(Comparator.naturalOrder()).toList();
OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, null, request); OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, null, request);
@@ -115,16 +121,18 @@ public class ChannelApiController {
HttpServletRequest request HttpServletRequest request
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
LOG.debug("Listing admin suggestions for {} by {}", broadcaster, sessionUsername); LOG.debug("Listing admin suggestions for {} by {}", logBroadcaster, logSessionUsername);
var channel = channelDirectoryService.getOrCreateChannel(broadcaster); var channel = channelDirectoryService.getOrCreateChannel(broadcaster);
OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, null, request); OAuth2AuthorizedClient authorizedClient = resolveAuthorizedClient(oauthToken, null, request);
if (authorizedClient == null) { if (authorizedClient == null) {
LOG.warn( LOG.warn(
"No authorized Twitch client found for {} while fetching admin suggestions for {}", "No authorized Twitch client found for {} while fetching admin suggestions for {}",
sessionUsername, logSessionUsername,
broadcaster.replaceAll("[\n\r]", "_") logBroadcaster
); );
return List.of(); return List.of();
} }
@@ -139,8 +147,8 @@ public class ChannelApiController {
if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) { if (accessToken == null || accessToken.isBlank() || clientId == null || clientId.isBlank()) {
LOG.warn( LOG.warn(
"Missing Twitch credentials for {} while fetching admin suggestions for {}", "Missing Twitch credentials for {} while fetching admin suggestions for {}",
sessionUsername, logSessionUsername,
broadcaster logBroadcaster
); );
return List.of(); return List.of();
} }
@@ -154,8 +162,11 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
String logUsername = LogSanitizer.sanitize(username);
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
LOG.info("User {} removing admin {} from {}", sessionUsername, username, broadcaster); LOG.info("User {} removing admin {} from {}", logSessionUsername, logUsername, logBroadcaster);
boolean removed = channelDirectoryService.removeAdmin(broadcaster, username); boolean removed = channelDirectoryService.removeAdmin(broadcaster, username);
return ResponseEntity.ok().body(removed); return ResponseEntity.ok().body(removed);
} }
@@ -182,11 +193,13 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername); authorizationService.userMatchesSessionUsernameOrThrowHttpError(broadcaster, sessionUsername);
LOG.info( LOG.info(
"Updating canvas for {} by {}: {}x{}", "Updating canvas for {} by {}: {}x{}",
broadcaster.replaceAll("[\n\r]", "_"), logBroadcaster,
sessionUsername, logSessionUsername,
request.getWidth(), request.getWidth(),
request.getHeight() request.getHeight()
); );
@@ -200,22 +213,25 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError( authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError(
broadcaster, broadcaster,
sessionUsername sessionUsername
); );
if (file == null || file.isEmpty()) { if (file == null || file.isEmpty()) {
LOG.warn("User {} attempted to upload empty file to {}", sessionUsername, broadcaster); LOG.warn("User {} attempted to upload empty file to {}", logSessionUsername, logBroadcaster);
throw new ResponseStatusException(BAD_REQUEST, "Asset file is required"); throw new ResponseStatusException(BAD_REQUEST, "Asset file is required");
} }
try { try {
LOG.info("User {} uploading asset {} to {}", sessionUsername, file.getOriginalFilename(), broadcaster); String logOriginalFilename = LogSanitizer.sanitize(file.getOriginalFilename());
LOG.info("User {} uploading asset {} to {}", logSessionUsername, logOriginalFilename, logBroadcaster);
return channelDirectoryService return channelDirectoryService
.createAsset(broadcaster, file) .createAsset(broadcaster, file)
.map(ResponseEntity::ok) .map(ResponseEntity::ok)
.orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Unable to read image")); .orElseThrow(() -> new ResponseStatusException(BAD_REQUEST, "Unable to read image"));
} catch (IOException e) { } catch (IOException e) {
LOG.error("Failed to process asset upload for {} by {}", broadcaster, sessionUsername, e); LOG.error("Failed to process asset upload for {} by {}", logBroadcaster, logSessionUsername, e);
throw new ResponseStatusException(BAD_REQUEST, "Failed to process image", e); throw new ResponseStatusException(BAD_REQUEST, "Failed to process image", e);
} }
} }
@@ -228,16 +244,19 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logAssetId = LogSanitizer.sanitize(assetId);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError( authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError(
broadcaster, broadcaster,
sessionUsername sessionUsername
); );
LOG.debug("Applying transform to asset {} on {} by {}", assetId, broadcaster, sessionUsername); LOG.debug("Applying transform to asset {} on {} by {}", logAssetId, logBroadcaster, logSessionUsername);
return channelDirectoryService return channelDirectoryService
.updateTransform(broadcaster, assetId, request) .updateTransform(broadcaster, assetId, request)
.map(ResponseEntity::ok) .map(ResponseEntity::ok)
.orElseThrow(() -> { .orElseThrow(() -> {
LOG.warn("Transform request for missing asset {} on {} by {}", assetId, broadcaster, sessionUsername); LOG.warn("Transform request for missing asset {} on {} by {}", logAssetId, logBroadcaster, logSessionUsername);
return createAsset404(); return createAsset404();
}); });
} }
@@ -250,11 +269,14 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logAssetId = LogSanitizer.sanitize(assetId);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError( authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError(
broadcaster, broadcaster,
sessionUsername sessionUsername
); );
LOG.info("Triggering playback for asset {} on {} by {}", assetId, broadcaster, sessionUsername); LOG.info("Triggering playback for asset {} on {} by {}", logAssetId, logBroadcaster, logSessionUsername);
return channelDirectoryService return channelDirectoryService
.triggerPlayback(broadcaster, assetId, request) .triggerPlayback(broadcaster, assetId, request)
.map(ResponseEntity::ok) .map(ResponseEntity::ok)
@@ -269,15 +291,18 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logAssetId = LogSanitizer.sanitize(assetId);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError( authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError(
broadcaster, broadcaster,
sessionUsername sessionUsername
); );
LOG.info( LOG.info(
"Updating visibility for asset {} on {} by {} to hidden={} ", "Updating visibility for asset {} on {} by {} to hidden={} ",
assetId, logAssetId,
broadcaster.replaceAll("[\n\r]", "_"), logBroadcaster,
sessionUsername, logSessionUsername,
request.isHidden() request.isHidden()
); );
return channelDirectoryService return channelDirectoryService
@@ -286,9 +311,9 @@ public class ChannelApiController {
.orElseThrow(() -> { .orElseThrow(() -> {
LOG.warn( LOG.warn(
"Visibility update for missing asset {} on {} by {}", "Visibility update for missing asset {} on {} by {}",
assetId.replaceAll("[\n\r]", "_"), logAssetId,
broadcaster.replaceAll("[\n\r]", "_"), logBroadcaster,
sessionUsername logSessionUsername
); );
return createAsset404(); return createAsset404();
}); });
@@ -299,7 +324,9 @@ public class ChannelApiController {
@PathVariable("broadcaster") String broadcaster, @PathVariable("broadcaster") String broadcaster,
@PathVariable("assetId") String assetId @PathVariable("assetId") String assetId
) { ) {
LOG.debug("Serving asset {} for broadcaster {}", assetId, broadcaster); String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logAssetId = LogSanitizer.sanitize(assetId);
LOG.debug("Serving asset {} for broadcaster {}", logAssetId, logBroadcaster);
return channelDirectoryService return channelDirectoryService
.getAssetContent(assetId) .getAssetContent(assetId)
.map((content) -> .map((content) ->
@@ -317,7 +344,9 @@ public class ChannelApiController {
@PathVariable("broadcaster") String broadcaster, @PathVariable("broadcaster") String broadcaster,
@PathVariable("assetId") String assetId @PathVariable("assetId") String assetId
) { ) {
LOG.debug("Serving preview for asset {} for broadcaster {}", assetId, broadcaster); String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logAssetId = LogSanitizer.sanitize(assetId);
LOG.debug("Serving preview for asset {} for broadcaster {}", logAssetId, logBroadcaster);
return channelDirectoryService return channelDirectoryService
.getAssetPreview(assetId, true) .getAssetPreview(assetId, true)
.map((content) -> .map((content) ->
@@ -346,16 +375,19 @@ public class ChannelApiController {
OAuth2AuthenticationToken oauthToken OAuth2AuthenticationToken oauthToken
) { ) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logAssetId = LogSanitizer.sanitize(assetId);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError( authorizationService.userIsBroadcasterOrChannelAdminForBroadcasterOrThrowHttpError(
broadcaster, broadcaster,
sessionUsername sessionUsername
); );
boolean removed = channelDirectoryService.deleteAsset(assetId); boolean removed = channelDirectoryService.deleteAsset(assetId);
if (!removed) { if (!removed) {
LOG.warn("Attempt to delete missing asset {} on {} by {}", assetId, broadcaster, sessionUsername); LOG.warn("Attempt to delete missing asset {} on {} by {}", logAssetId, logBroadcaster, logSessionUsername);
throw createAsset404(); throw createAsset404();
} }
LOG.info("Asset {} deleted on {} by {}", assetId, broadcaster, sessionUsername); LOG.info("Asset {} deleted on {} by {}", logAssetId, logBroadcaster, logSessionUsername);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }

View File

@@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import dev.kruhlmann.imgfloat.model.OauthSessionUser; import dev.kruhlmann.imgfloat.model.OauthSessionUser;
import dev.kruhlmann.imgfloat.model.Settings; import dev.kruhlmann.imgfloat.model.Settings;
import dev.kruhlmann.imgfloat.util.LogSanitizer;
import dev.kruhlmann.imgfloat.service.AuthorizationService; import dev.kruhlmann.imgfloat.service.AuthorizationService;
import dev.kruhlmann.imgfloat.service.ChannelDirectoryService; import dev.kruhlmann.imgfloat.service.ChannelDirectoryService;
import dev.kruhlmann.imgfloat.service.SettingsService; import dev.kruhlmann.imgfloat.service.SettingsService;
@@ -49,7 +50,8 @@ public class ViewController {
public String home(OAuth2AuthenticationToken oauthToken, Model model) { public String home(OAuth2AuthenticationToken oauthToken, Model model) {
if (oauthToken != null) { if (oauthToken != null) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
LOG.info("Rendering dashboard for {}", sessionUsername); String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
LOG.info("Rendering dashboard for {}", logSessionUsername);
model.addAttribute("username", sessionUsername); model.addAttribute("username", sessionUsername);
model.addAttribute("channel", sessionUsername); model.addAttribute("channel", sessionUsername);
model.addAttribute("adminChannels", channelDirectoryService.adminChannelsFor(sessionUsername)); model.addAttribute("adminChannels", channelDirectoryService.adminChannelsFor(sessionUsername));
@@ -70,7 +72,8 @@ public class ViewController {
public String settingsView(OAuth2AuthenticationToken oauthToken, Model model) { public String settingsView(OAuth2AuthenticationToken oauthToken, Model model) {
String sessionUsername = OauthSessionUser.from(oauthToken).login(); String sessionUsername = OauthSessionUser.from(oauthToken).login();
authorizationService.userIsSystemAdministratorOrThrowHttpError(sessionUsername); authorizationService.userIsSystemAdministratorOrThrowHttpError(sessionUsername);
LOG.info("Rendering settings for {}", sessionUsername); String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
LOG.info("Rendering settings for {}", logSessionUsername);
Settings settings = settingsService.get(); Settings settings = settingsService.get();
try { try {
model.addAttribute("settingsJson", objectMapper.writeValueAsString(settings)); model.addAttribute("settingsJson", objectMapper.writeValueAsString(settings));
@@ -92,10 +95,12 @@ public class ViewController {
broadcaster, broadcaster,
sessionUsername sessionUsername
); );
String logBroadcaster = LogSanitizer.sanitize(broadcaster);
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
LOG.info( LOG.info(
"Rendering admin console for {} (requested by {})", "Rendering admin console for {} (requested by {})",
broadcaster.replaceAll("[\n\r]", "_"), logBroadcaster,
sessionUsername logSessionUsername
); );
Settings settings = settingsService.get(); Settings settings = settingsService.get();
model.addAttribute("broadcaster", broadcaster.toLowerCase()); model.addAttribute("broadcaster", broadcaster.toLowerCase());
@@ -116,7 +121,8 @@ public class ViewController {
@org.springframework.web.bind.annotation.PathVariable("broadcaster") String broadcaster, @org.springframework.web.bind.annotation.PathVariable("broadcaster") String broadcaster,
Model model Model model
) { ) {
LOG.debug("Rendering broadcast overlay for {}", broadcaster); String logBroadcaster = LogSanitizer.sanitize(broadcaster);
LOG.debug("Rendering broadcast overlay for {}", logBroadcaster);
model.addAttribute("broadcaster", broadcaster.toLowerCase()); model.addAttribute("broadcaster", broadcaster.toLowerCase());
return "broadcast"; return "broadcast";
} }

View File

@@ -7,6 +7,7 @@ import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import dev.kruhlmann.imgfloat.model.OauthSessionUser; import dev.kruhlmann.imgfloat.model.OauthSessionUser;
import dev.kruhlmann.imgfloat.service.ChannelDirectoryService; import dev.kruhlmann.imgfloat.service.ChannelDirectoryService;
import dev.kruhlmann.imgfloat.service.SystemAdministratorService; import dev.kruhlmann.imgfloat.service.SystemAdministratorService;
import dev.kruhlmann.imgfloat.util.LogSanitizer;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -33,15 +34,17 @@ public class AuthorizationService {
LOG.warn("Access denied for broadcaster-only action by unauthenticated user"); LOG.warn("Access denied for broadcaster-only action by unauthenticated user");
throw new ResponseStatusException(UNAUTHORIZED, "You must be logged in to manage your channel"); throw new ResponseStatusException(UNAUTHORIZED, "You must be logged in to manage your channel");
} }
String logSessionUsername = LogSanitizer.sanitize(sessionUsername);
if (submittedUsername == null) { if (submittedUsername == null) {
LOG.warn("User match with oauth token failed: submitted username is null for user {}", sessionUsername); LOG.warn("User match with oauth token failed: submitted username is null for user {}", logSessionUsername);
throw new ResponseStatusException(NOT_FOUND, "You can only manage your own channel"); throw new ResponseStatusException(NOT_FOUND, "You can only manage your own channel");
} }
String logSubmittedUsername = LogSanitizer.sanitize(submittedUsername);
if (!sessionUsername.equals(submittedUsername)) { if (!sessionUsername.equals(submittedUsername)) {
LOG.warn( LOG.warn(
"User match with oauth token failed: session user {} does not match submitted user {}", "User match with oauth token failed: session user {} does not match submitted user {}",
sessionUsername, logSessionUsername,
submittedUsername logSubmittedUsername
); );
throw new ResponseStatusException(FORBIDDEN, "You are not this user"); throw new ResponseStatusException(FORBIDDEN, "You are not this user");
} }
@@ -51,12 +54,12 @@ public class AuthorizationService {
String broadcaster, String broadcaster,
String sessionUsername String sessionUsername
) { ) {
broadcaster = broadcaster.replaceAll("[\n\r]", "_"); String logBroadcaster = LogSanitizer.sanitize(broadcaster);
if (!userIsBroadcasterOrChannelAdminForBroadcaster(broadcaster, sessionUsername)) { if (!userIsBroadcasterOrChannelAdminForBroadcaster(logBroadcaster, sessionUsername)) {
LOG.warn( LOG.warn(
"Access denied for broadcaster/admin-only action by user {} on broadcaster {}", "Access denied for broadcaster/admin-only action by user {} on broadcaster {}",
sessionUsername, LogSanitizer.sanitize(sessionUsername),
broadcaster logBroadcaster
); );
throw new ResponseStatusException(FORBIDDEN, "You do not have permission to manage this channel"); throw new ResponseStatusException(FORBIDDEN, "You do not have permission to manage this channel");
} }
@@ -64,14 +67,18 @@ public class AuthorizationService {
public void userIsSystemAdministratorOrThrowHttpError(String sessionUsername) { public void userIsSystemAdministratorOrThrowHttpError(String sessionUsername) {
if (!userIsSystemAdministrator(sessionUsername)) { if (!userIsSystemAdministrator(sessionUsername)) {
LOG.warn("Access denied for system administrator-only action by user {}", sessionUsername); LOG.warn("Access denied for system administrator-only action by user {}", LogSanitizer.sanitize(sessionUsername));
throw new ResponseStatusException(FORBIDDEN, "You do not have permission to perform this action"); throw new ResponseStatusException(FORBIDDEN, "You do not have permission to perform this action");
} }
} }
public boolean userIsBroadcaster(String a, String b) { public boolean userIsBroadcaster(String a, String b) {
if (a == null || b == null) { if (a == null || b == null) {
LOG.warn("Broadcaster check failed: one or both usernames are null (a: {}, b: {})", a, b); LOG.warn(
"Broadcaster check failed: one or both usernames are null (a: {}, b: {})",
LogSanitizer.sanitize(a),
LogSanitizer.sanitize(b)
);
return false; return false;
} }
return a.equals(b); return a.equals(b);
@@ -81,8 +88,8 @@ public class AuthorizationService {
if (sessionUsername == null || broadcaster == null) { if (sessionUsername == null || broadcaster == null) {
LOG.warn( LOG.warn(
"Channel admin check failed: broadcaster or session username is null (broadcaster: {}, sessionUsername: {})", "Channel admin check failed: broadcaster or session username is null (broadcaster: {}, sessionUsername: {})",
broadcaster, LogSanitizer.sanitize(broadcaster),
sessionUsername LogSanitizer.sanitize(sessionUsername)
); );
return false; return false;
} }

View File

@@ -0,0 +1,17 @@
package dev.kruhlmann.imgfloat.util;
import java.util.regex.Pattern;
public final class LogSanitizer {
private static final Pattern NEWLINE_CHARACTERS = Pattern.compile("[\\r\\n]+");
private LogSanitizer() {}
public static String sanitize(String value) {
if (value == null) {
return null;
}
return NEWLINE_CHARACTERS.matcher(value).replaceAll("_");
}
}