I've got a problem with spring security ui plugin. I'm trying to add an email field to my SecUser class (which stores information about users). However there is no field "email" in Spring Security Management Console generated with s2ui-override. On the other hand, due to documentation this is avaible(link):
By default only the standard fields (username, enabled, accountExpired, accountLocked, and passwordExpired) are available but this is customizable with the grails s2ui-override script - see the section on configuration.
I try to use s2ui overide scripts but with no effect.
I think this is somethink simple but after hours I can't find solution myself. So if anyone know how to do this - please tell me ;)
At the and my user class:
class SecUser {
transient springSecurityService
String username
String password
String email
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static constraints = {
username blank: false, unique: true
password blank: false
email blank: false, email:true
}
static mapping = {
password column: '`password`'
}
Set<SecRole> getAuthorities() {
SecUserSecRole.findAllBySecUser(this).collect { it.secRole } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
#Override
public String toString(){
return "User - "+username;
}
}
You can watch the source code from user/edit view:
<table>
<tbody>
<s2ui:textFieldRow name='username' labelCode='user.username.label' bean="${user}"
labelCodeDefault='Username' value="${user?.username}"/>
<s2ui:passwordFieldRow name='password' labelCode='user.password.label' bean="${user}"
labelCodeDefault='Password' value="${user?.password}"/>
<s2ui:checkboxRow name='enabled' labelCode='user.enabled.label' bean="${user}"
labelCodeDefault='Enabled' value="${user?.enabled}"/>
<s2ui:checkboxRow name='accountExpired' labelCode='user.accountExpired.label' bean="${user}"
labelCodeDefault='Account Expired' value="${user?.accountExpired}"/>
<s2ui:checkboxRow name='accountLocked' labelCode='user.accountLocked.label' bean="${user}"
labelCodeDefault='Account Locked' value="${user?.accountLocked}"/>
<s2ui:checkboxRow name='passwordExpired' labelCode='user.passwordExpired.label' bean="${user}"
labelCodeDefault='Password Expired' value="${user?.passwordExpired}"/>
</tbody>
</table>
There are not email field. You should to copy the edit.gsp and create.gsp files from source to grails-app/views/user/ and edit it pasting this code after username field:
<s2ui:textFieldRow name='email' labelCode='user.email.label' bean="${user}"
labelCodeDefault='email' value="${user?.email}"/>
You must to create a spring-security-core-local.properties in grails-app/i18n/ path and to write the new property
user.email.label=Email
And of course, you should to create the property email in the correct domain class.
Related
I am using grails 3.2.9 with spring-security-core plugin 3.1.2.
I've run the s2-quickstart script to create User, Role, and UserRole domain classes. I've found that the id field in the User and Role domains is being considered synthetic. For instance, if I run the following bit of code the id field is not shown:
def u = User.class.declaredFields.findAll {!it.synthetic}
u.each {
println it
}
Here is my User class:
#GrailsCompileStatic
#EqualsAndHashCode(includes='username')
#ToString(includes='username', includeNames=true, includePackage=false)
class User extends BaseDomain implements Serializable {
private static final long serialVersionUID = 1
String username
String password
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
Date lastLogin
Set<Role> getAuthorities() {
(UserRole.findAllByUser(this) as List<UserRole>)*.role as Set<Role>
}
static constraints = {
password blank: false, password: true
username blank: false, unique: true
}
static mapping = {
id generator: 'identity', column: 'user_id', sqlType: 'bigint(20) unsigned'
password column: '`password`'
lastLogin sqlType: 'timestamp'
}
}
This is not happening with my other domain classes, and the first problem I see this causing is with the exa-datatables plugin (2.0.1). The plugin uses similar code to find the fields of a domain, so when requesting the id field to be displayed by the plugin it fails with an unknown column error.
I found this was being caused because I was extending a class (BaseDomain in the example), and I did not mark the BaseDomain as abstract. As soon as I qualified BaseDomain as abstract, everything started working as expected.
abstract class BaseDomain {...
I'm trying to create (my first ever) taglib in grails and I sort of got it down except that I'm trying to return a list/array of strings that has to be displayed in a table but all I'm getting is the first value of the list. Here's what I got:
Taglib:
def roles = { attrs, body ->
def user = User.get(attrs.user)
if(user) {
def roles = Role.findAllById(UserRole.findByUser(user).roleId)
def realRoles = []
roles.each { role ->
realRoles.add(role.authority.substring(5))
}
out << realRoles
out << body()
}
}
Html:
<table class="table table-bordered table-hover" style="background-color: #FFFFFF;">
<thead>
<tr style="background-color: #007FB2;color: #FFFFFF;">
<th>ID</th>
<th>Name</th>
<th>Surname</th>
<th>Email</th>
<th>Roles</th>
<th>Delete</th>
</tr>
</thead>
<g:each in="${users}" var="user">
<tr>
<td>${user.id}</td>
<td>${user.firstName}</td>
<td>${user.surname}</td>
<td>${user.username}</td>
<td><g:roles user="${user.id}" /></td>
<td><button type="button" class="gen-btn btn-red" style="width: 100%;" onclick="confDelete(${user.id}, '${user.firstName}');">Delete</button></td>
</tr>
</g:each>
</table>
What's supposed to happen is when the taglib gets called it's supposed to go get all the roles associated with the user id then take each role's authority property and cut the preceding "ROLE_" off of it. So ROLE_ADMIN just becomes ADMIN and ROLE_USER just becomes USER, etc. What I'd ideally like is a list that I can loop through in the gsp but I realise that's alot to ask so if I can just get help getting a comma separated list roles back from my taglib I'd really appreciated it.
To make it more clear, what I'm currently getting back is literally [ROLE_ADMIN] regardless of whether the user has more roles than that. What I want is a complete list of roles, e.g. ROLE_ADMIN, ROLE_MANAGER, ROLE_WHATEVER.
Thanks in advance
EDIT Here are my User, Role and UserRole domain classes. I am using Spring security to generate these classes.
User:
package fake.package.name
class User {
transient springSecurityService
String username
String password
String firstName
String surname
Date dateCreated
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static constraints = {
username blank: false, unique: true
password blank: false
firstName blank: false
surname blank: false
}
static mapping = {
password column: '`password`'
}
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
String toString(){
if((firstName)&&(surname))
firstName+ " " +surname
else
username
}
}
Role:
package fake.package.name
class Role {
String authority
static mapping = {
cache true
}
static constraints = {
authority blank: false, unique: true
}
}
UserRole:
package fake.package.name
import org.apache.commons.lang.builder.HashCodeBuilder
class UserRole implements Serializable {
User user
Role role
boolean equals(other) {
if (!(other instanceof UserRole)) {
return false
}
other.user?.id == user?.id &&
other.role?.id == role?.id
}
int hashCode() {
def builder = new HashCodeBuilder()
if (user) builder.append(user.id)
if (role) builder.append(role.id)
builder.toHashCode()
}
static UserRole get(long userId, long roleId) {
find 'from UserRole where user.id=:userId and role.id=:roleId',
[userId: userId, roleId: roleId]
}
static UserRole create(User user, Role role, boolean flush = false) {
new UserRole(user: user, role: role).save(flush: flush, insert: true)
}
static boolean remove(User user, Role role, boolean flush = false) {
UserRole instance = UserRole.findByUserAndRole(user, role)
if (!instance) {
return false
}
instance.delete(flush: flush)
true
}
static void removeAll(User user) {
executeUpdate 'DELETE FROM UserRole WHERE user=:user', [user: user]
}
static void removeAll(Role role) {
executeUpdate 'DELETE FROM UserRole WHERE role=:role', [role: role]
}
static mapping = {
id composite: ['role', 'user']
version false
}
}
Do note that because this a work project and not a personal one I'm not allowed to edit the code in here. Also, I changed the package names to fake.package.name, that's not the actual names of the packages.
I don't know, where your User<->Role Association comes from (Spring Security Framework?) or if it's coded by yourself. And you gave no examples of your domain-classes.
Let me explain another solution a bit:
If you design your domain-classes somehow like the following:
package xyz
class User {
String name
static hasMany = [authorities: Role]
static constraints = {
}
}
---------------
package xyz
class Role {
static belongsTo = User
String name
static hasMany = [user: User]
static constraints = {
}
}
a third table 'user_roles' will be created for saving the many-to-many relation/association
With this configuration, you could shorten your taglib to:
def roles = { attrs, body ->
def user = User.get(attrs.user)
if(user) {
def realRoles = []
user.authorities.each { r ->
realRoles.add(r.authority.substring(5))
}
out << realRoles.join(', ')
out << body()
}
}
Thanks to susi I managed to figure this thing out. Here: def roles = Role.findAllById(UserRole.findByUser(user).roleId) I only returned the first UserRole so I couldn't possibly bring back more than one Role. This is what my taglib method looks like now. I realize it might not be the most efficient and if someone would like to improve on it feel free.
def roles = { attrs, body ->
def user = User.get(attrs.user)
if(user) {
def userRoles = UserRole.findAllByUser(user)
def roles = []
def realRoles = []
userRoles.each { ur ->
roles.add(Role.findById(ur.roleId))
}
roles.each { r ->
realRoles.add(r.authority.substring(5))
}
realRoles = realRoles.join(", ")
println(realRoles)
out << realRoles
out << body()
}
}
Starting after the line if(user) {:
I find all the userRoles associated with the user Id
Then I loop through the list of userRoles and add each ur to a roles list
Then I loop through the list of Roles and add the formatted role's authority to the realRoles list
Lastly I use realRoles = realRoles.join(", ") to remove the square brackets around eachrealRoles` item
the root cause is the line
def roles = Role.findAllById(UserRole.findByUser(user).roleId)
because the UserRole.findByUser(user) just returnes 1 UserRole, roles is just 1 Role object inside a List, instead of all the User's Role objects.
change it to
def roles = Role.findAllByIdInList(UserRole.findAllByUser(user)*.roleId)
as you seem to be relatively new to Grails, a quick explanation: you can use dynamic finders (find*) in 2 ways: findBy* and findAllBy*. as the naming of the methods suggest, findBy* returns 1 object or null. findAllBy* returns a collection of objects.
Additionally, you can combine the property-names with operations (in our case "InList") to specify the type of search operation.
In this example we first retrieve a list of UserRole objects, transform this list into a list of role ids (using the spread operator "*.") and with these ids retrieve all Role objects having one of these IDs.
as the UserRole class contains a Role property directly, of course we can directly write
def roles = UserRole.findAllByUser(user)*.role
keep in mind, that this list could possibly contain null if the property role on UserRole class was nullable (it's not the case here, but if any reader has a similar problem but with a nullable association).
summarized, what you are doing in the taglib at the moment, could be written as:
out << UserRole.findAllByUser(user)*.role.collect({ it.substring(5) }).join(', ')
and just a little comment to the previous answer from susi:
that code shows a direct many-to-many association between User and Role.
as Burt Beckwith wrote some time ago https://mrpaulwoods.wordpress.com/2011/02/07/implementing-burt-beckwiths-gorm-performance-no-collections/
this can have impact on performance as every new User that gets added to a Role requires hibernate to load all existing User-Role-Connections, which could cause trouble when you have lots of users.
That's why the scaffolded domain classes of spring-security-core (since 2.x version iirc) now explicitely define the UserRole-class.
I have customised grails.plugin.springsecurity.userLookup.usernamePropertyName = "email" but the default rendering behaviour for the email body will fail with:
No such property: username for class
Therefore I customised emailBody in my application.groovy:
grails.plugin.springsecurity.ui.forgotPassword.emailBody = "Dear ${user.email} , Please follow <a href='${url}'>this link</a> to reset your password. This link will expire shortly."
because according to the docs:
The emailBody property should be a GString and will have the User domain class instance in scope in the user variable, and the generated url to click to reset the password in the url variable.
However, the params map that contains the properties in my MailStrategy is empty for the user.email and url values:
[to:blah#blah.com,
from:no-reply#blah.com, subject:Reset your password for your account,
html:Dear [:], Please follow <a href='[:']>this link</a> to reset your password. This link will expire shortly.]
Notice the [:] and [:] for the user.email and url values.
The spring-security plugin is configured with these values in application.groovy:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'blah.Account'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'blah.AccountRole'
grails.plugin.springsecurity.authority.className = 'blah.Role'
grails.plugin.springsecurity.requestMap.className = 'blah.Requestmap'
grails.plugin.springsecurity.securityConfigType = 'Annotation'
The Account class is defined as:
String email
String password
Date emailVerified = null
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
Set<Role> getAuthorities() {
AccountRole.findAllByAccount(this)*.role
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
static transients = ['springSecurityService']
static constraints = {
password blank: false, password: true
email blank: false, unique: true
emailVerified nullable: true
}
static mapping = {
password column: '`password`'
}
How can I have the username and more importantly the URL rendered for me so I can send the forgotten password email?
GString should start from " not from '
so instead of grails.plugin.springsecurity.ui.forgotPassword.emailBody = '...'
you should use: grails.plugin.springsecurity.ui.forgotPassword.emailBody = "..."
This tutorial:
http://spring.io/blog/2010/08/11/simplified-spring-security-with-grails/
Says you should create users like this:
def adminUser = SecUser.findByUsername('admin') ?: new SecUser(
username: 'admin',
password: springSecurityService.encodePassword('admin'),
enabled: true).save(failOnError: true)
However, this does not work. It only works if you do this:
password: 'admin'
Which I am assuming (but could be wrong) that stores the password in the internal DB in plain text (not hashed).
Is there a way to tell spring to encrypt or hash passwords? Its not in any of the tutorials, and can't find it in the manual
Grails 2.3.6, security core 2.0-RC2 & UI, default install.
I have seen it said that grails by default does hash with bcrypt, but I dont know how to verify this. I guess I need to install mysql, tell grails to use this, then I can query the values.
Take a deep breath. By default the spring security plugin for Grails (recent versions) isn't going to store you passwords in clear text.
Take a look at your SecUser domain class and you will see that it's handling the encryption of the password for you. You can also see an example of this in the documentation.
This is directly from the documentation.
package com.mycompany.myapp
class User {
transient springSecurityService
String username
String password
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static transients = ['springSecurityService']
static constraints = {
username blank: false, unique: true
password blank: false
}
static mapping = {
password column: '`password`'
}
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
If you haven't already read through the documentation I suggest you do. It's well written and will likely answer a lot of other questions you have about the plugin.
Am using grails 2.0.3 with default h2 database and have the following user domain class:
class User {
transient springSecurityService
String username
String password
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
Preferences preferences
Company company
Personal personal
static constraints = {
username email: true, blank: false, unique: true
password blank: false
preferences unique: true
company unique: true
personal unique: true
}
static mapping = {
password column: '`password`'
}
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
In the controller, I save the user using the following code:
userInstance.save(flush: true)
Now, this afternoon, I realized that the password field should have a size constraint and hence modified the domain class so that it became as follows (only change is in the constraints):
class User {
transient springSecurityService
String username
String password
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired
Preferences preferences
Company company
Personal personal
static constraints = {
username email: true, blank: false, unique: true
password blank: false, size: 6..15
preferences unique: true
company unique: true
personal unique: true
}
static mapping = {
password column: '`password`'
}
Set<Role> getAuthorities() {
UserRole.findAllByUser(this).collect { it.role } as Set
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService.encodePassword(password)
}
}
Subsequently I generated the views and controllers again. Now when I am trying to save the user object from the controller, using:
userInstance.save(flush: true)
I am getting the following exception:
Class: org.hibernate.AssertionFailure
Message: null id in login.User entry (don't flush the Session after an exception occurs)
Any help will be appreciated.
Info: If I remove the size constraint from the new/modified class the
saving happens fine.
I ran into the same problem using Grails 3.1.12. This is what I found out and how I solved it.
Problem:
You are trying to put a size constraint to a field that is going to be enconded. This means that a password like "admin5" will turn at the end of the domain life cycle as an encoded pwd. For example the db will stored the pwd as: "$2a$10$dn7MyN.nsU8l05fMkL/rfek/d1odko9H4QUpiNp8USGhqx9g0R6om".
The validation process will apply the size constraint to the unencoded pwd (validation step on the domain life cycle), wich will pass because the pwd typed by the user is in that range. but on the save() method (persistance step on the domain life cycle) the pwd will be encoded before an insert or update. The enconding method will create a pwd with a size bigger than your constraint and Hibernate will fail the assert() for the pwd size.
Solution:
Use the minSize constraint if you don't need to worry about the maxSize
static constraints = {
password blank: false, minSize:6
}
If you need to validate the maxSize, then I recommend you do the validation on your Service or Controller layer before creating the domain instance.