TheCoder4eu / BootsFaces-OSP

BootsFaces - Open Source Project
Apache License 2.0
247 stars 102 forks source link

Adding a Navbar to a stateless page causes a session to be created #1117

Closed exabrial closed 1 month ago

exabrial commented 4 years ago

Open source work often goes unthanked, so thank you for being great stewards of BootFaces. We love the framework.

I'm not completely convinced this is a bug in BootsFaces, but I figured I'd log a bug anyway. We noticed that adding a Navbar (and likely any bootsfaces components) will cause a session to get created, even on a page with a stateless view. We're running Apache TomEE 7.0.6 which uses OpenWebBeans 1.7.6 and MyFaces 2.2.12. I grabbed a stack trace so you can see how it gets created.

It could be that CDIManagedBeanHandlerImpl needs to be a little smarter and not dip down into the session when on a stateless view.

SessionContext.<init>() line: 39    
CdiAppContextsService(WebContextsService).initSessionContext(Object) line: 480  
CdiAppContextsService(WebContextsService).startContext(Class<Annotation>, Object) line: 311 
BeginWebBeansListener.sessionCreated(HttpSessionEvent) line: 150    
StandardSession.tellNew() line: 388 
StandardSession.setId(String, boolean) line: 360    
StandardSession.setId(String) line: 341 
StandardManager(ManagerBase).createSession(String) line: 656    
Request.doGetSession(boolean) line: 3087    
Request.getSession(boolean) line: 2476  
RequestFacade.getSession(boolean) line: 896 
CdiAppContextsService(WebContextsService).lazyStartSessionContext(boolean) line: 820    
CdiAppContextsService(WebContextsService).getSessionContext(boolean) line: 737  
CdiAppContextsService(WebContextsService).getCurrentContext(Class<Annotation>) line: 277    
BeanManagerImpl.getContext(Class<Annotation>) line: 291 
SessionScopedBeanInterceptorHandler(NormalScopedBeanInterceptorHandler).getContextualInstance() line: 89    
SessionScopedBeanInterceptorHandler.getContextualInstance() line: 76    
SessionScopedBeanInterceptorHandler(NormalScopedBeanInterceptorHandler).get() line: 71  
ViewScopeBeanHolder$$OwbNormalScopeProxy0.generateUniqueViewScopeId() line: not available [local variables unavailable] 
CDIManagedBeanHandlerImpl.generateViewScopeId(FacesContext) line: 92    
ViewScopeProxyMap.getWrapped() line: 79 
ViewScopeProxyMap.get(Object) line: 119 
AddResourcesListener.addResource(String, String, String, String) line: 896  
AddResourcesListener.addResourceToHeadButAfterJQuery(String, String) line: 839  
Tooltip.addResourceFiles() line: 134    
NavBar.<init>() line: 52    
NativeConstructorAccessorImpl.newInstance0(Constructor<?>, Object[]) line: not available [native method]    
NativeConstructorAccessorImpl.newInstance(Object[]) line: 62    
DelegatingConstructorAccessorImpl.newInstance(Object[]) line: 45    
Constructor<T>.newInstance(Object...) line: 423 
Class<T>.newInstance() line: 442 [local variables unavailable]  
ApplicationImpl.createComponent(FacesContext, String) line: 1442    
ApplicationImpl.createComponent(FacesContext, String, String) line: 1405    
OwbApplication(ApplicationWrapper).createComponent(FacesContext, String, String) line: 123  
OmniApplication(ApplicationWrapper).createComponent(FacesContext, String, String) line: 123 
ComponentTagHandlerDelegate.createComponent(FaceletContext) line: 605   
ComponentTagHandlerDelegate.apply(FaceletContext, UIComponent) line: 285    
ComponentHandler(DelegatingMetaTagHandler).apply(FaceletContext, UIComponent) line: 50  
CompositeFaceletHandler.apply(FaceletContext, UIComponent) line: 46 
HtmlComponentHandler(DelegatingMetaTagHandler).applyNextHandler(FaceletContext, UIComponent) line: 55   
ComponentTagHandlerDelegate.apply(FaceletContext, UIComponent) line: 374    
HtmlComponentHandler(DelegatingMetaTagHandler).apply(FaceletContext, UIComponent) line: 50  
CompositeFaceletHandler.apply(FaceletContext, UIComponent) line: 46 
ViewHandler.apply(FaceletContext, UIComponent) line: 195    
CompositeFaceletHandler.apply(FaceletContext, UIComponent) line: 46 
NamespaceHandler.apply(FaceletContext, UIComponent) line: 59    
CompositeFaceletHandler.apply(FaceletContext, UIComponent) line: 46 
CompositeFaceletHandler.apply(FaceletContext, UIComponent) line: 46 
EncodingHandler.apply(FaceletContext, UIComponent) line: 48 
DefaultFacelet.apply(FacesContext, UIComponent) line: 189   
FaceletViewDeclarationLanguage.buildView(FacesContext, UIViewRoot) line: 477    
RenderResponseExecutor.execute(FacesContext) line: 78   
LifecycleImpl.render(FacesContext) line: 267    
FacesServlet.service(ServletRequest, ServletResponse) line: 200 
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 231  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 166  
WsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 52    
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 193  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 166  
RewriteFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 226  
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 193  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 166  
ApplicationDispatcher.invoke(ServletRequest, ServletResponse, ApplicationDispatcher$State) line: 728    
ApplicationDispatcher.processRequest(ServletRequest, ServletResponse, ApplicationDispatcher$State) line: 470    
ApplicationDispatcher.doForward(ServletRequest, ServletResponse) line: 395  
ApplicationDispatcher.forward(ServletRequest, ServletResponse) line: 316    
HttpRewriteResultHandler.handleResult(Rewrite) line: 42 
RewriteFilter.rewrite(InboundServletRewrite<ServletRequest,ServletResponse>) line: 297  
RewriteFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 198  
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 193  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 166  
StandardWrapperValve.invoke(Request, Response) line: 199    
StandardContextValve.invoke(Request, Response) line: 96 
OpenEJBValve.invoke(Request, Response) line: 44 
NonLoginAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 493    
StandardHostValve.invoke(Request, Response) line: 137   
ErrorReportValve.invoke(Request, Response) line: 81 
OpenEJBSecurityListener$RequestCapturer.invoke(Request, Response) line: 97  
AccessLogValve(AbstractAccessLogValve).invoke(Request, Response) line: 660  
StandardEngineValve.invoke(Request, Response) line: 87  
CoyoteAdapter.service(Request, Response) line: 343  
Http11Processor.service(SocketWrapperBase<?>) line: 798 
Http11Processor(AbstractProcessorLight).process(SocketWrapperBase<?>, SocketEvent) line: 66 
AbstractProtocol$ConnectionHandler<S>.process(SocketWrapperBase<S>, SocketEvent) line: 808  
NioEndpoint$SocketProcessor.doRun() line: 1498  
NioEndpoint$SocketProcessor(SocketProcessorBase<S>).run() line: 49  
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1149  
ThreadPoolExecutor$Worker.run() line: 624   
TaskThread$WrappingRunnable.run() line: 61  
TaskThread(Thread).run() line: 748  
stephanrauh commented 4 years ago

