Question

I am using grails 2.3.4 with ":spring-security-ui:1.0-RC1" and ':spring-security-core:2.0-RC2' plugin. I have configured both plugins in the Config.groovy like that:

grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.SaaSTemplate.auth.Person'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.SaaSTemplate.auth.PersonRole'
grails.plugin.springsecurity.authority.className = 'com.SaaSTemplate.auth.Role'


//**********************************
// Spring Security UI Settings
//**********************************

//password validation of RegisterController.groovy
grails.plugin.springsecurity.ui.password.validationRegex='^.*(?=.*\\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&]).*$' //example: 1Test@!!
grails.plugin.springsecurity.ui.password.minLength=8
grails.plugin.springsecurity.ui.password.maxLength=64

My RegisterController.groovy looks like that:

@Secured(['permitAll'])
class RegisterController extends grails.plugin.springsecurity.ui.RegisterController {

    // override default value from base class
    static defaultAction = 'index'

    // override default value from base class
    static allowedMethods = [register: 'POST']

    def mailService
    def messageSource
    def saltSource

    def index() {
        def copy = [:] + (flash.chainedParams ?: [:])
        copy.remove 'controller'
        copy.remove 'action'
        [command: new RegisterCommand(copy)] // here is where I get the exception!!!!
    }

    def register(RegisterCommand command) {

        if (command.hasErrors()) {
            render view: 'index', model: [command: command]
            return
        }

        String salt = saltSource instanceof NullSaltSource ? null : command.username
        def user = lookupUserClass().newInstance(email: command.email, username: command.username,
        accountLocked: true, enabled: true)

        RegistrationCode registrationCode = springSecurityUiService.register(user, command.password, salt)
        if (registrationCode == null || registrationCode.hasErrors()) {
            // null means problem creating the user
            flash.error = message(code: 'spring.security.ui.register.miscError')
            flash.chainedParams = params
            redirect action: 'index'
            return
        }

        String url = generateLink('verifyRegistration', [t: registrationCode.token])

        def conf = SpringSecurityUtils.securityConfig
        def body = conf.ui.register.emailBody
        if (body.contains('$')) {
            body = evaluate(body, [user: user, url: url])
        }
        mailService.sendMail {
            to command.email
            from conf.ui.register.emailFrom
            subject conf.ui.register.emailSubject
            html body.toString()
        }

        render view: 'index', model: [emailSent: true]
    }

    def verifyRegistration() {

        def conf = SpringSecurityUtils.securityConfig
        String defaultTargetUrl = conf.successHandler.defaultTargetUrl

        String token = params.t

        def registrationCode = token ? RegistrationCode.findByToken(token) : null
        if (!registrationCode) {
            flash.error = message(code: 'spring.security.ui.register.badCode')
            redirect uri: defaultTargetUrl
            return
        }

        def user
        // TODO to ui service
        RegistrationCode.withTransaction { status ->
            String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
            user = lookupUserClass().findWhere((usernameFieldName): registrationCode.username)
            if (!user) {
                return
            }
            user.accountLocked = false
            user.save(flush:true)
            def UserRole = lookupUserRoleClass()
            def Role = lookupRoleClass()
            for (roleName in conf.ui.register.defaultRoleNames) {
                UserRole.create user, Role.findByAuthority(roleName)
            }
            registrationCode.delete()
        }

        if (!user) {
            flash.error = message(code: 'spring.security.ui.register.badCode')
            redirect uri: defaultTargetUrl
            return
        }

        springSecurityService.reauthenticate user.username

        flash.message = message(code: 'spring.security.ui.register.complete')
        redirect uri: conf.ui.register.postRegisterUrl ?: defaultTargetUrl
    }

    def forgotPassword() {

        if (!request.post) {
            // show the form
            return
        }

        String username = params.username
        if (!username) {
            flash.error = message(code: 'spring.security.ui.forgotPassword.username.missing')
            redirect action: 'forgotPassword'
            return
        }

        String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
        def user = lookupUserClass().findWhere((usernameFieldName): username)
        if (!user) {
            flash.error = message(code: 'spring.security.ui.forgotPassword.user.notFound')
            redirect action: 'forgotPassword'
            return
        }

        def registrationCode = new RegistrationCode(username: user."$usernameFieldName")
        registrationCode.save(flush: true)

        String url = generateLink('resetPassword', [t: registrationCode.token])

        def conf = SpringSecurityUtils.securityConfig
        def body = conf.ui.forgotPassword.emailBody
        if (body.contains('$')) {
            body = evaluate(body, [user: user, url: url])
        }
        mailService.sendMail {
            to user.email
            from conf.ui.forgotPassword.emailFrom
            subject conf.ui.forgotPassword.emailSubject
            html body.toString()
        }

        [emailSent: true]
    }

    def resetPassword(ResetPasswordCommand command) {

        String token = params.t

        def registrationCode = token ? RegistrationCode.findByToken(token) : null
        if (!registrationCode) {
            flash.error = message(code: 'spring.security.ui.resetPassword.badCode')
            redirect uri: SpringSecurityUtils.securityConfig.successHandler.defaultTargetUrl
            return
        }

        if (!request.post) {
            return [token: token, command: new ResetPasswordCommand()]
        }

        command.username = registrationCode.username
        command.validate()

        if (command.hasErrors()) {
            return [token: token, command: command]
        }

        String salt = saltSource instanceof NullSaltSource ? null : registrationCode.username
        RegistrationCode.withTransaction { status ->
            String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
            def user = lookupUserClass().findWhere((usernameFieldName): registrationCode.username)
            user.password = springSecurityUiService.encodePassword(command.password, salt)
            user.save()
            registrationCode.delete()
        }

        springSecurityService.reauthenticate registrationCode.username

        flash.message = message(code: 'spring.security.ui.resetPassword.success')

        def conf = SpringSecurityUtils.securityConfig
        String postResetUrl = conf.ui.register.postResetUrl ?: conf.successHandler.defaultTargetUrl
        redirect uri: postResetUrl
    }

