I'm trying to customize spring security ui plugin. What I want to achieve:
1) admin creates a new user with email from spring security ui
2) app generates link and send it to user's email
3) user clicks on the link and sees page where he can specify password and save it
4) after user submits page account become active with password that he specified
for this purpose I've decided to customize UserController that is provided by spring security UI by default:
class UserController extends grails.plugin.springsecurity.ui.UserController {
def mailService
def save() {
def user = lookupUserClass().newInstance(params)
if (!user.save(flush: true)) {
render view: 'create', model: [user: user, authorityList: sortedRoles()]
return
}
//generate secured link
def registrationCode = new RegistrationCode(username: user.username).save(flush: true)
String url = generateLink('verifyRegistration', [t: registrationCode.token])
def conf = SpringSecurityUtils.securityConfig
//here we should send an invitation mail
mailService.sendMail {
async true
to user.email
from conf.ui.register.emailFrom
subject conf.ui.register.emailSubject
html url
}
addRoles(user)
flash.message = "${message(code: 'default.created.message', args: [message(code: 'user.label', default: 'User'), user.id])}"
redirect action: 'edit', id: user.id
}
def verifyRegistration() {
def conf = SpringSecurityUtils.securityConfig
String defaultTargetUrl = conf.successHandler.defaultTargetUrl
String token = params.t
println token
def registrationCode = token ? RegistrationCode.findByToken(token) : null
if (!registrationCode) {
println registrationCode
flash.error = message(code: 'spring.security.ui.register.badCode')
redirect uri: defaultTargetUrl
return
}
render(view: "initpassword", model: [token: token])
}
def savePassword() {
String token = params.token
def conf = SpringSecurityUtils.securityConfig
String defaultTargetUrl = conf.successHandler.defaultTargetUrl
def registrationCode = token ? RegistrationCode.findByToken(token) : null
//TODO check if passwords are equal
if (!registrationCode) {
flash.error = message(code: 'spring.security.ui.register.badCode')
redirect uri: defaultTargetUrl
return
}
def user
RegistrationCode.withTransaction { status ->
String usernameFieldName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
user = lookupUserClass().findWhere((usernameFieldName): registrationCode.username)
if (!user) {
return
}
user.accountLocked = false
String salt = saltSource instanceof NullSaltSource ? null : params.username
user.password = springSecurityUiService.encodePassword(params.password1, salt)
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
}
}
}
and initpassword.gsp is very simple and looks like this:
<g:form action="savePassword" absolute="true" method="POST">
<g:passwordField name="password1"/>
<g:passwordField name="password2"/>
<g:hiddenField value="${token}" name="token"/>
<g:actionSubmit value="Save"/>
</g:form>
Everything looks good so far (at least for me, please, let me know if you've observed smth wrong) but very strange thing happens when I try to test it:
1) I click on the link that was sent in email (verifyRegistration() method is invoked)
2) app shows this page where I can type password (initpassword page is shown)
3) I submit the page (here savePassword() should be invoked but for some reason its not invoked) and app shows me default page for creating a new user from spring security ui plugin with validation erros where it said that fields for user (all one by one) can't be empty
4) When I try to debug (with breakpoint in the begining of savePassword() method) nothing happens - it seems like controller just don't catch request to savePassword()
But when I try to just open savePassword() method from browser (typing localhost/myApp/user/savePassword) controller catches the request and debugging on that breakpoint works.
I've spent almost 4 hours dealing with this. Have no idea what actually going on. Some magic with Grails. Can someone help me with this? Thank you!!!
In your initpassword.gsp
just add the action="savePassword"
attribute to the g:actionSubmit
tag. You even may delete this attribute from the g:form
tag. That is Grails ability to have multiple actions in form. I'm not sure but in clean HTML it may work too.
<html>
<head>
<title></title>
</head>
<body>
<g:form controller="user" absolute="true" method="POST">
<g:passwordField name="password1"/>
<g:passwordField name="password2"/>
<g:hiddenField value="${token}" name="token"/>
<g:actionSubmit value="Save" action="savePassword"/>
</g:form>
</body>
</html>