hit a problem and think its a bug (?) in the grails spring security 3.1.1, and latest grails 3.2.6.
i have installed the spring security plugin.
from the command line console i did the following
grails s2-quickstart org.softwood.security User Role --groupClassName=UserGroup
to create a user, role, and UserGroup table as i want to use the allocate roles to groups feature. I then configured the domain classes a tad, and added a few users in the bootstrap to test it out like this
def loadSecurityUserAndRoles () {
//plugin requires ROLE_ prefix see section 4.2/p18
Role adminRole = new Role(authority: 'ROLE_ADMIN').save(failOnError:true)
Role userRole = new Role(authority: 'ROLE_USER').save(failOnError:true)
Role xtraRole = new Role(authority: 'ROLE_XTRA').save(failOnError:true)
UserGroup adminGroup = new UserGroup (name:"GROUP_ADMIN").save(failOnError:true)
UserGroup userGroup = new UserGroup (name:"GROUP_USERS").save(failOnError:true)
User userWill = new User(username: 'will', password: 'password').save(failOnError:true)
User userMaz = new User(username: 'maz', password: 'password').save(failOnError:true)
User userMeg = new User(username: 'meg', password: 'password').save(failOnError:true)
//give adminGroup admin and user roles
UserGroupToRole sgr = UserGroupToRole.create(adminGroup, adminRole)
sgr = UserGroupToRole.create(adminGroup, userRole)
sgr = UserGroupToRole.create(userGroup, userRole)
assert UserGroupToRole.count() == 3
def auth2 = adminGroup.getAuthorities()
println "adminGroup authorities returned $auth2 "
//assign test user to adminGroup, and maz+meg to user group, inherit all group roles
UserToUserGroup su2g = UserToUserGroup.create (userWill, adminGroup, true)
su2g = UserToUserGroup.create (userMaz, userGroup, true)
su2g = UserToUserGroup.create (userMeg, userGroup, true)
//assign individual 'xtra' role to user
UserToRole sxtra = UserToRole.create(userWill, xtraRole, true)
assert UserToRole.count() == 1
def auth = userWill.getAuthorities()
assert auth.collect{it.authority}.sort() == ['ROLE_ADMIN', 'ROLE_USER', 'ROLE_XTRA']
println "userWill authorities returned $auth "
def mazAuth = userMaz.getAuthorities()
def megAuth = userMeg.getAuthorities()
println "user authorities returned maz: '$mazAuth', and meg: '$megAuth' "
def groups = userWill.getUserGroups()
assert groups.collect{it.name}.sort() == ['GROUP_ADMIN']
assert UserGroup.count() == 2
assert User.count() == 3
assert Role.count() == 3
assert UserToUserGroup.count() == 3
assert UserGroupToRole.count() == 3
assert UserToRole.count() == 1
}
this all seems to work as id expect and the basic asserts return the right numbers of roles for each user when i assert the .getAuthorities()
i then setup a controller secureTest with open action and secured one
class SecureTestController {
def index() {
render "hello Will you passed the permit_any"
}
@Secured ('ROLE_ADMIN')
def secure () {
render "hello Will you passed the ROLE_ADMIN"
}
}
i run the app - it starts, i point the browser in secureTest/index - works fine as open url
when i point the browser at secureTest/secure, it throws default login page. I fill in will/password at it throws stacktrace and fails to login
the key part of that trace is here i think
Caused by: groovy.lang.MissingPropertyException: No such property: authorities for class: org.softwood.security.Role
Possible solutions: authority
at org.grails.datastore.gorm.GormInstanceApi.propertyMissing(GormInstanceApi.groovy:55)
at org.grails.datastore.gorm.GormEntity$Trait$Helper.propertyMissing(GormEntity.groovy:57)
at org.grails.datastore.gorm.GormEntity$Trait$Helper$propertyMissing$9.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:48)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:113)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:133)
at org.softwood.security.Role.propertyMissing(Role.groovy)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaClassImpl.invokeMissingProperty(MetaClassImpl.java:880)
at groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:1861)
at groovy.lang.MetaClassImpl.getProperty(MetaClassImpl.java:3735)
at org.softwood.security.Role.getProperty(Role.groovy)
at org.codehaus.groovy.runtime.InvokerHelper.getProperty(InvokerHelper.java:172)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.getProperty(ScriptBytecodeAdapter.java:456)
at grails.plugin.springsecurity.userdetails.GormUserDetailsService$_loadAuthorities_closure2.doCall(GormUserDetailsService.groovy:92)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:93)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:294)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1024)
at groovy.lang.Closure.call(Closure.java:414)
at groovy.lang.Closure.call(Closure.java:430)
the method fails really here i think (GormUserDetailsService.groovy:92)
when you click that link the editor takes you to this in the plugin.
protected Collection<GrantedAuthority> loadAuthorities(user, String username, boolean loadRoles) {
if (!loadRoles) {
return []
}
def conf = SpringSecurityUtils.securityConfig
String authoritiesPropertyName = conf.userLookup.authoritiesPropertyName
String authorityPropertyName = conf.authority.nameField
boolean useGroups = conf.useRoleGroups
String authorityGroupPropertyName = conf.authority.groupAuthorityNameField
Collection<?> userAuthorities = user."$authoritiesPropertyName"
def authorities
if (useGroups) {
if (authorityGroupPropertyName) {
authorities = userAuthorities.collect { it."$authorityGroupPropertyName" }.flatten().unique().collect { new SimpleGrantedAuthority(it."$authorityPropertyName") }
}
else {
log.warn 'Attempted to use group authorities, but the authority name field for the group class has not been defined.'
}
}
else {
authorities = userAuthorities.collect { new SimpleGrantedAuthority(it."$authorityPropertyName") }
}
authorities ?: [NO_ROLE]
}
the key part here is this call sequence
if (useGroups) {
if (authorityGroupPropertyName) {
authorities = userAuthorities.collect { it."$authorityGroupPropertyName" }.flatten().unique().collect { new SimpleGrantedAuthority(it."$authorityPropertyName") }
}
useGroups is true. I have a authorityGroupPropertyName that was set in application.groovy file by quick install script
grails.plugin.springsecurity.authority.groupAuthorityNameField = 'authorities'
so this code line above calls
userAuthorities.collect { it."$authorityGroupPropertyName" }.flatten().unique()
this returns a hashSet of role.authority names as string and the flatten/unique just makes sure there are no nested structure and strings are unique. so far so good
the last bit is the bug i think.
<hashSet of role Names>.collect { new SimpleGrantedAuthority(it."$authorityPropertyName") }
in this bit the collect method is called on the set of strings but the string passed to 'SimpleGrantedAuthority' should just be the string. instead its calling
it."$authorityPropertyName"
where it is a string and has no such property
the key bits set up in application.groovy are
grails.plugin.springsecurity.userLookup.userDomainClassName = 'org.softwood.security.User'
grails.plugin.springsecurity.userLookup.authoritiesPropertyName = 'authorities'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'org.softwood.security.UserToUserGroup'
grails.plugin.springsecurity.authority.className = 'org.softwood.security.Role'
grails.plugin.springsecurity.authority.groupAuthorityNameField = 'authorities' //'authority'
grails.plugin.springsecurity.useRoleGroups = true
as you can see i tried to change authorities to 'authority' as thats the property name in the role class. that fails with missing property message also
I think this is a bug and the code should just have passed 'it'
.collect {new SimpleGrantedAuthority(it)}
to generate the hashSet of types.
Has any one else had this problem with spring security? I cant believe i'm the first to have fallen over it, or maybe no one is trying to use groups, not sure
would appreciate some feedback before i raise a defect on the project - and figure out how i work round it until its 'fixed'
thanks in advance
I have configured users role and role group too, this is how mine is working:
In user class:
In RoleGroup.groovy I have: (think I changed these unsure)
In my bootstrap something like this creates default admin account bound to role and role group:
ok vahid - think i've unpeeled the onion and found the issue.
step1. Went back to beginning and created a new project from scratch and added the grails-security plugin on empty project. From grails console used this as per oneline guide
grails s2-quickstart org.softwood User Role --groupClassName=RoleGroup
This worked, so i compared what it had generated with what i'd got myself into and think i understand where the problem for me was.
my role class was the same as empty test one - no difference.
then i got to the User class, and somehow i'd got the getAuthorities() returning Set (this seemed sensible as the getAuthorites() in my UserGroup (aka RoleGroup in fresh start) returns Set. And basic User/Role returns Set
if you generate a project with just User and Role, then User.getAuthorities() returns set. But when using a group the template is changed and Set was being returned.
That difference is crucial. as the code that adjusts its de-referencing strategy is in code in GormUserDetailsService.loadAuthorities() where it cant be seen it - see my 'tweaked version id been trying to fix ...
the problem with this is that the
def authorities
variable changes type depending on whether using user/role or user/group/role model.i fell bang into the middle of this was i was trying to use groups (default allocation model) but permit individual Role assignments (individual granted Roles, as override facility).
when i read the
User.authorities
property - i assumed this would return the Roles it was linked with (via groups, and via my individual assignment override) (like in single user/role model). I'd sorted this out in my domain class code like this in class userso that all seemed sensible and did what i wanted (my tests showed the the correct Roles assigned via the superposition). however the GormUserDetailsService wasn't expecting that and broke as if useGroups is true it de-references two sets to get a Set, and use that to build the SimpleGrantedAuthority Set from.
Upshot of this is i'm stuffed with what i was trying to do, its to far a deviation from the assumed model.
i'll make it as a suggestion on the git site, as it would just be so much cleaner and less opaque, and i think you intuitively assume that the
authorities
property on users will give you a Set.For now just going to have to stick with just the basic user/group/role model and live with that.
ok just before i go to bed - i copied the code out of the GormUserDetailsService into my bootstrap so i could expand expand/play in my own file space.
I modified the if block and expanded in out like this
my debug string shows this on the console
the first result returned into variable 'roles' is an ArrayList of String (each of the role.authority name instances. with correct values for this user as in earlier bootstrap setup for userWill.
the next of code now fails as i have an ArrayList of string, and it tries to access a property
which fails with
whichever way i cut this this single line in the original source just doesn't work. original line reads again
if it had read instead like this - the code would work
surely the code in the v3.1.1 plugin cant work ?