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.
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!
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); }
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!
Comment