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> * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $ * @version $Revision: 1 $
*/ */
public class RoleAuthHandler implements HttpHandler { public class ConstraintAuthorizationHandler implements HttpHandler {
protected Collection<String> roles;
protected HttpHandler next; protected HttpHandler next;
protected String errorPage;
public RoleAuthHandler(Collection<String> roles, HttpHandler next) { public ConstraintAuthorizationHandler(String errorPage, HttpHandler next) {
this.roles = roles; this.errorPage = errorPage;
this.next = next; 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.setResponseCode(403);
exchange.endExchange(); exchange.endExchange();

View file

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

View file

@ -4,7 +4,6 @@ import io.undertow.Undertow;
import io.undertow.security.api.AuthenticationMechanism; import io.undertow.security.api.AuthenticationMechanism;
import io.undertow.security.api.AuthenticationMode; import io.undertow.security.api.AuthenticationMode;
import io.undertow.security.handlers.AuthenticationCallHandler; import io.undertow.security.handlers.AuthenticationCallHandler;
import io.undertow.security.handlers.AuthenticationConstraintHandler;
import io.undertow.security.handlers.AuthenticationMechanismsHandler; import io.undertow.security.handlers.AuthenticationMechanismsHandler;
import io.undertow.security.handlers.SecurityInitialHandler; import io.undertow.security.handlers.SecurityInitialHandler;
import io.undertow.security.idm.Account; import io.undertow.security.idm.Account;
@ -72,7 +71,7 @@ public class ProxyServerBuilder {
proxyHandler = new HttpHandler() { proxyHandler = new HttpHandler() {
@Override @Override
public void handleRequest(HttpServerExchange exchange) throws Exception { 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); handler.handleRequest(exchange);
} }
}; };
@ -93,12 +92,21 @@ public class ProxyServerBuilder {
protected String base; protected String base;
protected SecurityPathMatches.Builder constraintBuilder = new SecurityPathMatches.Builder(); protected SecurityPathMatches.Builder constraintBuilder = new SecurityPathMatches.Builder();
protected SecurityPathMatches matches; protected SecurityPathMatches matches;
protected String errorPage;
public ApplicationBuilder base(String base) { public ApplicationBuilder base(String base) {
this.base = base; this.base = base;
return this; 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) { public ApplicationBuilder(AdapterConfig config) {
this.deployment = KeycloakDeploymentBuilder.build(config); this.deployment = KeycloakDeploymentBuilder.build(config);
this.deploymentContext = new AdapterDeploymentContext(deployment); this.deploymentContext = new AdapterDeploymentContext(deployment);
@ -172,8 +180,16 @@ public class ProxyServerBuilder {
private HttpHandler addSecurity(final HttpHandler toWrap) { private HttpHandler addSecurity(final HttpHandler toWrap) {
HttpHandler handler = toWrap; HttpHandler handler = toWrap;
handler = new UndertowAuthenticatedActionsHandler(deploymentContext, 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 AuthenticationCallHandler(handler);
handler = new ConstraintMatcherHandler(matches, handler, toWrap); handler = new ConstraintMatcherHandler(matches, handler, toWrap, errorPage);
final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>(); final List<AuthenticationMechanism> mechanisms = new LinkedList<AuthenticationMechanism>();
mechanisms.add(new CachedAuthenticatedSessionMechanism()); mechanisms.add(new CachedAuthenticatedSessionMechanism());
mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1)); mechanisms.add(new UndertowAuthenticationMechanism(deploymentContext, userSessionManagement, nodesRegistrationManagement, -1));

View file

@ -98,7 +98,7 @@ public class ProxyTest {
Integer count = (Integer)req.getSession().getAttribute("counter"); Integer count = (Integer)req.getSession().getAttribute("counter");
if (count == null) count = new Integer(0); if (count == null) count = new Integer(0);
req.getSession().setAttribute("counter", new Integer(count.intValue() + 1)); 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); 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; static Tomcat tomcat = null;
@ -133,7 +145,7 @@ public class ProxyTest {
static Undertow proxyServer = null; static Undertow proxyServer = null;
//@BeforeClass @BeforeClass
public static void initProxy() throws Exception { public static void initProxy() throws Exception {
initTomcat(); initTomcat();
ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost"); ProxyServerBuilder builder = new ProxyServerBuilder().addHttpListener(8080, "localhost");
@ -143,7 +155,12 @@ public class ProxyTest {
builder.target("http://localhost:8082") builder.target("http://localhost:8082")
.application(config) .application(config)
.base("/customer-portal") .base("/customer-portal")
.constraint("/*").add().add(); .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 = builder.build();
proxyServer.start(); proxyServer.start();
@ -167,34 +184,57 @@ public class ProxyTest {
@Test @Test
public void testLoginSSOAndLogout() throws Exception { public void testLoginSSOAndLogout() throws Exception {
initProxy(); driver.navigate().to("http://localhost:8080/customer-portal/users");
driver.navigate().to("http://localhost:8080/customer-portal");
System.out.println("Current url: " + driver.getCurrentUrl()); System.out.println("Current url: " + driver.getCurrentUrl());
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL));
loginPage.login("bburke@redhat.com", "password"); loginPage.login("bburke@redhat.com", "password");
System.out.println("Current url: " + driver.getCurrentUrl()); 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(); String pageSource = driver.getPageSource();
System.out.println(pageSource); System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("customer-portal")); Assert.assertTrue(pageSource.contains("customer-portal/users"));
Assert.assertTrue(pageSource.contains("0")); Assert.assertTrue(pageSource.contains("count:0"));
driver.navigate().to("http://localhost:8080/customer-portal"); driver.navigate().to("http://localhost:8080/customer-portal/users");
Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal"); Assert.assertEquals(driver.getCurrentUrl(), "http://localhost:8080/customer-portal/users");
pageSource = driver.getPageSource(); pageSource = driver.getPageSource();
System.out.println(pageSource); System.out.println(pageSource);
Assert.assertTrue(pageSource.contains("customer-portal")); Assert.assertTrue(pageSource.contains("customer-portal/users"));
Assert.assertTrue(pageSource.contains("1")); // test http session 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 // test logout
String logoutUri = OpenIDConnectService.logoutUrl(UriBuilder.fromUri("http://localhost:8081/auth")) 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); driver.navigate().to(logoutUri);
Assert.assertTrue(driver.getCurrentUrl().startsWith(LOGIN_URL)); 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(); String currentUrl = driver.getCurrentUrl();
Assert.assertTrue(currentUrl.startsWith(LOGIN_URL)); 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", { "type" : "password",
"value" : "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" ], "realmRoles": [ "user", "admin" ],
"applicationRoles": { "applicationRoles": {
"account": [ "manage-account" ] "account": [ "manage-account" ]

View file

@ -10,7 +10,15 @@
<servlet-name>SendUsername</servlet-name> <servlet-name>SendUsername</servlet-name>
<servlet-class>org.keycloak.testsuite.ProxyTest$SendUsernameServlet</servlet-class> <servlet-class>org.keycloak.testsuite.ProxyTest$SendUsernameServlet</servlet-class>
</servlet> </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-mapping>
<servlet-name>SendUsername</servlet-name> <servlet-name>SendUsername</servlet-name>
<url-pattern>/*</url-pattern> <url-pattern>/*</url-pattern>