1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  package org.abstracthorizon.danube.http.auth;
14  
15  import org.abstracthorizon.danube.connection.Connection;
16  import org.abstracthorizon.danube.connection.ConnectionException;
17  import org.abstracthorizon.danube.http.HTTPConnection;
18  import org.abstracthorizon.danube.http.HTTPContext;
19  import org.abstracthorizon.danube.http.Status;
20  import org.abstracthorizon.danube.http.session.HTTPSessionManager;
21  import org.abstracthorizon.danube.http.session.Session;
22  import org.abstracthorizon.danube.http.session.SimpleSessionManager;
23  import org.abstracthorizon.danube.http.util.Base64;
24  import org.abstracthorizon.danube.support.RuntimeIOException;
25  
26  import java.io.IOException;
27  import java.security.PrivilegedActionException;
28  import java.security.PrivilegedExceptionAction;
29  import java.util.HashMap;
30  import java.util.Iterator;
31  import java.util.Map;
32  
33  import javax.security.auth.Subject;
34  import javax.security.auth.callback.Callback;
35  import javax.security.auth.callback.CallbackHandler;
36  import javax.security.auth.callback.ConfirmationCallback;
37  import javax.security.auth.callback.NameCallback;
38  import javax.security.auth.callback.PasswordCallback;
39  import javax.security.auth.callback.TextOutputCallback;
40  import javax.security.auth.callback.UnsupportedCallbackException;
41  import javax.security.auth.login.LoginContext;
42  import javax.security.auth.login.LoginException;
43  
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  
47  
48  
49  
50  
51  
52  
53  
54  public class JAASAuthenticatedHTTPContext extends HTTPContext {
55  
56  
57      
58      protected final Logger logger = LoggerFactory.getLogger(JAASAuthenticatedHTTPContext.class);
59  
60  
61      
62      public static final String AUTHORIZATION_DATA_ATTRIBUTE = "org.abstracthorizon.danube.http.auth.Subject";
63  
64      
65      public static final String AUTHORIZATION_REQUEST_HEADER = "Authorization";
66  
67      
68      public static final String AUTHORIZATION_RESPONSE_HEADER = "WWW-Authenticate";
69  
70      
71      public static final int DEFAULT_CACHE_TIMEOUT = 10 * 60 * 1000; 
72  
73      
74      public static final int DEFAULT_MINIMUM_SCAN_PERIOD = 10 * 1000; 
75  
76      
77      protected HTTPSessionManager sessionManager;
78  
79      
80      protected String realm;
81  
82      
83      protected String loginContextName;
84  
85      
86      protected LoginContext loginContext;
87  
88      
89      protected Map<String, AuthData> cachedAuth = new HashMap<String, AuthData>();
90  
91      
92      protected int cacheTimeout = DEFAULT_CACHE_TIMEOUT;
93  
94      
95      protected int minScanPeriod = DEFAULT_MINIMUM_SCAN_PERIOD;
96  
97      
98      protected long lastScan;
99  
100     
101     protected boolean forceAuthorisation = true;
102     
103     
104 
105 
106     public JAASAuthenticatedHTTPContext() {
107     }
108 
109     
110 
111 
112 
113 
114 
115 
116 
117     public void handleConnection(final Connection connection) throws ConnectionException {
118         Subject subject = null;
119         Session session = null;
120         boolean fromSession = false;
121         HTTPSessionManager sessionManager = getSessionManager();
122         if (sessionManager != null) {
123             session = (Session)sessionManager.findSession(connection, false);
124             if (session != null) {
125                 subject = (Subject)session.getAttributes().get(AUTHORIZATION_DATA_ATTRIBUTE);
126                 if (subject != null) {
127                     fromSession = true;
128                 }
129             }
130         }
131 
132         HTTPConnection httpConnection = (HTTPConnection)connection.adapt(HTTPConnection.class);
133         if (subject == null) {
134 
135             String authHeader = httpConnection.getRequestHeaders().getOnly(AUTHORIZATION_REQUEST_HEADER);
136             if (authHeader != null) {
137                 if (authHeader.startsWith("Basic ")) {
138                     String base64 = authHeader.substring(6);
139                     subject = authorise(base64);
140                 }
141             }
142         }
143 
144         if (subject != null) {
145             if (!fromSession && (session != null)) {
146                 session.getAttributes().put(AUTHORIZATION_DATA_ATTRIBUTE, subject);
147             }
148             try {
149                 Subject.doAs(subject, new PrivilegedExceptionAction<Object>() {
150                     public Object run() throws Exception {
151                         superHandleConnection(connection);
152                         return null;
153                     }
154                 });
155             } catch (PrivilegedActionException e) {
156                 if (e.getException() instanceof ConnectionException) {
157                     throw (ConnectionException)e.getException();
158                 } else if (e.getException() instanceof IOException) {
159                     throw new RuntimeIOException((IOException)e.getException());
160                 } else {
161                     throw new ConnectionException(e);
162                 }
163             }
164         } else {
165             if (forceAuthorisation) {
166                 String oldComponentPath = httpConnection.getComponentPath();
167                 httpConnection.addComponentPathToContextPath();
168     
169     
170     
171                 String realm = getRealm();
172                 if (realm == null) {
173                     realm = oldComponentPath;
174                 }
175                 httpConnection.getResponseHeaders().putOnly(AUTHORIZATION_RESPONSE_HEADER, "Basic realm=\"" + realm + "\"");
176                 httpConnection.setResponseStatus(Status.UNAUTHORIZED);
177             } else {
178                 try {
179                     superHandleConnection(connection);
180                 } catch (IOException e) {
181                     throw new RuntimeIOException(e);
182                 }
183             }
184         }
185     }
186 
187     
188 
189 
190 
191 
192     protected void superHandleConnection(Connection connection) throws IOException {
193         super.handleConnection(connection);
194     }
195 
196     
197 
198 
199 
200 
201     protected Subject authorise(String base64) {
202         AuthData authData = null;
203         
204         synchronized (cachedAuth) {
205             long now = System.currentTimeMillis() - minScanPeriod;
206             if (lastScan + minScanPeriod < now) {
207                 Iterator<AuthData> it = cachedAuth.values().iterator();
208                 while (it.hasNext()) {
209                     authData = it.next();
210                     if (authData.lastAccessed + cacheTimeout < now) {
211                         it.remove();
212                     }
213                 }
214                 lastScan = System.currentTimeMillis();
215             }
216             authData = cachedAuth.get(base64);
217         }
218 
219         if (authData != null) {
220             authData.lastAccessed = System.currentTimeMillis();
221             return authData.subject;
222         }
223 
224         String userPass = Base64.decode(base64);
225         int i = userPass.indexOf(':');
226         if (i < 0) {
227             return null;
228         }
229 
230         final String user = userPass.substring(0, i);
231         final char[] pass = userPass.substring(i+1).toCharArray();
232 
233         LoginContext loginContext; 
234         try {
235             
236                 loginContext = new LoginContext(getLoginContextName(), new CallbackHandler() {
237 
238                     public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
239                         for (int i = 0; i < callbacks.length; i++) {
240                             if (callbacks[i] instanceof TextOutputCallback) {
241                             } else if (callbacks[i] instanceof ConfirmationCallback) {
242                             } else if (callbacks[i] instanceof NameCallback) {
243                                 NameCallback nameCallback = (NameCallback)callbacks[i];
244                                 nameCallback.setName(user);
245                             } else if (callbacks[i] instanceof PasswordCallback) {
246                                 PasswordCallback passwordCallback = (PasswordCallback)callbacks[i];
247                                 passwordCallback.setPassword(pass);
248                             } else {
249                                 throw new UnsupportedCallbackException
250                                  (callbacks[i], "Unrecognized Callback");
251                             }
252                           }
253                     }
254                 });
255                 if (loginContext == null) {
256                     return null;
257                 } else {
258                     setLoginContext(loginContext);
259                 }
260             
261             logger.debug("Trying to authenticate user " + user);
262             loginContext.login();
263             logger.debug("Successfully authenticated user " + user);
264         } catch (LoginException e) {
265             logger.debug("Exception trying to get LoginContext " + getLoginContextName(), e);
266             return null;
267         }
268         Subject subject = loginContext.getSubject();
269         synchronized (cachedAuth) {
270             authData = new AuthData();
271             authData.lastAccessed = System.currentTimeMillis();
272             authData.subject = subject;
273             cachedAuth.put(base64, authData);
274         }
275         return subject;
276     }
277 
278     
279 
280 
281 
282     public HTTPSessionManager getSessionManager() {
283         if (sessionManager == null) {
284             sessionManager = new SimpleSessionManager();
285         }
286         return sessionManager;
287     }
288 
289     
290 
291 
292 
293     public void setSessionManager(HTTPSessionManager sessionManager) {
294         this.sessionManager = sessionManager;
295     }
296 
297     
298 
299 
300 
301     public String getRealm() {
302         return realm;
303     }
304 
305     
306 
307 
308 
309 
310     public void setRealm(String realm) {
311         this.realm = realm;
312     }
313 
314     
315 
316 
317 
318     public String getLoginContextName() {
319         return loginContextName;
320     }
321 
322     
323 
324 
325 
326     public void setLoginContextName(String loginContextName) {
327         this.loginContextName = loginContextName;
328     }
329 
330     
331 
332 
333 
334     public LoginContext getLoginContext() {
335         return loginContext;
336     }
337 
338     
339 
340 
341 
342     public void setLoginContext(LoginContext loginContext) {
343         this.loginContext = loginContext;
344     }
345 
346     
347 
348 
349 
350     public int getCacheTimeout() {
351         return cacheTimeout;
352     }
353 
354     
355 
356 
357 
358     public void setCacheTimeout(int cacheTimeout) {
359         this.cacheTimeout = cacheTimeout;
360     }
361 
362     
363 
364 
365 
366     public int getMinimumScanPeriod() {
367         return minScanPeriod;
368     }
369 
370     
371 
372 
373 
374     public void setMinimumScanPeriod(int minScanPeriod) {
375         this.minScanPeriod = minScanPeriod;
376     }
377     
378     public boolean getForceAuthorisation() {
379         return forceAuthorisation;
380     }
381     
382     public void setForceAuthorisation(boolean forceAuthorisation) {
383         this.forceAuthorisation = forceAuthorisation;
384     }
385 
386 
387     
388 
389 
390     protected class AuthData {
391 
392         public long lastAccessed;
393         public Subject subject;
394 
395     }
396 }