Primefaces DialogFramework - How to show a dialog located in WEB-INF? - jsf-2

I am using Primefaces DialogFramework with
Primefaces 5.0
Mojarra 2.1.27
Glassfish 3.1.2.2 Build 5
My problem is, that if the user knows the location of my dialog, he is able to access it directly via the URL. I do not want that to be possible, so I thought it would be able to put the dialog in WEB-INF folder of my web-app, but now, if I want to open the dialog, I get a FileNotFound-Exception.
If my dialog is located in some regular folder, it works fine
RequestContext.getCurrentInstance().openDialog("/myfolder/mydialog");
// this works as expected
but if it is located in WEB-INF, it does not work any longer
RequestContext.getCurrentInstance().openDialog("/WEB-INF/mydialog",options,null);
// this is causing a fileNotFoundException
I also tried to set up a navigation rule for this in faces-config but again with no success
<navigation-case>
<from-outcome>mydialog</from-outcome>
<to-view-id>/WEB-INF/mydialog.xhtml</to-view-id>
<redirect />
</navigation-case>
How may I open dialogs located in WEB-INF folder, or is it not possible at all?
Thanks in advance

Unfortunately, putting PrimeFaces Dialog Framework dialogs in /WEB-INF in order to prevent direct access is indeed not going to work. The dialogs are loaded entirely client side. On the POST request which opens the dialog, JSF/PrimeFaces returns an oncomplete script with the (public!) URL of the dialog to JavaScript/jQuery, which in turn shows a basic dialog template with an <iframe> whose URL is set to the dialog URL, which in turn loads the content. In effects, 2 requests are being sent, the first to get the dialog's URL and the second to get the dialog's content based on that URL in the <iframe>.
There's no way to keep the dialog in /WEB-INF without falling back to the "traditional" dialog approach via <p:dialog> and conditional display via JS/CSS. There's also no way in the server side to verify based on some headers if the request is coming from an <iframe>, so that all others could simply be blocked. Your closest bet is the referer header, but this can be spoofed.
One way to minimize abuse is checking the presence of pfdlgcid request parameter (identified by Constants.DIALOG_FRAMEWORK.CONVERSATION_PARAM) when a dialog is being requested. PrimeFaces namely appends this request parameter representing "conversation ID" to the dialog URL. Presuming that all dialogs are stored in a folder /dialogs, then you could do the job with a simple servlet filter. Here's a kickoff example which sends a HTTP 400 error when /dialogs/* is being requested without the pfdlgcid request parameter.
#WebFilter("/dialogs/*")
public class DialogFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String id = request.getParameter(Constants.DIALOG_FRAMEWORK.CONVERSATION_PARAM);
if (id != null) {
chain.doFilter(req, res); // Okay, just continue request.
}
else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST); // 400 error.
}
}
// ...
}
However, the abuser might not be that stupid and discover the pfdlgcid request parameter during the normal flow and still be able to open the dialog individually when supplying that parameter, even with a random value. I thought of comparing the actual pfdlgcid value to the known ones. I checked the PrimeFaces DialogNavigationHandler source code, but unfortunately, PrimeFaces doesn't store this value anywhere in the session. You'd need to provide a custom DialogNavigationHandler implementation wherein you store the pfdlgcid value in the session map which in turn is also compared in the servlet filter.
First add the following method to the DialogFilter:
public static Set<String> getIds(HttpServletRequest request) {
HttpSession session = request.getSession();
Set<String> ids = (Set<String>) session.getAttribute(getClass().getName());
if (ids == null) {
ids = new HashSet<>();
session.setAttribute(getClass().getName(), ids);
}
return ids;
}
Then copypaste the PrimeFaces DialogNavigationHandler source code into your own package and add the following line after line 62:
DialogFilter.getIds((HttpServletRequest) context.getExternalContext().getRequest()).add(pfdlgcid);
Replace the <navigation-handler> in faces-config.xml with the customized one.
Finally, alter the if condition in the DialogFilter#doFilter() method as follows:
if (getIds(request).contains(id)) {
// ...
}
Now, this prevents the abuser from attempting to open the dialog with a random ID. This however doesn't prevent the abuser from attempting to open the dialog by copypasting the exact <iframe> URL immediately after opening it. Given the way how the PrimeFaces dialog framework works, there's no way to prevent that. You could at most remove the pfdlgcid value from the session when the dialog is about to returns to the parent. However, when the dialog is closed by pure JS means, then this is also bypassed.
All in all, if you really, really, want to avoid the enduser being able to open the dialog individually, then you can't go around the "traditional" <p:dialog> approach.

