We have a simple operator object, which uses spring security to encode the password thusly:
class Operator
transient springSecurityService
def siteService
String username
Site site
String password
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
Now we want to validate that the password matches a regexp which is defined at runtime. Operators can be created via the UI (i.e. standard CRUD forms). We have a different regexp for each site. The fact that the password gets overwritten with an ecoded one, and we should not test that, makes it more challenging.
Attempt 1: do the validation in the encodePassword():
def beforeInsert() {
protected void encodePassword() {
String regexp = siteService.getInheritedValue(site, "operatorPasswordRegexp")
if (!password.matches(regexp) {
thrown new RuntimeException "invalid password format"
}
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
}
This partially works, in that it stops passwords which don't match a regexp found at runtime being created. The problem is it throws an exception, which generates a 500 error page, when we want the operator edit form to highlight the password field with a nice friendly validation message.
Attempt two, using a custom validator
static constraints = {
password password: true, blank:false, validator: { val, obj ->
if (obj.isDirty(val)) {
return true
}
String regexp = obj.siteService.getInheritedValue(obj.operates, "operatorPasswordRegexp")
if (regexp != null && regexp != "") {
return val.matches(regexp)
}
return true
}
This appears to work, but the save always fails silently. It took me some time to realise why - when you do this:
operator.password="valid1"
opertor.save(failonError:true)
No errors are thrown. Even if you remove failonError, and check the return value,its always null (no errors). BUT IT DOES NOT SAVE THE OPERATOR.
The problem is that the beforeInsert is updating the password to an encoded version which does not pass the validator of course (and isnt supposed to), the validator says no at this point, and the save silently fails. I.e. the validiator is being called twice for a single save.
The question is, how to I get the beforeInsert() code to NOT call the validator, or the validator to ignore being called from beforeInsert?
obj.isDirty('password')
andobj.siteService...
? – Joshua Moore