I am working on a Grails 3.2.2 Plugin in which I am trying to override the RegisterController from spring-security-ui as well as the register.gsp. I have used the s2ui-override command which has created me the required controller and at the moment I only added a println to see it working, but it seems to be still going to the original one. So far I have:
RegisterController
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UserDetails
import grails.plugin.springsecurity.ui.RegisterCommand
class RegisterController extends grails.plugin.springsecurity.ui.RegisterController {
static defaultAction = 'register'
UserDetailsService userDetailsService
#Override
def register(RegisterCommand registerCommand) {
println "IN MY CONTROLLER!!"
def registerReturn = super.register(registerCommand)
if (registerReturn?.emailSent) {
UserDetails createdUser = userDetailsService.loadUserByUsername(registerCommand.username)
println "New user ${createdUser}"
}
registerReturn
}
def otherAction() {
println "IN MY CONTROLLER!!"
println "userDetailsService = ${userDetailsService}"
"Hellloooo"
}
}
register.gsp same as the original, only added
<p style="color:red;"> HELLLOOOOOOO </p>
immediately after the body tag
UrlMappings
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?(.$format)?"{
constraints {
// apply constraints here
}
}
"/"(view:"/index")
"500"(view:'/error')
"404"(view:'/notFound')
}
}
Result when running the app:
I can see my sample red text at the top, but cannot see the output in the console, although I am getting a warning when running the app. Console output when going to the "register" URL:
Running application...
WARNING: The [register] action accepts a parameter of type [grails.plugin.springsecurity.ui.RegisterCommand] which does not implement grails.validation.Validateable. Data binding will still be applied to this command object but the instance will not be validateable.
#Override
^
Configuring Spring Security Core ...
... finished configuring Spring Security Core
Configuring Spring Security UI ...
... finished configuring Spring Security UI
Configuring Spring Security REST 2.0.0.M2...
... finished configuring Spring Security REST
Grails application running at http://localhost:8060 in environment: development
Though when going to "register/otherAction" I am getting:
IN MY CONTROLLER
userDetailsService = grails.plugin.springsecurity.userdetails.GormUserDetailsService#10ed50b0
Interestingly, if I publish the plugin locally (my .m2 dir) and import it into an app, I am getting my printlns in the console, BUT my gsp is now not being used (I cannot see my red Hello text). Console output:
IN MY CONTROLLER!!
// unimportant mailing error
New user grails.plugin.springsecurity.userdetails.GrailsUser#364492: Username: test; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: false; Granted Authorities: ROLE_NO_ROLES
Anyone can help on where am I going wrong? I am happy to provide any other resources which may help.
So I have a fix (maybe a workaround):
RegisterController
class RegisterController extends grails.plugin.springsecurity.ui.RegisterController {
static namespace = 'my-great-plugin'
UserDetailsService userDetailsService
#Override
def register(RegisterCommand registerCommand) {
println "IN MY CONTROLLER!!"
def registerReturn = super.register(registerCommand)
println "Got return from super call ${registerReturn}"
if (registerReturn?.emailSent) {
UserDetails createdUser = userDetailsService.loadUserByUsername(registerCommand.username)
println "New user ${createdUser}"
}
registerReturn
}
def otherAction() {
println "IN MY CONTROLLER"
println "userDetailsService = ${userDetailsService}"
"Hellloooo"
}
MyPluginUrlMappings.groovy
static mappings = {
"/register/$action?/$id?(.$format)?" {
controller = 'register'
namespace = 'my-great-plugin'
}
}
MyPluginGrailsPlugn.groovy
def loadAfter = ['springSecurityCore', 'springSecurityUi', 'springSecurityRest']
And it works now!
I am still open to suggestions on how to handle this better
Related
I'm adapting an existing Grails 3 project to a multi-tenant structure, using the schema mode provided by GORM, and I'm having trouble getting the GORM listeners to work when I specify a tenant.
My listener looks like this:
#CompileStatic
class VehicleListenerService {
#Listener(Vehicle)
void onPreInsertEvent(PreInsertEvent event) {
println "*** Vehicle preInsert"
event.entityAccess.setProperty('model', 'preInsert')
}
#Listener(Vehicle)
void onPreUpdateEvent(PreUpdateEvent event) {
println "*** Vehicle preUpdate"
event.entityAccess.setProperty('model', 'preUpdate')
}
}
So every time a vehicle is created or updated, its model should be changed to preInsert or preUpdate.
The current tenant is determined by the subdomain specified in the URL. If I access the app with no subdomain (via http://localhost:8080), the listener works as expected, but if I provide a subdomain (http://test.localhost:8080), the listener doesn't do anything, and the vehicle model doesn't change.
What do I have to do to make the GORM listener work with any tenant?
I've created a sample project (https://github.com/sneira/mtschema) which reproduces the error.
With help from the Grails Slack channel and some more research, I've come up with a solution to this.
First, the listener service has to extend AbstractPersistenceEventListener:
#CompileStatic
class VehicleListenerService extends AbstractPersistenceEventListener {
protected VehicleListenerService(Datastore datastore) {
super(datastore)
}
#Override
protected void onPersistenceEvent(AbstractPersistenceEvent event) {
String newModel =
event.eventType == EventType.PreInsert ? 'preInsert' : 'preUpdate'
event.entityAccess.setProperty('model', newModel)
}
#Override
boolean supportsEventType(Class<? extends ApplicationEvent> eventType) {
boolean supportsEvent = eventType.isAssignableFrom(PreInsertEvent) ||
eventType.isAssignableFrom(PreUpdateEvent)
return supportsEvent
}
}
Now we can create a service instance for each schema (except for the default) in Bootstrap.groovy, and add it to our app:
def init = { servletContext ->
def ctx = grailsApplication.mainContext
['TEST', 'TEST2'].each { String name ->
HibernateDatastore ds = hibernateDatastore.getDatastoreForConnection(name)
VehicleListenerService listener = new VehicleListenerService(ds)
ctx.addApplicationListener(listener)
}
}
I've uploaded the complete code to https://github.com/sneira/mtschema/tree/listeners.
I would like to mock a service method in an integration test for one test, however I don't know how to get a reference to the service as it's added to the controller via dependency injection. To further complicate things the service is in a webflow, but I know it's not stored in the flow as the service is not serialized.
Ideal mocking scenario:
Get reference to the service
Mock the method via the metaClass
Main test
Set the metaClass to null so it's replaced with the original
Methods like mockFor so far don't seem to effect the service.
Example of the setup:
Controller:
package is.webflow.bad
import is.webflow.bad.service.FakeService
class FakeController
{
def index = {
redirect(action: 'fake')
}
def fakeFlow = {
start {
action {
flow.result = fakeService.fakeCall()
test()
}
on('test').to('study')
}
study {
on('done').to('done')
}
done {
System.out.println('done')
}
}
}
Service:
package is.webflow.bad.service
class FakeService
{
def fakeCall()
{
return 'failure'
}
}
Test:
package is.webflow.bad
import static org.junit.Assert.*
import grails.test.WebFlowTestCase
import is.webflow.bad.service.FakeService
import org.junit.*
class FakeControllerFlowIntegrationTests extends WebFlowTestCase
{
def controller = new FakeController()
def getFlow() { controller.fakeFlow }
String getFlowId() { "fake" }
#Before
void setUp() {
// Setup logic here
super.setUp()
}
#Test
void testBasic()
{
startFlow()
assertCurrentStateEquals 'study'
assertEquals 'failure', getFlowScope().result
}
#Test
void testServiceMetaClassChange()
{
// want to modify the metaClass here to return success
startFlow()
assertCurrentStateEquals 'study'
assertEquals 'success', getFlowScope().result
}
}
You can inject the service into your Integration test using "#AutoWired" or using application context you get reference. Am i missing something?
#Autowired
private YourService yourservice;
or
#Autowired
private ApplicationContext appContext;
YourService yourService = (YourService)appContext.getBean("yourService");
Here you go:
void "test something"() {
given: "Mocked service"
someController.someInjectedService = [someMethod: { args ->
// Mocked code
return "some data"
] as SomeService
when: "Controller code is tested"
// test condition
then: "mocked service method will be called"
// assert
}
I am using grails 2.2.2 and spring-security-core:2.0-RC2
My domain User object does not actually get deleted from the database - instead i set a property called "deleted" to true.
The problem is: how can I stop spring security from granting successful login attempts to a user id that is marked as deleted?
I'd like to be able to support creating a new user with a previously deleted name.
Burt Beckwith's answer below got me on the track of checking out overriding the spring security bean implementaions.
I've tried overriding a couple of the methods in the userDetailsService bean and springSecurityService with my implementations as shown below (all I did was use the same parent class implementation but change the User.findWhere() methods to use deleted: false).
I've also added these bean definitions to resources.groovy but i find that sometimes the original SpringSecurityService.getCurrentUser() method is called instead of my implementation. (If i change the spring security source to my implementation this all works fine, but I'd rather use an override so future version upgrades don't break).
class MySpringSecurityService extends SpringSecurityService {
#Override
Object getCurrentUser() {
if (!isLoggedIn()) {
return null
}
String className = SpringSecurityUtils.securityConfig.userLookup.userDomainClassName
String usernamePropName = SpringSecurityUtils.securityConfig.userLookup.usernamePropertyName
grailsApplication.getClassForName(className).findWhere(
(usernamePropName): principal.username,
deleted: false)
}
}
class MyUserDetailsService extends GormUserDetailsService {
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, deleted: false)
if (!user) {
log.warn "User not found: $username"
throw new NoStackUsernameNotFoundException()
}
Collection<GrantedAuthority> authorities = loadAuthorities(user, username, loadRoles)
createUserDetails user, authorities
}
}
}
My resources.groovy looks something like this:
beans = {
userDetailsService(MyUserDetailsService)
springSecurityService(MySpringSecurityService) {
authenticationTrustResolver = ref('authenticationTrustResolver')
grailsApplication = ref('grailsApplication')
passwordEncoder = ref('passwordEncoder')
objectDefinitionSource = ref('objectDefinitionSource')
userDetailsService = ref('userDetailsService')
userCache = ref('userCache')
}
}
This isn't tested, but should work or be very close to working.
You'll need to override the logic in the preAuthenticationChecks Spring bean that the plugin registers. The default implementation does the isAccountNonLocked(), isEnabled(), and isAccountNonExpired() checks, so you can subclass it and add your check(s):
package com.foo.bar
import grails.plugin.springsecurity.userdetails.DefaultPreAuthenticationChecks
import org.springframework.security.authentication.AccountStatusException
class MyPreAuthenticationChecks extends DefaultPreAuthenticationChecks {
private static final EXCEPTION = new DeletedException()
void check(UserDetails user) {
// do the standard checks
super.check user
// then the custom check(s)
if (user.deleted) {
log.debug 'User account is deleted'
throw EXCEPTION
}
}
static class DeletedException extends AccountStatusException {
LockedException() {
super('User account is deleted')
}
// avoid the unnnecessary cost
Throwable fillInStackTrace() {
this
}
}
}
Put this in a subdirectory of your /src/groovy directory corresponding to the package you choose, and register yours in grails-app/conf/spring/resources.groovy:
import com.foo.bar.MyPreAuthenticationChecks
beans = {
preAuthenticationChecks(MyPreAuthenticationChecks)
}
Note that the "user" instance you'll be working with is a UserDetails instance, not the domain class instance that is loaded to populate the UserDetails. So to have access to the deleted property you'll also need a custom UserDetailsService. This is easy and a common enough thing to do that it has its own chapter in the docs: http://grails-plugins.github.io/grails-spring-security-core/docs/manual/guide/userDetailsService.html
Note that having the exception as a static inner class, using a singleton, and overriding the fillInStackTrace method are all optional and independent of this question. You can put it in its own top-level class if you prefer (it makes sense to me to keep it internal since it's unlikely to be used outside of this class). You can also create a new instance each time, but there's no difference between instances (there's no useful state) so that's not needed. I override to fillInStackTrace to avoid incurring the cost of filling all those stack frames when they're not going to be needed at all.
Late, but if it serves somebody, here it is:
Just simplifying Burt Beckwith's answer, if you just want to throw the same exception as if the user does not exist, you can just extend DefaultPreAuthenticationChecks like this (where Person is the table with the users), and there is no need for a custom UserDetailsService:
import grails.plugin.springsecurity.userdetails.DefaultPreAuthenticationChecks;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
class MyPreAuthenticationChecks extends DefaultPreAuthenticationChecks {
void check(UserDetails user) {
// do the standard checks
super.check user
if (Person.findByUsername(user.getUsername())?.deleted){
log.debug("Wrong credentials");
throw new UsernameNotFoundException("Wrong credentials");
}
}
}
I am lookin for a way to let the admin-role of my grails-app add a "feature"/"plugin"
to the running server, so that the system makes use of it instantly.
To be more concrete here is a small example:
package domains
abstract class Provider {
def protected name;
def protected activated = false;
def private valid;
def Provider( providerName ) {
if( providerName != null ) {
name = providerName;
valid = true;
else valid = false;
}
def isValid() { valid }
def getName() { name }
def isActivated() { activated }
def setActivated( bool ) { activaed = bool }
abstract List<String> search( String searchKey );
}
Some Subclass:
package googleprovider
import Provider;
class GoogleProvider extends Provider {
def GooleProvider( active ) {
super( "Google" );
activated = active;
}
#Override
List<String> search( String searchKey ) {
return ["http://www.google.com"]
}
}
Now every "plugin"/"feature" should extend from Provider and be placed as what ever file in a directory "plugins/providers/".
And the server should create an instance of this GoogleProvider on an "onAdd"-event or something leashed by that admin.
Is there any chance this could be done? Or am I totally dreaming?
If it is somehow possible and it's just that I am going a completly wrong direction,
just tell me! ;-)
Thanks for your time!
I suggest you look for plugins that registers new Artefacts, so in your startup you can lookup for this classes. You can also create a folder in grails-app to store the providers classes.
See the Grails Dao Artefacts plugin, for example. It creates the daos folder inside grails-app, and consider all classes as a DAO Artefact. You also gain the ability of use Depenceny Injection in your classes (e.g. services).
Some points to look
Install Script creates the directory
You have some classes that declare the Artefact
The plugin descriptor is responsible to register them as Spring Beans
More info in
Grails developer wiki
Blog post with example
Im having some problems trying to actually determine if my beans have been properly loaded.
Is there some log4j property which can show me in the log what beans that are properly loaded?.
After some trying i went off and tried another example from here
redefining my resources.groovy like follows:
import grails.spring.BeanBuilder
BeanBuilder bb = new BeanBuilder()
bb.beans = {
ldapUserDetailsMapper(example.CustomDetailsContextMapper) {
}
}
CustomDetailsContextMapper in its turn is defined as:
class CustomDetailsContextMapper implements UserDetailsContextMapper {
def springSecuritySource
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection authorities) {
This is the error im getting when using this kind of resource.groovy setup:
2012-10-12 12:52:31,663 [main] ERROR spring.GrailsRuntimeConfigurator - [RuntimeConfiguration] Unable to load beans from resources.groovy
org.codehaus.groovy.runtime.metaclass.MissingPropertyExceptionNoStack: No such property: beans for class: resources
Possible solutions: class
When not using beanbuilder i get no errors but i cant really verify that my beans are loaded, since they seem never to be called.
In the other case i get the below error.
Any suggestions?
in resource.groovy simply write
beans = {
ldapUserDetailsMapper(example.CustomDetailsContextMapper) {
}
The BeanBuilder is already defined for you
A bean is called when used... inside a controller try thi
VeryBeanController{
def ldapUserDetailsMapper
def index = {
render( ldapUserDetailsMapper )
}
}
And call the controller