How to use a filter in a Struts1 application? - servlet-filters

I have a Struts1 application and am unsuccessfully trying to get a filter to work in order to add headers/etc to the response after the action has completed, but am not able to get it to work.
By the time the struts action is completed and control is returned to my filter, the response is already committed.
web.xml:
<filter>
<filter-name>workflow</filter-name>
<filter-class>webapp.session.WorkflowModifierFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>workflow</filter-name>
<servlet-name>action</servlet-name>
</filter-mapping>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/struts-config.xml</param-value>
</init-param>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>detail</param-name>
<param-value>2</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
My filter is the following:
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
if( !servletResponse.isCommitted() )
log.debug("Not Committed! Can modify it!");
filterChain.doFilter(servletRequest, servletResponse);
// depending on the response, I'd like to add headers here
if( servletResponse.getStatus() == 200 )
servletResponse.addIntHeader( "x-success", 1 );
if( servletResponse.isCommitted() )
log.debug("ACCCK! Response Already Committed");
}
However, I noticed that my x-success header was never added. A little digging, and I noticed that my response was already returned/committed by the time the control returned to my filter chain.
What is the proper way to do this in Struts1? Does the filter execution not supposed to wrap the entire servlet? Why is my response being committed prior to my filter finishing? Where else can I add headers based on the response (post action processing) in Struts1?

When you call filterChain.doFilter you pass control from your filter to requested page (ie Struts), which is then free to commit the response if it chooses. Since you want to examine the result of the Struts servlet, you should create a response wrapper class (extends HttpServletRequestWrapper) and pass that to filterChain.doFilter rather than the response parameter that your filter is passed.
HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
ServletResponse wrapper = new MyHttpServletRequestWrapper(httpResponse);
filter.doChain(servletRequest, wrapper);
The HttpServletRequestWrapper constructor accepts a HttpServletResponse as input and delegates all methods to the wrapped response, but you can override them in your class as necessary. If you want to prevent the response from being committed you'll want to override methods such as flushBuffer, getOutputSteam (the returned output stream can be flushed, committing the response), and getWriter.
But you may not need to do that - try overriding the setStatus method in the wrapper class to check the status code and add the header when it is called:
public class MyHttpServletResponseWrapper extends HttpServletResponseWrapper {
public MyHttpServletResponseWrapper(HttpServletResponse response) {
super(response);
}
#Override
public void setStatus(int sc) {
if(sc == 200) {
addIntHeader("x-success", 1);
}
super.setStatus(sc);
}
}
Although this question was asked in relation to Struts 1, it applies to any web framework; my answer is based on work I did for JSF filters.

Related

Block server-to-server or Postman calls to ASP.NET Core 3.1 API

I have a ASP.NET Core 3.1 API where I have not used CORS. As I understand, CORS is a browser thing. And as my ajax calls from another site on another origin is blocked to the API endpoints (which is great), I can still reach the same endpoints by using Postman or a HttpClient and GetAsync() calls.
My question is of it's possible to also block server-to-server calls (or Postman calls) to my API? Or like CORS, only allow certain origins?
Most of my endpoints are protected by a bearer JWT token, but I have an anonymous endpoint that I would like to let only origins I control (or can configure) to have access to that anonymous API.
I solved it after i bumped in to this post on stackoverflow:
How do you create a custom AuthorizeAttribute in ASP.NET Core?
I simply made a custom Authorize attribute [ApiAuthorize()], that I call this way:
[ApiController]
[ApiAuthorize(new string[] { "https://localhost:44351", "https://mysite.onthe.net" })]
public class MyInternalApiController : ControllerBase
{
...
}
It may also be implemented on the Action instead of the Controller. The implementation was done like this:
public class ApiAuthorizeAttribute : TypeFilterAttribute
{
public ApiAuthorizeAttribute(string[] origins) : base(typeof(ApiAuthorizeFilter))
{
Arguments = new object[] { origins };
}
}
public class ApiAuthorizeFilter : IAuthorizationFilter
{
readonly string[] _origins;
public ApiAuthorizeFilter(string[] origins)
{
_origins = origins;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
if (_origins == null)
return;
string referer = context.HttpContext.Request.Headers["Referer"].ToString();
if (string.IsNullOrWhiteSpace(referer) || !_origins.Any(origin => referer.StartsWith(origin, StringComparison.OrdinalIgnoreCase)))
context.Result = new ForbidResult();
}
}
Things to consider:
The implementation and check of the referer could be exact match instead of StartsWith
The handling could use RegEx or any good alternative to handle subdomains, wildcards etc
The referer could be translated to a Uri objects to get better results and variations
A jQuery ajax call gets a "403 - Forbidden" as expected, but Postman gets a "404 - Not Found". To me that does not matter, but that's something to look into if it matters.
But it covers what I need, so I'm happy with this.

