View Javadoc

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