Cors modifications for UserInfo endpoint
Closes #26782 Signed-off-by: rmartinc <rmartinc@redhat.com>
This commit is contained in:
parent
67f6f2f657
commit
bc82929e3a
6 changed files with 53 additions and 2 deletions
|
@ -83,6 +83,8 @@ public interface Cors extends Provider {
|
|||
|
||||
public Cors exposedHeaders(String... exposedHeaders);
|
||||
|
||||
public Cors addExposedHeaders(String... exposedHeaders);
|
||||
|
||||
public Response build();
|
||||
|
||||
public boolean build(HttpResponse response);
|
||||
|
|
|
@ -223,7 +223,7 @@ public class UserInfoEndpoint {
|
|||
throw error.invalidToken("Client disabled");
|
||||
}
|
||||
|
||||
UserSessionModel userSession = UserSessionUtil.findValidSession(session, realm, token, event, clientModel);
|
||||
UserSessionModel userSession = UserSessionUtil.findValidSession(session, realm, token, event, clientModel, error);
|
||||
|
||||
UserModel userModel = userSession.getUser();
|
||||
if (userModel == null) {
|
||||
|
|
|
@ -121,6 +121,16 @@ public class DefaultCors implements Cors {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cors addExposedHeaders(String... exposedHeaders) {
|
||||
if (this.exposedHeaders == null) {
|
||||
this.exposedHeaders(exposedHeaders);
|
||||
} else {
|
||||
this.exposedHeaders.addAll(Arrays.asList(exposedHeaders));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response build() {
|
||||
if (builder == null) {
|
||||
|
|
|
@ -30,6 +30,11 @@ public class UserSessionUtil {
|
|||
|
||||
public static UserSessionModel findValidSession(KeycloakSession session, RealmModel realm, AccessToken token, EventBuilder event, ClientModel client) {
|
||||
OAuth2Error error = new OAuth2Error().json(false).realm(realm);
|
||||
return findValidSession(session, realm, token, event, client, error);
|
||||
}
|
||||
|
||||
public static UserSessionModel findValidSession(KeycloakSession session, RealmModel realm,
|
||||
AccessToken token, EventBuilder event, ClientModel client, OAuth2Error error) {
|
||||
if (token.getSessionState() == null) {
|
||||
return createTransientSessionForClient(session, realm, token, client, event);
|
||||
}
|
||||
|
|
|
@ -125,7 +125,6 @@ public class OAuth2Error {
|
|||
|
||||
try {
|
||||
Constructor<? extends WebApplicationException> constructor = clazz.getConstructor(new Class[] { Response.class });
|
||||
cors.ifPresent(_cors -> { _cors.build(builder::header); });
|
||||
|
||||
if (json) {
|
||||
OAuth2ErrorRepresentation errorRep = new OAuth2ErrorRepresentation(error, errorDescription);
|
||||
|
@ -137,8 +136,10 @@ public class OAuth2Error {
|
|||
bearer.setErrorDescription(errorDescription);
|
||||
WWWAuthenticate wwwAuthenticate = new WWWAuthenticate(bearer);
|
||||
wwwAuthenticate.build(builder::header);
|
||||
cors.ifPresent(_cors -> _cors.addExposedHeaders(WWW_AUTHENTICATE));
|
||||
builder.entity("").type(MediaType.TEXT_PLAIN_UTF_8_TYPE);
|
||||
}
|
||||
cors.ifPresent(_cors -> { _cors.build(builder::header); });
|
||||
|
||||
return constructor.newInstance(builder.build());
|
||||
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
|
||||
|
|
|
@ -2,6 +2,7 @@ package org.keycloak.testsuite.oauth;
|
|||
|
||||
import org.jboss.resteasy.client.jaxrs.ResteasyClient;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.representations.AccessToken;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.testsuite.AbstractKeycloakTest;
|
||||
import org.keycloak.testsuite.util.AdminClientUtil;
|
||||
|
@ -14,6 +15,8 @@ import jakarta.ws.rs.core.HttpHeaders;
|
|||
import jakarta.ws.rs.core.Response;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.containsString;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||
|
@ -110,9 +113,39 @@ public class UserInfoEndpointCorsTest extends AbstractKeycloakTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void userInfoCorsInvalidSession() throws Exception {
|
||||
oauth.realm("test");
|
||||
oauth.clientId("test-app2");
|
||||
oauth.redirectUri(VALID_CORS_URL + "/realms/master/app");
|
||||
|
||||
OAuthClient.AccessTokenResponse accessTokenResponse = oauth.doGrantAccessTokenRequest(null, "test-user@localhost", "password");
|
||||
|
||||
// remove the session in keycloak
|
||||
AccessToken accessToken = oauth.verifyToken(accessTokenResponse.getAccessToken());
|
||||
adminClient.realm("test").deleteSession(accessToken.getSessionState());
|
||||
|
||||
try (ResteasyClient resteasyClient = AdminClientUtil.createResteasyClient()) {
|
||||
WebTarget userInfoTarget = UserInfoClientUtil.getUserInfoWebTarget(resteasyClient);
|
||||
Response userInfoResponse = userInfoTarget.request()
|
||||
.header(HttpHeaders.AUTHORIZATION, "bearer " + accessTokenResponse.getAccessToken())
|
||||
.header("Origin", VALID_CORS_URL) // manually trigger CORS handling
|
||||
.get();
|
||||
|
||||
// We should have errorResponse, but CORS headers should be there as origin was valid
|
||||
assertEquals(Response.Status.UNAUTHORIZED.getStatusCode(), userInfoResponse.getStatus());
|
||||
|
||||
assertCors(userInfoResponse);
|
||||
}
|
||||
}
|
||||
|
||||
private static void assertCors(Response response) {
|
||||
assertEquals("true", response.getHeaders().getFirst("Access-Control-Allow-Credentials"));
|
||||
assertEquals(VALID_CORS_URL, response.getHeaders().getFirst("Access-Control-Allow-Origin"));
|
||||
assertThat((String) response.getHeaders().getFirst("Access-Control-Expose-Headers"), containsString("Access-Control-Allow-Methods"));
|
||||
if (response.getStatus() == Response.Status.UNAUTHORIZED.getStatusCode()) {
|
||||
assertThat((String) response.getHeaders().getFirst("Access-Control-Expose-Headers"), containsString("WWW-Authenticate"));
|
||||
}
|
||||
response.close();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue