Test web resource security

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 556 - 561)

Problem

You want to verify that you have protected your web resources correctly.

Background

The typical way to verify security is with End-to-End Tests: deploy the application, log in as different users, and verify that you receive “Authorization Failure” or

“Forbidden” at the expected moment. In this recipe we will describe how to auto- mate this kind of testing, but bear in mind that these tests might violate our prin- ciple of don’t test the platform. If you are using J2EE’s declarative security feature—

526 CHAPTER 13

Testing J2EE applications

and we would be surprised if you were not—then you can test your security set- tings without getting the container involved.

Recipe

First, let us look at how to test security from the outside, using an End-to-End Test.

HtmlUnit provides support for specifying credentials along with a request, so that you can simulate having a user logged in. You can also use HtmlUnit to test the login procedure itself. Returning to our Coffee Shop application, imagine an administrative interface for such simple day-to-day things as changing products and prices.7 Obviously, these features need to be protected behind some resource security. In particular, we want to be sure that if a user tries to access this page without logging in, that the application forces them to identify themselves. The test in listing 13.8 verifies that very condition. All the administrative pages are under the URIadmin inside our application.

package junit.cookbook.coffee.endtoend.test;

import java.net.URL;

import junit.framework.TestCase;

import com.gargoylesoftware.HtmlUnit.*;

import com.gargoylesoftware.HtmlUnit.html.HtmlPage;

public class AdminWelcomePageTest extends TestCase { private static final int AUTHORIZATION_FAILED = 401;

private WebClient webClient;

protected void setUp() throws Exception { webClient = new WebClient();

}

public void testWithoutCredentials() throws Exception { try {

Page page =

webClient.getPage(

new URL("http://localhost:8080/coffeeShop/admin/"));

fail("Got through without a challenge?!");

}

7 You are right: no company in its right mind would dare make pricing changes available to the web. Nev- ertheless, we need an example, and this is the one we have chosen. Suspend your disbelief for a few pages.

Listing 13.8 Test the authorization rules for administrative web resources

527 Test web resource security

catch (FailingHttpStatusCodeException expected) { assertEquals(

AUTHORIZATION_FAILED, expected.getStatusCode());

} } }

Here we use HtmlUnit to try to retrieve the page, expecting a 401 response code:

“Authorization Failed.” In order to make this test work, we had to configure secu- rity in our application’s web deployment descriptor. Listing 13.9 shows the rele- vant portion.

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3/

/EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app id="WebApp">

<!-- Most of the file omitted for brevity -->

<display-name>CoffeeShopWeb</display-name>

<security-constraint>

<web-resource-collection>

<web-resource-name>CatalogAdministration</web-resource-name>

<url-pattern>/admin/*</url-pattern>

</web-resource-collection>

<auth-constraint>

<role-name>administrator</role-name>

</auth-constraint>

</security-constraint>

<login-config>

<auth-method>BASIC</auth-method>

</login-config>

</web-app>

Now that we know these pages require the requester to log in, we need to limit access to those users that play the administrator role. Our next test uses Html- Unit’s CredentialProviderAPI to simulate having a particular user logged in. The class SimpleCredentialProvider allows you to specify the user name and password to simulate for all requests originating from the same WebClient object. Suppose that admin is the user name of an administrator who should have access to the administrative part of the online store. Here is the test we need to verify that admin can log in and see the welcome page.

Listing 13.9 A web deployment descriptor that passes the tests in AdminWelcomePageTest

528 CHAPTER 13

Testing J2EE applications

public void testAdminLoggedIn() throws Exception { webClient.setCredentialProvider(

new SimpleCredentialProvider("admin", "adm1n"));

Page page =

webClient.getPage(

new URL("http://localhost:8080/coffeeShop/admin/"));

assertEquals(

HttpServletResponse.SC_OK,

page.getWebResponse().getStatusCode());

assertTrue(page instanceof HtmlPage);

HtmlPage welcomePage = (HtmlPage) page;

assertEquals(

"Coffee Shop - Administration", welcomePage.getTitleText());

}

You will notice two differences with this test: first, we invoke WebClient.setCre- dentialProvider() to simulate user admin logging in with password adm1n. From this point, until you change the CredentialProvider, all requests from this Web- Client object will pass those credentials, the behavior you would expect from such an API.8 The other difference to note is that we verify that we receive the response code 200, meaning “OK.” We need this because we have made a small but useful change to our WebClient. In the setUp()method we have added the line highlighted in bold print:

protected void setUp() throws Exception { webClient = new WebClient();

webClient.setThrowExceptionOnFailingStatusCode(false);

}

This line configures how the WebClient reacts when it receives a failing status code from the server—that is, a code outside of the range 200–299. By default, the Web- Client throws an exception, which explains our first test: because it expected sta- tus code 401, a failing status code, the test expects its WebClient to throw a FailingHttpStatusCodeException. By turning this behavior off we can avoid hav- ing to catch all these exceptions, but in return, we need to check the status code of every request—even the ones we expect to pass. It is a small price to pay to sim- plify tests that generally expect the server to respond with a failing status code.

8 Remember that each test executes in its own object, and so uses a different WebClient object. Do not expect those credentials to remain set for other tests in your suite.

529 Test web resource security

As an example, this next test tries to log in to the administrative application as shopper. We expect the server to answer with “forbidden.”

public void testShopperLoggedIn() throws Exception { webClient.setCredentialProvider(

new SimpleCredentialProvider("shopper", "sh0pper"));

Page page =

webClient.getPage(

new URL("http://localhost:8080/coffeeShop/admin/"));

assertEquals(

HttpServletResponse.SC_FORBIDDEN, page.getWebResponse().getStatusCode());

}

We were using hard-coded status code in the original version of our tests, but remembered later that HttpServletResponse defines constants for them all, so we use the constants instead. The resulting tests are much easier to read. You have seen how to simulate having no credentials (not being logged in), being logged in as a user with the required access, and being logged in as a user without the required access. These are the building blocks you need to write as sophisticated a test as you need, centered on authentication and authorizationfor web resources.

Discussion

Be aware of one downside to testing application resource security “from the out- side” as we have described here. If you verify the container’s enforcement of your security policies for all user roles on all resources, there is a real danger of dupli- cating your entire access control list in the tests. This defeats, at least in part, the point of declarative security.9 As a result, we recommend that you test the way you declare security information, rather than whether your application server applies it correctly. To test the latter would require essentially duplicating the application server’s assembly descriptor-processing algorithm. If you are building an applica- tion server, then that effort is appropriate; however, if you are only building an enterprise application to execute on an application server, then it goes too far.

Don’t test the platform. Instead, verify the content of each of your deployment descriptors using the techniques we described in chapter 9, “Testing and XML.”

The specific techniques you need to test your server-side security settings depend on the application server: some servers are not open enough to make

9 You know: security without a large pile of code.

530 CHAPTER 13

Testing J2EE applications

server configuration data available to outside components.10 With JBoss, for exam- ple, a combination of XMLUnit and Plain Old JUnit can help you verify your set- tings in jboss-web.xml, login-config.xml, and—in the case of the user registry (which is based on properties files)—users.properties and roles.properties. With other application servers, check your local documentation.

Related

■ Chapter 9—Testing and XML

Một phần của tài liệu Manning JUnit recipes practical methods for program (Trang 556 - 561)

Tải bản đầy đủ (PDF)

(753 trang)