Announcement

Collapse
No announcement yet.
X
  • Filter
  • Time
Clear All
new posts

    RPCManager.setLoginRequiredCallback

    Hi,

    I use smartGWt nightbuild 02-06-2011, GWT 2.2, Firefox 4.

    I have secured my application with spring 3.05

    Now when my application starts I'd like to determine whether I need to show a login screen or if the user already has a session.

    So in onModuleLoad
    Code:
    RPCManager.setLoginRequiredCallback(new LoginRequiredCallback(){
      @Override
      public void loginRequired(int i, RPCRequest rpcRequest, RPCResponse rpcResponse) {
        GWT.log("in login required");
        setLoginScreen();
      }
    }
    
    
    DataSource loginDS = DataSource.get("loginDS");
    loginDS.fetchData(new Criteria(), new DSCallback() {
    	@Override
    	public void execute(DSResponse response, Object rawData, DSRequest request) {
    		Record[] records = response.getData();
    		for (Record pojo: records) {
    			userName = pojo.getAttribute("login");
    			setHomeScreen();
    
    		}
    	}
    });
    I expected that I'll try to load up the current user. If it fails I'would continue in RPCManager LoginRequiredCallback. But it never reaches there. Instead there is an exception:

    Code:
    === 2011-06-03 16:48:20,880 [l0-2] INFO  RequestContext - URL: '/smartrise/sc/IDACall', User-Agent: 'Mozilla/5.0 (Windows NT 6.1; rv:2.0.1) Gecko/20100101 Firefox/4.0.1': Moz (Gecko) with Accept-Encoding header
    === 2011-06-03 16:48:20,915 [l0-2] DEBUG XML - Parsed XML from (in memory stream): 2ms
    === 2011-06-03 16:48:20,920 [l0-2] DEBUG XML - Parsed XML from C:\zdary\svn\smartRISE\RISE2\war\smartrise\sc\system\schema\List.ds.xml: 3ms
    === 2011-06-03 16:48:20,922 [l0-2] DEBUG RPCManager - Processing 1 requests.
    === 2011-06-03 16:48:20,933 [l0-2] DEBUG RPCManager - Request #1 (DSRequest) payload: {
        criteria:{
        },
        operationConfig:{
            dataSource:"loginDS",
            operationType:"fetch"
        },
        appID:"builtinApplication",
        operation:"loginDS_fetch",
        oldValues:{
        }
    }
    === 2011-06-03 16:48:20,938 [l0-2] INFO  IDACall - Performing 1 operation(s)
    === 2011-06-03 16:48:20,940 [l0-2] WARN  RequestContext - dsRequest.execute() failed: 
    java.lang.SecurityException: DataSource 'loginDS', operationType 'fetch', operationId 'loginDS_fetch'.  No authenticated user
    	at com.isomorphic.datasource.DSRequest.execute(DSRequest.java:1525)
    	at com.isomorphic.servlet.IDACall.handleDSRequest(IDACall.java:173)
    	at com.isomorphic.servlet.IDACall.processRPCTransaction(IDACall.java:130)
    	at com.isomorphic.servlet.IDACall.processRequest(IDACall.java:95)
    	at com.isomorphic.servlet.IDACall.doPost(IDACall.java:54)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:637)
    	at com.isomorphic.servlet.BaseServlet.service(BaseServlet.java:152)
    	at javax.servlet.http.HttpServlet.service(HttpServlet.java:717)
    	at org.mortbay.jetty.servlet.ServletHolder.handle(ServletHolder.java:487)
    	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1097)
    	at com.isomorphic.servlet.CompressionFilter.doFilter(CompressionFilter.java:259)
    	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1088)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:368)
    	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:109)
    	at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:83)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:97)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:100)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:54)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:35)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:187)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:79)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at cz.bcom.smartrise.server.authentication.SpringConcurrentSessionFilter.doFilter(SpringConcurrentSessionFilter.java:54)
    	at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:380)
    	at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:169)
    	at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:237)
    	at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:167)
    	at org.mortbay.jetty.servlet.ServletHandler$CachedChain.doFilter(ServletHandler.java:1088)
    	at org.mortbay.jetty.servlet.ServletHandler.handle(ServletHandler.java:360)
    	at org.mortbay.jetty.security.SecurityHandler.handle(SecurityHandler.java:216)
    	at org.mortbay.jetty.servlet.SessionHandler.handle(SessionHandler.java:181)
    	at org.mortbay.jetty.handler.ContextHandler.handle(ContextHandler.java:729)
    	at org.mortbay.jetty.webapp.WebAppContext.handle(WebAppContext.java:405)
    	at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    	at org.mortbay.jetty.handler.RequestLogHandler.handle(RequestLogHandler.java:49)
    	at org.mortbay.jetty.handler.HandlerWrapper.handle(HandlerWrapper.java:152)
    	at org.mortbay.jetty.Server.handle(Server.java:324)
    	at org.mortbay.jetty.HttpConnection.handleRequest(HttpConnection.java:505)
    	at org.mortbay.jetty.HttpConnection$RequestHandler.content(HttpConnection.java:843)
    	at org.mortbay.jetty.HttpParser.parseNext(HttpParser.java:647)
    	at org.mortbay.jetty.HttpParser.parseAvailable(HttpParser.java:211)
    	at org.mortbay.jetty.HttpConnection.handle(HttpConnection.java:380)
    	at org.mortbay.io.nio.SelectChannelEndPoint.run(SelectChannelEndPoint.java:395)
    	at org.mortbay.thread.QueuedThreadPool$PoolThread.run(QueuedThreadPool.java:488)
    === 2011-06-03 16:48:20,943 [l0-2] DEBUG RPCManager - Content type for RPC transaction: text/plain; charset=UTF-8
    === 2011-06-03 16:48:20,944 [l0-2] DEBUG RPCManager - non-DMI response, dropExtraFields: false
    === 2011-06-03 16:48:20,945 [l0-2] INFO  Compression - /smartrise/sc/IDACall: 217 -> 173 bytes
    when I add
    Code:
    loginDS.addHandleErrorHandler(new HandleErrorHandler() {
    	@Override
    	public void onHandleError(ErrorEvent event) {
    		if (event.getResponse().getStatus() == -1) {
    			event.cancel();
    			
    		}
    	}
    });
    then I can catch the exception here. But I guess this isn't the right approach.

    Why wasn't LoginRequiredCallback called? What did I miss?

    Thanks,
    Zdary

    #2
    I had to extend IDACall and return the login marker code myself.

    I'm not sure if this is best practice. But I also do other things like set AppEngine namespaces and other stuff in my extended IDACall class.

    Comment


      #3
      I'd say that this is a framework bug since
      event.getResponse().getStatus() is -1 while event.getResponse().getHttpResponseCode() is 200.

      It means HTTP status OK and response status FAILED.
      HttpResponseCode needs to be set to 401 - Unauthorized.

      Comment


        #4
        No, this is a configuration error. You should not be allowing this operation to be called at all until the user has authenticated.

        Comment


          #5
          Atomatom: Can you send me an example of your extended IDAcall, please?

          I no longer allow user to call DS before they authenticate. Pro problem persist when a session expires. For example, the same login gets used on a different computer or it just times out.

          In those cases I need to show a login screen and since LoginRequiredCallback doesn't register anything I have no chance to controll further actions.

          Cheers,
          Zdary

          Comment


            #6
            This took me quite a bit of time to develop, so I hope it saves you some. :) Likewise, if you see any issues with it, I would be grateful if you point them out.

            The code runs on AppEngine - so in addition to setting user access, it also sets namespaces. There is a check for JXPath which crashes on AppEngine unless you set the property as below.

            Somewhere in there you'll see the loginRequired code. Since users need to login, they are allowed to check certain datasources (like check to see if their login details match in the userAccount datasource).

            Code:
            import java.io.IOException;
            import java.util.Arrays;
            import java.util.List;
            import java.util.Map;
            
            import javax.servlet.ServletException;
            import javax.servlet.http.HttpServletRequest;
            import javax.servlet.http.HttpServletResponse;
            import javax.servlet.http.HttpSession;
            
            import org.apache.commons.lang.StringUtils;
            import org.apache.commons.lang.time.DurationFormatUtils;
            import org.apache.commons.lang.time.StopWatch;
            
            import com.google.appengine.api.NamespaceManager;
            import com.isomorphic.datasource.DSRequest;
            import com.isomorphic.datasource.DSResponse;
            import com.isomorphic.rpc.RPCManager;
            import com.isomorphic.rpc.RPCRequest;
            import com.isomorphic.rpc.RPCResponse;
            import com.isomorphic.servlet.IDACall;
            import com.isomorphic.servlet.RequestContext;
            
            public class SecureIDACall extends IDACall {
            	@Override
            	public void init() throws ServletException {
            		super.init();
            		checkJXPathKey();
            	}
            
            	private void checkJXPathKey() {
            		try {
            			String key = "org.apache.commons.jxpath.JXPathContextFactory";
            			String value = System.getProperty(key);
            			if (value == null)
            				System.setProperty(key,
            						"org.apache.commons.jxpath.ri.JXPathContextFactoryReferenceImpl");
            		} catch (Exception e) {
            			e.printStackTrace();
            		}
            	}
            
            	private static final long serialVersionUID = 1410730002634700876L;
            	Service service;
            	private long totalTime = 0;
            	private long timeStarted = System.currentTimeMillis();
            	private Boolean loginRequired;
            
            	public void processRequest(HttpServletRequest request,
            			HttpServletResponse response) throws ServletException, IOException {
            		StopWatch timer = new StopWatch();
            		timer.start();
            		try {
            			HttpSession session = request.getSession();
            
            			UserAccount userAccount = getService().getUserService()
            					.getUserAccount(session);
            
            			boolean isAuthenticated = userAccount != null;
            			String roles;
            			if (!isAuthenticated) {
            				roles = "unregistered";
            			} else {
            				List<String> userRolesList = userAccount.getRoles();
            				if (userRolesList == null)
            					roles = ".";
            				else
            					roles = StringUtils.join(userRolesList, ",");
            				NamespaceManager.set(userAccount.getNamespace());
            			}
            
            			if (roles != null) {
            				try {
            					RequestContext context = RequestContext.instance(this,
            							request, response);
            					RPCManager rpc = new RPCManager(request, response);
            					rpc.setAuthenticated(isAuthenticated);
            					rpc.setUserRoles(roles);
            
            					StringBuffer buff = new StringBuffer();
            					buff.append(request.getRemoteAddr() + " ");
            
            					List requests = rpc.getRequests();
            					for (Object r : requests) {
            						buff.append("Request is a "
            								+ r.getClass().getSimpleName() + " for ");
            						if (r instanceof RPCRequest) {
            							Object data = ((RPCRequest) r).getData();
            							buff.append(data);
            						} else if (r instanceof DSRequest) {
            							DSRequest dsRequest = (DSRequest) r;
            							buff.append(dsRequest.getDataSourceName());
            						} else {
            						}
            					}
            
            					NotifyService.notifyAdminAccess(userAccount,
            							buff.toString());
            					processRPCTransaction(rpc, context);
            				} catch (Throwable e) {
            					e.printStackTrace();
            					handleError(response, e);
            				}
            			} else {
            				super.processRequest(request, response);
            			}
            
            			if (loginRequired != null && loginRequired) {
            				response.getWriter().append(Constants.loginRequiredResponse);
            				loginRequired = null;
            			}
            		} catch (Exception e) {
            			e.printStackTrace();
            			handleError(response, e);
            		}
            
            		totalTime += timer.getTime();
            		System.out.println("Last  Time : " + timer.toString());
            		System.out.println("Total Time : "
            				+ DurationFormatUtils.formatDurationHMS(totalTime));
            		System.out.println("Alive Time : "
            				+ DurationFormatUtils.formatDurationHMS(System
            						.currentTimeMillis() - timeStarted));
            	}
            
            	public Service getService() {
            		if (service == null)
            			service = new Service();
            		return service;
            	}
            
            	@Override
            	public RPCResponse handleRPCRequest(RPCRequest rpcRequest, RPCManager rpc,
            			RequestContext context) throws Exception {
            		
            		return super.handleRPCRequest(rpcRequest, rpc, context);
            	}
            
            	@Override
            	public DSResponse handleDSRequest(DSRequest dsRequest, RPCManager rpc,
            			RequestContext context) throws Exception {
            		Map<String, Object> dsconfig = dsRequest.getDataSource().getConfig();
            		Object globalProperty = dsconfig.get("globalNameSpace");
            		boolean isGlobal = globalProperty != null
            				&& ((String) globalProperty).toLowerCase().equals("true");
            		if (!isGlobal) {
            			UserAccount userAccount = getService().getUserService()
            					.getUserAccount(
            							dsRequest.getHttpServletRequest().getSession());
            
            			if (userAccount == null) {
            				System.out
            						.println("Tried to access datasource but not logged in");
            				loginRequired = true;
            				return createReloginResponse(dsRequest);
            			} else
            				NamespaceManager.set(userAccount.getNamespace());
            		} else {
            			System.out
            					.println("global NS for " + dsRequest.getDataSourceName());
            			NamespaceManager.set(Constants.globalNamespace);
            		}
            
            		System.out.println("\n\n\nExecuting request: dataSource="
            				+ dsRequest.getDataSourceName() + " operation="
            				+ dsRequest.getOperation() + " operationId="
            				+ dsRequest.getOperationId() + " with namespace "
            				+ NamespaceManager.get());
            
            		try {
            			DSResponse handleDSRequest = super.handleDSRequest(dsRequest, rpc,
            					context);
            			return handleDSRequest;
            
            		} catch (NotSignedInException e) {
            			System.out.println("Tried to access datasource but not logged in");
            			loginRequired = true;
            			return createReloginResponse(dsRequest);
            		}
            	}
            
            	private DSResponse createReloginResponse(DSRequest dsRequest)
            			throws Exception {
            		DSResponse dSResponse = new DSResponse(dsRequest.getDataSource());
            		dSResponse.setStatus(DSResponse.STATUS_LOGIN_REQUIRED);
            		return dSResponse;
            	}
            
            }

            Comment


              #7
              Hi,

              Atomatom: thank you very much.

              Isomorphic:
              Originally posted by Isomorphic
              No, this is a configuration error. You should not be allowing this operation to be called at all until the user has authenticated.
              What about a situation when the session expires? I'll receive the same error and the LoginRequiredCallback will not trigger...

              cheers,
              Zdary

              Comment


                #8
                Zdary: for what it's worth, I handle session stuff in

                Code:
                UserAccount userAccount = getService().getUserService()
                					.getUserAccount(session);
                If the session is expired or not logged in, I just return null. (which makes the SecureIDA class return the not logged in marker, which prompts the user to login again)

                Comment


                  #9
                  Hi,
                  yes I know. I took the logic out of your class and make it as follow:
                  Code:
                  package cz.bcom.smartrise.server.authentication;
                  
                  import org.springframework.security.core.context.SecurityContextHolder;
                  
                  import com.isomorphic.datasource.DSRequest;
                  import com.isomorphic.datasource.DSResponse;
                  import com.isomorphic.rpc.RPCManager;
                  import com.isomorphic.servlet.IDACall;
                  import com.isomorphic.servlet.RequestContext;
                  
                  public class IDACall4Spring extends IDACall {
                  	private static final long serialVersionUID = 516231357196292820L;
                  
                  	public IDACall4Spring() {
                  		super();
                  	}
                  
                  	@Override
                  	public DSResponse handleDSRequest(DSRequest dsRequest, RPCManager rpcManager, RequestContext requestContext) throws Exception {
                  		if (SecurityContextHolder.getContext() == null || SecurityContextHolder.getContext().getAuthentication() == null) {
                  			DSResponse dSResponse = new DSResponse(dsRequest.getDataSource());
                  			dSResponse.setStatus(DSResponse.STATUS_LOGIN_REQUIRED);
                  			return dSResponse;
                  		}
                  		return super.handleDSRequest(dsRequest, rpcManager, requestContext);
                  	}
                  }
                  I just think that Isomorphic should either fix javadoc for RPCManager and explain when the LoginRequiredCallback works since it is not in all cases or fix this situation.

                  Atomatom thank you for you work. It saved me time.

                  Cheers,
                  Zdary

                  Comment

                  Working...
                  X