/*
* Copyright 2011 Sonian Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sonian.elasticsearch.http.jetty.security;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.server.LocalConnector;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.testing.HttpTester;
import org.eclipse.jetty.util.ByteArrayISO8859Writer;
import org.eclipse.jetty.util.security.Constraint;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import sun.misc.BASE64Encoder;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static javax.servlet.http.HttpServletResponse.*;
import static org.hamcrest.Matchers.startsWith;
/**
* @author imotov
*/
public class RestConstraintSecurityHandlerTests {
private Server server;
private LocalConnector connector;
@BeforeMethod
public void createServer() {
server = new Server();
connector = new LocalConnector();
server.addConnector(connector);
}
@AfterMethod
public void stopServer() throws Exception {
server.stop();
server.join();
}
protected HttpTester execute(HttpTester request) throws Exception {
HttpTester response = new HttpTester();
response.parse(connector.getResponses(request.generate()));
return response;
}
protected HttpTester request(String method, String url) {
HttpTester request = new HttpTester();
request.setMethod(method);
request.setURI(url);
request.setVersion("HTTP/1.0");
return request;
}
protected HttpTester request(String method, String url, String username, String password) {
HttpTester request = new HttpTester();
request.setMethod(method);
request.setURI(url);
request.setVersion("HTTP/1.0");
BASE64Encoder enc = new sun.misc.BASE64Encoder();
String userPassword = username + ":" + password;
request.addHeader("Authorization", "Basic " + enc.encode(userPassword.getBytes()));
return request;
}
protected ConstraintMapping constraintMapping(String method, String url, String... roles) {
ConstraintMapping constraintMapping = new ConstraintMapping();
Constraint constraint = new Constraint();
constraintMapping.setMethod(method);
constraintMapping.setPathSpec(url);
if (roles.length > 0) {
constraint.setAuthenticate(true);
constraint.setRoles(roles);
}
constraintMapping.setConstraint(constraint);
return constraintMapping;
}
protected ConstraintMapping forbidden(String method, String url) {
ConstraintMapping constraintMapping = new ConstraintMapping();
Constraint constraint = new Constraint();
constraintMapping.setMethod(method);
constraintMapping.setPathSpec(url);
constraint.setAuthenticate(true);
constraintMapping.setConstraint(constraint);
return constraintMapping;
}
protected LoginService loginService() {
HashLoginService loginService = new HashLoginService();
loginService.setName("DefaultRealm");
loginService.setConfig("config/realm.properties");
return loginService;
}
@Test
public void testServerRequest() throws Exception {
RequestCollectingHandler handler = new RequestCollectingHandler();
server.setHandler(handler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(handler.requests().size(), equalTo(1));
assertThat(handler.requests().get(0), equalTo("GET:/some-page"));
}
@Test
public void testBasicPermissions() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page", "admin"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(handler.requests().size(), equalTo(1));
}
@Test
public void testMethodPermissions() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page"));
securityHandler.addConstraintMapping(constraintMapping("POST", "/admin-page", "admin"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("POST", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("PUT", "/admin-page")).getStatus(), equalTo(SC_OK));
assertThat(handler.requests().size(), equalTo(3));
}
@Test
public void testDefaultConstraint() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page"));
securityHandler.addConstraintMapping(constraintMapping("POST", "/admin-page", "admin"));
securityHandler.addConstraintMapping(constraintMapping(null, "*", "admin"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("POST", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("PUT", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(handler.requests().size(), equalTo(1));
}
@Test
public void testDefaultConstraintForbidden() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page"));
securityHandler.addConstraintMapping(constraintMapping("POST", "/admin-page", "admin"));
securityHandler.addConstraintMapping(forbidden(null, "*"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(execute(request("POST", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("PUT", "/admin-page")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(handler.requests().size(), equalTo(1));
}
@Test
public void testConstraintForbidden() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page"));
securityHandler.addConstraintMapping(constraintMapping("POST", "/admin-page", "admin"));
securityHandler.addConstraintMapping(forbidden("PUT", "/admin-page"));
securityHandler.addConstraintMapping(forbidden("DELETE", "*"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("POST", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("PUT", "/admin-page")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(execute(request("DELETE", "/admin-page")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(execute(request("DELETE", "/some-page")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(handler.requests().size(), equalTo(2));
}
@Test
public void testRestrictingConstraintsOnTheFly() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/non-admin-page"));
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page", "admin"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
securityHandler.addConstraintMapping(constraintMapping("GET", "/non-admin-page", "admin"));
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
}
@Test
public void testRemovingConstraintsOnTheFly() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/non-admin-page"));
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page", "admin"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
// Remove authentication requirement from the page
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page"));
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_OK));
}
@Test
public void testRelaxingConstraintsOnTheFly() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/non-admin-page", "readwrite"));
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page", "admin"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/non-admin-page", "user", "Passw0rd")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page", "user", "Passw0rd")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(execute(request("GET", "/admin-page", "superuser", "Adm1n")).getStatus(), equalTo(SC_OK));
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page", "readwrite"));
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/non-admin-page", "user", "Passw0rd")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/admin-page", "user", "Passw0rd")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/admin-page", "superuser", "Adm1n")).getStatus(), equalTo(SC_OK));
}
@Test
public void testAnyRoleNoneStrict() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/non-admin-page", "*"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
securityHandler.setStrict(false);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/non-admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/non-admin-page", "user", "Passw0rd")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/non-admin-page", "superuser", "Adm1n")).getStatus(), equalTo(SC_OK));
}
@Test
public void testAnyRoleStrict() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/admin-page", "*"));
securityHandler.addRole("admin");
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
securityHandler.setStrict(true);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/admin-page")).getStatus(), equalTo(SC_UNAUTHORIZED));
// Readwrite wasn't defined - should be forbidden
assertThat(execute(request("GET", "/admin-page", "user", "Passw0rd")).getStatus(), equalTo(SC_FORBIDDEN));
assertThat(execute(request("GET", "/admin-page", "superuser", "Adm1n")).getStatus(), equalTo(SC_OK));
}
@Test
public void testMultiplePathSpecs() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping("GET", "/page1,/page2,\n /page3,/page4 ", "readwrite"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
server.start();
assertThat(execute(request("GET", "/some-page")).getStatus(), equalTo(SC_OK));
assertThat(execute(request("GET", "/page1")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/page2")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/page3")).getStatus(), equalTo(SC_UNAUTHORIZED));
assertThat(execute(request("GET", "/page4")).getStatus(), equalTo(SC_UNAUTHORIZED));
}
@Test
public void testPageWithoutMethod() throws Exception {
RestConstraintSecurityHandler securityHandler = new RestConstraintSecurityHandler();
securityHandler.setLoginService(loginService());
securityHandler.addConstraintMapping(constraintMapping(null, "/page"));
RequestCollectingHandler handler = new RequestCollectingHandler();
securityHandler.setHandler(handler);
server.setHandler(securityHandler);
try {
server.start();
assertThat("Server shouldn't start", false);
} catch (IllegalArgumentException ex) {
assertThat(ex.getMessage(), startsWith("No method specified"));
}
}
private class RequestCollectingHandler extends DefaultHandler {
private List<String> requests = new ArrayList<String>();
@Override
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
if (response.isCommitted() || baseRequest.isHandled())
return;
baseRequest.setHandled(true);
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MimeTypes.TEXT_PLAIN);
ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
String requestStr = request.getMethod() + ":" + request.getRequestURI();
requests.add(requestStr);
writer.write(requestStr);
writer.flush();
response.setContentLength(writer.size());
OutputStream out = response.getOutputStream();
writer.writeTo(out);
out.close();
}
public List<String> requests() {
return requests;
}
}
}