14 Replies Latest reply on Dec 2, 2009 6:01 AM by meetoblivion

    Embedding the rest server in an EAR

    meetoblivion

      All,

      I want to embed the REST server in an EAR file, and make it so that there will be a couple of different web apps in the same EAR, all sharing the JCR repo. I still haven't decided if this was a good idea or not, but hey let's see!

      I only plan on using REST for GETs, not other operations. To do this, I think I need to extend DnaJcrRepositoryProvider. To make it easier, I'm wondering if we could make the JcrEngine protected, not public.

      Of course, this all goes away by just overriding all methods, which is what I'm thinking I'll do. The next thing is I wanted to move all of the DNA related jar files into the ear lib, Idon't see a problem with that, does anyone else?

        • 1. Re: Embedding the rest server in an EAR
          bcarothers

          I've never tried deploying DNA in an EAR, but I can't think of any reason why it wouldn't work. I'd like to get to a JackRabbit-like deployment model where DNA gets packaged in such a way that it's exposed to other applications through JNDI on the server, but that's some extra config that other people might not want to go through. If anyone has an opinion on this, I'd love to hear it.

          If you want to expose a DNA repository through REST in a read-only fashion, wouldn't it be simpler to deploy the stock code and just not grant READWRITE or ADMIN to any web users?

          If you did want to REALLY lock this down through custom code, I think you'd need to deploy a different JcrResources class instead of modifying DnaJcrRepositoryProvider.

          I don't see an issue with the JAR motion that you've described. That should work and make the same DNA objects available to all the WARs in the EAR through classloader magic. Please let me know if that doesn't work.

          • 2. Re: Embedding the rest server in an EAR
            meetoblivion

            well, to be perfectly honest, there are definite pluses and minuses to what jackrabbit is trying to do, in my opinion.

            for one, like we just saw an issue w/ oracle, jackrabbit has a lot of issues w/ oracle. the bottom line is that it just doesn't work well, even if you apply the source patches they recommend. they also seem to have some issues deploying to jboss (both the WAR file and the JCA implementation).

            to be honest, i just realized that dna doesn't support mix:versionable. This is probably a big problem for me, but not necessarily. what we're trying to implement, is kind of a hold over until we get our portal solution situation figured out. we're currently on oracle portal (surprising huh?) but it's just a complete disaster. every few weeks we run out of disk space, and we can't figure out why. this is really to alleviate our public site from being impacted by the issues internally. what my plan is is to have on one side of our DMZ some basic forms that will allow users to do basic CMS related activities - create content, approve content, preview, etc. this would be internal. on the external side have a JSON based client that can poll the CMS (the repository war file) for changes daily and update itself. the data would be cached so really no big deal I figure. i figure i can add a version-by-copy style to the forms that will copy the previous contents to maybe a version path, and have the client end up being smart enough to figure all of this out.

            • 3. Re: Embedding the rest server in an EAR
              meetoblivion

              Hmmm, I"ve come across a stumper for me.

              I configured the system for no http authentication, commented out all of the security parts from web.xml in the resources war file. I'm able to get the initial JSON response from the REST APIs:

              {"default":{"repository":{"name":"default","resources":{"workspaces":"\/resources\/default"}}}}


              However, when I attempt to access the workspace, I get the following stacktrace... which I can't figure out. My app will only return true true for reads, not writes.

              23:13:33,711 ERROR [[Resteasy]] Servlet.service() for servlet Resteasy threw exception
              org.jboss.resteasy.spi.UnhandledException: org.jboss.resteasy.spi.UnauthorizedException: Client is not authorized
               at org.jboss.resteasy.core.SynchronousDispatcher.handleApplicationException(SynchronousDispatcher.java:247)
               at org.jboss.resteasy.core.SynchronousDispatcher.handleException(SynchronousDispatcher.java:159)
               at org.jboss.resteasy.core.SynchronousDispatcher.handleInvokerException(SynchronousDispatcher.java:138)
               at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:374)
               at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:336)
               at org.jboss.resteasy.core.SynchronousDispatcher.invoke(SynchronousDispatcher.java:103)
               at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:114)
               at org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher.service(HttpServletDispatcher.java:70)
               at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
               at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
               at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
               at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
               at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
               at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
               at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235)
               at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
               at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:190)
               at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:92)
               at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
               at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
               at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
               at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
               at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
               at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
               at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330)
               at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:829)
               at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:598)
               at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
               at java.lang.Thread.run(Thread.java:619)
              Caused by: org.jboss.resteasy.spi.UnauthorizedException: Client is not authorized
               at org.jboss.dna.web.jcr.rest.JcrResources.getSession(JcrResources.java:192)
               at org.jboss.dna.web.jcr.rest.JcrResources.getWorkspaces(JcrResources.java:245)
               at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
               at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
               at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
               at java.lang.reflect.Method.invoke(Method.java:597)
               at org.jboss.resteasy.core.MethodInjectorImpl.invoke(MethodInjectorImpl.java:119)
               at org.jboss.resteasy.core.ResourceMethod.invokeOnTarget(ResourceMethod.java:211)
               at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:176)
               at org.jboss.resteasy.core.ResourceMethod.invoke(ResourceMethod.java:166)
               at org.jboss.resteasy.core.SynchronousDispatcher.getResponse(SynchronousDispatcher.java:359)
               ... 25 more
              


              • 4. Re: Embedding the rest server in an EAR
                meetoblivion

                Ok, dug through the code a bit more..

                in the default provider class, you have the following for getSession

                public Session getSession( HttpServletRequest request,
                 String repositoryName,
                 String workspaceName ) throws RepositoryException {
                 assert request != null;
                 assert request.getUserPrincipal() != null : "Request must be authorized";
                
                 // Sanity check in case assertions are disabled
                 if (request.getUserPrincipal() == null) {
                 throw new UnauthorizedException("Client is not authorized");
                 }
                
                 Repository repository;
                
                 try {
                 repository = getRepository(repositoryName);
                
                 } catch (RepositoryException re) {
                 throw new NotFoundException(re.getMessage(), re);
                 }
                
                 return repository.login(new SecurityContextCredentials(new ServletSecurityContext(request)), workspaceName);
                
                 }


                Which is fine. In my case, I dont' care about user principal. However, in JcrResources, you have pretty much the same thing, at least in the beginning.

                private Session getSession( HttpServletRequest request,
                 String rawRepositoryName,
                 String rawWorkspaceName ) throws RepositoryException {
                 assert request != null;
                 assert request.getUserPrincipal() != null : "Request must be authorized";
                
                 // Sanity check
                 if (request.getUserPrincipal() == null) {
                 throw new UnauthorizedException("Client is not authorized");
                 }
                
                 return RepositoryFactory.getSession(request, repositoryNameFor(rawRepositoryName), workspaceNameFor(rawWorkspaceName));
                 }
                


                to me, I don't see a reason to do the check twice. more over, if the check is delegated down the provider, and teh implementation of the provider decides to not check on user principal, is it necessary to do it in JcrResource ?

                I'm going to make that change locally, but I wanted to see if there were any security implications of not allowing it to be delegated.

                • 5. Re: Embedding the rest server in an EAR
                  meetoblivion

                  and just to be clear, i wrote a custom provider that removes this check as well.

                  • 6. Re: Embedding the rest server in an EAR
                    bcarothers

                     

                    I configured the system for no http authentication


                    I think this is the root of the problem. HttpServletRequest.getUserPrincipal is always going to return null if the user is not authenticated. I think it's reasonable to remove the check within JcrResources since it's redundant with the default JcrProvider's check and prevents the kind of read-only anonymous access that you're talking about, but I'd appreciate any thoughts from others on the subjects.



                    • 7. Re: Embedding the rest server in an EAR
                      meetoblivion

                      so is the thought that the issue goes away if i just put in basic authentication, and just use a url like http://someone:someone@localhost:8080/resources ? I don't see that being a big deal, other than our proxy getting confused.

                      another thing, I did make the change you seemed to agree to as well, however, now I just get an error about the workspace being an invalid workspace for my repository, even though the listed repository is a Source. i'm still able to get the first level of REST response, but the second level, when I want the workspaces, gives me nothing

                      this is what my configuration looks like.

                      config = new JcrConfiguration();
                       RepositorySourceDefinition<JcrConfiguration> configSource = config.repositorySource("LittleSource")
                       .setProperty("predefinedWorkspaceNames","defworkspace")
                       .setProperty("defaultWorkspaceName", "defworkspace")
                       .setProperty("nameOfDefaultWorkspace", "defworkspace")
                       //.usingClass(JpaSource.class);
                       .usingClass(InMemoryRepositorySource.class);
                       config.repository("defrepo").setSource("LittleSource").registerNamespace("m", "http://tad.com/jcr/ns/1.0")
                       .registerNamespace("jcr","http://www.jcp.org/jcr/1.0").registerNamespace("nt","http://www.jcp.org/jcr/nt/1.0")
                       .registerNamespace("mix","http://www.jcp.org/jcr/mix/1.0").registerNamespace("dna","http://www.jboss.org/dna/1.0");
                      


                      When my code starts up, i have it create a few top level nodes, and it seems to create them just fine. but the rest api seems to not like me, i guess.

                      • 8. Re: Embedding the rest server in an EAR
                        bcarothers

                         

                        so is the thought that the issue goes away if i just put in basic authentication, and just use a url like http://someone:someone@localhost:8080/resources ? I don't see that being a big deal, other than our proxy getting confused.


                        Well, that should certainly work, but what you've been trying to do should work as well.

                        another thing, I did make the change you seemed to agree to as well, however, now I just get an error about the workspace being an invalid workspace for my repository, even though the listed repository is a Source. i'm still able to get the first level of REST response, but the second level, when I want the workspaces, gives me nothing


                        What does your custom provider look like? The workspace retrieval works by creating a session and calling getAccessibleWorkspaceNames(), so if you create a session with a SecurityContext that returns false for "READONLY", "READWRITE", and "ADMIN", then you would see this behavior.

                        • 9. Re: Embedding the rest server in an EAR
                          meetoblivion

                          Yep, that was it. I apparently spelled "readonly" as "readoney" in my code, so it was always returning false. It's working fine now.

                          • 10. Re: Embedding the rest server in an EAR
                            meetoblivion

                            alright so back on to this subject. was a conclusion ever reached related to this? i assume the code that does double validation still exists, so it won't work correctly.

                            • 11. Re: Embedding the rest server in an EAR
                              bcarothers

                              It seems to me like the underlying problem is that the DNA JCR implementation doesn't really support anonymous access natively. Everything you've described is just working around that shortcoming.

                              Let me know if you think I'm off-base, but I should have a patch in for this soon.

                              I added https://jira.jboss.org/jira/browse/DNA-565 to track this issue.

                              • 12. Re: Embedding the rest server in an EAR
                                meetoblivion

                                no, i think that's at least partially accurate. really though, i think it's the fact that the class JcrResources does the same check as the default provider. be design, i would say that DNA allows you to customize your provider by extending it, but then the underlying code still executes the same logic... so you can't override it.

                                Take a look at this previous post, in the code I had been using I removed the second check and created a custom provider that did not do this check.

                                "meetoblivion" wrote:
                                Ok, dug through the code a bit more..

                                in the default provider class, you have the following for getSession

                                public Session getSession( HttpServletRequest request,
                                 String repositoryName,
                                 String workspaceName ) throws RepositoryException {
                                 assert request != null;
                                 assert request.getUserPrincipal() != null : "Request must be authorized";
                                
                                 // Sanity check in case assertions are disabled
                                 if (request.getUserPrincipal() == null) {
                                 throw new UnauthorizedException("Client is not authorized");
                                 }
                                
                                 Repository repository;
                                
                                 try {
                                 repository = getRepository(repositoryName);
                                
                                 } catch (RepositoryException re) {
                                 throw new NotFoundException(re.getMessage(), re);
                                 }
                                
                                 return repository.login(new SecurityContextCredentials(new ServletSecurityContext(request)), workspaceName);
                                
                                 }


                                Which is fine. In my case, I dont' care about user principal. However, in JcrResources, you have pretty much the same thing, at least in the beginning.

                                private Session getSession( HttpServletRequest request,
                                 String rawRepositoryName,
                                 String rawWorkspaceName ) throws RepositoryException {
                                 assert request != null;
                                 assert request.getUserPrincipal() != null : "Request must be authorized";
                                
                                 // Sanity check
                                 if (request.getUserPrincipal() == null) {
                                 throw new UnauthorizedException("Client is not authorized");
                                 }
                                
                                 return RepositoryFactory.getSession(request, repositoryNameFor(rawRepositoryName), workspaceNameFor(rawWorkspaceName));
                                 }
                                


                                to me, I don't see a reason to do the check twice. more over, if the check is delegated down the provider, and teh implementation of the provider decides to not check on user principal, is it necessary to do it in JcrResource ?

                                I'm going to make that change locally, but I wanted to see if there were any security implications of not allowing it to be delegated.


                                • 13. Re: Embedding the rest server in an EAR
                                  bcarothers

                                  I doubled down and removed both of the checks in the DNA-565 patch.

                                  The patch also adds a configuration setting that sets the roles for anonymous users. It's disabled by default.

                                  The provider code now looks like this:


                                   public Session getSession( HttpServletRequest request,
                                   String repositoryName,
                                   String workspaceName ) throws RepositoryException {
                                   assert request != null;
                                  
                                   Repository repository;
                                  
                                   try {
                                   repository = getRepository(repositoryName);
                                  
                                   } catch (RepositoryException re) {
                                   throw new NotFoundException(re.getMessage(), re);
                                   }
                                  
                                   // If there's no authenticated user, try an anonymous login
                                   if (request.getUserPrincipal() == null) {
                                   return repository.login(workspaceName);
                                   }
                                  
                                   return repository.login(new SecurityContextCredentials(new ServletSecurityContext(request)), workspaceName);
                                   }
                                  


                                  And then you can just configure the anonymous access in your configRepository.xml file like so:

                                  
                                   <options jcr:primaryType="dna:options">
                                   <projectNodeTypes jcr:primaryType="dna:option" value="false"/>
                                   <jaasLoginConfigName jcr:primaryType="dna:option" value="dna-jcr"/>
                                   <anonymousUserRoles jcr:primaryType="dna:option" value="connect,readonly"/>
                                   </options>
                                  
                                  


                                  It seems like what you're trying to do should just work out-of-the-box. Please let me know if this approach suits.

                                  Thanks for catching this and bringing it back up.

                                  • 14. Re: Embedding the rest server in an EAR
                                    meetoblivion

                                    I'm probably still going to stick with my custom provider, as we do some additional auditing when we get the request in (by IP, path, etc); we want to watch to make sure there are no illegal accesses.

                                    But yes I do agree it should have a public RO mode out of the box. good feature.