KEYCLOAK-6068 Fix preflight request on admin endpoints
This commit is contained in:
parent
7d2d7e41d9
commit
e96c6a4bcb
3 changed files with 96 additions and 16 deletions
|
@ -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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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);
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in a new issue