1

I'm on Grails 2.4.4, using Spring Security Core 2.0-RC4, Spring Security REST 1.5.0.M1 with functional Spock 0.7.

I'm using a functional test to test a controller for the REST API I'm building. When I set up the test, I create a User, an admin Role and then create a UserRole. When I check to see that the User has been persisted with the appropriate credentials INSIDE of the spock functional test, I see that everything is okay.

However, when authenticating, GormUserDetailsService doesn't yield anything when calling User.findWhere(username:<appropriate username>). The specific method is from GormUserDetailsService is here:

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

    def conf = SpringSecurityUtils.securityConfig
    String userClassName = conf.userLookup.userDomainClassName
    def dc = grailsApplication.getDomainClass(userClassName)
    if (!dc) {
        throw new IllegalArgumentException("The specified user domain class '$userClassName' is not a domain class")
    }

    Class<?> User = dc.clazz

    User.withTransaction { status ->
        def user = User.findWhere((conf.userLookup.usernamePropertyName): username)
        if (!user) {
            log.warn "User not found: $username"
            throw new NoStackUsernameNotFoundException()
        }

        Collection<GrantedAuthority> authorities = loadAuthorities(user, username, loadRoles)
        createUserDetails user, authorities
    }
}

The problem is that the User.findWhere() call doesn't find anything, even thought I've confirmed in my functional test that I've flushed the User instance to the database. I've tried printin all of the UserRoles and Users with findAll() calls inside of the withTransaction block of this method and they still return nothing.

Anyone have any thoughts? Should I be mocking something that I'm not? I'm using @TestFor and @Mock annotations on the functional test.

UPDATE

Here is a simple version of my functional test:

package com.help

import grails.plugins.rest.client.RestBuilder
import grails.plugins.rest.client.RestResponse
import grails.test.mixin.Mock
import grails.test.mixin.TestFor
import groovy.json.JsonBuilder
import org.codehaus.groovy.grails.web.json.JSONObject
import spock.lang.Shared
import spock.lang.Specification

@TestFor(PersonController)
@Mock([Person, Role, PersonRole])
class PersonControllerSpec extends Specification {

    @Shared 
    RestBuilder rest = new RestBuilder(connectTimeout:10000, readTimeout:20000)
    @Shared 
    String baseUrl = "http://localhost:8080"
    @Shared
    int iterationCount = 0 

    Collection<Person> persons = []

    def setup() {
        Role adminRole = Role.findByAuthority("ROLE_ADMIN") ?: new Role(authority:"ROLE_ADMIN").save(failOnError:true, flush:true)
        Role userRole = Role.findByAuthority("ROLE_USER") ?: new Role(authority:"ROLE_USER").save(failOnError:true, flush:true)

        Person admin = new Person(name:"reserved", keyword:"admin", password:"password", email:"email@email.com", phone:"2222222222", personalPhoneNumber:"1112223333").save(failOnError:true, flush:true)
        Person user = new Person(name:"reserved", keyword:"user", password:"password", email:"email@email.com", phone:"2222222222", personalPhoneNumber:"1112223333").save(failOnError:true, flush:true)

        PersonRole.create(admin, adminRole, true)
        PersonRole.create(user, userRole, true)

        for (i in 0..20) {
            persons << new Person(name:"person-$iterationCount-$i", password:"password", keyword:"p-$iterationCount-$i", email:"email@email.com", phone:"1112223333", personalPhoneNumber:"1112223333")
        } 
        persons*.save(failOnError:true, flush:true)
    }

    def cleanup() { iterationCount++ }

    void "test listing persons"() {
        when: "we have an authenticated request"
        JsonBuilder jsonBuilder = new JsonBuilder()
        JSONObject json = jsonBuilder keyword:"admin", password: "password"
        RestResponse authResponse = rest.post("$baseUrl/api/login") {
            contentType "application/json"
            body(json)
        }

        then: "can access both restricted and public resources"
        ...
    }
}

Here are the spring security settings in Config.groovy:

grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.help.Person'
grails.plugin.springsecurity.userLookup.usernamePropertyName = 'keyword'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.help.PersonRole'
grails.plugin.springsecurity.authority.className = 'com.help.Role'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    '/':                              ['permitAll'],
    '/index':                         ['permitAll'],
    '/index.gsp':                     ['permitAll'],
    '/assets/**':                     ['permitAll'],
    '/**/js/**':                      ['permitAll'],
    '/**/css/**':                     ['permitAll'],
    '/**/images/**':                  ['permitAll'],
    '/**/favicon.ico':                ['permitAll'],
    '/dbconsole/**':                  ['ROLE_USER', 'ROLE_ADMIN']
]
grails.plugin.springsecurity.filterChain.chainMap = [
    '/v1/public/**': 'anonymousAuthenticationFilter,restTokenValidationFilter,restExceptionTranslationFilter,filterInvocationInterceptor',
    '/v1/**': 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter',  // v1 rest api stateless
    '/api/**': 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter',  // api utility methods stateless
    '/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter'
]

grails {
    plugin {
        springsecurity {
            rest {
                login.useJsonCredentials = true
                login.usernamePropertyName = "keyword"
                token {
                    rendering.usernamePropertyName = "keyword"
                    rendering.authoritiesPropertyName = "roles"
                    rendering.tokenPropertyName = "access_token"
                    validation.useBearerToken = true
                    validation.enableAnonymousAccess = true
                }
            }
        }
    }
}
  • Does this work when you are not running the test and simply running the app? Is it possible that the spring security properties aren't setup correctly in your Config.groovy? What are you mocking in a functional test? – th3morg Feb 18 '15 at 00:54
  • Yup! Works just fine when I'm running the app. Let me update the answer with my springsecurity properties and with how I'm setting up mocks in my functional test. I think it has something to do with not mocking some aspect of GORM that I should be mocking, but I don't know enough about spock or mocks to tell. – user3780979 Feb 18 '15 at 22:22
  • I could be mistaken, but I think it is kind of unusual to create Mocks during a functional test. My understanding is that these should be closer to Integration tests than Unit tests, meaning the app actually starts up with an embedded container and data source, etc. This is how you can make HTTP requests to your controllers. Trying to work mocks into this seems unnatural. You can still use Spock, but I think you should create and cleanup data with a data source instead of trying to Mock the domains. – th3morg Feb 19 '15 at 02:47
  • I see. Can you elaborate on what you mean by "create and cleanup data with a data source"? Should I be extending `IntegrationSpec` instead of `Specification`? I felt I needed to use Mocks because I couldn't create `Person` instances to test on otherwise. Thank you! – user3780979 Feb 19 '15 at 05:51
  • You could use IntegrationSpec, sure. You are calling .save() on your objects, but it looks like you've mocked those objects, so they won't actually save to a database. So I would expect that you don't Mock those and at the end of your test in a cleanup block you call .delete() on each object created. I'm a bit out of touch with Grails right now, but I think this is still the correct approach. – th3morg Feb 20 '15 at 04:03
  • Thanks for the help! Sorry for taking so long to get back. Turns out that `IntegrationSpec` didn't work either because the `ApplicationContext` isn't initialized in spock functional tests. I ended up needing to install the remote control plugin and that solved my problems because the server instance was running on a separate jvm (forked jvm mode) and so all of the calls needed to be send through the remote. – user3780979 Mar 09 '15 at 23:33

1 Answers1

1

Turns out the problem of domain classes not showing up on the server instance has to do with the fact that the server instance in functional tests is running on a separate jvm (forked jvm mode) and so all of the calls needed to be send through the remote.

I ended up using the remote-control plugin as follows (recall that I'm on Grails 2.4.4) as seen in BuildConfig.groovy:

plugins {
    ...
    compile ":rest-client-builder:2.0.4-SNAPSHOT"
    compile ":remote-control:1.5"
    ...
}

I had several issues with sending requests to the right urls that were resolved after updating to the latest versions as seen above. Also, don't forget to configure your springsecurity core settings to permitAll for the grails-remote-control url.

  • Hey, can you tell my how you configured your spring security core to permitAll for the grails-remote-control url? – Jim Chertkov Jun 10 '15 at 13:16
  • 1
    Sure thing. In `Config.groovy`, write this: `grails.plugin.springsecurity.controllerAnnotations.staticRules = [ '/grails-remote-control': ['permitAll'] ]` – user3780979 Jun 11 '15 at 14:54