Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

grails spring security role and group

I have configured my spring security to work with groups.

I used this scipt to create the domain classes:

grails s2-quickstart com.yourapp User Role --groupClassName=RoleGroup

I assumed that a user can have many groups where a group can have many roles

This is what a generated method looks like in the User class:

Set<RoleGroup> getAuthorities() {
    UserRoleGroup.findAllByUser(this).collect { it.roleGroup }
}

But now I seen that the script also created a class called UserRole which is the association between User and Role. So a User can ALSO have many Roles directly?

I tried it and it is saved in the database. I modified the method like this:

def getAuthorities() {
    def authorities = UserRoleGroup.findAllByUser(this).collect { it.roleGroup }

    authorities.addAll(UserRole.findAllByUser(this).collect { it.role })
    return authorities
}

Now, when I create a entry in the database in the User <--> Role association. I cannot login anymore. I get the default message by spring security basically saying that no credentials have been found.

When I delete the entry manually, I can login again. I think this is because the method is then only returning RoleGroup objects.

My questions are:

a) can I also assign roles directly when I have configured Groups

b) if no, why is the script creating this class

c) if yes, how do I do that?

like image 254
Bernhard Avatar asked Dec 09 '15 15:12

Bernhard


3 Answers

I don't think one would expect you to assign a Role directly to User when you are using Groups.

Assign a Group to User ,and Role to Group.

I think the presented code structure might be useful when "downgrading" your app

to use only User and Roles, Without breaking your current set of rules.

like image 137
shaydel Avatar answered Oct 24 '22 01:10

shaydel


It's only my opinion: script created UserRole because it just optimisation class for example if you receive Role from db and tryied to find some user hibernate should been receive all user from proxy. It just optimisation class. If you want one role per user you can set up it in UserRole.create() Add restriction and it's should work. Hope you will understand me and I understood you right. Have a nice day

like image 40
Koloritnij Avatar answered Oct 24 '22 00:10

Koloritnij


A bit old but might come in hand for anyone else trying groups:

In the User class:

Set<RoleGroup> getAuthorities() {
    (UserRoleGroup.findAllByUser(this) as List<UserRoleGroup>)*.roleGroup as Set<RoleGroup>
}



Set<Role> getRoles() {
  (UserRoleGroup?.findAllByUser(this)?.roleGroup?.authorities.collect{it}?.flatten() ?: oldRoles) as Set<Role>
}

List<String> getRoleNames() {
        (UserRoleGroup?.findAllByUser(this)?.roleGroup?.collect{it.authorities.authority}?.flatten()?: oldRoles.authority) as List<String>
}

//Above will look up from userRoleGroup roleGroup.authorities =  UserRole below
Set<Role> getOldRoles() {
    (UserRole.findAllByUser(this) as List<Role>)*.role as Set<Role>
}

I had been using Roles even though groups was enabled and authenticating against the old oldRoles method:

import grails.plugin.springsecurity.userdetails.GormUserDetailsService
import grails.transaction.Transactional
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.authority.SimpleGrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UsernameNotFoundException

class MySpringSecurityAuthenticator extends GormUserDetailsService{

    UserDetails loadUserByUsername(String username, boolean loadRoles)
            throws UsernameNotFoundException {
        return loadUserByUsername(username)
    }

