View Javadoc

1   /**
2    * Copyright (C) 2005-2009 Alfresco Software Limited.
3    *
4    * This file is part of the Spring Surf Extension project.
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  
19  package org.springframework.extensions.webscripts.connector;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.io.OutputStream;
24  
25  import javax.servlet.http.HttpServletRequest;
26  import javax.servlet.http.HttpServletResponse;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.springframework.extensions.surf.exception.AuthenticationException;
31  
32  /**
33   * A special implementation of an Authenticating Connector.
34   * 
35   * The AuthenticatingConnector is a wrapper around a Connector object
36   * and an Authenticator object.  It appears as a Connector to the
37   * outside world but provides additional functionality.
38   * 
39   * When a call is made, the underlying connector is used to call over
40   * to the resource.  The underlying connector retrieves cookie state
41   * from the connector session (if available) and attempts to access the
42   * remote resource.
43   * 
44   * If this succeeds, then the AuthenticatingConnector returns this response.
45   * 
46   * On the other hand, if this fails (i.e. it receives a 401 unauthorized
47   * response), the AuthenticatingConnector calls into the underlying
48   * Authenticator instance to perform an "authentication handshake".
49   * 
50   * This handshake retrieves the necessary cookies or tokens and places
51   * them into the connector session.  The connector session is persisted
52   * to the session (if it was originally bound to the session).
53   * 
54   * The AuthenticatingConnector then reattempts the connection using the
55   * newly retrieved cookies or tokens.  If a 401 is received again, the
56   * credentials are assumed to be invalid (or something is incorrect
57   * about the handshake model).
58   * 
59   * @author muzquiano
60   */
61  public class AuthenticatingConnector implements Connector
62  {
63      protected static Log logger = LogFactory.getLog(AuthenticatingConnector.class);
64      protected Connector connector = null;
65      protected Authenticator authenticator = null;
66      
67      /**
68       * Instantiates a new authenticating connector.
69       * 
70       * @param connector the connector
71       * @param authenticator the authenticator
72       */
73      public AuthenticatingConnector(Connector connector, Authenticator authenticator)
74      {
75          this.connector = connector;
76          this.authenticator = authenticator;
77      }
78      
79      /* (non-Javadoc)
80       * @see org.alfresco.connector.Connector#call(java.lang.String)
81       */
82      public Response call(String uri)
83      {
84          Response response = null;
85          boolean handshake = false;
86          boolean firstcall = true;
87          
88          if (isAuthenticated())
89          {
90              // try to call into the connector to see if we can successfully do this
91              response = this.connector.call(uri);
92              firstcall = false;
93              
94              if (logger.isDebugEnabled())
95                  logger.debug("Received " + response.getStatus().getCode() + " on first call to: " + uri);
96              
97              // if there was an authentication challenge, handle here
98              if (response.getStatus().getCode() == ResponseStatus.STATUS_UNAUTHORIZED)
99              {
100                 handshake = true;
101             }
102         }
103         else
104         {
105             handshake = true;
106         }
107         
108         if (handshake)
109         {
110             handshake(); // ignore result
111             
112             // now that we've authenticated, try again
113             response = this.connector.call(uri);
114             
115             if (logger.isDebugEnabled())
116                 logger.debug("Received " + response.getStatus().getCode() + " on " +
117                         (firstcall ? "first" : "second") + " call to: " + uri);
118         }
119         
120         return response;
121     }
122     
123     /* (non-Javadoc)
124      * @see org.alfresco.connector.Connector#call(java.lang.String, org.alfresco.connector.ConnectorContext)
125      */
126     public Response call(String uri, ConnectorContext context)
127     {
128         Response response = null;
129         boolean handshake = false;
130         boolean firstcall = true;
131         
132         if (isAuthenticated())
133         {
134             // try to call into the connector to see if we can successfully do this
135             response = this.connector.call(uri, context);
136             firstcall = false;
137             
138             if (logger.isDebugEnabled())
139                 logger.debug("Received " + response.getStatus().getCode() + " on first call to: " + uri);
140             
141             // if there was an authentication challenge, handle here
142             if (response.getStatus().getCode() == ResponseStatus.STATUS_UNAUTHORIZED)
143             {
144                 handshake = true;
145             }
146         }
147         else
148         {
149             handshake = true;
150         }
151         
152         if (handshake)
153         {
154             handshake(); // ignore result
155             
156             // now that we've authenticated, try again
157             response = this.connector.call(uri, context);
158             
159             if (logger.isDebugEnabled())
160                 logger.debug("Received " + response.getStatus().getCode() + " on " +
161                         (firstcall ? "first" : "second") + " call to: " + uri);
162         }
163 
164         return response;        
165     }
166 
167     /* (non-Javadoc)
168      * @see org.alfresco.connector.Connector#call(java.lang.String, org.alfresco.connector.ConnectorContext, java.io.InputStream)
169      */
170     public Response call(String uri, ConnectorContext context, InputStream in)
171     {
172         Response response = null;
173         boolean handshake = false;
174         boolean firstcall = true;
175         
176         if (isAuthenticated())
177         {
178             // try to call into the connector to see if we can successfully do this
179             response = this.connector.call(uri, context, in);
180             firstcall = false;
181             
182             if (logger.isDebugEnabled())
183                 logger.debug("Received " + response.getStatus().getCode() + " on first call to: " + uri);
184             
185             // if there was an authentication challenge, handle here
186             if (response.getStatus().getCode() == ResponseStatus.STATUS_UNAUTHORIZED)
187             {
188                 handshake = true;
189             }
190         }
191         else
192         {
193             handshake = true;
194         }
195         
196         if (handshake)
197         {
198             handshake(); // ignore result
199 
200             // now that we've authenticated, try again
201             if (in.markSupported())
202             {
203                 try
204                 {
205                     in.reset();
206                 }
207                 catch (IOException ioErr)
208                 {
209                     // if we cannot reset the stream - there's nothing else we can do
210                 }
211             }
212             response = this.connector.call(uri, context, in);
213             
214             if (logger.isDebugEnabled())
215                 logger.debug("Received " + response.getStatus().getCode() + " on " +
216                         (firstcall ? "first" : "second") + " call to: " + uri);
217         }
218         
219         return response;
220     }
221     
222     /* (non-Javadoc)
223      * @see org.alfresco.connector.Connector#call(java.lang.String, org.alfresco.connector.ConnectorContext, java.io.InputStream, java.io.OutputStream)
224      */
225     public Response call(String uri, ConnectorContext context, InputStream in, OutputStream out)
226     {
227         Response response = null;
228         boolean handshake = false;
229         boolean firstcall = true;
230         
231         if (isAuthenticated())
232         {
233             // try to call into the connector to see if we can successfully do this
234             response = this.connector.call(uri, context, in, out);
235             firstcall = false;
236             
237             if (logger.isDebugEnabled())
238                 logger.debug("Received " + response.getStatus().getCode() + " on first call to: " + uri);
239             
240             // if there was an authentication challenge, handle here
241             if (response.getStatus().getCode() == ResponseStatus.STATUS_UNAUTHORIZED)
242             {
243                 handshake = true;
244             }
245         }
246         else
247         {
248             handshake = true;
249         }
250         
251         if (handshake)
252         {
253             handshake(); // ignore result
254             
255             // now that we've authenticated, try again
256             if (in.markSupported())
257             {
258                 try
259                 {
260                     in.reset();
261                 }
262                 catch (IOException ioErr)
263                 {
264                     // if we cannot reset the stream - there's nothing else we can do
265                 }
266             }
267             response = this.connector.call(uri, context, in, out);
268             
269             if (logger.isDebugEnabled())
270                 logger.debug("Received " + response.getStatus().getCode() + " on " +
271                         (firstcall ? "first" : "second") + " call to: " + uri);
272         }
273         
274         return response;
275     }
276 
277     /* (non-Javadoc)
278      * @see org.alfresco.connector.Connector#call(java.lang.String, org.alfresco.connector.ConnectorContext, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
279      */
280     public Response call(String uri, ConnectorContext context, HttpServletRequest req, HttpServletResponse res)
281     {
282         Response response = null;
283         boolean handshake = false;
284         boolean firstcall = true;
285         
286         if (isAuthenticated())
287         {
288             // try to call into the connector to see if we can successfully do this
289             response = this.connector.call(uri, context, req, res);
290             firstcall = false;
291             
292             if (logger.isDebugEnabled())
293                 logger.debug("Received " + response.getStatus().getCode() + " on first call to: " + uri);
294             
295             // if there was an authentication challenge, handle here
296             if (response.getStatus().getCode() == ResponseStatus.STATUS_UNAUTHORIZED)
297             {
298                 handshake = true;
299             }
300         }
301         else
302         {
303             handshake = true;
304         }
305         
306         if (handshake)
307         {
308             handshake(); // ignore result
309             
310             // now that we've authenticated, try again
311             response = this.connector.call(uri, context, req, res);
312             
313             if (logger.isDebugEnabled())
314                 logger.debug("Received " + response.getStatus().getCode() + " on " +
315                         (firstcall ? "first" : "second") + " call to: " + uri);
316         }
317         
318         return response;
319     }
320     
321     /* (non-Javadoc)
322      * @see org.alfresco.connector.Connector#setCredentials(org.alfresco.connector.Credentials)
323      */
324     public void setCredentials(Credentials credentials)
325     {
326         this.connector.setCredentials(credentials);
327     }
328 
329     /* (non-Javadoc)
330      * @see org.alfresco.connector.Connector#getCredentials()
331      */
332     public Credentials getCredentials()
333     {
334         return this.connector.getCredentials();
335     }
336 
337     /* (non-Javadoc)
338      * @see org.alfresco.connector.Connector#setEndpoint(java.lang.String)
339      */
340     public void setEndpoint(String endpoint)
341     {
342         this.connector.setEndpoint(endpoint);
343     }
344 
345     /* (non-Javadoc)
346      * @see org.alfresco.connector.Connector#getEndpoint()
347      */
348     public String getEndpoint()
349     {
350         return connector.getEndpoint();
351     }
352     
353     /* (non-Javadoc)
354      * @see org.alfresco.connector.AbstractConnector#setConnectorSession(org.alfresco.connector.ConnectorSession)
355      */
356     public void setConnectorSession(ConnectorSession connectorSession)
357     {
358         this.connector.setConnectorSession(connectorSession);
359     }
360     
361     /* (non-Javadoc)
362      * @see org.alfresco.connector.AbstractConnector#getConnectorSession()
363      */
364     public ConnectorSession getConnectorSession()
365     {
366         return this.connector.getConnectorSession();
367     }
368     
369     /**
370      * Returns whether the current session is authenticated already.
371      * 
372      * @return true, if checks if is authenticated
373      */
374     protected boolean isAuthenticated()
375     {
376         return this.authenticator.isAuthenticated(getEndpoint(), getConnectorSession());        
377     }
378     
379     
380     /**
381      * Performs the authentication handshake.
382      * 
383      * @return true, if successful
384      */
385     final public boolean handshake()
386     {
387         boolean success = false;
388         
389         if (logger.isDebugEnabled())
390             logger.debug("Performing authentication handshake");
391         
392         if (EndpointManager.allowConnect(getEndpoint()))
393         {
394             ConnectorSession cs = null;
395             try
396             {
397                 if (logger.isDebugEnabled())
398                 {
399                     logger.debug("Authentication handshake using credentials: " + getCredentials());
400                     logger.debug("Authentication handshake using connectorSession: " + getConnectorSession());
401                 }
402                 
403                 cs = this.authenticator.authenticate(getEndpoint(), getCredentials(), getConnectorSession());
404             }
405             catch (AuthenticationException ae)
406             {
407                 logger.error("An exception occurred while attempting authentication handshake for endpoint: " + getEndpoint(), ae);
408             }
409             if (cs != null)
410             {
411                 this.setConnectorSession(cs);
412                 success = true;
413             }
414         }
415         else
416         {
417             if (logger.isDebugEnabled())
418                 logger.debug("Skipping authentication handshake, waiting for reconnect on: " + getEndpoint());
419         }
420         
421         return success;
422     }
423 
424     /* (non-Javadoc)
425      * @see java.lang.Object#toString()
426      */
427     @Override
428     public String toString()
429     {
430         return this.connector.toString();
431     }
432 }