How to remove "Server" header from the restlet/jetty response?

I use Restlet integration with Jetty in my project. I would need to remove the "Server" header from the response as it discloses server information. But since I use Restlet integration with Jetty (restlet, jetty, org.restlet.ext.jetty.jar) the HttpConfiguration object is instantiated inside Restlet and not in my code. So I am not able to set "_sendServerVersion" as false and hence not able to remove the server header from the response. How to remove the server header from the response in this case ?
The best way to create a Filter and remove the header through the Filter:
public class ServerFilter extends Filter {
public ServerFilter(Context context) {
super(context);
}
#Override
protected void afterHandle(Request request, Response response) {
response.getHeaders().set("Server", null);
super.afterHandle(request, response);
}
}
Then use it like:
ServerFilter serverFilter = new ServerFilter(getContext());
serverFilter.setNext(router);
return serverFilter;
See: https://javadocs.restlet.talend.com/2.4/jee/api/index.html for documentation

Bad Request Error (400) or Not Found Error (404) during extracting request parameters in jax-rs

In docs.oracle ( https://docs.oracle.com/javaee/7/tutorial/jaxrs002.htm ) about Extracting Request Parameters we have:
If the URI path template variable cannot be cast to the specified type, the JAX-RS runtime returns an HTTP 400 (“Bad Request”) error to the client.
If the #PathParam annotation cannot be cast to the specified type, the JAX-RS runtime returns an HTTP 404 (“Not Found”) error to the client.
Could someone explain the difference, maybe give an example?
I don't know why, but if you define method with path param and it fails on cast string to custom type, jax-rs (in my case jersey servlet) returns 404 error, and it's very strange and unexpected.
package com.example.jaxrs.api;
...
#GET
#Path("/{id}/info")
public Response info(#PathParam UUID id) { ... }
curl -X GET http://127.0.0.1:8080/api/123/info --> HTTP ERROR 404 Not Found
This behavior can be changed. Just create simple ExceptionMapper, catch the ParamException, and return correct "400 Bad Request" response.
package com.example.jaxrs.errors;
#Provider
public class PathParamExceptionMapper implements ExceptionMapper<ParamException> {
#Override
public Response toResponse(ParamException exception) {
return Response.status(Response.Status.BAD_REQUEST).build();
}
}
Do not forgot annotation #Provider and add the package with error handler to servlet init param.
<servlet>
<servlet-name>jersey-serlvet</servlet-name>
<servlet-class>org.glassfish.jersey.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>jersey.config.server.provider.packages</param-name>
<param-value>com.example.jaxrs.api;com.example.jaxrs.errors</param-value>
</init-param>
</servlet>

Glassfish 4, JSF 2.2 and PrimeFaces FileUploadEvent not working together

After upgrading to GlassFish 4 and JSF 2.2 Primefaces FileUploadEvent stop working. With JSF 2.1 it was working with no problem. Everything is working fine except file uploading. Is there something that I am missing?
GlassFish 4
JSF 2.2
PrimeFaces 3.4.2 and 3.5
Commons io version: 2.4
Commons fileupload version: 1.3
Controller side
public void handleFileUpload(FileUploadEvent event) {
System.out.println("HandleFileUpload");
byte[] file = event.getFile().getContents();
newFieldset.setData(file);
FacesMessage msg = new FacesMessage("Succesful", event.getFile().getFileName() + " is uploaded.");
FacesContext.getCurrentInstance().addMessage(null, msg);
}
View
<h:form enctype="multipart/form-data">
<p:fieldset legend="Create new feed" toggleable="true" collapsed="true" >
<p:fileUpload fileUploadListener="#{adminHomeController.handleFileUpload}" style="margin-top: 20px;"
mode="advanced"
update="messages"
sizeLimit="1000000"
multiple="false"
allowTypes="/(\.|\/)(gif|jpe?g|png)$/"/>
<p:inputText label="Baslik" style="margin-top: 20px;" required="true" value="#{adminHomeController.newFieldset.legend}" />
<p:editor style="margin-top: 20px;"
value="#{adminHomeController.newFieldset.content}" />
<p:commandButton style="margin-top: 20px;" value="#{msg['common.save']}" update="messages" icon="ui-icon-disk" actionListener="#{adminHomeController.saveFieldset()}"/>
</p:fieldset>
<p:growl id="messages" showDetail="true"/>
</h:form>
I was finally able to figure it out. Commons-fileuploads method parseRequest(httpServletRequest) tries to read the request's inputStream. Since the container already read it, it is empty. So what can be done to solve this? The answer is a bit more complicated than I initially thought it would be. First you will need your own FileUploadFilter which could look like this:
public class FileUploadFilter implements Filter
{
private final static Logger LOGGER = LoggerFactory.getLogger(FileUploadFilter.class);
/*
* (non-Javadoc)
*
* #see javax.servlet.Filter#init(javax.servlet.FilterConfig)
*/
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
/*
* (non-Javadoc)
*
* #see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse, javax.servlet.FilterChain)
*/
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
boolean isMultipart = (httpServletRequest.getContentType() == null) ? false : httpServletRequest.getContentType().toLowerCase().startsWith("multipart/");
if (isMultipart)
{
MultipartRequest multipartRequest = new MultipartRequest(httpServletRequest);
LOGGER.info("File upload request parsed succesfully, continuing with filter chain with a wrapped multipart request");
filterChain.doFilter(multipartRequest, response);
}
else
{
filterChain.doFilter(request, response);
}
}
/*
* (non-Javadoc)
*
* #see javax.servlet.Filter#destroy()
*/
#Override
public void destroy()
{
LOGGER.info("Destroying UploadFilter");
}
Next: Register this filter in your web.xml and remove/replace the Primefaces filter. This should look something like this:
<filter>
<filter-name>FileUpload Filter</filter-name>
<filter-class><YourPackage>.FileUploadFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>FileUpload Filter</filter-name>
<servlet-name>Faces Servlet</servlet-name>
</filter-mapping>
Unfortunately thats not it. You will need your own MultipartRequest since you have to assemble the list of FileItems by yourself. But Stop. We have to work with the javax.servlet.Part classes which are not compatible with the FileItem. So i wrote a new class which bridges these two. You can find this class here: http://pastebin.com/JcfAYjey
The last piece of the puzzle is the mentioned MultipartRequest which links the PartItem and the FileUploadFilter. I took this class from the Primefaces-Repository and changed it according to out needs (see http://pastebin.com/Vc5h2rmJ). The difference is between lines 47 and 57.
So what do you have to do:
1. Create the three classes FileUploadFilter, MultipartRequest and PartItem
2. Register the FileUploadFilter in your web.xml
3. Enjoy!
PLEASE NOTE: This is not intended as a solve-all-problems solution but a merely a direction you may take in further implementations. The MultipartRequest for example will only work for parts with content-type image/*. You may need to change this.
Feel free to change the code ;) Hope it helps!
EDIT: I forgot to mention one important step. You will additionally need your Own FileIUploadRenderer. The one Primefaces implemented uses an instanceof check to find the MultipartRequest. Since you are now using a different one the import has to be changed. The rest of the class can stay the same (http://pastebin.com/rDUkPqf6). Don't forget to register it inside of your faces-config.xml :
<render-kit>
<renderer>
<component-family>org.primefaces.component</component-family>
<renderer-type>org.primefaces.component.FileUploadRenderer</renderer-type>
<renderer-class><YourPackage>.FileUploadRenderer</renderer-class>
</renderer>
</render-kit>
Answer lies in UploadFile getInputstream() method. Don't rely on getContents() method.
This is my simple solution which worked with the below dependencies in glassfish 4
Primefaces 4.0.RC1
jsf 2.2
commons-fileupload 1.3
private byte[] getFileContents(InputStream in) {
byte[] bytes = null;
try {
// write the inputStream to a FileOutputStream
ByteArrayOutputStream bos = new ByteArrayOutputStream();
int read = 0;
bytes = new byte[1024];
while ((read = in.read(bytes)) != -1) {
bos.write(bytes, 0, read);
}
bytes = bos.toByteArray();
in.close();
in = null;
bos.flush();
bos.close();
bos = null;
logger.debug("New file created!");
} catch (IOException e) {
System.out.println(e.getMessage());
}
return bytes;
}
getFileContents(getFile().getInputstream());
Try to delete beans.xml (CDI configuration file) and use JSF beans.
I saw on PrimeFaces blog that full JSF 2.2 support will be as of version 4.0.
See 3.5 is missing dependency - so won't launch
I think it's a commons-fileupload issue. When I debug through the code, the PrimeFaces' UploadFilter triggers correctly the commons-fileupload's FileUploadBase.parseRequest method (identically flow when I use GlassFish 3.1.22 or GlassFish 4), but the check on FileItemIterator.hasNext returns false.

JSF 2.0 Simple login page

I need to restrict the access to a part of the application. In order to access that part, user needs to log in. I have a table in my database called User, with usernames and hashed passwords and a login form that consists of two inputs and a submit. However, I don't know which classes/mathids should I use to log in the user (I assume that there is a support for this functionality in jsf). Also, as far as I know, I need to edit my web.xml to support the authentification. Could someone propose a typical solutions and general steps that I need to do in order to get that functionality (links, tutorials of a value greatly appreciated)?
i also wonder how do I limit the access to another page if the person is not logged in so when the user types in the direct link to a page, he will be redirected to a main login page.
Thanks in advance for any help.
Grem.
You could use the HttpServletRequest API introduced in Servlet 3.0:
/**
* Performs authentication via HttpServletRequest API
*/
public String login(String username, String password) throws IOException {
try {
getRequest().login(username, password);
this.user = userDao.find(username);
} catch (ServletException e) {
JsfUtil.addErrorMessage(JsfUtil.getStringResource("loginFailed"));
return null;
}
return "/index?faces-redirect=true";
}
public String logout() throws ServletException {
this.user = null;
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
if (isAuthenticated())
getRequest().logout();
return "logout";
}
public boolean isAuthenticated() {
return getRequest().getUserPrincipal() != null;
}
public static HttpServletRequest getRequest() {
Object request = FacesContext.getCurrentInstance().getExternalContext().getRequest();
return request instanceof HttpServletRequest
? (HttpServletRequest) request : null;
}
You can use j_security_check. All you do is post to it, and it will handle authentication based on the realm you've defined, and the application-specific configuration in your web.xml.
Depending on your app server, there is an additional step of linking the defined role (app-specific) to a group (realm-specific).
Here is a typical configuration:
<servlet>
<servlet-name>Login</servlet-name>
<servlet-class>com.example.Login</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Login</servlet-name>
<url-pattern>/Login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>Error</servlet-name>
<servlet-class>com.example.Error</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Error</servlet-name>
<url-pattern>/Error</url-pattern>
</servlet-mapping>
<login-config>
<auth-method>FORM</auth-method>
<realm-name>example.com</realm-name>
<form-login-config>
<form-login-page>/Login</form-login-page>
<form-error-page>/Error</form-error-page>
</form-login-config>
</login-config>
<security-role>
<role-name>arbitraryRoleName</role-name>
</security-role>
<security-constraint>
<web-resource-collection>
<web-resource-name>All Pages</web-resource-name>
<url-pattern>/index.xhtml</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>arbitraryRoleName</role-name>
</auth-constraint>
</security-constraint>
Note the security-role. This still needs linked into a group, or whatever you are defining to differentiate users that can use a page from users who can't.