Thanks for the kind words! BootsFaces is a leisure time project, so a little "thumbs up!" means a lot to us!

stephanrauh commented 4 years ago

These days I'm mostly occupied with another open source project of mine, but I must admit your bug looks interesting. I'm not sure I can reproduce it quickly. Would you mind to send us a reproducer? I.e. a small, minimal program showing the bug without requiring any configuration? In particular, no database?

Thanks in advance, Stephan

exabrial commented 4 years ago

You're welcome :)

I forgot to mention the scenario why this is important: If you want to use bootsfaces on a page that's unauthenticated (like a login page), an attacker can run your server out of resources easy[ier] by creating millions of sessions. It's best to not let a public facing page modify the state of the server.

I'll get a reproducer to you tomorrow or wed, it's pretty easy to reproduce. Cheers, -Jonathan

exabrial commented 4 years ago

Digging a little further Reading the Javadoc, I do think TomEE/MyFaces/OpenWebBeans is behaving correctly.

https://docs.oracle.com/javaee/7/api/javax/faces/component/UIViewRoot.html#getViewMap--

For reasons made clear in ViewScoped, this map must ultimately be stored in the session. For this reason, a true value for the create argument will force the session to be created with a call to ExternalContext.getSession(boolean)

So essentially, NavBar is forcing a session object to be created with this call:

