package com.wilutions.jsfs; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.net.Authenticator; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.PasswordAuthentication; import java.net.URL; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import byps.BAsyncResult; import byps.BAuthentication; import byps.BClient; /** * This class performs the authentication to the JSFS Dispatcher. * */ public class JsfsAuthentication implements BAuthentication { private String yourWebappUrl, tokenServiceUrl, userName, userPwd; private volatile String token; private Log log = LogFactory.getLog(JsfsAuthentication.class); /** * Constructor * @param tokenServiceUrl URL to the token service of "Your Web Application" * @param userName User name * @param userPwd Password */ public JsfsAuthentication(String yourWebappUrl, String tokenServiceUrl, String userName, String userPwd) { super(); this.yourWebappUrl = yourWebappUrl; this.tokenServiceUrl = tokenServiceUrl; this.userName = userName; this.userPwd = userPwd; } /** * Return the token received from the token service. * @return Token */ public String getToken() { return token; } /** * Send a GET request to the URL of the token service. * @param asyncResult Callback object */ private void internalAuthenticateRetry(BAsyncResult<String> asyncResult) { if (log.isDebugEnabled()) log.debug("internalAuthenticate("); Throwable ex = null; String token = null; int retryCount = 0; while (true) { try { token = internalAuthenticate2(); ex = null; break; } catch (Exception e) { if (log.isDebugEnabled()) log.debug("internalAuthenticate failed", e); ex = e; if (++retryCount == 3) break; if (e.toString().indexOf("403") < 0) break; // Assume name/password wrong String[] args = new String[2]; if (DlgCredentials.showDialog(args)) { userName = args[0]; userPwd = args[1]; } else { System.exit(-1); } } } asyncResult.setAsyncResult(token, ex); if (log.isDebugEnabled()) log.debug(")internalAuthenticate"); } private String internalAuthenticate2() throws IOException, MalformedURLException { final AtomicInteger retryLogin = new AtomicInteger(); // Create an Authenticator object that is used to send the credentials // during BASIC authentication. Authenticator.setDefault( new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { if (log.isDebugEnabled()) log.debug("getPasswordAuthentication(), userName=" + userName); // If authentication fails, Tomcat returns 401 with BASIC authentication // and the JVM retries to login. // This causes an endless loop that is broken by the exception // "java.net.ProtocolException: Server redirected too many times". // Tomcat should return 403 if authentication was unsuccessful. // Try to authenticate only once if (retryLogin.incrementAndGet() > 1) { throw new IllegalStateException("HTTP 403"); } return new PasswordAuthentication( userName, userPwd.toCharArray() ); } }); if (log.isDebugEnabled()) log.debug("GET appUrl=" + tokenServiceUrl); HttpURLConnection conn = null; LineNumberReader rd = null; try { conn = (HttpURLConnection) new URL(tokenServiceUrl).openConnection(); rd = new LineNumberReader(new InputStreamReader(conn.getInputStream())); String token = rd.readLine(); if (log.isDebugEnabled()) log.debug("token=" + token); if (token == null) { throw new IOException("No token returned from " + tokenServiceUrl); } return token; } catch (Exception e) { InputStream es = conn.getErrorStream(); if (es != null) { while (es.read() != -1) {} } throw e; } finally { if (rd != null) { try { rd.close(); } catch (Throwable e) {} } if (conn != null) { conn.disconnect(); } } } @Override public void authenticate(BClient bclient1, final BAsyncResult<Boolean> asyncResult) { if (log.isDebugEnabled()) log.debug("authenticate(bclient1=" + bclient1); final BClient_JSFS bclient = (BClient_JSFS)bclient1; BAsyncResult<String> outerResult = new BAsyncResult<String>() { public void setAsyncResult(String token, Throwable ex) { if (log.isDebugEnabled()) log.debug("setAsyncResult(token=" + token + ", ex=" + ex); if (ex != null) { asyncResult.setAsyncResult(Boolean.FALSE, ex); } else { JsfsAuthentication.this.token = token; try { // Required by BYPS: add the implementation of a service that is // called via reverse HTTP to the client object. final FileSystemImpl fssrv = new FileSystemImpl(bclient, yourWebappUrl); bclient.addRemote(fssrv); if (log.isDebugEnabled()) log.debug("fssrv=" + fssrv); // Publish the service to the JSFS Dispatcher. bclient.dispatcherService.registerService(token, fssrv, new BAsyncResult<Object>() { public void setAsyncResult(Object ignored, Throwable ex) { if (log.isDebugEnabled()) log.debug("registerService asyncResult: ex=" + ex); asyncResult.setAsyncResult(Boolean.TRUE, ex); } }); } catch (Throwable ex2) { if (log.isDebugEnabled()) log.debug("authentication failed", ex2); asyncResult.setAsyncResult(Boolean.FALSE, ex2); } } if (log.isDebugEnabled()) log.debug(")setAsyncResult"); } }; internalAuthenticateRetry(outerResult); if (log.isDebugEnabled()) log.debug(")authenticate"); } @Override public boolean isReloginException(BClient client, Throwable ex, int typeId) { boolean ret = client.getTransport().isReloginException(ex, typeId); if (log.isDebugEnabled()) log.debug("isReloginException(client="+ client +", ex=" + ex + ", typeId=" + typeId + ")=" + ret); return ret; } @Override public void getSession(BClient client, int typeId, BAsyncResult<Object> asyncResult) { if (log.isDebugEnabled()) log.debug("getSession(client=" + client + ", typeId=" + typeId + ")"); asyncResult.setAsyncResult(null, null); } /** * Requests the token from Your Web Application * @param asyncResult The token is supplied in the result parameter. */ public void keepAlive(BAsyncResult<String> asyncResult) { if (log.isDebugEnabled()) log.debug("keepAlive()"); internalAuthenticateRetry(asyncResult); } }