Using Grails spring security REST (which in itself uses Grails Spring Security Core ) I've generated User
, Role
, UserRole
classes.
User:
class User extends DomainBase{
transient springSecurityService
String username
String password
String firstName
String lastNameOrTitle
String email
boolean showEmail
String phoneNumber
boolean enabled = true
boolean accountExpired
boolean accountLocked
boolean passwordExpired
static transients = ['springSecurityService']
static hasMany = [
roles: Role,
ratings: Rating,
favorites: Favorite
]
static constraints = {
username blank: false, unique: true
password blank: false
firstName nullable: true, blank: false
lastNameOrTitle nullable: false, blank: false
email nullable: false, blank: false
phoneNumber nullable: true
}
static mapping = {
DomainUtil.inheritDomainMappingFrom(DomainBase, delegate)
id column: 'user_id', generator: 'sequence', params: [sequence: 'user_seq']
username column: 'username'
password column: 'password'
enabled column: 'enabled'
accountExpired column: 'account_expired'
accountLocked column: 'account_locked'
passwordExpired column: 'password_expired'
roles joinTable: [
name: 'user_role',
column: 'role_id',
key: 'user_id']
}
Set<Role> getAuthorities() {
// UserRole.findAllByUser(this).collect { it.role }
// userRoles.collect { it.role }
this.roles
}
def beforeInsert() {
encodePassword()
}
def beforeUpdate() {
super.beforeUpdate()
if (isDirty('password')) {
encodePassword()
}
}
protected void encodePassword() {
password = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(password) : password
}
}
Role:
class Role {
String authority
static mapping = {
cache true
id column: 'role_id', generator: 'sequence', params: [sequence: 'role_seq']
authority column: 'authority'
}
static constraints = {
authority blank: false, unique: true
}
}
UserRole:
class UserRole implements Serializable {
private static final long serialVersionUID = 1
static belongsTo = [
user: User,
role: Role
]
// 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) {
UserRole.where {
user == User.load(userId) &&
role == Role.load(roleId)
}.get()
}
static boolean exists(long userId, long roleId) {
UserRole.where {
user == User.load(userId) &&
role == Role.load(roleId)
}.count() > 0
}
static UserRole create(User user, Role role, boolean flush = false) {
def instance = new UserRole(user: user, role: role)
instance.save(flush: flush, insert: true)
instance
}
static boolean remove(User u, Role r, boolean flush = false) {
if (u == null || r == null) return false
int rowCount = UserRole.where {
user == User.load(u.id) &&
role == Role.load(r.id)
}.deleteAll()
if (flush) {
UserRole.withSession { it.flush() }
}
rowCount > 0
}
static void removeAll(User u, boolean flush = false) {
if (u == null) return
UserRole.where {
user == User.load(u.id)
}.deleteAll()
if (flush) {
UserRole.withSession { it.flush() }
}
}
static void removeAll(Role r, boolean flush = false) {
if (r == null) return
UserRole.where {
role == Role.load(r.id)
}.deleteAll()
if (flush) {
UserRole.withSession { it.flush() }
}
}
static constraints = {
role validator: { Role r, UserRole ur ->
if (ur.user == null) return
boolean existing = false
UserRole.withNewSession {
existing = UserRole.exists(ur.user.id, r.id)
}
if (existing) {
return 'userRole.exists'
}
}
}
static mapping = {
id composite: ['role', 'user']
version false
}
}
Now I wish to create an admin area where admins can modify/enable user accounts, but can't touch other admins, so for that I've decided to create a pageable query which would select only the users which don't have the ROLE_ADMIN
role, since admins have both ROLE_USER
and ROLE_ADMIN
roles.
As can be seen from the above code, I've modified the default generated code a bit and added a joinTable
to the User
class instead of hasMany: [roles:UserRole]
or keeping it at the default without any references to roles. The reason for this change is because when querying UserRole I'd occasionally get duplicates which would make pagination difficult.
So with this current setup I've managed to create two queries which allow me to fetch only users which do not have an admin role.
def rolesToIgnore = ["ROLE_ADMIN"]
def userIdsWithGivenRoles = User.createCriteria().list() {
projections {
property "id"
}
roles {
'in' "authority", rolesToIgnore
}
}
def usersWithoutGivenRoles = User.createCriteria().list(max: 10, offset: 0) {
not {
'in' "id", userIdsWithGivenRoles
}
}
First query fetches a list of all the user ids which have the ROLE_ADMIN
role, and then the second query fetches all the users whose id is not in the previous list.
This works and is pageable, however It bothers me for two reasons:
joinTable
on User just seems "icky" to me. Why use a joinTable
when i already have a specific class for that purpose UserRole
, however that class is more difficult to query and I'm afraid of possible overhead of mapping Role
for each found User
even though I only need the User
. So my questions are: Is there a more optimal way to construct a query for fetching users which don't contain certain roles (without restructuring the database into a pyramid role system where every user has only one role)?
Are two queries absolutely necessary? I've tried to construct a pure SQL query and I couldn't do it without subqueries.
If your UserRole has user and role properties instead of belongsTo , like this:
class UserRole implements Serializable {
private static final long serialVersionUID = 1
User user
Role role
...
}
Then you can do this:
def rolesToIgnore = ["ROLE_ADMIN"]
def userIdsWithGivenRoles = UserRole.where {
role.authority in rolesToIgnore
}.list().collect { it.user.id }.unique()
def userIdsWithoutGivenRoles = UserRole.where {
!(role.authority in rolesToIgnore)
}.list().collect { it.user.id }.unique()
I suck at projections, so I remove duplicates with unique().
The SQL equivalents are:
SELECT DISTINCT ur.user_id
FROM user_role AS ur INNER JOIN role AS r
ON ur.authority_id = r.id
WHERE r.authority IN ('ROLE_ADMIN');
SELECT DISTINCT ur.user_id
FROM user_role AS ur INNER JOIN role AS r
ON ur.authority_id = r.id
WHERE r.authority NOT IN ('ROLE_ADMIN');
You can do something like it:
return UserRole.createCriteria().list {
distinct('user')
user {
ne("enabled", false)
}
or {
user {
eq('id', springSecurityService.currentUser.id)
}
role {
not {
'in'('authority', ['ADMIN', 'EXECUTIVE'])
}
}
}
}
With distinct('user')
you only will get the Users
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.