This is a small issue, but I encountered it and found a solution on the mailing lists, so I thought I’d document it here.
I was demonstrating a trivial Grails application in class today and decided to unit test it. The app has a single controller, called WelcomeController
:
[sourcecode language=”groovy”]
class WelcomeController {
def index = {
def name = params.name ?: "Grails"
render "Hello, $name"
}
}
[/sourcecode]
When I deploy the application and access the Welcome controller (via http://localhost:8080/hellograils/welcome/index), it displays “Hello, Grails!”. If I append “?name=Dolly
” to the URL, the result is “Hello, Dolly!”. All nice and simple.
I decided I wanted to write a test case for this, and lately I’ve been learning how to favor unit tests over integration tests as much as possible, mostly for speed. I therefore wrote the following tests:
[sourcecode language=”groovy”]
import grails.test.*
class WelcomeControllerTests extends ControllerUnitTestCase {
void testWelcomeWithoutParameter() {
def wc = new WelcomeController()
wc.index()
assertEquals "Hello, Grails!", wc.response.contentAsString
}
void testWelcomeWithParameter() {
def wc = new WelcomeController()
wc.params.name = "Dolly"
wc.index()
assertEquals "Hello, Dolly!", wc.response.contentAsString
}
}
[/sourcecode]
When I run the unit tests (i.e., grails test-app unit:
), everything runs correctly.
One of the students pointed out that though this is a trivial example, it’s open to XSS (cross-site scripting) attacks. In the URL, replace “name=Dolly
” with “name=alert('dude, you've been hacked')
” and the embedded JavaScript code executes and pops up an alert box.
I knew that an easy solution to this would be to modify the index action in the controller to look like:
[sourcecode language=”groovy”]
class WelcomeController {
def index = {
def name = params.name ?: "Grails"
render "Hello, $name".encodeAsHTML()
}
}
[/sourcecode]
The “encodeAsHTML
” method escapes all the HTML, so the output of the hack is just “Hello, alert(…” (i.e., the script is shown as a string, rather than executed) and the problem goes away.
The issue I encountered, though, is that my unit tests started failing, with a missing method exception that claimed that the String class doesn’t have a method called encodeAsHTML
. That’s correct, of course, because that method is dynamically injected by Grails based on the org.codehaus.groovy.grails.plugin.codecs.HTMLCodec
class. In a unit test, though, the injection doesn’t happen, and I get the exception.
One solution to this, as pointed out on the very valuable grails-users email list, is to add the method to the String class via its metaclass. In other words, in my test, I can add
[sourcecode language=”groovy”]
void setUp() {
super.setUp()
String.metaclass.encodeAsHTML = {
org.codehaus.groovy.grails.plugins.codecs.HTMLCodec.encode(delegate)
}
}
[/sourcecode]
Now the String class has the encodeAsHTML
method, and everything works again.
Then I started browsing the Grails API, and found that in ControllerUnitTestCase
there’s a method called loadCodec
. The GroovyDocs weren’t very informative, but I found in the jira for Grails that issue GRAILS-3816 recommends the addition of the loadCodec
method for just this sort of purpose.
That means that I can actually write
[sourcecode language=”groovy”]
void setUp() {
super.setUp()
loadCode(org.codehaus.groovy.grails.plugins.codecs.HTMLCodec)
}
[/sourcecode]
and everything works as it should. Since this isn’t terribly well documented, I thought I’d say something here. Hopefully this will save somebody some looking around.
A couple of typos: I wrapped the XSS attack in a <script> tag, but of course when I posted the result here the tag was lost. I should have escaped the angle brackets.
Also, in the last code listing, the method is loadCodec, not loadCode.
Thanks! Had the very same problem a couple of days ago but solved it with a metaClass-hack, this is a much nicer solution.
Another little type – the code should say String.metaClass, not String.metaclass
Sweet, thanks.. asked the question; found the answer
Nice
Came across a similar use case in which I had to use a bit of code which was using encodeAsURL()
This approach worked like a charm! Thanks. 🙂
— Vivek
I found you can use the loadCodec to access the included codecs… For some reason, the customers ones in grails-app/utils don’t load either directly or via the loadCodec.
What I used.
import org.codehaus.groovy.grails.plugins.codecs.*
.
.
.
protected void setUp() {
super.setUp()
println “loading codecs”
loadCodec (HTMLCodec)
loadCodec (Base64Codec)
loadCodec (SHA1Codec)
// loadCodec (UnderscoreCodec) // custom but if included, not loaded for some reason.
println “loaded Codecs”
loadedCodecs.each { println it.toString() }
}
.
.
.
void testSimpleConstraints() {
// in order to not need to be an intgr. test.
//
mockLogging(User)
mockForConstraintsTests(User)
def user = new User(login:”someone”,
password:”blah”.encodeAsSHA1(),
role:”SuperUser”)
// oops—role should be either ‘admin’ or ‘user’
// will the validation pick that up?
assertFalse user.validate()
// mocking check.
assertEquals “inList”, user.errors[“role”]
}
Thanks, this was really helpful
Thanks for this!
I’m happy to discover all these.
this helped a lot! although my final solution is using String.metaClass, not String.metaclass like the original post says (thanks to the commenter that noticed this also)
Grails 2.4.3 GrailsUnitTestMixin offers mockCodec method.
http://grails.org/doc/latest/api/grails/test/mixin/support/GrailsUnitTestMixin.html#mockCodec%28Class%3C?%3E%29
@TestMixin(GrailsUnitTestMixin)
class CheckForOrderUpdateJobSpec extends Specification {
def setup() {
mockCodec(MD5Codec.class)
}
}
Thanks, this helped me see why my code was failing!
I think for Unit tests, you should mock the encodeAsHTML method, since your not testing its code.
For example:
String.metaClass.encodeAsHTML = { -> return ‘Some String’ }
This way you can just test that it was called.
Thanks again!