Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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

like image 421
stg Avatar asked Jul 25 '14 16:07

stg


1 Answers

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.

like image 93
BalusC Avatar answered Oct 14 '22 18:10

BalusC