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