Skip to content

Commit

Permalink
Merge pull request quarkusio#42684 from gsmet/fix-31802
Browse files Browse the repository at this point in the history
Encode URL in OIDC cookie
  • Loading branch information
sberyozkin authored Sep 1, 2024
2 parents 2c1cdd9 + 65c3611 commit b702e31
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ public class CodeAuthenticationMechanism extends AbstractOidcAuthenticationMecha
static final String EQ = "=";
static final String COOKIE_DELIM = "|";
static final Pattern COOKIE_PATTERN = Pattern.compile("\\" + COOKIE_DELIM);
static final String STATE_COOKIE_RESTORE_PATH = "restore-path";
static final Uni<Void> VOID_UNI = Uni.createFrom().voidItem();
static final String NO_OIDC_COOKIES_AVAILABLE = "no_oidc_cookies";
static final String HTTP_SCHEME = "http";
Expand Down Expand Up @@ -940,21 +939,24 @@ private CodeAuthenticationStateBean getCodeAuthenticationBean(String[] parsedSta
if (parsedStateCookieValue.length == 2) {
CodeAuthenticationStateBean bean = new CodeAuthenticationStateBean();
Authentication authentication = configContext.oidcConfig.authentication;

boolean pkceRequired = authentication.pkceRequired.orElse(false);
if (!pkceRequired && !authentication.nonceRequired) {
bean.setRestorePath(parsedStateCookieValue[1]);
JsonObject json = new JsonObject(OidcUtils.base64UrlDecode(parsedStateCookieValue[1]));
bean.setRestorePath(json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH));
return bean;
}

JsonObject json = null;

try {
json = OidcUtils.decryptJson(parsedStateCookieValue[1], configContext.getStateEncryptionKey());
} catch (Exception ex) {
LOG.errorf("State cookie value can not be decrypted for the %s tenant",
configContext.oidcConfig.tenantId.get());
throw new AuthenticationCompletionException(ex);
}
bean.setRestorePath(json.getString(STATE_COOKIE_RESTORE_PATH));
bean.setRestorePath(json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH));
bean.setCodeVerifier(json.getString(OidcConstants.PKCE_CODE_VERIFIER));
bean.setNonce(json.getString(OidcConstants.NONCE));
return bean;
Expand Down Expand Up @@ -1161,16 +1163,17 @@ private boolean isRestorePath(Authentication auth) {
}

