KEYCLOAK-6068 Fix preflight request on admin endpoints

This commit is contained in:
stianst 2017-12-20 08:50:36 +01:00 committed by Stian Thorgersen
parent 7d2d7e41d9
commit e96c6a4bcb
3 changed files with 96 additions and 16 deletions

View file

@ -0,0 +1,32 @@
package org.keycloak.services.resources.admin;
import org.jboss.resteasy.spi.HttpRequest;
import org.keycloak.services.resources.Cors;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.Path;
import javax.ws.rs.core.Response;
/**
* Created by st on 21/03/17.
*/
public class AdminCorsPreflightService {
private HttpRequest request;
public AdminCorsPreflightService(HttpRequest request) {
this.request = request;
}
/**
* CORS preflight
*
* @return
*/
@Path("{any:.*}")
@OPTIONS
public Response preflight() {
return Cors.add(request, Response.ok()).preflight().allowedMethods("GET", "PUT", "POST", "DELETE").auth().build();
}
}

View file

@ -19,14 +19,12 @@ package org.keycloak.services.resources.admin;
import org.jboss.logging.Logger; import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest; import org.jboss.resteasy.spi.HttpRequest;
import org.jboss.resteasy.spi.HttpResponse; import org.jboss.resteasy.spi.HttpResponse;
import org.jboss.resteasy.spi.NoLogWebApplicationException;
import org.jboss.resteasy.spi.NotFoundException; import org.jboss.resteasy.spi.NotFoundException;
import org.jboss.resteasy.spi.ResteasyProviderFactory; import org.jboss.resteasy.spi.ResteasyProviderFactory;
import org.jboss.resteasy.spi.UnauthorizedException; import org.jboss.resteasy.spi.UnauthorizedException;
import org.keycloak.common.ClientConnection; import org.keycloak.common.ClientConnection;
import org.keycloak.jose.jws.JWSInput; import org.keycloak.jose.jws.JWSInput;
import org.keycloak.jose.jws.JWSInputException; import org.keycloak.jose.jws.JWSInputException;
import org.keycloak.models.AdminRoles;
import org.keycloak.models.ClientModel; import org.keycloak.models.ClientModel;
import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel; import org.keycloak.models.RealmModel;
@ -39,11 +37,11 @@ import org.keycloak.services.managers.RealmManager;
import org.keycloak.services.resources.Cors; import org.keycloak.services.resources.Cors;
import org.keycloak.services.resources.admin.info.ServerInfoAdminResource; import org.keycloak.services.resources.admin.info.ServerInfoAdminResource;
import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.services.resources.admin.permissions.AdminPermissions;
import org.keycloak.services.resources.admin.permissions.RealmsPermissionEvaluator;
import org.keycloak.theme.Theme; import org.keycloak.theme.Theme;
import org.keycloak.theme.ThemeProvider; import org.keycloak.theme.ThemeProvider;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
@ -197,7 +195,6 @@ public class AdminRoot {
return adminBaseUrl(base).path(AdminRoot.class, "getRealmsAdmin"); return adminBaseUrl(base).path(AdminRoot.class, "getRealmsAdmin");
} }
/** /**
* Base Path to realm admin REST interface * Base Path to realm admin REST interface
* *
@ -205,8 +202,10 @@ public class AdminRoot {
* @return * @return
*/ */
@Path("realms") @Path("realms")
public RealmsAdminResource getRealmsAdmin(@Context final HttpHeaders headers) { public Object getRealmsAdmin(@Context final HttpHeaders headers) {
handlePreflightRequest(); if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new AdminCorsPreflightService(request);
}
AdminAuth auth = authenticateRealmAdminRequest(headers); AdminAuth auth = authenticateRealmAdminRequest(headers);
if (auth != null) { if (auth != null) {
@ -227,8 +226,10 @@ public class AdminRoot {
* @return * @return
*/ */
@Path("serverinfo") @Path("serverinfo")
public ServerInfoAdminResource getServerInfo(@Context final HttpHeaders headers) { public Object getServerInfo(@Context final HttpHeaders headers) {
handlePreflightRequest(); if (request.getHttpMethod().equals(HttpMethod.OPTIONS)) {
return new AdminCorsPreflightService(request);
}
AdminAuth auth = authenticateRealmAdminRequest(headers); AdminAuth auth = authenticateRealmAdminRequest(headers);
if (!AdminPermissions.realms(session, auth).isAdmin()) { if (!AdminPermissions.realms(session, auth).isAdmin()) {
@ -246,14 +247,6 @@ public class AdminRoot {
return adminResource; return adminResource;
} }
protected void handlePreflightRequest() {
if (request.getHttpMethod().equalsIgnoreCase("OPTIONS")) {
logger.debug("Cors admin pre-flight");
Response response = Cors.add(request, Response.ok()).preflight().allowedMethods("GET", "PUT", "POST", "DELETE").auth().build();
throw new NoLogWebApplicationException(response);
}
}
public static Theme getTheme(KeycloakSession session, RealmModel realm) throws IOException { public static Theme getTheme(KeycloakSession session, RealmModel realm) throws IOException {
ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending"); ThemeProvider themeProvider = session.getProvider(ThemeProvider.class, "extending");
return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN); return themeProvider.getTheme(realm.getAdminTheme(), Theme.Type.ADMIN);

View file

@ -0,0 +1,55 @@
package org.keycloak.testsuite.admin;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpOptions;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.keycloak.services.resources.Cors;
import java.io.IOException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class AdminPreflightTest extends AbstractAdminTest {
private CloseableHttpClient client;
@Before
public void before() {
client = HttpClientBuilder.create().build();
}
@After
public void after() {
try {
client.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Test
public void testPreflight() throws IOException {
HttpOptions options = new HttpOptions(getAdminUrl("realms/master/users"));
options.setHeader("Origin", "http://test");
CloseableHttpResponse response = client.execute(options);
assertEquals(200, response.getStatusLine().getStatusCode());
assertEquals("true", response.getFirstHeader(Cors.ACCESS_CONTROL_ALLOW_CREDENTIALS).getValue());
assertEquals("DELETE, POST, GET, PUT", response.getFirstHeader(Cors.ACCESS_CONTROL_ALLOW_METHODS).getValue());
assertEquals("http://test", response.getFirstHeader(Cors.ACCESS_CONTROL_ALLOW_ORIGIN).getValue());
assertEquals("3600", response.getFirstHeader(Cors.ACCESS_CONTROL_MAX_AGE).getValue());
assertTrue(response.getFirstHeader(Cors.ACCESS_CONTROL_ALLOW_HEADERS).getValue().contains("Authorization"));
assertTrue(response.getFirstHeader(Cors.ACCESS_CONTROL_ALLOW_HEADERS).getValue().contains("Content-Type"));
}
private String getAdminUrl(String resource) {
return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/admin/" + resource;
}
}