View Javadoc

1   // ========================================================================
2   // Copyright (c) 2008-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.client.security;
15  
16  import java.io.IOException;
17  import java.util.HashMap;
18  import java.util.Map;
19  import java.util.StringTokenizer;
20  
21  import org.eclipse.jetty.client.HttpDestination;
22  import org.eclipse.jetty.client.HttpEventListenerWrapper;
23  import org.eclipse.jetty.client.HttpExchange;
24  import org.eclipse.jetty.http.HttpHeaders;
25  import org.eclipse.jetty.http.HttpStatus;
26  import org.eclipse.jetty.io.Buffer;
27  import org.eclipse.jetty.util.StringUtil;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  
31  
32  /**
33   * SecurityListener
34   * 
35   * Allow for insertion of security dialog when performing an
36   * HttpExchange.
37   */
38  public class SecurityListener extends HttpEventListenerWrapper
39  {
40      private static final Logger LOG = Log.getLogger(SecurityListener.class);
41  	
42      private HttpDestination _destination;
43      private HttpExchange _exchange;
44      private boolean _requestComplete;
45      private boolean _responseComplete;  
46      private boolean _needIntercept;
47      
48      private int _attempts = 0; // TODO remember to settle on winning solution
49  
50      public SecurityListener(HttpDestination destination, HttpExchange ex)
51      {
52          // Start of sending events through to the wrapped listener
53          // Next decision point is the onResponseStatus
54          super(ex.getEventListener(),true);
55          _destination=destination;
56          _exchange=ex;
57      }
58      
59      
60      /**
61       * scrapes an authentication type from the authString
62       * 
63       * @param authString
64       * @return the authentication type
65       */
66      protected String scrapeAuthenticationType( String authString )
67      {
68          String authType;
69  
70          if ( authString.indexOf( " " ) == -1 )
71          {
72              authType = authString.toString().trim();
73          }
74          else
75          {
76              String authResponse = authString.toString();
77              authType = authResponse.substring( 0, authResponse.indexOf( " " ) ).trim();
78          }
79          return authType;
80      }
81      
82      /**
83       * scrapes a set of authentication details from the authString
84       * 
85       * @param authString
86       * @return the authentication details
87       */
88      protected Map<String, String> scrapeAuthenticationDetails( String authString )
89      {
90          Map<String, String> authenticationDetails = new HashMap<String, String>();
91          authString = authString.substring( authString.indexOf( " " ) + 1, authString.length() );
92          StringTokenizer strtok = new StringTokenizer( authString, ",");
93          
94          while ( strtok.hasMoreTokens() )
95          {
96              String token = strtok.nextToken();
97              String[] pair = token.split( "=" );
98              
99              // authentication details ought to come in two parts, if not then just skip
100             if ( pair.length == 2 )
101             {
102                 String itemName = pair[0].trim();
103                 String itemValue = pair[1].trim();
104                 
105                 itemValue = StringUtil.unquote( itemValue );
106                 
107                 authenticationDetails.put( itemName, itemValue );
108             }    
109             else
110             {
111                 LOG.debug("SecurityListener: missed scraping authentication details - " + token );
112             }
113         }
114         return authenticationDetails;
115     }
116 
117   
118     @Override
119     public void onResponseStatus( Buffer version, int status, Buffer reason )
120         throws IOException
121     {
122         if (LOG.isDebugEnabled())
123             LOG.debug("SecurityListener:Response Status: " + status );
124 
125         if ( status == HttpStatus.UNAUTHORIZED_401 && _attempts<_destination.getHttpClient().maxRetries()) 
126         {
127             // Let's absorb events until we have done some retries
128             setDelegatingResponses(false);
129             _needIntercept = true;
130         }
131         else 
132         {
133             setDelegatingResponses(true);
134             setDelegatingRequests(true);
135             _needIntercept = false;
136         }
137         super.onResponseStatus(version,status,reason);
138     }
139 
140 
141     @Override
142     public void onResponseHeader( Buffer name, Buffer value )
143         throws IOException
144     {
145         if (LOG.isDebugEnabled())
146             LOG.debug( "SecurityListener:Header: " + name.toString() + " / " + value.toString() );
147         
148         
149         if (!isDelegatingResponses())
150         {
151             int header = HttpHeaders.CACHE.getOrdinal(name);
152             switch (header)
153             {
154                 case HttpHeaders.WWW_AUTHENTICATE_ORDINAL:
155 
156                     // TODO don't hard code this bit.
157                     String authString = value.toString();
158                     String type = scrapeAuthenticationType( authString );                  
159 
160                     // TODO maybe avoid this map creation
161                     Map<String,String> details = scrapeAuthenticationDetails( authString );
162                     String pathSpec="/"; // TODO work out the real path spec
163                     RealmResolver realmResolver = _destination.getHttpClient().getRealmResolver();
164                     
165                     if ( realmResolver == null )
166                     {
167                         break;
168                     }
169                     
170                     Realm realm = realmResolver.getRealm( details.get("realm"), _destination, pathSpec ); // TODO work our realm correctly 
171                     
172                     if ( realm == null )
173                     {
174                         LOG.warn( "Unknown Security Realm: " + details.get("realm") );
175                     }
176                     else if ("digest".equalsIgnoreCase(type))
177                     {
178                         _destination.addAuthorization("/",new DigestAuthentication(realm,details));
179                         
180                     }
181                     else if ("basic".equalsIgnoreCase(type))
182                     {
183                         _destination.addAuthorization(pathSpec,new BasicAuthentication(realm));
184                     }
185                     
186                     break;
187             }
188         }
189         super.onResponseHeader(name,value);
190     }
191     
192 
193     @Override
194     public void onRequestComplete() throws IOException
195     {
196         _requestComplete = true;
197 
198         if (_needIntercept)
199         {
200             if (_requestComplete && _responseComplete)
201             {
202                if (LOG.isDebugEnabled())
203                    LOG.debug("onRequestComplete, Both complete: Resending from onResponseComplete "+_exchange); 
204                 _responseComplete = false;
205                 _requestComplete = false;
206                 setDelegatingRequests(true);
207                 setDelegatingResponses(true);
208                 _destination.resend(_exchange);  
209             } 
210             else
211             {
212                 if (LOG.isDebugEnabled())
213                     LOG.debug("onRequestComplete, Response not yet complete onRequestComplete, calling super for "+_exchange);
214                 super.onRequestComplete(); 
215             }
216         }
217         else
218         {
219             if (LOG.isDebugEnabled())
220                 LOG.debug("onRequestComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
221             super.onRequestComplete();
222         }
223     }
224 
225 
226     @Override
227     public void onResponseComplete() throws IOException
228     {   
229         _responseComplete = true;
230         if (_needIntercept)
231         {  
232             if (_requestComplete && _responseComplete)
233             {              
234                 if (LOG.isDebugEnabled())
235                     LOG.debug("onResponseComplete, Both complete: Resending from onResponseComplete"+_exchange);
236                 _responseComplete = false;
237                 _requestComplete = false;
238                 setDelegatingResponses(true);
239                 setDelegatingRequests(true);
240                 _destination.resend(_exchange); 
241 
242             }
243             else
244             {
245                if (LOG.isDebugEnabled())
246                    LOG.debug("onResponseComplete, Request not yet complete from onResponseComplete,  calling super "+_exchange);
247                 super.onResponseComplete(); 
248             }
249         }
250         else
251         {
252             if (LOG.isDebugEnabled())
253                 LOG.debug("OnResponseComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
254             super.onResponseComplete();  
255         }
256     }
257 
258     @Override
259     public void onRetry()
260     {
261         _attempts++;
262         setDelegatingRequests(true);
263         setDelegatingResponses(true);
264         _requestComplete=false;
265         _responseComplete=false;
266         _needIntercept=false;
267         super.onRetry();
268     }  
269     
270     
271 }