JSF <protected-views> token inconsistency - jsf

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.

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

Related

JSF ExceptionHandler - Response already committed in some cases

We have implemented a custom ExceptionHandler that attempts to intercept any NonexistentConversationException that are thrown if a user requests a URL with an old cid in it (eg. bookmarked link etc.).
The handler simply removed the cid from the URL and performs a redirect to the updated URL.
This solution seems to work in most cases, we have a number of different user 'roles' within the App each with their own login areas and for one of these user types it seems that fc.getExternalContext().isResponseCommitted() is always true when the ExceptionHandler fires which means we are unable to perform the redirect. All other user types it works ok.
I am not sure exactly what the difference is with this user type for this to happen, I am guessing some CDI bean we using setup differently
Is there a way to unsure the ExceptionHandler kicks in earlier before the response is committed?
Below is the handler...
public class ConversationExpiredExceptionHandler
extends ExceptionHandlerWrapper {
final static Logger log = LoggerFactory.getLogger(ConversationExpiredExceptionHandler.class);
private ExceptionHandler wrapped;
private static String CONVERSATION_PARAM = "cid";
public ConversationExpiredExceptionHandler(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
#Override
public ExceptionHandler getWrapped() {
return this.wrapped;
}
#Override
public void handle() throws FacesException {
for (Iterator<ExceptionQueuedEvent> i =
getUnhandledExceptionQueuedEvents().iterator();
i.hasNext();) {
ExceptionQueuedEvent event = i.next();
ExceptionQueuedEventContext context = (ExceptionQueuedEventContext) event.getSource();
Throwable t = context.getException();
if (t instanceof javax.enterprise.context.NonexistentConversationException
|| t instanceof org.jboss.weld.contexts.NonexistentConversationException
|| t.getCause() instanceof org.jboss.weld.contexts.NonexistentConversationException /* can be wrapped in FacesException */) {
final FacesContext fc = FacesContext.getCurrentInstance();
try {
if (!fc.getExternalContext().isResponseCommitted()) {
if(Faces.getRequestQueryStringMap().containsKey(CONVERSATION_PARAM)) {
String requestedUrl = Faces.getRequestURLWithQueryString();
String updatedUrl = this.removeQueryParameter(requestedUrl,CONVERSATION_PARAM);
log.debug("No conversation active for {}, redirecting to {}",requestedUrl,updatedUrl);
fc.getExternalContext().redirect(updatedUrl);
}
}
fc.renderResponse();
break;
} catch (Exception ex) {
throw new FacesException(ex);
} finally {
i.remove();
}
}
}
getWrapped().handle();
}
private String removeQueryParameter(String url, String parameterName) throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(url);
List<NameValuePair> queryParameters = uriBuilder.getQueryParams();
for (Iterator<NameValuePair> queryParameterItr = queryParameters.iterator(); queryParameterItr.hasNext();) {
NameValuePair queryParameter = queryParameterItr.next();
if (queryParameter.getName().equals(parameterName)) {
queryParameterItr.remove();
}
}
uriBuilder.setParameters(queryParameters);
return uriBuilder.build().toString();
}

Managed bean property becomes null in a different request

I am facing a problem when initializing a List with new ArrayList<E>(). This is my class:-
#ManagedBean(name = "clientBean")
#SessionScoped
public class ClientBean {
public String msg = "", input, pendingMsg = "", user = "";
public GossipClient client;
public boolean inputDisabled = true, sendBtnDisabled = true, startBtnDisabled = false;
String key = null;
public List<ClientBeanDto> dtos;
ClientBeanDto dto;
String style;
public List<OnlineList> onlineList;
OnlineList userInfo;
OnlineList newUser = null;
public void start() throws UnknownHostException, IOException {
client = new GossipClient(this);
if (user != null && user != "" && client.connect()) {
try {
BufferedReader reader = new BufferedReader(new FileReader("D://key.txt"));
key = reader.readLine();
reader.close();
} catch (Exception e) {
e.printStackTrace();
}
client.start();
inputDisabled = sendBtnDisabled = false;
startBtnDisabled = true;
msg = pendingMsg = "";
pendingMsg = user + ", you are connected!";
client.sendNick(key + user);
onlineList = new ArrayList<OnlineList>();
dtos = new ArrayList<ClientBeanDto>();
}
.
.
.
}
The problem is with public List<OnlineList> onlineList. In start() method when the line onlineList = new ArrayList<OnlineList>(); is executed, in debugging mode I find its value null whereas a similar object dtos in the very next line gets initialized and assigned a id successfully.
I can't seem to find any reason for this behavior and I am not getting any error/exception. Any wisdom will be appreciated.
UPDATE: Found out that the list is becoming null whenever a button of xhtml (which the bean is backing) is clicked. I cannot find the reason though. The bean is SessionScoped and the other list(dtos) object is retained.

Download file, filename doesn't work in portlet

I set filename in the HttpServletResponse header but when I download it has not this filename
FileInputStream fis = new FileInputStream(fileDocumento);
PortletResponse portletResponse=(PortletResponse) FacesContext.getCurrentInstance().getExternalContext().getResponse();
HttpServletResponse res = PortalUtil.getHttpServletResponse(portletResponse);
res.setHeader("Content-Disposition", "attachment; filename=schedaObiettivoTAC_.docx");
res.setHeader("Content-Transfer-Encoding", "binary");
res.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
res.flushBuffer();
OutputStream out=res.getOutputStream();
out.write(IOUtils.toByteArray(fis));
out.close();
fis.close();
You should not use the HttpServletResponse. Instead you should create a custom Resource and ResourceHandler:
CustomResourceHandler.java:
public final class CustomResourceHandler extends ResourceHandlerWrapper {
// Public Constants
public static final String LIBRARY_NAME = "exampleLib";
public static final String RESOURCE_NAME = "exampleName";
// Private Data Members
private ResourceHandler wrappedResourceHandler;
public CustomResourceHandler(ResourceHandler resourceHandler) {
this.wrappedResourceHandler = resourceHandler;
}
#Override
public Resource createResource(String resourceName, String libraryName) {
if (LIBRARY_NAME.equals(libraryName)) {
if (RESOURCE_NAME.equals(resourceName)) {
return new CustomResource(libraryName, resourceName,
"exampleFileName.txt", "Example Content");
}
else {
return super.createResource(resourceName, libraryName);
}
}
else {
return super.createResource(resourceName, libraryName);
}
}
#Override
public ResourceHandler getWrapped() {
return wrappedResourceHandler;
}
#Override
public boolean libraryExists(String libraryName) {
if (LIBRARY_NAME.equals(libraryName)) {
return true;
}
else {
return super.libraryExists(libraryName);
}
}
/* package-private */ static final class CustomResource extends Resource {
private final String content;
private final Map<String, String> responseHeaders;
private final String requestPath;
private final URL url;
public CustomResource(String libraryName, String resourceName,
String fileName, String content) {
super.setLibraryName(libraryName);
super.setResourceName(resourceName);
super.setContentType("text/plain");
Map<String, String> responseHeaders = new HashMap<String, String>();
responseHeaders.put("Content-Disposition",
"attachment; filename=" + fileName + ";");
this.responseHeaders = Collections.unmodifiableMap(responseHeaders);
StringBuilder sb = new StringBuilder();
sb.append(ResourceHandler.RESOURCE_IDENTIFIER);
sb.append("/");
sb.append(super.getResourceName());
sb.append("?ln=");
sb.append(super.getLibraryName());
this.requestPath = sb.toString();
URL url;
try {
FacesContext facesContext = FacesContext.getCurrentInstance();
ExternalContext externalContext = facesContext.getExternalContext();
url = new URL(externalContext.encodeResourceURL(this.requestPath));
}
catch (MalformedURLException e) {
url = null;
}
this.url = url;
this.content = content;
}
#Override
public InputStream getInputStream() throws IOException {
return new ByteArrayInputStream(
content.getBytes(StandardCharsets.UTF_8));
}
#Override
public String getRequestPath() {
return requestPath;
}
#Override
public Map<String, String> getResponseHeaders() {
return responseHeaders;
}
#Override
public URL getURL() {
return url;
}
#Override
public boolean userAgentNeedsUpdate(FacesContext facesContext) {
// Return false if the content cannot change dynamically.
return true;
}
}
}
WEB-INF/faces-config.xml:
<?xml version="1.0" encoding="UTF-8"?>
<faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd">
<application>
<!-- ... -->
<resource-handler>custom.resource.handler.CustomResourceHandler</resource-handler>
</application>
<!-- ... -->
</faces-config>
Here's some example code for downloading the resource:
<h:outputLink target="_blank" value="#{bean.getDownloadURL(facesContext)}">
<h:outputText value="download" />
</h:outputLink>
public String getDownloadURL(FacesContext facesContext) {
return facesContext.getApplication().getResourceHandler()
.createResource(CustomResourceHandler.RESOURCE_NAME,
CustomResourceHandler.LIBRARY_NAME)
.getURL().toString();
}
You can also look at the Liferay Faces JSF Export PDF for a full portlet example to download/export a file.
If you are in the action or render phase of your portlet (what I would guess for a JSF portlet): there is a ResponseWrapper which will suppress any header. I'm using this method to find the response for sending binary data during the JSF lifecycle:
public static HttpServletResponse getHttpServletResponse() {
final FacesContext facesContext = FacesContext.getCurrentInstance();
if (facesContext == null) {
throw new IllegalStateException("Not inside a JSF request");
}
final Object responseObject = facesContext.getExternalContext().getResponse();
if (responseObject instanceof HttpServletResponse) {
return (HttpServletResponse) responseObject;
}
if (responseObject instanceof PortletResponse) {
// Use Liferays util to find the real response
HttpServletResponse response = PortalUtil.getHttpServletResponse((PortletResponse) responseObject);
// Find the outer most response (setting the headers would have no effect, as we are included)
while (response instanceof ServletResponseWrapper) {
final ServletResponse servletResponse = ((ServletResponseWrapper) response).getResponse();
if (!(servletResponse instanceof HttpServletResponse)) {
break;
}
response = (HttpServletResponse) servletResponse;
}
return response;
}
throw new IllegalStateException("Unknown type of response object: " + responseObject);
}

JSF "faces-redirect=true" redirects from https:// to http:// URL due to Apache ProxyPass

As I have an Apache Webserver which does ProxyPass to the Glassfish server, the latter does not know that the customers are talking "https".
Thus when using things like
return "shop.xhtml?faces-redirect=true";
the generated HTTP Location: header contains a "http://" URL.
I've read JSF redirects from HTTPS to HTTP but found this solution not very elegant. Is there a way to tell Glassfish that this or all incoming requests are https so that I don't have to fiddle with the generated Navigation rules?
You may try to add some request header which can be interpreted on java side, for example 'X-redirect-to-https'. Then create filter that will wrap HttpServletResponse, and in that wrapper override sendRedirect method to replace http with https in redirect URL when 'X-redirect-to-https' header is present.
Code (a little messy, but illustrates a solution) adapted from:
http://javahunter.wordpress.com/2011/06/01/why-does-https-become-http-on-a-sendredirect/
#WebFilter("/*")
public class HttpsSendRedirectFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
ServletException {
chain.doFilter(request, new HttpsRedirectResponseWrapper((HttpServletRequest) request,
(HttpServletResponse) response));
}
}
public class HttpsRedirectResponseWrapper extends HttpServletResponseWrapper {
private HttpServletRequest req;
private String prefix = null;
public HttpsRedirectResponseWrapper(HttpServletRequest req, HttpServletResponse res) {
super(res);
this.req = req;
prefix = getPrefix(req);
}
#Override
public void sendRedirect(String location) throws IOException {
String finalurl = null;
if (isUrlAbsolute(location)) {
finalurl = location;
} else {
finalurl = fixForScheme(prefix + location);
}
super.sendRedirect(finalurl);
}
public boolean isUrlAbsolute(String url) {
String lowercaseurl = url.toLowerCase();
if (lowercaseurl.startsWith("http") == true) {
return true;
} else {
return false;
}
}
public String fixForScheme(String url) {
if (this.req.getHeader("X-redirect-to-https") != null) {
return url.replaceFirst("http", "https");
} else {
return url;
}
}
public String getPrefix(HttpServletRequest request) {
StringBuffer str = request.getRequestURL();
String url = str.toString();
String uri = request.getRequestURI();
int offset = url.indexOf(uri);
String prefix_t = url.substring(0, offset);
return prefix_t;
}
}

Orchestra and RichFaces problem

I use Orchestra and RichFaces in my application. When accessing a page in my application I get the following error many times and the page doesn't load:
WARN _ReentrantLock:103 - Waited for longer than 30000 milliseconds for access to lock org.apache.myfaces.orchestra.lib._ReentrantLock#78214f6b which is locked by thread http-8080-2
I believe at the heart the problem is a filter that I use for authentication. Here is its code (conversationController is a conversation scoped bean):
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
FacesContextBuilder builder = new FacesContextBuilder();
FacesContext facesContext = builder.getFacesContext(request, response);
ServletContext servletContext = (ServletContext) facesContext.getExternalContext().getContext();
Application application = facesContext.getApplication();
ELContext elContext = facesContext.getELContext();
ConversationController conversationController = (ConversationController) application.getELResolver().getValue(elContext, null, "conversationController");
SessionController sessionController = (SessionController) application.getELResolver().getValue(elContext, null, "sessionController");
ApplicationController applicationController = (ApplicationController) application.getELResolver().getValue(elContext, null, "applicationController");
EntityRegistry entityRegistry = conversationController.getEntityRegistry();
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse= (HttpServletResponse) response;
User currentUser = sessionController.getCurrentUser();
Boolean isTesting = (Boolean) servletContext.getAttribute("ginger.TESTING");
if (isTesting == null) isTesting = false;
if (currentUser == null)
{
if (httpRequest.isSecure() || isTesting)
{
Cookie[] cookies = httpRequest.getCookies();
Cookie cookie = null;
if (cookies != null)
{
for (int i=0; i<cookies.length; i++)
{
if (cookies[i].getName().equals("ginger.USERCOOKIE"))
{
cookie = cookies[i];
break;
}
}
}
if (cookie != null)
{
currentUser = entityRegistry.getUserByCookie(cookie.getValue());
}
if (currentUser == null)
{
currentUser = new UnregisteredUser();
String cookieValue = String.valueOf(applicationController.getRandom());
currentUser.setCookie(cookieValue);
entityRegistry.storeUser(currentUser);
cookie = new Cookie("ginger.USERCOOKIE", cookieValue);
cookie.setPath(applicationController.getPath());
cookie.setMaxAge(365*24*60*60);
if (!isTesting) cookie.setSecure(true);
httpResponse.addCookie(cookie);
}
sessionController.setCurrentUser(currentUser);
#SuppressWarnings("unchecked")
String url = URLConstructor.constructUrl(servletContext, httpRequest, httpResponse, false, httpRequest.getRequestURI(), httpRequest.getParameterMap());
httpResponse.sendRedirect(url);
}
else
{
#SuppressWarnings("unchecked")
String url = URLConstructor.constructUrl(servletContext, httpRequest, httpResponse, true, httpRequest.getRequestURI(), httpRequest.getParameterMap());
httpResponse.sendRedirect(url);
}
}
else
{
chain.doFilter(request, response);
}
builder.removeFacesContext();
}
I solved this by releasing the FacesContext before the response is committed. Like this:
#SuppressWarnings("unchecked")
String url = URLConstructor.constructUrl(servletContext, httpRequest, httpResponse, true, httpRequest.getRequestURI(), httpRequest.getParameterMap());
builder.removeFacesContext();
httpResponse.sendRedirect(url);
I don't understand this completely but it seems that the new request was still using the old FacesContext and this interfered with the _ReentrantLock getting unlocked.

Resources