View Javadoc

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