    @Transactional
    UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //enable login with either username or password
        User user = User.find {
            username == username || attributes.emailAddress == username || userCode == username
        }
        //if (!user) throw new UsernameNotFoundException('User not found', username)
        if (!user) throw new UsernameNotFoundException('User not found')
       return loadUserByUsername( user)
    }
    @Transactional
    UserDetails loadUserByUsername(User user) throws UsernameNotFoundException {
        if (!user) throw new UsernameNotFoundException('User not found')
       //UserDetails.withNewSession {
//getAuthorities(user.oldRoles)
            UserDetails userDetails = new org.springframework.security.core.userdetails.User(user.username, user.password,
                    user.enabled, !user.accountExpired, !user.passwordExpired, !user.accountLocked,getAuthoritiesFromGroup(user.authorities) )
            Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities())
            SecurityContextHolder.getContext().setAuthentication(authentication)
            return userDetails
        //}
    }

    public static List<GrantedAuthority> getAuthoritiesFromGroup(Set<RoleGroup>  roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>()
        roles?.each { role ->
            println "-- role = ${role} vs ${role.getClass()}"
            authorities.add(new SimpleGrantedAuthority(role.name))
        }
        return authorities
    }
    public static List<GrantedAuthority> getAuthorities(Set<Role> roles) {
        List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>()
        roles?.each { role ->
            println "-- role = ${role} vs ${role.getClass()}"
            authorities.add(new SimpleGrantedAuthority(role.authority))
        }
        return authorities
    }
}

and in my spring/resources.groovy:

userDetailsService(MySpringSecurityAuthenticator){
    grailsApplication = ref('grailsApplication')
}

What above had been doing so far was going back to user ROLES and adding them to authentication handler via.. getAuthorities(user.oldRoles)

I have now changed the above method to read in the group names via getAuthoritiesFromGroup(user.authorities)

This simply parses the group names which as a side (effect) of version I am using must also include the word ROLE_GROUPNAME

So now if create

@Transactional
def grantPermission(User user, String  role='ROLE_ADMIN', String group='ROLE_SUPERGROUP') {
    def adminRole = Role.findByAuthority(role)
    if (!adminRole) {
        adminRole = new Role(authority: role).save(flush: true)

    }
    UserRole.create user, adminRole, true

    def adminRoleGroup = RoleGroup.findByName(group)
    if (!adminRoleGroup) {
        adminRoleGroup = new RoleGroup(name: group).save(flush: true)
    }
    def adminRoleGroupRole = RoleGroupRole.findByRole(adminRole)
    if (!adminRoleGroupRole) {
        adminRoleGroupRole = new RoleGroupRole(role: adminRole, roleGroup: adminRoleGroup).save(flush: true)
    }
    def alreadyDone = UserRoleGroup.findByUserAndRoleGroup(user,adminRoleGroup)
    if (!alreadyDone) {
        new UserRoleGroup(user: user, roleGroup: adminRoleGroup).save(flush: true)
    }
}

I am expecting to authenticate against the group names and not the user roles so in short I have had to change my controller to

@Secured("hasAnyRole('ROLE_SUPERGROUP')")

hope it makes sense should be straight forward to follow just takes time to get your head around it all ..

At this point I am still playing around and I wouldn't use as the concrete answer since I am kind of hacking it to say add my groups as my authorities and if I wanted to I could add another hook to go further through each of those groups and each of the actual roles into it as well - unsure what that will produce - at the moment

I want to change it to requestMaps and move it over to db so a lot to change and will decide if i should revert back or use groups I know this way I can configure less names over controllers and rely on the group names..

Either way its under the hood of it all and gives you a clear idea a few years on but may come in handy for others

Update So I have decided to go with: ,getAuthorities(user.roles)

Where Set<Role> getRoles() { method inside the above bit of code

The reason is quite simple:

results?.each {
            it.user=User.get(it.id)
            println "-- \n${it.id}:${it.user.roles} \n${it.id}:${it.user.oldRoles}"


7:[Role(authority:ROLE_ADMIN), Role(authority:ROLE_ADMINAHHA)] 
7:[Role(authority:ROLE_ADMIN)]

As you can see I added a new user using the getOldRoles I only see 1 role on the getRoles I get 2 roles.. I did add user with 2 roles..

So this will now parse through all the user roles and add them to List<GrantedAuthority> the authentication will still be via the actual ROLE names generated as previously.

It just means when I disable a group from the user this should stop loading that permission for that user..

that is what the model should be doing

like image 32
V H Avatar answered Oct 24 '22 01:10

V H