I have a unique problem with passing an action message in a JSON result.
Right now, if I add an action message within my action (in a JSON action), I will pick that up in JavaScript and capture the action message and alert the user via JGrowl. However, I don't want the logic of adding an action message in each action. I have an underlying service project with a request context that is shared among the request, and I am able to add warning messages there. What I'd like to do is to transform those warning messages to action messages for use on my front end, but the action will never have to be aware of them. This is useful because I can insert warnings when accessing databases, or if there are hairy business rules, etc.
As I mentioned before, it already works when adding them directly in the action so I know the JSON result works fine and passes along the action messages correctly. We have an interceptor that is hit every time for managing this request context already, so I'd like to append on the action messages in this interceptor to the action being called.
However, the problem I'm finding is that I need to call actionInvocation.invoke() first as any warning messages will be generated as a result of that. After that, I check for the messages and attempt to apply them as action messages. These action messages never show up in my JSON response, so I'm wondering if it's possible to add those messages into the response in the interceptor AFTER the invoke() call. Here's the bulk of my intercept method:
try {
// Invoke the action.
String result = actionInvocation.invoke();
//add all warning messages as an action message to be displayed on that front end
if (CollectionUtils.isNotEmpty(context.getWarningMessages())) {
ActionSupport action = (ActionSupport) actionInvocation.getAction();
for (String s : context.getWarningMessages()) {
action.addActionError(s);
}
}
return result;
I tried adding the logic to add the action messages in the finally block instead of after the call to invoke() to no avail.
Thanks,
Andy
The result has been rendered by the time invoke returns.
You need to implement a PreResultListener as discussed in the "Writing Interceptors" docs.
Related
I'm trying to customise the response for authentication failures in Quarkus. I need to set a custom header, a custom response code and a custom response body. I developed my custom (Kotlin) HttpAuthenticationMechanism because I need a custom authentication logic. In it, I tried returning a failure Uni.
return Uni.createFrom().failure(AuthenticationFailedException())
And then implement my own ExceptionMapper
#Provider
#Priority(Priorities.AUTHENTICATION)
class AuthenticationFailedExceptionMapper : ExceptionMapper<AuthenticationFailedException> {
override fun toResponse(exception: AuthenticationFailedException): Response {
logger.info("====== custom auth error")
It doesn't get triggered. I have proactive authentication disabled:
quarkus.http.auth.proactive=false
Instead, I noticed that the getChallenge method in my custom HttpAuthenticationMechanism gets triggered, which can be used to overwrite the response, but it doesn't allow to set the body, just the result code and one header.
I also tried returning and mapping a different, custom exception, but then I only see some errors in the log and the client gets a 500 response code - the exception mapper is still not triggered. Why?
I'm using react-admin to update DB via internal API (not directly from the server react-admin is talking to).
The API call can fail due to various reasons.
Therefore, I want to hold the edit page until it gets the response from the server which is waiting for the response from the internal API.
Is there a way to do this?
You can set its mutationMode to pessimistic.
Extract from the documentation:
The <Edit> view exposes two buttons, Save and Delete, which perform “mutations” (i.e. they alter the data). React-admin offers three modes for mutations. The mode determines when the side effects (redirection, notifications, etc.) are executed:
-pessimistic: The mutation is passed to the dataProvider first. When the dataProvider returns successfully, the mutation is applied locally, and the side effects are executed.
-optimistic: The mutation is applied locally and the side effects are executed immediately. Then the mutation is passed to the dataProvider. If the dataProvider returns successfully, nothing happens (as the mutation was already applied locally). If the dataProvider returns in error, the page is refreshed and an error notification is shown.
-undoable (default): The mutation is applied locally and the side effects are executed immediately. Then a notification is shown with an undo button. If the user clicks on undo, the mutation is never sent to the dataProvider, and the page is refreshed. Otherwise, after a 5 seconds delay, the mutation is passed to the dataProvider. If the dataProvider returns successfully, nothing happens (as the mutation was already applied locally). If the dataProvider returns in error, the page is refreshed and an error notification is shown.
So what is happening is that I have a condition within my override of the HandleUnauthorizedRequest method in my custom authorize attribute. Up to this point, I've been throwing a 403 which gets picked up and redirects to a custom error page. Well now, that's not really what I want. What I actually want is to show the same login page but add a message to the validation summary "You do not have access to this resource.", that way it's a bit more user friendly. It'll indicate that your creds were good, but you don't belong here.
I thought something like this would work:
protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
{
// passed authentication, failed authorization
if (filterContext.HttpContext.User.Identity.IsAuthenticated)
{
filterContext.Controller.ViewData.ModelState.AddModelError("", "Not Authorized");
return;
}
base.HandleUnauthorizedRequest(filterContext);
}
But this isn't working. What's happening is that the login page simply reloads. So this makes me feel like I'm close, but I need that model error to show up.
Any ideas?
UPDATE:
It would seem that the Controller that I'm adding an error to here is actually controller of whichever action had the attribute that led to here. I need to somehow add the error to the login controller. Not sure if that's even possible.
You are calling the base method here:
base.HandleUnauthorizedRequest(filterContext);
If you are using Forms Authentication this base method simply redirects you to the login page. And a redirect means a new HTTP request from the client. The current context and whatever you stored in it is lost. Well, to be more precise, the base method is returning a 401 HTTP status code which is then intercepted by the FormsAuthenticationModule which redirects to the login page defined in your web.config. But this implementation details is not important.
What you could do is perform the redirect yourself to the login page instead of leaving it to the base method. You could do this by setting the filterContext.Result property to a RedirectToRouteResult instance. In this case you could pass the error message as a query string parameter.
UPDATE:
According to your updated question it seems that you are calling return; after setting the ModelState value and not calling the base method and thus no redirect will happen to the login url. You could in this case return some error view by setting the filterContext.Result to an instance of a ViewResult in which view you could use the value you stored in the ModelState.
Before jumping in with an answer, please make sure you understand my scenario.
I have ajax calls that CREATE flashes.
I have other ajax calls that FETCH the flashes as JSON.
What is currently happening: I click a button which creates the flash. After which I run a ajax call that executes:
public function actionGetAllFlashesAsJSON() {
$flashMessages = Yii::app()->user->getFlashes(true);
$returnResult = array();
foreach ($flashMessages as $key => $value) {
$newItem = array();
$newItem['message'] = $value;
$newItem['kind'] = $key;
$returnResult[]= $newItem;
}
print json_encode($returnResult);
die();
}
My problem is, when I execute this function twice in a row, it still keeps returning the flashes. However, if I refresh the site, it shows the error, and then if I press refresh again, it's gone. My theory is that page refresh is causing some other kind of deletion of messages... but what? And how can I force the deletion of these messages after I receive the message in the above code?
More background info: I am using the flashes as ERROR messages, but i want them to appear at the top of my site AS THEY ARE CREATED. Flashes might get created via Ajax, so I have javascript running to check for new messages, and display them, but my problem is it shows the messages several times, because they are not getting deleted after calling getFlashes?
The flash messages are controlled by SESSION variables, which Yii destroys when the page is loaded (probably somewhere quite deep in the framework). You will have to manually destroy all the previous flash messages at the start of the ajax request
You can use: getFlashes() to get all the existing flash messages
For the other flash message methods have a look at the CWebUser docs here
I'm trying to write an extension that will block access to (configurable) list of URLs if they are accessed more than N times per hour. From what I understand, I need to have a start script pass a "should I load this" message to a global HTML page (who can access the settings object to get the list of URLs), who will give a thumbs up/thumbs down message back to the start script to deny/allow loading.
That works out fine for me, but when I use the usual beforeLoad/canLoad handlers, I get messages for all the sub-items that need to be loaded (images/etc..), which screws up the #accesses/hour limit I'm trying to make.
Is there a way to synchronously pass messages back and forth between the two sandboxes so I can tell the global HTML page, "this is the URL in the window bar and the timestamp for when this request came in", so I can limit duplicate requests?
Thanks!
You could use a different message for the function that checks whether to allow the page to load, rather than using the same message as for your beforeLoad handler. For example, in the injected script (which must be a "start" script), put:
safari.self.tab.dispatchMessage('pageIsLoading');
And in the global script:
function handleMessage(event) {
if (event.name == 'pageIsLoading') {
if (event.target.url.indexOf('forbidden.site.com') > -1) {
console.log(event.timeStamp);
event.target.url = 'about:blank';
}
}
}