Related

How to resolve links to content items inside a rich text ? (Kontent.ai - ASP.NET framework)

So I followed the Kontent doc from the github which allows to retrieve content from a link (https://github.com/Kentico/kontent-delivery-sdk-net/wiki/Resolving-links-to-content-items)
First I implement a resolver to redirect when we click on the link like this :
public class CustomContentLinkUrlResolver : IContentLinkUrlResolver
{
public string ResolveBrokenLinkUrl()
{
return "/404";
}
public string ResolveLinkUrl(ContentLink link)
{
switch(link.ContentTypeCodename)
{
case "author":
return $"/author/{link.UrlSlug}";
default:
return $"/not_found";
}
}
}
Then I register my resolver within a IDeliveryClient
client = DeliveryClientBuilder
.WithProjectId(myid)
.WithContentLinkUrlResolver(new CustomContentLinkUrlResolver())
.Build();
At this moment if i click on the link it will redirect to /author/linkName with an error on the page what I think is normal
I don't get the last part of the doc (how just by doing a getString on the contentItem the link will work ?) so I would like to know how to display the content on the redirect page
I don't know if i was clear enough and sorry for my english
Here is the error thrown on the redirect page
Description: HTTP 404. The resource you are looking for (or one of its dependencies) could have been removed, had its name changed, or is temporarily unavailable. Please review the following URL and make sure that it is spelled correctly.
The last part of the wiki article refers to something that you already have:
At this moment if i click on the link
If you have a link that you can click on, then you have done what that part of the article describes.
What you need is to resolve the request. If you are getting a 404 that you expect, then you know that you need to add a route to your application to handle the request. In the handler (a controller, a component, etc.) extract the urlSlug from the route and use it with a IDeliveryClient to retrieve the item and then render the content. You will need to filter the GetItems call with something like new EqualsFilter("elements.urlSlug", urlSlug).

Why is Phase Listener-based JSF redirect not working for AJAX requests when session has timed-out?

I have a JSF Phase Listerner that checks to see if the user is logged in, and if not, redirects them to the login page. This is working fine for non-ajax requests. However, if the user is on a page, in my case, one that has a primefaces data table, and clicks on a button that invokes an ajax request -- but their session has timed out -- the code gets executed that issues the redirect (using ExternalContext#redirect), however the user is not navigated to the login page.
Any idea why this is not working?
Here is my phase listener:
private static final String IS_LOGGED_IN_INDICATOR = "loggedIn";
private static final String LOGIN_PAGE = "/login.jsp";
public PhaseId getPhaseId() {
return PhaseId.RESTORE_VIEW;
}
public void beforePhase(PhaseEvent event) {
ExternalContext ec = FacesContext.getCurrentInstance().getExternalContext();
HttpSession session = (HttpSession)ec.getSession(false);
if (session==null || session.getAttribute(IS_LOGGED_IN_INDICATOR) == null) {
try {
ec.redirect(LOGIN_PAGE);
}
catch(IOException e) {
// log exception...
}
}
}
public void afterPhase(PhaseEvent event) {
// no-op
}
}
It failed because the ajax context is trying to obtain the render kit from the view root, while there is no view root at all. It has not been restored at that point yet. This resulted in a NullPointerException in PartialViewContext#createPartialResponseWriter(). This exception is in turn not been thrown, but instead been put in an ajax exception queue which is supposed to be handled by a custom ExceptionHandler. You apparently don't have any one. This exception is visible if you create/use such one like the FullAjaxExceptionHandler (see also this blog for more detail).
To fix the particular problem, do the job in afterPhase() instead. The view root is then fully restored and the ajax context can obtain the render kit from it in order to write a specialized XML response which instructs the JSF ajax engine in JavaScript to change the window location. Without ajax, a render kit was not necessary as a redirect is basically just a matter of setting a response header.
Whether the particular NullPointerException is in turn a bug in Mojarra or not is a different question which can better be posted in flavor of an issue report at their own issue tracker.
this is because you have to send a special response in XML for Ajax request in order to do redirect (check this answer) , I have implemented this in a Filter like this..
// Check if it's an Ajax Request
if ("partial/ajax".equals(((HttpServletRequest) request).getHeader("Faces-Request"))) {
//redirect
response.setContentType("text/xml");
response.getWriter()
.append("<?xml version= \"1.0\" encoding=\"UTF-8\"?>")
.printf("<partial-response><redirect url=\"%s\"></redirect></partial-response>",url);
you should port this to your Phase Listener.

browser back + viewscope beans

What the problem is :
What happens when clicking on the browser back button --> opens up a page whose viewscoped-managedbean is already destroyed --> submit a request from a commandButton from that page with grid-record-selections ?
What i expect :
The associated viewscope-managebean is re-created, receives the grid-record-selections, and deal with them as if the browser back button is never involved.
What i experience :
The associated viewscope-managebean is NOT re-created, doesnt receive the grid-record-selections. Have to reenter the URL, or F5 after clicking on the browser-back button for it to work properly again.
So here's the success scenario, all beans are viewscoped beans :
GET page1.xhtml --> page1Bean created, querying data, etc in #PostConstruct
check/select several records from a datatable, click on process button
page1Bean's process method stores the selected records in the flash object, and redirect to the page2.xhtml
page1Bean destroyed, page2Bean created, and in preRenderView listener method, fetches the selected records from the flash object, and deal with them
click the "go to main page" commandButton to redirect to page1.xhtml, and page2Bean destroyed, page1Bean created again
loop from no 2 - 5 is still doable
Now, this is the errornous scenario involving the browser back button (different stuffs happening starting from #6) :
GET page1.xhtml --> page1Bean created, querying data, etc in #PostConstruct
check/select several records from a datatable, click on process button
page1Bean's process method stores the selected records in the flash object, and redirect to the page2.xhtml
page1Bean destroyed, page2Bean created, and in preRenderView listener method, fetches the selected records from the flash object, and deal with them
click the browser back button page2Bean is not destroyed, page1Bean is not created
check/select several records from a datatable, click on process button
the page1Bean method executes (strange, because the page1Bean should've been destroyed), but cannot see the record-selections made, and redirect to page2.xhtml
page1Bean is not destroyed (no logging output), page2Bean is not created (since it's not been destroyed), executes the preRenderView listener as usual, but this time, no selected records in the flash object
Is it possible to have the normal experience (as if without the browser back button) with viewscope-beans with the browser back button ?
Here's my dependency :
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.faces</artifactId>
<version>2.1.3</version>
<scope>compile</scope>
</dependency>
Please share your ideas !
The browser seems to have served the page from its cache instead of sending a fullworthy HTTP GET request to the server, while you have JSF state saving method set to server (which is the default).
There are 2 ways to solve this problem:
Tell the browser to not cache the dynamic JSF pages. You can do this with help of a filter.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
if (!req.getRequestURI().startsWith(req.getContextPath() + ResourceHandler.RESOURCE_IDENTIFIER)) { // Skip JSF resources (CSS/JS/Images/etc)
res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
res.setHeader("Pragma", "no-cache"); // HTTP 1.0.
res.setDateHeader("Expires", 0); // Proxies.
}
chain.doFilter(request, response);
}
Map the filter on the FacesServlet or its same URL-pattern.
Set the JSF state saving method to client, so that the entire view state is stored in a hidden field of the form instead of in the session in the server side.
<context-param>
<param-name>javax.faces.STATE_SAVING_METHOD</param-name>
<param-value>client</param-value>
</context-param>
The filter way is preferable.
The disadvantage of disabling the browser cache of a page is that the user will see an browsers error page if he use browser back to navigate to previous page.
So another solutions is to identify if the page comes from the server or from the browser cache using javascript:
First create a simple backing bean which serves a unique id (in my case current system time):
#Named("browserCacheController")
#RequestScoped
public class BrowserCacheController implements Serializable {
private static final long serialVersionUID = 1L;
/**
* Returns a unique increasing id for each request
* #return
*/
public long getCacheID() {
return System.currentTimeMillis();
}
}
So now you can test if a page is served from the server or browser and redirecting the user if the current page comes from browser cache. See the following javascript code placed into a jsf page which should not be cached by browser:
<script type="text/javascript">
// check for latestCacheID
if (!isValidCacheID(#{browserCacheController.cacheID})) {
//redirect to some page
document.location="#{facesContext.externalContext.requestContextPath}/index.jsf";
}
// test cacheID if it comes from the server....
function isValidCacheID(currentCacheID) {
if (!('localStorage' in window && window['localStorage'] !== null))
return true; // old browsers not supported
var latestCacheID=localStorage.getItem("org.imixs.latestCacheID");
if (latestCacheID!=null && currentCacheID<=latestCacheID) {
return false; // this was a cached browser page!
}
// set new id
localStorage.setItem("org.imixs.latestCacheID", currentCacheID);
return true;
}
</script>
The script can also be placed into facelet to make the jsf code more clean.

Struts2 how to create multiple views for mobile and desktop

I am creating a site which will be accessible via mobile and desktop devices. So I want to create 2 views of my application. My action code and everything else in the backend (manageers, DAOs) is same. Just JSP changes for both.
How I can do this via Struts 2?
In struts there are many way to obtain the same thing.
In this case, the one I prefer is:
You could write an interceptor that changes the return code based on the user-agent of
the client, such that there would be versions for PC and mobile of each jsp.
In your configuration you need to have all the result codes for all jsp (or you could simply define the result through the wildcard mapping).
For example: change the result code from "success" to "mobile_success". In case you want map both results in the same jsp you can map, as I said before, in this way
<result name="*success">
not sure whether there is library for automating such task for struts 2. but if there is, using such libraries might be better
anyway, here is the theory. every browser has its own "signature" written in the request header, called "User-Agent". different browser (supposedly) has different user agent. for example, my firefox user agent is as following:
Mozilla/5.0 (Windows NT 6.0; rv:5.0) Gecko/20100101 Firefox/5.0 FirePHP/0.5
basically, by detecting the user agent, you can know what browser is used to access your site. the list of mobile browser user agents can be found in http://www.zytrax.com/tech/web/mobile_ids.html
if i'm not wrong, you can retrieve the user agent in server by httpServletRequest.getHeader("User-Agent"); (correct me if i'm wrong)
you can then create an interceptor which will decide whether a client is from mobile or from desktop. that interceptor can return different result for different client type. for example, if the client is desktop, you can return "successDesktop" and if the client is mobile, you can return "successMobile".
well, hopefully someone else can come up with (far) easier solution
I am currently trying to solve this very same problem. A framework would be nice, and I'm all ears if anyone has tested and approved one. That said, I can't find anything mature enough for me to be justify moving from Struts for the mobile view.
My best solution currently is to create actions for each of the parts of my full page which will be displayed on full browsers. Then to reuse those actions to display page segments on the mobile side.
I found trying to make one page look right for a desktop browser and a mobile browser simultaneously was not a sustainable approach.
jQuery mobile looks like a very promising library for styling the elements retrieved by struts.
So while it is surely possible to cram both versions of the site into one action I think taking the time to create small reusable actions that result in jsp snippits will pay off as your app scales.
Here are some possibilities for the near future:
(I can't add these as links as I don't have enough reputation...you'll have to add the 'http://www.')
Struts2 jQuery Mobile Project homepage: http://code.google.com/p/struts2-jquery/
Struts2 jQuery Mobile project: code.google.com/p/struts2-jquery/downloads/detail?name=struts2-jquery-mobile-showcase-3.1.1.war
an example of struts2 jQuery Mobile: weinfreund.de/struts2-jquery-mobile-showcase/index.action
#fajrian - using 'user agent' to determine a browser type could become a real pain as more and more mobile and desktop browsers are released. A better approach would be to determine whether to display a mobile version or full version based on the window's dimensions. A perfect example.
edit - Check out CSS3 media queries.
As Maurizio said you could use interceptors. Here is what I found.... http://www.benmccann.com/blog/struts-2-tutorial-interceptors/
This works for me and should basically get round the problem. You do need to know at least part of the user agent strings though:
public class MobileInterceptor extends AbstractInterceptor {
private static final String RESULT_CODE_SUFFIX_MOBILE = "mobile";
private static final String REQUEST_HEADER_ACCEPT = "Accept";
private static final String[] MOBILE_BROWSER_UAS = {"iPhone OS","Android","BlackBerry","Windows Phone"};
public String intercept(ActionInvocation invocation) throws Exception {
invocation.addPreResultListener(new PreResultListener() {
public void beforeResult(ActionInvocation invocation, String resultCode) {
// check if a wireless version of the page exists
// by looking for a wireless action mapping in the struts.xml
Map results = invocation.getProxy().getConfig().getResults();
System.out.println("Results:"+results.toString());
if(!results.containsKey(resultCode + RESULT_CODE_SUFFIX_MOBILE)) {
return;
}
// send to mobile version if mobile browser is used
final String acceptHeader = ServletActionContext.getRequest().getHeader(REQUEST_HEADER_ACCEPT);
//Get User Agent String
String userAgent = ServletActionContext.getRequest().getHeader("User-Agent");
System.out.println("UA: "+userAgent);
//Boolean to indicate whether to show mobile version
boolean showMobileVersion = false;
//Run through each entry in the list of browsers
for(String ua : MOBILE_BROWSER_UAS){
if(userAgent.toLowerCase().matches(".*"+ua.toLowerCase()+".*")){
showMobileVersion = true;
}
}
if(showMobileVersion) {
invocation.setResultCode(resultCode + RESULT_CODE_SUFFIX_MOBILE);
}
}
});
return invocation.invoke();
}

What is the correct response to an HTTP POST request?

For a POST method, the W3 specs say:
If a resource has been created on the origin server, the response
SHOULD be 201 (Created) and contain an entity which describes the
status of the request and refers to the new resource, and a Location
header (see Section 10.4).
http://www.ietf.org/internet-drafts/draft-ietf-httpbis-p2-semantics-05.txt (section 8.5)
The standard response actually seems to be to send a Redirect to the newly created resource.
I'm building my site with ASP.NET MVC, and tried to follow the spec, so created a ResourceCreatedResult class:
public class ResourceCreatedResult : ActionResult
{
public string Location { get; set; }
public override void ExecuteResult(ControllerContext context)
{
context.HttpContext.Response.Clear();
context.HttpContext.Response.StatusCode = 201;
context.HttpContext.Response.ClearHeaders();
context.HttpContext.Response.AddHeader("Location", Location);
}
}
And my action looks something like this:
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult CreateNew(string entityStuff)
{
Entity newEntity = new Entity(entityStuff);
IEntityRepository entityRepository = ObjectFactory.GetInstance<IEntityRepository>();
entityRepository.Add(newEntity);
ActionResult result = new ResourceCreatedResult()
{ Location = Url.Action("Show", new { id = newEntity.Id }) };
return result;
}
However, IE, Firefox and Chrome all fail to redirect to the new resource. Have I messed up generating the correct response, or do web browsers not expect this type of response, instead relying on servers to send a Redirect response?
To be explicit, browsers (including modern browsers like Firefox 3 and IE8) do not "take the hint" and follow up an HTTP 201: Created response with a GET request to the URI supplied in the Location header.
If you want browsers to go to the URI supplied in the Location header, you should send an HTTP 303: See Other status instead.
Redirect after post or post/redirect/get is something your application must do to be user friendly.
Edit. This is above and beyond the HTTP specifications. If we simply return a 201 after a POST, the browser back button behaves badly.
Note that Web Services requests (which do NOT respond to a browser) follow the standard completely and do NOT redirect after post.
It works like this.
The browser POSTS the data.
Your application validates the data. If it's invalid, you respond with the form so they can fix it and POST.
Your application responds with a redirect.
The browser gets the redirect and does a GET.
Your application sees the GET and responds.
Now -- hey presto! -- the back button works.
My solution is to respond with a '201 Created' containing a simple page with a link to the new resource, and a javascript redirect using location.replace().
This lets the same code work for API and browser requests, plays nicely with Back and Refresh buttons, and degrades gracefully in old browsers.
As stated in the spec the response SHOULD be a HTTP 201 with redirect. So it isn't mandatory for a browser vendor to implement the correct answer...
You should try to change to a 30x code to see if it is correctly redirected. If so, it's a browser problem, else it may come from your code (I don't know anything in ASP.NET so I can't "validate" your code)
Shouldn't that only count for when something is "Created" and therefore a simple redirect to action should be genuinely sufficient?

Resources