Announcement

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

    Digest authentication with DataSource

    Hi

    I'm having quite a unique problem here and couldn't find any solution or related stuff on the web that would help me solve this.

    The problem is as followed:

    The web application I am building needs to support digest authentication (optional, so it can be turned on or switched off).
    Now if it's enabled (or gets enabled by another user while the application is open) the next request would return a 401 response which causes the browser to open the login dialog.
    Since this application needs to have it's own custom login dialog, unfortunately the use of the browser dialog is not possible and therefore a 401 must never be returned (cause as far as we figured out, the browsers can't reliably be stopped from opening their default dialog if a 401 is returned).

    Therefore the protocoll we agreed on is the following:

    The client sends a request to the server on the /auth endpoint first (which never requires authentication).
    200 is returned if authentication is OFF, 403 otherwise.
    on 200: just request.
    on 403: user is prompted to log in (if not already) and the username and password are sent again on /auth for validation
    200 indicates a valid username + password, 403 is invalid.
    on 200: valid -> request protected content using username+password
    on 403: ask for password again.

    So what i have to accomplish now is:

    I need a custom dataSource that is used across the whole application that guarantees:
    - for EVERY request made, the above "ping-pong" is made before anything is requested or submitted from/to the server
    - after the ping-pong is finished the authentication headers are added to the request and the request is executed normally
    - the dataSource can be set on a listgrid or anything similar just like the default ones
    - a 401 should never accure

    Now the ping-pong thingy itself is no problem and all the simple request the application makes are already using it.
    What i can't figure out is how the integrate that into a dataSource.

    I already tried by setting the protocol to clientCustom, overriding "transformRequest" and calling execute(dsRequest) after i had added the authentication headers, but that just resulted in endless request to /auth without the actual dsRequest ever beeing sent.

    Here's the code, but i guess i am making a fundamental mistake here.

    Code:
    public class DataSourceTerminal extends DataSource {
    
    	private URL dataSourceURL;
    
    	public DataSourceTerminal(String dataURL) {
    		super(dataURL);
    		this.dataSourceURL = new URL(dataURL);
    
    		XmlNamespaces xmlns = new XmlNamespaces();
    		xmlns.addNamespace("atom", "http://www.w3.org/2005/Atom");
    		setXmlNamespaces(xmlns);
    		setDataFormat(DSDataFormat.XML);
    		setShowPrompt(false);
    		
    		setDataProtocol(DSProtocol.CLIENTCUSTOM);
    	}
    
    		
    	@Override
    	protected Object transformRequest(DSRequest request) {
    		fetch(request);			
    				
    		return super.transformRequest(request);
    	}
    		
    	@Override
    	public void setDataURL(String data) {
    		super.setDataURL(data);
    		this.dataSourceURL = new URL(data);
    	}
    
    	private void fetch(final DSRequest dsrequest) {
    
    		final String host = dataSourceURL.getHost();
    
    		try {
    
    			AuthUtils.sendLoginRequest(host, new RequestCallback() {
    
    				@Override
    				public void onResponseReceived(Request request, Response response) {
    					if (response.getStatusCode() != 200) {
    						// authentication needed
    						final String responseString = response.getHeader("WWW-Authenticate");
    						if (AuthUtils.hasCredentials(host)) {						
    							sendAuthRequest(host, responseString, dsrequest);
    						} else {
    							// show login page				
    							PageContent.showLoginPage(host);							
    						}
    					} else {
    						// no login needed -> just request
    						DataSourceTerminal.super.execute(dsrequest);
    					}
    				}
    
    				@Override
    				public void onError(Request request, Throwable exception) {
    					DataSourceTerminal.this.onError(exception);
    				}
    			});
    
    		} catch (Exception exception) {
    			onError(exception);
    		}
    	}
    	
    	
    	private void sendAuthRequest(final String host, final String authResponse, final DSRequest outerRequest) {
    		try {
    
    			final String username = AuthUtils.getCredentialsUsername(host);
    			final String password = AuthUtils.getCredentialsHa1md5(host);
    
    			final String realm = AuthUtils.parseSubStringFromResponse(authResponse, "realm");
    			final String nonce = AuthUtils.parseSubStringFromResponse(authResponse, "nonce");
    
    			RequestBuilder answerReq = new RequestBuilder(RequestBuilder.GET, UrlBuilder.getUrl(host, "auth/login"));
    			answerReq.setHeader("Authorization",
    					AuthUtils.getAuthorizationHeader(username, false, password, realm, "GET", nonce, "/auth/login"));
    			answerReq.setIncludeCredentials(true);
    			answerReq.setCallback(new RequestCallback() {
    
    				@Override
    				public void onResponseReceived(Request request, Response response) {
    					if (response.getStatusCode() == 200) {
    						String digestURI = dataSourceURL.getPath();
    						String method = "GET";
    
    						new RequestBuilder(RequestBuilder.GET, UrlBuilder.getUrl(host, digestURI));
    
    						HashMap<String, String> authMap = new HashMap<String, String>();
    						authMap.put("Authorization",
    								AuthUtils.getAuthorizationHeader(username, false, password, realm, method, nonce, digestURI));
    						outerRequest.setHttpHeaders(authMap);
    
    						DataSourceTerminal.super.execute(outerRequest);
    					} else {
    						// saved credentials are not valid -> login
    						PageContent.showLoginPage(host);
    					}
    				}
    
    				@Override
    				public void onError(Request request, Throwable exception) {
    					onError(exception);
    				}
    			});
    			answerReq.send();
    
    		} catch (RequestException ex) {
    			onError(ex);
    		}
    	}
    
    	protected void onError(Throwable exception) {
    		NotificationUtils.sayError("Request error", "An error occured while trying to request '" + dataSourceURL + "'!", exception);
    	}
    SmartGWT version is 5.0

    If anyone knows what i'm doing wrong here or could provide a hint that points me in the right direction that would be awsome.
    I'm sitting in front of this for far to long now.

    Thanks in advance!

    #2
    Read about Relogin in the QuickStart Guide, and instead of a 403 response, send a loginRequired response.

    Comment


      #3
      Big thanks for pointing me towards that, i wasn't aware of Relogin.

      As far as I can see, this would require changes on the server side, so I guess we will have a discussion about Relogin and how we handle this on the next working day.

      Comment


        #4
        So, we had a talk about this issue again.

        Unfortunately we won't be able to use Relogin, since the whole application needs to be RESTful and we ar not using any sessions.

        So based on the work we already did(with the server side almost finished) , the quickest and easiest solution for the problem would really be to have a dataSource with the initially described behaviour.

        So all i really need is to "intercept" the request of the dataSource, add the authentication headers, and then tell the dataSource to normally proceed with the now slitely altered request.

        So, if just calling "execute(dsRequest)" isn't sufficient, how would i accomplish this instead?

        Comment


          #5
          Unfortunately we won't be able to use Relogin, since the whole application needs to be RESTful and we ar not using any sessions.
          This doesn't prevent the use of the Relogin system, and it's difficult to comment further, because we have no idea why you think it does.

          We would suggest re-reading all the documentation on Relogin, taking a bit more care this time. If you still think it doesn't apply, try to clearly articulate what the problem is.

          Comment

          Working...
          X