private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue, TenantConfigContext configContext) {
JsonObject json = new JsonObject();

if (extraStateValue.getCodeVerifier() != null || extraStateValue.getNonce() != null) {
JsonObject json = new JsonObject();
if (extraStateValue.getCodeVerifier() != null) {
json.put(OidcConstants.PKCE_CODE_VERIFIER, extraStateValue.getCodeVerifier());
}
if (extraStateValue.getNonce() != null) {
json.put(OidcConstants.NONCE, extraStateValue.getNonce());
}
if (extraStateValue.getRestorePath() != null) {
json.put(STATE_COOKIE_RESTORE_PATH, extraStateValue.getRestorePath());
json.put(OidcUtils.STATE_COOKIE_RESTORE_PATH, extraStateValue.getRestorePath());
}
try {
return OidcUtils.encryptJson(json, configContext.getStateEncryptionKey());
Expand All @@ -1179,7 +1182,9 @@ private String encodeExtraStateValue(CodeAuthenticationStateBean extraStateValue
throw new AuthenticationCompletionException(ex);
}
} else {
return extraStateValue.getRestorePath();
json.put(OidcUtils.STATE_COOKIE_RESTORE_PATH, extraStateValue.getRestorePath());

return Base64.getUrlEncoder().withoutPadding().encodeToString(json.encode().getBytes(StandardCharsets.UTF_8));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
public final class OidcUtils {
private static final Logger LOG = Logger.getLogger(OidcUtils.class);

public static final String STATE_COOKIE_RESTORE_PATH = "restore-path";
public static final String CONFIG_METADATA_ATTRIBUTE = "configuration-metadata";
public static final String USER_INFO_ATTRIBUTE = "userinfo";
public static final String INTROSPECTION_ATTRIBUTE = "introspection";
Expand Down Expand Up @@ -252,7 +253,7 @@ private static JsonObject decodeAsJsonObject(String encodedContent) {
}
}

private static String base64UrlDecode(String encodedContent) {
public static String base64UrlDecode(String encodedContent) {
return new String(Base64.getUrlDecoder().decode(encodedContent), StandardCharsets.UTF_8);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ quarkus.oidc.tenant-split-tokens.token-state-manager.encryption-secret=eUk1p7UB3
quarkus.oidc.tenant-split-tokens.application-type=web-app
quarkus.oidc.tenant-split-tokens.authentication.cookie-same-site=strict

quarkus.http.auth.permission.roles1.paths=/index.html
quarkus.http.auth.permission.roles1.paths=/index.html,/index.html;/checktterer
quarkus.http.auth.permission.roles1.policy=authenticated

quarkus.http.auth.permission.logout.paths=/tenant-logout
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,29 @@ public void testAccessTokenInjection() throws IOException {
}
}

@Test
public void testInvalidPath() throws IOException {
try (final WebClient webClient = createWebClient()) {
HtmlPage page = webClient.getPage("http://localhost:8081/index.html;/checktterer");
assertEquals("/index.html;/checktterer", getStateCookieSavedPath(webClient, null));

assertEquals("Sign in to quarkus", page.getTitleText());

HtmlForm loginForm = page.getForms().get(0);

loginForm.getInputByName("username").setValueAttribute("alice");
loginForm.getInputByName("password").setValueAttribute("alice");

try {
page = loginForm.getInputByName("login").click();
} catch (FailingHttpStatusCodeException ex) {
assertEquals(404, ex.getStatusCode());
}

webClient.getCookieManager().clearCookies();
}
}

@Test
public void testAccessAndRefreshTokenInjection() throws IOException {
try (final WebClient webClient = createWebClient()) {
Expand Down Expand Up @@ -1386,8 +1409,8 @@ public void testAccessAndRefreshTokenInjectionWithoutIndexHtmlAndListenerMultiTa
@Test
public void testAccessAndRefreshTokenInjectionWithQuery() throws Exception {
try (final WebClient webClient = createWebClient()) {
HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh-query?a=aValue");
assertEquals("/web-app/refresh-query?a=aValue", getStateCookieSavedPath(webClient, null));
HtmlPage page = webClient.getPage("http://localhost:8081/web-app/refresh-query?a=aValue%");
assertEquals("/web-app/refresh-query?a=aValue%25", getStateCookieSavedPath(webClient, null));

assertEquals("Sign in to quarkus", page.getTitleText());

Expand All @@ -1398,7 +1421,8 @@ public void testAccessAndRefreshTokenInjectionWithQuery() throws Exception {

page = loginForm.getInputByName("login").click();

assertEquals("RT injected:aValue", page.getBody().asNormalizedText());
// Query parameters are decoded by the time they reach the JAX-RS endpoint
assertEquals("RT injected:aValue%", page.getBody().asNormalizedText());
webClient.getCookieManager().clearCookies();
}
}
Expand Down Expand Up @@ -1561,12 +1585,17 @@ private String getStateCookieStateParam(Cookie stateCookie) {

private String getStateCookieSavedPath(WebClient webClient, String tenantId) {
String[] parts = getStateCookie(webClient, tenantId).getValue().split("\\|");
return parts.length == 2 ? parts[1] : null;
return parts.length == 2 ? getSavedPathFromJson(parts[1]) : null;
}

private String getStateCookieSavedPath(Cookie stateCookie) {
String[] parts = stateCookie.getValue().split("\\|");
return parts.length == 2 ? parts[1] : null;
return parts.length == 2 ? getSavedPathFromJson(parts[1]) : null;
}

private String getSavedPathFromJson(String value) {
JsonObject json = new JsonObject(OidcUtils.base64UrlDecode(value));
return json.getString(OidcUtils.STATE_COOKIE_RESTORE_PATH);
}

private Cookie getSessionCookie(WebClient webClient, String tenantId) {
Expand Down

0 comments on commit b702e31

Please sign in to comment.