fix: only allow relative urls in redirects

This commit is contained in:
MiniDigger | Martin 2023-03-25 11:02:18 +01:00
parent 1f46cbf4b1
commit c4585f7e41
2 changed files with 24 additions and 12 deletions

View File

@ -17,6 +17,8 @@ import io.papermc.hangar.service.ValidationService;
import io.papermc.hangar.service.internal.auth.SSOService; import io.papermc.hangar.service.internal.auth.SSOService;
import jakarta.servlet.http.Cookie; import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpSession; import jakarta.servlet.http.HttpSession;
import java.net.URI;
import java.net.URISyntaxException;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -51,9 +53,9 @@ public class LoginController extends HangarComponent {
final UserTable fakeUser = this.authenticationService.loginAsFakeUser(); final UserTable fakeUser = this.authenticationService.loginAsFakeUser();
this.tokenService.issueRefreshToken(fakeUser); this.tokenService.issueRefreshToken(fakeUser);
return this.addBaseAndRedirect(returnUrl); return this.addBaseAndRedirect(this.cutoffAbsoluteUrls(returnUrl));
} else { } else {
this.response.addCookie(new Cookie("url", returnUrl)); this.response.addCookie(new Cookie("url", this.cutoffAbsoluteUrls(returnUrl)));
return this.redirectToSso(this.ssoService.getLoginUrl(this.config.getBaseUrl() + "/login")); return this.redirectToSso(this.ssoService.getLoginUrl(this.config.getBaseUrl() + "/login"));
} }
} }
@ -70,7 +72,18 @@ public class LoginController extends HangarComponent {
throw new HangarApiException("nav.user.error.invalidUsername"); throw new HangarApiException("nav.user.error.invalidUsername");
} }
this.tokenService.issueRefreshToken(user); this.tokenService.issueRefreshToken(user);
return this.addBaseAndRedirect(url); return this.addBaseAndRedirect(this.cutoffAbsoluteUrls(url));
}
private String cutoffAbsoluteUrls(final String url) {
try {
if (!new URI(url).isAbsolute()) {
return url;
}
} catch (final URISyntaxException ignored) {
// ignored
}
return "/";
} }
@ResponseBody @ResponseBody
@ -93,16 +106,16 @@ public class LoginController extends HangarComponent {
@GetMapping(path = "/logout", params = "returnUrl") @GetMapping(path = "/logout", params = "returnUrl")
public String logout(@RequestParam(defaultValue = "/?loggedOut") final String returnUrl) { public String logout(@RequestParam(defaultValue = "/?loggedOut") final String returnUrl) {
if (this.config.fakeUser.enabled()) { if (this.config.fakeUser.enabled()) {
this.response.addCookie(new Cookie("url", returnUrl)); this.response.addCookie(new Cookie("url", this.cutoffAbsoluteUrls(returnUrl)));
return "/fake-logout"; return "/fake-logout";
} else { } else {
this.response.addCookie(new Cookie("url", returnUrl)); this.response.addCookie(new Cookie("url", this.cutoffAbsoluteUrls(returnUrl)));
final Optional<HangarPrincipal> principal = this.getOptionalHangarPrincipal(); final Optional<HangarPrincipal> principal = this.getOptionalHangarPrincipal();
if (principal.isPresent()) { if (principal.isPresent()) {
return this.ssoService.getLogoutUrl(this.config.getBaseUrl() + "/handle-logout", principal.get()).url(); return this.ssoService.getLogoutUrl(this.config.getBaseUrl() + "/handle-logout", principal.get()).url();
} else { } else {
this.tokenService.invalidateToken(null); this.tokenService.invalidateToken(null);
return this.addBase(returnUrl); return this.addBase(this.cutoffAbsoluteUrls(returnUrl));
} }
} }
} }
@ -118,7 +131,7 @@ public class LoginController extends HangarComponent {
if (session != null) { if (session != null) {
session.invalidate(); session.invalidate();
} }
return this.addBaseAndRedirect(returnUrl); return this.addBaseAndRedirect(this.cutoffAbsoluteUrls(returnUrl));
} }
@GetMapping(path = "/handle-logout", params = "state") @GetMapping(path = "/handle-logout", params = "state")
@ -143,7 +156,7 @@ public class LoginController extends HangarComponent {
if (session != null) { if (session != null) {
session.invalidate(); session.invalidate();
} }
return this.addBaseAndRedirect(returnUrl); return this.addBaseAndRedirect(this.cutoffAbsoluteUrls(returnUrl));
} }
@GetMapping("/signup") @GetMapping("/signup")
@ -151,7 +164,7 @@ public class LoginController extends HangarComponent {
if (this.config.fakeUser.enabled()) { if (this.config.fakeUser.enabled()) {
throw new HangarApiException("nav.user.error.fakeUserEnabled", "Signup"); throw new HangarApiException("nav.user.error.fakeUserEnabled", "Signup");
} }
return new RedirectView(this.ssoService.getSignupUrl(returnUrl)); return new RedirectView(this.ssoService.getSignupUrl(this.cutoffAbsoluteUrls(returnUrl)));
} }
@PostMapping("/sync") @PostMapping("/sync")

View File

@ -5,7 +5,6 @@ import { useAuthStore } from "~/store/auth";
import { useCookies } from "~/composables/useCookies"; import { useCookies } from "~/composables/useCookies";
import { useInternalApi } from "~/composables/useApi"; import { useInternalApi } from "~/composables/useApi";
import { authLog } from "~/lib/composables/useLog"; import { authLog } from "~/lib/composables/useLog";
import { useConfig } from "~/lib/composables/useConfig";
import { handleRequestError, useRequestEvent } from "#imports"; import { handleRequestError, useRequestEvent } from "#imports";
import { useAxios } from "~/composables/useAxios"; import { useAxios } from "~/composables/useAxios";
import { useNotificationStore } from "~/lib/store/notification"; import { useNotificationStore } from "~/lib/store/notification";
@ -16,12 +15,12 @@ class Auth {
if (redirectUrl.endsWith("?loggedOut")) { if (redirectUrl.endsWith("?loggedOut")) {
redirectUrl = redirectUrl.replace("?loggedOut", ""); redirectUrl = redirectUrl.replace("?loggedOut", "");
} }
return `/login?returnUrl=${useConfig().publicHost}${redirectUrl}`; return `/login?returnUrl=${redirectUrl}`;
} }
async logout() { async logout() {
const result = await useAxios() const result = await useAxios()
.get(`/logout?returnUrl=${useConfig().publicHost}?loggedOut`) .get(`/logout?returnUrl=/?loggedOut`)
.catch((e) => handleRequestError(e)); .catch((e) => handleRequestError(e));
if (result?.status === 200 && result?.data) { if (result?.status === 200 && result?.data) {
location.replace(result?.data); location.replace(result?.data);