This appears to go against the "convention" nature of Grails. If I don't explicitly call the render() method with my view, then the service injection in my Interceptor is null.
Interceptor:
class GlobalParamsInterceptor {
def SysConfigService sysConfigService;
GlobalParamsInterceptor() {
matchAll();
}
boolean before() {
true
}
boolean after() {
model.isApplicationOpen = sysConfigService.isApplicationOpen();
true
}
void afterView() {
// no-op
}
}
Controller:
class ConfigController {
static namespace = "coordinator";
def index() {
render(view: "index");
}
}
If I comment out render(view: "index") in my controller then sysConfigService in my Interceptor is null, otherwise it works great. Can someone please explain why that is?
Edit:
Grails 3.1.3
Edit #2
It appears i was mistaken, the sysConfigService is not null...its the model that is null so I am unable to set the isApplicationOpen property on it. Just the same though, I don't believe I should have to call the render() method in order to access the model as I thought that was an inherited property from the framework.
If I comment out render(view: "index") in my controller then
sysConfigService in my Interceptor is null, otherwise it works great.
I don't think think that is possible. The dependency injection into your interceptor happens and is completed before any requests can ever enter the application. Invoking render (or anything else) in a controller action can't affect that.
Also, you have def SysConfigService sysConfigService. The def there is superfluous. You can delete it and the compiler will generate the same code. def is only sensible there if you remove SysConfigService.
EDIT:
I realized after posting that that I did not address the question in the title:
Is render() is required when using Interceptors?
The answer is "no".
In your example though, your calling render from your controller. My
issue is that If I don't call render and rely on the "convention" of
Grails to auto detect my "index.gsp" instead, that is when model in my
interceptor is NULL.
See the commit at https://github.com/jeffbrown/raymond/commit/f354047158038f571630ab7e3e0416850bdde8a8.
When I send a request to /demo/report I see the following output on stdout:
Model: [name:Raymond]
That output is coming from https://github.com/jeffbrown/raymond/blob/f354047158038f571630ab7e3e0416850bdde8a8/grails-app/controllers/demo/FooInterceptor.groovy#L13.
Related
interceptors typically come with a matchAll/match matchers. Especially for match
e.g.
match(controller: 'draws', action: 'index|details|prizeBreakdown')
My question is , when I do a GET to DrawsController, like
/draws
action name is
null
and consequently I never arrive in
boolean before()
How to match against these requests?
Thanks.
P.S. I am on Grails 3.2.7 with jdk 8u144
I would say this is a bug. One way to deal with it is something like this:
class SampleInterceptor {
SampleInterceptor() {
match controller: 'draws', action: 'index|details|prizeBreakdown'
match uri: '/draws'
}
boolean before() {
log.debug 'The before interceptor is executing'
true
}
}
If you file an issue at https://github.com/grails/grails-core/issues we can look into it.
Sorry for the trouble.
I'm carrying the locale of my Grails application in the URL, such as
http://myapp.com/LANG/controller/action/id.
Therefore, I adjusted the URLMappings.groovy with the language attribute. Since I would like to save work and don't apply the lang parameter at every link, I have overridden the g:link taglib with the language from params.lang. This works pretty good.
When I do a redirect from a controller, such as
redirect(action: "logout")
I'd like to append the params.lang automatically instead of writing every time
redirect(action: "logout", params: [lang: params.lang])
I found the thread grails override redirect controller method, but I'm unsure how to proceed. Where does this code take part?
How can I override the redirect() method and add the params.lang-attribute?
Please advice, thank you very much.
The easiest way to do this in your application would be to use a bit of meta programming and the BootStrap.groovy for your Grails application. The following is just a simple example of what that might look like:
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes
class BootStrap {
def init = { servletContext ->
def ctx = servletContext.getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT)
def app = ctx.getBean("grailsApplication")
app.controllerClasses.each() { controllerClass ->
def oldRedirect = controllerClass.metaClass.pickMethod("redirect", [Map] as Class[])
controllerClass.metaClass.redirect = { Map args ->
// new pre-redirect logic
if (!args['params']) args['params'] = [:] // just in case redirect was called without any parameters
args['params']['whatever'] = 'something' // add something into the parameters map
oldRedirect.invoke delegate, args
// new post-redirect logic
}
}
}
def destroy = {
}
}
The above example just wraps the implementation for redirect on all controllers within your Grails application and injects a new parameter called whatever with the value of something.
The above was tested (quickly) using Grails 2.4.2.
I am modifying the AccountController to use a separate class that queries Active Directory for information and stores that info in the Login Model. In my account controller I have this:
try{
LDAPAuth.LDAPQuery(model);
return RedirectToAction("Homepage", "HomePage");
}
catch (Exception e)
{
throw new Exception(e.message);
}
I am surrounding it in a try/catch becasue if the DirectorySearcher did not find the user it will tell them that the username or passwrod is wrong and I am just trying to pass the exception on the view. What is happening is that when it gets inside the try block the model is set perfectly fine with the attributes I want but the redirection takes me to
http://localhost:7606/Account/Login?ReturnUrl=%2fHomePage%2fHomepage
LDAPAuth class is implemented according to this solution, I also surrounded this in try/catch to catch invalid users which I am trying to pass to the View
http://stackoverflow.com/questions/1295157/how-do-i-query-activedirectory-using-ldap-with-a-username-not-a-cn
I am not sure what is the problem and debugging it is not helping either.
I hope somebody can help! Thanks
Before you redirect to action, issue a FormsAuthentication.SetAuthCookie. This should override the default behaviour of the return URL:
Its also worth noting that the bool in SetAuthCookie(string username, bool createPersistantCookie) can be used to remember the user. For this example I have set it to false.
try{
LDAPAuth.LDAPQuery(model);
FormsAuthentication.SetAuthCookie(model.UserName, false);
return RedirectToAction("Homepage", "HomePage");
}
Is it possible to inject a Spring bean into a Grails webflow? I tried the following
class CheckoutController {
ShoppingService shoppingService
def checkoutFlow = {
start {
action {
// This attempt to access the service doesn't work
flow.addresses = shoppingService.getOrder()
}
}
}
}
I can access shoppingService from a regular controller action, but I can't access it from an action of the webflow (see above).
add the following to your controller:
def transient shoppingService
There are issues with dependency injection with webflows in controllers that contain traditional actions plus webflows. It worked for me if the traditional action executed first.
see:
GRAILS-7095
GRAILS-4141
Webflows also break notions of defaultAction in mixed controllers. I have found the first webflow wins and becomes the default action.
separately using transient keeps your service from be serialized between flow states. (e.g. don't have to implement serializable)
At first I thought what you listed was pseudocode but I made a sample app using your example and got the NPE as well. I think it may be your flow structure that is the problem. action blocks should go within a flow state. Your flow definition should look something like:
class CheckoutController {
ShoppingService shoppingService
def checkoutFlow = {
start {
action {
flow.addresses = shoppingService.getOrder()
if(flow.addresses) {
showForm()
}
else {
showError()
}
}
on("showForm").to "showForm"
on("showError").to "showError"
}
showError {
...
}
//etc.
}
}
You can definitely use injected services in your web flows. I am guessing that the problem lies in your flow structure.
Now: resolved - no reproducible anymore
For some specific application security, I have the following createQuery function on a table, ie you can only access the table record if you have the "Administrator" credential or if you are the user that is stored in the MembershipDelegate relation.
class OrganisationTable extends Doctrine_Table
function createQuery($alias = ''){
if (!$alias){
$alias = 'o';
}
$query = parent::createQuery($alias);
try {
$user = sfContext::getInstance()->getUser();
}catch(Exception $e){
if ($e->getMessage() == 'The "default" context does not exist.'){
return $query;
}else{
throw $e;
}
}
if ($user->hasCredential('Administrator')){
//all good
}else{
$userId = $user->getAttribute('userId');
print "<!--testaa ".print_r($user->getCredentials(),1).'-->';
$query->
leftJoin("$alias.MembershipDelegate mdelsec")->
addWhere ("mdelsec.joomla_user_id=$userId");
}
return $query;
}
This seems to work fine at all levels, however there is a choice validator for which the $user object seems to come back empty
/**
* Person form base class.
*
*/
...
abstract class BasePersonForm extends BaseFormDoctrine
{
public function setup()
{
$this->setWidgets(array(
...
'organisation_id' => new sfWidgetFormDoctrineChoice(array('model' => $this->getRelatedModelName('Organisation'), 'add_empty' => true)),
class PersonForm extends BasePersonForm{
public function configure(){
$this->widgetSchema['organisation_id']->setOption('renderer_class', 'sfWidgetFormDoctrineJQueryAutocompleter');
$this->widgetSchema['organisation_id']->setOption('renderer_options', array(
'model' => 'Organisation',
'url' => NZGBCTools::makeUriJoomlaCompatible(
sfContext::getInstance()->getController()->genUrl('organisation/jsonList'))
));
$this->validatorSchema['organisation_id']->setOption('required',false);
is there any other way to get the user object in the model?
This approach to row level security may not be MVC by-the-book, but is IMO safer and superior than implementing the same security concepts in the actions:
It can be used with out of the box admin-generator modules
It is much harder to forget to implement it somewhere
It may at times not require any credentials, only Super Admin access
I'm not aware of another way to get the session user in the models (I don't think there is one), but if you're able to, you ought to pass the user object down to the models.
I think you are missing something here, you have mixed the MVC layers quite a lot. My suggestion is to make the Model independent from the Controller (delegate to the Controller the Context-specific situations), and validation should be done by the Controller also ( there should be an action that receives the request , binds the form and validates it. There is where you have to definy anithing thats context speficif). Here is what i would do:
The OrganisationTable class should have 2 methods: createQueryForAdministrator , and createQueryForMembershipDelegate . Each one does the things it should do, and now you should change the Controller (the action that handles it) and do something like:
public function executeAction(sfWebRequest $request)
{
if ($this->getUser()->hasCredential('administrator'))
{
//call the administrator method
} else {
//call the other method
}
}
The action that instances the Form should also check the user credentials and do something like:
If ($user->hasCredential("administrator"))
{
sfContext::getInstance()->getConfiguration()->loadHelper("Url");
$form->getValidator("organization_id")->setOption("url",url_for("#action"));
[..]
} else {
[..]
}
Check the url helper reference, you can do thinks like loading helpers on actions etc and you could also create your own helpers too. Hope this helps!
EDITED: Check this post about sfContext class and alternatives
It now appears that this question was a fluke as I can't reproduce the problem after fixing other issues around the user authentication.