0

I'm currently having trouble regarding the token generated by <protected-views> of JSF.

I added the page I want to protect in faces-config.xml

<protected-views>
    <url-pattern>/restricted/account-management/users.xhtml</url-pattern>
    <url-pattern>/restricted/account-management/users.jsf</url-pattern>
</protected-views>

Then for example when I go the users page using an <h:link>

<h:link outcome="users" title="View">
    <f:param name="user" value="#{e.id}" />
</h:link>

the token generated in the URL is this

/restricted/account-management/users.jsf?javax.faces.Token=OW5KkkfJZrrfmZSXwA%253D%253D&user=4

The page returns a ProtectedViewException

Then I found out that the correct token is actually:

/restricted/account-management/users.jsf?javax.faces.Token=OW5KkkfJZrrfmZSXwA%3D%3D

The token was encoded in the URL, where % became %25. When I copy-paste the correct token into the URL, I get into the users page successfully.

Any help would be appreciated.

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34
mcspiral
  • 147
  • 1
  • 10

1 Answers1

0

This is a problem with the versions 2.2.11 and above of Mojarra JSF Implementation, you can see the details about issue in https://github.com/javaee/javaserverfaces-spec/issues/1161 and here https://github.com/javaserverfaces/mojarra/issues/4139

One of the alternatives to handle the problem is to create a CustomExternalContext to handle the double encoding.

First you need declare in faces-config.xml a CustomExternalContextFactory:

<factory>
    <external-context-factory>com.proitc.config.CustomExternalContextFactory</external-context-factory>
</factory>

In the ExternalContextFactory you define the CustomExternalContext:

public class CustomExternalContextFactory extends ExternalContextFactory {

  private ExternalContextFactory externalContextFactory;

  public CustomExternalContextFactory() {}

  public CustomExternalContextFactory(ExternalContextFactory externalContextFactory) {
    this.externalContextFactory = externalContextFactory;
  }

  @Override
  public ExternalContext getExternalContext(Object context, Object request, Object response)
      throws FacesException {

    ExternalContext handler = new CustomExternalContext((ServletContext) context,
        (HttpServletRequest) request, (HttpServletResponse) response);

    return handler;
  }

}

The CustomExternalContext override the methods encodeBookmarkableURL and encodeRedirectURL:

public class CustomExternalContext extends ExternalContextImpl {

  public CustomExternalContext(ServletContext sc, ServletRequest request,
      ServletResponse response) {
    super(sc, request, response);
  }

  @Override
  public String encodeBookmarkableURL(String baseUrl, Map<String, List<String>> parameters) {
    FacesContext context = FacesContext.getCurrentInstance();
    String encodingFromContext =
        (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
    if (null == encodingFromContext) {
      encodingFromContext =
          (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
    }
    String currentResponseEncoding =
        (null != encodingFromContext) ? encodingFromContext : getResponseCharacterEncoding();
    UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding);
    builder.addParameters(parameters);
    String secureUrl = builder.createUrl();

    //Handle double encoding
    if (parameters.size() > 0 && baseUrl.contains("javax.faces.Token")) {
      try {
        int beginToken = secureUrl.indexOf("javax.faces.Token");
        int endToken = secureUrl.indexOf("&") - 1;
        String doubleEncodeToken = secureUrl.substring(beginToken, endToken);
        String encodeToken = URLDecoder.decode(doubleEncodeToken, currentResponseEncoding);
        secureUrl = secureUrl.replace(doubleEncodeToken, encodeToken);
      } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
      }
    }
    return secureUrl;
  }

  @Override
  public String encodeRedirectURL(String baseUrl, Map<String, List<String>> parameters) {
    FacesContext context = FacesContext.getCurrentInstance();
    String encodingFromContext =
        (String) context.getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
    if (null == encodingFromContext) {
      encodingFromContext =
          (String) context.getViewRoot().getAttributes().get(RIConstants.FACELETS_ENCODING_KEY);
    }

    String currentResponseEncoding =
        (null != encodingFromContext) ? encodingFromContext : getResponseCharacterEncoding();

    UrlBuilder builder = new UrlBuilder(baseUrl, currentResponseEncoding);
    builder.addParameters(parameters);
    String secureUrl = builder.createUrl();
    //Handle double encoding
    if (parameters.size() > 0 && baseUrl.contains("javax.faces.Token")) {
      try {
        int beginToken = secureUrl.indexOf("javax.faces.Token");
        int endToken = secureUrl.indexOf("&") - 1;
        String doubleEncodeToken = secureUrl.substring(beginToken, endToken);
        String encodeToken = URLDecoder.decode(doubleEncodeToken, currentResponseEncoding);
        secureUrl = secureUrl.replace(doubleEncodeToken, encodeToken);
      } catch (UnsupportedEncodingException e) {
        throw new RuntimeException(e);
      }
    }
    return secureUrl;
  }

}

You can find a working example in https://github.com/earth001/jsf-protected-view

JuanMoreno
  • 2,498
  • 1
  • 25
  • 34