https://github.com/TheCoder4eu/BootsFaces-OSP/blob/e4d4ba2c15a3357ed9e792b6551893af631e022d/src/main/java/net/bootsfaces/listeners/AddResourcesListener.java#L896

exabrial commented 4 years ago

https://github.com/exabrial/bootsfaces-session-bug Here's a demo that shows session is always created on the stateless view

stephanrauh commented 4 years ago

As far as I can tell without running the program, you've delivered an awesome analysis, complete with a solution, and even a reproducer. We don't see that often. In return, I've put your issue + PR on the top of my "to do" list. Actually, I wanted to do it this evening, but I didn't manage to find enough spare time.

Regarding the reproducer: which application server to I need? Currently, I've installed a Liberty server and a Wildfly 17. Can I run the reproducer on one of them?

exabrial commented 4 years ago

It's a one stop shop. Simply clone it, then run mvn clean package tomee:run. After it starts visit: http://localhost:8080/bootsfaces-session-bug/index.jsf

exabrial commented 4 years ago

If you want to run it on another application server, you can mvn clean package then install the war in OpenLiberty or Wildfly or Glassfish.

geopossachs commented 4 years ago

Hello, I have used your example project @exabrial and take also a look for a solution. I think to store data in a static property of a Listener is not the best way for an enterprise application.

if I use root. attributes I can also switch off the session use but if I switch on the session in a bean, the Listener does not recognize this

@stephanrauh do we need to store the data into root.viewMap or can we use root.attributes as allternative

or with other words 'how can you determine whether you can start a session or not'?

https://github.com/geopossachs/BootsFaces-OSP/tree/issues/1117

geopossachs commented 4 years ago

i have make a test - if store the data in root.attributes i has the old bug form the https://github.com/TheCoder4eu/BootsFaces-OSP/issues/1066

if i store the data into root.viewMap(true) the old bug is fixed

exabrial commented 4 years ago

@geopossachs Keep in mind, in my solution data is removed at the end of the request lifecycle when the listener is actually invoked, so after the whole request is done, we should be back to our original state: https://github.com/TheCoder4eu/BootsFaces-OSP/pull/1118/files#diff-7b5232f2606e479ecdc3e610b3991d99R147

exabrial commented 4 years ago

I assume by "enterprise application" you mean an environment where the session state is distributed (as in our environment). I'm not sure why this data would need to be distributed... one server can't handle half of the request lifecycle.

geopossachs commented 4 years ago

I thought more in the direction of the MVC pattern on which JSF is based.

with a partial AJAX update to a request scoped page, you still need a session because you want to remember which resources have already been loaded by the browser

exabrial commented 4 years ago

Yikes that kinda defeats the purpose of a request scoped page

geopossachs commented 4 years ago

@exabrial to use JSF without a session is not so easy - you have in your example an <f:view ... transient="true"> but when I add a also a session is started - that was not the expected behavior - I have change for the AddResourcesListener.java class in my PR the function for three checks if we can use the view map - transient view, http session and if a form exists

exabrial commented 4 years ago

Added one comment to your PR, I don't think using a form implies a session creation. Couldn't find it in the spec, and I found a few counterexamples

exabrial commented 4 years ago

I'm very grateful for what you guys do and the time you spend on this project. If I can assist, please feel free to assign tasks out to me to help move this or other bugs along towards a release.

t-oster commented 4 years ago

Hi, I was just about to create a similar issue. I also get a lot of warnings about @ViewScoped beans are not supported on stateless views, which boils down to the same problem I guess. I debugged it so far that, this line AddResourcesListener.addResourceToHeadButAfterJQuery(C.BSF_LIBRARY, "js/tooltip.js"); in the addResouceFiles method of Tooltip (https://github.com/TheCoder4eu/BootsFaces-OSP/blob/master/src/main/java/net/bootsfaces/render/Tooltip.java#L134) causes an (empty!) viewMap to be created, which in turn causes those warnings. Would this be also resolved by #1123?

stephanrauh commented 1 month ago

I'm afraid development of BootsFaces has slowed down considerably. We'll never manage to address this issue. Let's close it.