Merge pull request #864 from patriot1burke/master

proxy constraints
This commit is contained in:
Bill Burke 2014-11-21 11:21:23 -05:00
commit b53707a129
6 changed files with 122 additions and 25 deletions

View file

@ -10,13 +10,13 @@ import java.util.Collection;
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class RoleAuthHandler implements HttpHandler {
public class ConstraintAuthorizationHandler implements HttpHandler {
protected Collection<String> roles;
protected HttpHandler next;
protected String errorPage;
public RoleAuthHandler(Collection<String> roles, HttpHandler next) {
this.roles = roles;
public ConstraintAuthorizationHandler(String errorPage, HttpHandler next) {
this.errorPage = errorPage;
this.next = next;
}
@ -36,6 +36,14 @@ public class RoleAuthHandler implements HttpHandler {
}
}
}
if (errorPage != null) {
exchange.setRequestPath(errorPage);
exchange.setRelativePath(errorPage);
exchange.setResolvedPath(errorPage);
next.handleRequest(exchange);
return;
}
exchange.setResponseCode(403);
exchange.endExchange();

View file

@ -15,11 +15,13 @@ public class ConstraintMatcherHandler implements HttpHandler {
protected SecurityPathMatches matcher;
protected HttpHandler securedHandler;
protected HttpHandler unsecuredHandler;
protected String errorPage;
public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler) {
public ConstraintMatcherHandler(SecurityPathMatches matcher, HttpHandler securedHandler, HttpHandler unsecuredHandler, String errorPage) {
this.matcher = matcher;
this.securedHandler = securedHandler;
this.unsecuredHandler = unsecuredHandler;
this.errorPage = errorPage;
}
@Override
@ -31,8 +33,16 @@ public class ConstraintMatcherHandler implements HttpHandler {
}
if (match.getRequiredRoles().isEmpty() && match.getEmptyRoleSemantic() == SecurityInfo.EmptyRoleSemantic.DENY) {
exchange.setResponseCode(403);
exchange.endExchange();
if (errorPage != null) {
exchange.setRequestPath(errorPage);
exchange.setRelativePath(errorPage);
exchange.setResolvedPath(errorPage);
unsecuredHandler.handleRequest(exchange);
} else {
exchange.setResponseCode(403);
exchange.endExchange();
}
return;
}
exchange.getSecurityContext().setAuthenticationRequired();
exchange.putAttachment(CONSTRAINT_KEY, match);

View file

@ -4,7 +4,6 @@ import io.undertow.Undertow;
import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMode;
import io.undertow.security.handlers.AuthenticationCallHandler;
import io.undertow.security.handlers.AuthenticationConstraintHandler;
import io.undertow.security.handlers.AuthenticationMechanismsHandler;
import io.undertow.security.handlers.SecurityInitialHandler;
import io.undertow.security.idm.Account;
@ -72,7 +71,7 @@ public class ProxyServerBuilder {
proxyHandler = new HttpHandler() {
@Override
public void handleRequest(HttpServerExchange exchange) throws Exception {
exchange.setRelativePath(exchange.getResolvedPath()); // need this otherwise proxy forwards to chopped off path
exchange.setRelativePath(exchange.getRequestPath()); // need this otherwise proxy forwards to chopped off path
handler.handleRequest(exchange);
}
};
@ -93,12 +92,21 @@ public class ProxyServerBuilder {
protected String base;
protected SecurityPathMatches.Builder constraintBuilder = new SecurityPathMatches.Builder();
protected SecurityPathMatches matches;
protected String errorPage;
public ApplicationBuilder base(String base) {
this.base = base;
return this;
}
public ApplicationBuilder errorPage(String errorPage) {
if (errorPage != null && errorPage.startsWith("/")) {
errorPage = errorPage.substring(1);
}
this.errorPage = errorPage;
return this;
}
public ApplicationBuilder(AdapterConfig config) {
this.deployment = KeycloakDeploymentBuilder.build(config);
this.deploymentContext = new AdapterDeploymentContext(deployment);
@ -172,8 +180,16 @@ public class ProxyServerBuilder {
private HttpHandler addSecurity(final HttpHandler toWrap) {
HttpHandler handler = toWrap;
handler = new UndertowAuthenticatedActionsHandler(deploymentContext, toWrap);
if (errorPage != null) {
if (base.endsWith("/")) {
errorPage = base + errorPage;
} else {
errorPage = base + "/" + errorPage;
}
}
handler = new ConstraintAuthorizationHandler(errorPage, handler);
handler = new AuthenticationCallHandler(handler);
handler = new ConstraintMatcherHandler(matches, handler, toWrap);
handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
mechanisms.add(new CachedAuthenticatedSessionMechanism());
mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1));

View file

@ -98,7 +98,7 @@ public class ProxyTest {
Integer count = (Integer)req.getSession().getAttribute("counter");
if (count == null) count = new Integer(0);
req.getSession().setAttribute("counter", new Integer(count.intValue() + 1));
stream.write(count.toString().getBytes());
stream.write(("count:"+count).getBytes());
@ -108,6 +108,18 @@ public class ProxyTest {
doGet(req, resp);
}
}
public static class SendError extends HttpServlet {
@Override
protected void doGet(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
OutputStream stream = resp.getOutputStream();
stream.write("access error".getBytes());
}
@Override
protected void doPost(final HttpServletRequest req, final HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
static Tomcat tomcat = null;
@ -133,7 +145,7 @@ public class ProxyTest {
static Undertow proxyServer = null;
//@BeforeClass
@BeforeClass
public static void initProxy() throws Exception {
initTomcat();
ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost");
@ -142,8 +154,13 @@ public class ProxyTest {
builder.target("http://localhost:8082")
.application(config)
.base("/customer-portal")
.constraint("/*").add().add();
.base("/customer-portal")
.errorPage("/error.html")
.constraint("/users/*").role("user").add()
.constraint("/admins/*").role("admin").add()
.constraint("/users/permit").permit().add()
.constraint("/users/deny").deny().add()
.add();
proxyServer = builder.build();
proxyServer.start();
@ -167,34 +184,57 @@ public class ProxyTest {
@Test
public void testLoginSSOAndLogout() throws Exception {
initProxy();
driver.navigate().to("http://localhost:8080/customer-portal");
driver.navigate().to("http://localhost:8080/customer-portal/users");
System.out.println("Current url: " + driver.getCurrentUrl());
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
loginPage.login("bburke@redhat.com", "password");
System.out.println("Current url: " + driver.getCurrentUrl());
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users");
String pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("customer-portal"));
Assert.assertTrue(pageSource.contains("0"));
driver.navigate().to("http://localhost:8080/customer-portal");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal");
Assert.assertTrue(pageSource.contains("customer-portal/users"));
Assert.assertTrue(pageSource.contains("count:0"));
driver.navigate().to("http://localhost:8080/customer-portal/users");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users");
pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("customer-portal"));
Assert.assertTrue(pageSource.contains("1")); // test http session
Assert.assertTrue(pageSource.contains("customer-portal/users"));
Assert.assertTrue(pageSource.contains("count:1")); // test http session
driver.navigate().to("http://localhost:8080/customer-portal/users/deny");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users/deny");
pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("access error"));
driver.navigate().to("http://localhost:8080/customer-portal/admins");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/admins");
pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("access error"));
// test logout
String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth"))
.queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal").build("demo").toString();
.queryParam(OAuth2Constants.REDIRECT_URI, "http://localhost:8080/customer-portal/users").build("demo").toString();
driver.navigate().to(logoutUri);
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
driver.navigate().to("http://localhost:8080/customer-portal");
driver.navigate().to("http://localhost:8080/customer-portal/users");
String currentUrl = driver.getCurrentUrl();
Assert.assertTrue(currentUrl.startsWith(LOGIN_URL));
// test unsecured page
driver.navigate().to("http://localhost:8080/customer-portal") ;
pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("customer-portal"));
driver.navigate().to("http://localhost:8080/customer-portal/users/permit") ;
pageSource = driver.getPageSource();
System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("customer-portal/users/permit"));
}

View file

@ -24,6 +24,21 @@
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user" ],
"applicationRoles": {
"account": [ "manage-account" ]
}
},
{
"username" : "admin",
"enabled": true,
"email" : "admin.burke@redhat.com",
"firstName": "Admin",
"lastName": "Burke",
"credentials" : [
{ "type" : "password",
"value" : "password" }
],
"realmRoles": [ "user", "admin" ],
"applicationRoles": {
"account": [ "manage-account" ]

View file

@ -10,7 +10,15 @@
<servlet-name>SendUsername</servlet-name>
<servlet-class>org.keycloak.testsuite.ProxyTest$SendUsernameServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>Error</servlet-name>
<servlet-class>org.keycloak.testsuite.ProxyTest$SendError</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Error</servlet-name>
<url-pattern>/error.html</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>SendUsername</servlet-name>
<url-pattern>/*</url-pattern>