    protected String generateLink(String action, linkParams) {
        createLink(base: "$request.scheme://$request.serverName:$request.serverPort$request.contextPath",
        controller: 'register', action: action,
        params: linkParams)
    }

    protected String evaluate(s, binding) {
        new SimpleTemplateEngine().createTemplate(s).make(binding)
    }

    //  static final passwordValidator = { String password, command ->
    //      if (command.username && command.username.equals(password)) {
    //          return 'command.password.error.username'
    //      }
    //
    //      if (!checkPasswordMinLength(password, command) ||
    //      !checkPasswordMaxLength(password, command) ||
    //      !checkPasswordRegex(password, command)) {
    //          return 'command.password.error.strength'
    //      }
    //  }

    static boolean checkPasswordMinLength(String password, command) {
        def conf = SpringSecurityUtils.securityConfig

        int minLength = conf.ui.password.minLength instanceof Number ? conf.ui.password.minLength : 8

        password && password.length() >= minLength
    }

    static boolean checkPasswordMaxLength(String password, command) {
        def conf = SpringSecurityUtils.securityConfig

        int maxLength = conf.ui.password.maxLength instanceof Number ? conf.ui.password.maxLength : 64

        password && password.length() <= maxLength
    }

    static boolean checkPasswordRegex(String password, command) {
        def conf = SpringSecurityUtils.securityConfig

        String passValidationRegex = conf.ui.password.validationRegex ?:
                '^.*(?=.*\\d)(?=.*[a-zA-Z])(?=.*[!@#$%^&]).*$'

        password && password.matches(passValidationRegex)
    }

    //  static final password2Validator = { value, command ->
    //      if (command.password != command.password2) {
    //          return 'command.password2.error.mismatch'
    //      }
    //  }
}

class RegisterCommand {

    String username
    String email
    String password
    String password2

    def grailsApplication

    static constraints = {
        username blank: false, validator: { value, command ->
            if (value) {
                def User = command.grailsApplication.getDomainClass(
                        SpringSecurityUtils.securityConfig.userLookup.userDomainClassName).clazz
                if (User.findByUsername(value)) {
                    return 'registerCommand.username.unique'
                }
            }
        }
        email blank: false, email: true
        password blank: false, validator: RegisterController.passwordValidator
        password2 validator: RegisterController.password2Validator
    }
}

class ResetPasswordCommand {
    String username
    String password
    String password2

    static constraints = {
        password blank: false, validator: RegisterController.passwordValidator
        password2 validator: RegisterController.password2Validator
    }
}

As you can see I have just copied the original controller from the source code. Therefore the other classes ResetPasswordCommand and RegisterCommand are also included. However, I still get the exception:

ui.SpringSecurityUiService problem creating Person: com.TestApp.auth.Person : (unsaved)
    Die Eigenschaft [firstName] des Typs [class com.TestApp.auth.Person] darf nicht null sein
    Die Eigenschaft [lastName] des Typs [class com.TestApp.auth.Person] darf nicht null sein
errors.GrailsExceptionResolver MissingPropertyException occurred when processing request: [GET] /TestApp/register/index
No such property: format for class: TestApp.Register.RegisterCommand. Stacktrace follows:
groovy.lang.MissingPropertyException: No such property: format for class: TestApp.Register.RegisterCommand
    at TestApp.Register.RegisterController.index(RegisterController.groovy:26)
    at grails.plugin.cache.web.filter.PageFragmentCachingFilter.doFilter(PageFragmentCachingFilter.java:200)
    at grails.plugin.cache.web.filter.AbstractFilter.doFilter(AbstractFilter.java:63)
    at grails.plugin.springsecurity.web.filter.GrailsAnonymousAuthenticationFilter.doFilter(GrailsAnonymousAuthenticationFilter.java:53)
    at grails.plugin.springsecurity.web.authentication.RequestHolderAuthenticationFilter.doFilter(RequestHolderAuthenticationFilter.java:49)
    at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.java:82)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at java.lang.Thread.run(Thread.java:744)

Why do I get this exception? I have included the class RegisterCommand (as also in the original source) in my file? I really appreciate your input!

UPDATE

I changed my method like that:

def index() {
    def copy = [:] + (flash.chainedParams ?: [:])
    copy.remove 'controller'
    copy.remove 'action'

    ['controller', 'action', 'format'].each { copy.remove it }

    [command: new RegisterCommand(copy)]
}

However, now I still cannot process the registration, I get:

....ui.SpringSecurityUiService problem creating Person: com.TestApp.auth.Person : (unsaved)
    The property [firstName] of the type [class com.TestApp.auth.Person] cannot be null
    The property [lastName] of the type [class com.TestApp.auth.Person] cannot be null
Was it helpful?

Solution

format is added in the UrlMapping as an optional field.

"/$controller/$action?/$id?(.$format)?"{
    constraints {
        // apply constraints here
    }
}

So you would end up removing that from the map as done for action and controller.

//index action
['controller', 'action', 'format'].each { copy.remove it }

Another option will be to explicitly use bindData to avoid this collision

//index action
RegisterCommand cmd = new RegisterCommand()
bindData copy, cmd
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top