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
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
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[] pair = strtok.nextToken().split( "=" );
94              if ( pair.length == 2 )
95              {
96                  String itemName = pair[0].trim();
97                  String itemValue = pair[1].trim();
98                  
99                  itemValue = StringUtil.unquote( itemValue );
100                 
101                 authenticationDetails.put( itemName, itemValue );
102             }
103             else
104             {
105                 throw new IllegalArgumentException( "unable to process authentication details" );
106             }      
107         }
108         return authenticationDetails;
109     }
110 
111   
112     public void onResponseStatus( Buffer version, int status, Buffer reason )
113         throws IOException
114     {
115         if (Log.isDebugEnabled())
116             Log.debug("SecurityListener:Response Status: " + status );
117 
118         if ( status == HttpStatus.UNAUTHORIZED_401 && _attempts<_destination.getHttpClient().maxRetries()) 
119         {
120             // Let's absorb events until we have done some retries
121             setDelegatingResponses(false);
122             _needIntercept = true;
123         }
124         else 
125         {
126             setDelegatingResponses(true);
127             setDelegatingRequests(true);
128             _needIntercept = false;
129         }
130         super.onResponseStatus(version,status,reason);
131     }
132 
133 
134     public void onResponseHeader( Buffer name, Buffer value )
135         throws IOException
136     {
137         if (Log.isDebugEnabled())
138             Log.debug( "SecurityListener:Header: " + name.toString() + " / " + value.toString() );
139         
140         
141         if (!isDelegatingResponses())
142         {
143             int header = HttpHeaders.CACHE.getOrdinal(name);
144             switch (header)
145             {
146                 case HttpHeaders.WWW_AUTHENTICATE_ORDINAL:
147 
148                     // TODO don't hard code this bit.
149                     String authString = value.toString();
150                     String type = scrapeAuthenticationType( authString );                  
151 
152                     // TODO maybe avoid this map creation
153                     Map<String,String> details = scrapeAuthenticationDetails( authString );
154                     String pathSpec="/"; // TODO work out the real path spec
155                     RealmResolver realmResolver = _destination.getHttpClient().getRealmResolver();
156                     
157                     if ( realmResolver == null )
158                     {
159                         break;
160                     }
161                     
162                     Realm realm = realmResolver.getRealm( details.get("realm"), _destination, pathSpec ); // TODO work our realm correctly 
163                     
164                     if ( realm == null )
165                     {
166                         Log.warn( "Unknown Security Realm: " + details.get("realm") );
167                     }
168                     else if ("digest".equalsIgnoreCase(type))
169                     {
170                         _destination.addAuthorization("/",new DigestAuthorization(realm,details));
171                         
172                     }
173                     else if ("basic".equalsIgnoreCase(type))
174                     {
175                         _destination.addAuthorization(pathSpec,new BasicAuthorization(realm));
176                     }
177                     
178                     break;
179             }
180         }
181         super.onResponseHeader(name,value);
182     }
183     
184 
185     public void onRequestComplete() throws IOException
186     {
187         _requestComplete = true;
188 
189         if (_needIntercept)
190         {
191             if (_requestComplete && _responseComplete)
192             {
193                if (Log.isDebugEnabled())
194                    Log.debug("onRequestComplete, Both complete: Resending from onResponseComplete "+_exchange); 
195                 _responseComplete = false;
196                 _requestComplete = false;
197                 setDelegatingRequests(true);
198                 setDelegatingResponses(true);
199                 _destination.resend(_exchange);  
200             } 
201             else
202             {
203                 if (Log.isDebugEnabled())
204                     Log.debug("onRequestComplete, Response not yet complete onRequestComplete, calling super for "+_exchange);
205                 super.onRequestComplete(); 
206             }
207         }
208         else
209         {
210             if (Log.isDebugEnabled())
211                 Log.debug("onRequestComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
212             super.onRequestComplete();
213         }
214     }
215 
216 
217     public void onResponseComplete() throws IOException
218     {   
219         _responseComplete = true;
220         if (_needIntercept)
221         {  
222             if (_requestComplete && _responseComplete)
223             {              
224                 if (Log.isDebugEnabled())
225                     Log.debug("onResponseComplete, Both complete: Resending from onResponseComplete"+_exchange);
226                 _responseComplete = false;
227                 _requestComplete = false;
228                 setDelegatingResponses(true);
229                 setDelegatingRequests(true);
230                 _destination.resend(_exchange); 
231 
232             }
233             else
234             {
235                if (Log.isDebugEnabled())
236                    Log.debug("onResponseComplete, Request not yet complete from onResponseComplete,  calling super "+_exchange);
237                 super.onResponseComplete(); 
238             }
239         }
240         else
241         {
242             if (Log.isDebugEnabled())
243                 Log.debug("OnResponseComplete, delegating to super with Request complete="+_requestComplete+", response complete="+_responseComplete+" "+_exchange);
244             super.onResponseComplete();  
245         }
246     }
247 
248     public void onRetry()
249     {
250         _attempts++;
251         setDelegatingRequests(true);
252         setDelegatingResponses(true);
253         _requestComplete=false;
254         _responseComplete=false;
255         _needIntercept=false;
256         super.onRetry();
257     }  
258     
259     
260 }