View Javadoc
1   /*
2    * Copyright (c) 2002-2014, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.mylutece.modules.openid.authentication;
35  
36  import fr.paris.lutece.plugins.mylutece.authentication.PortalAuthentication;
37  import fr.paris.lutece.plugins.mylutece.modules.openid.web.OpenIDApp;
38  import fr.paris.lutece.portal.service.security.LoginRedirectException;
39  import fr.paris.lutece.portal.service.security.LuteceUser;
40  import fr.paris.lutece.portal.service.security.SecurityService;
41  import fr.paris.lutece.portal.service.util.AppLogService;
42  import fr.paris.lutece.portal.service.util.AppPathService;
43  import fr.paris.lutece.portal.service.util.AppPropertiesService;
44  import fr.paris.lutece.util.url.UrlItem;
45  
46  import org.apache.commons.lang.StringUtils;
47  import org.apache.log4j.Logger;
48  
49  import org.openid4java.OpenIDException;
50  
51  import org.openid4java.consumer.ConsumerException;
52  import org.openid4java.consumer.ConsumerManager;
53  import org.openid4java.consumer.VerificationResult;
54  
55  import org.openid4java.discovery.DiscoveryInformation;
56  import org.openid4java.discovery.Identifier;
57  
58  import org.openid4java.message.AuthRequest;
59  import org.openid4java.message.AuthSuccess;
60  import org.openid4java.message.ParameterList;
61  import org.openid4java.message.ax.AxMessage;
62  import org.openid4java.message.ax.FetchRequest;
63  import org.openid4java.message.ax.FetchResponse;
64  import org.openid4java.util.HttpClientFactory;
65  import org.openid4java.util.ProxyProperties;
66  
67  import java.util.List;
68  import java.util.Set;
69  
70  import javax.security.auth.login.LoginException;
71  
72  import javax.servlet.http.HttpServletRequest;
73  
74  
75  /**
76   * The Class provides an implementation of the inherited abstract
77   * class PortalAuthentication based on OpenID
78   */
79  public class OpenIDAuthentication extends PortalAuthentication
80  {
81      ////////////////////////////////////////////////////////////////////////////////////////////////
82      // Constants
83      private static final String PROPERTY_AUTH_SERVICE_NAME = "mylutece-openid.service.name";
84      private static final String URL_CALLBACK = "jsp/site/plugins/mylutece/modules/openid/OpenIDProviderCallBack.jsp";
85      private static final String CONSTANT_PATH_ICON = "images/local/skin/plugins/mylutece/modules/openid/mylutece-openid.png";
86      private static final String ATTRIBUTE_FIRST_NAME = "firstname";
87      private static final String ATTRIBUTE_LAST_NAME = "lastname";
88      private static final String ATTRIBUTE_EMAIL = "email";
89      private static final String MESSAGE_KEY_AUTHENTICATION_FAILED = "module.mylutece.openid.authenticationFailed";
90      private static final String PLUGIN_NAME = "mylutece-openid";
91      private static final String PROPERTY_HTTPACCESS_PROXYHOST = "httpAccess.proxyHost";
92      private static final String PROPERTY_HTTPACCESS_PROXYPORT ="httpAccess.proxyPort";
93      private static final String PROPERTY_HTTPACCESS_PROXYUSERNAME ="httpAccess.proxyUserName";
94      private static final String PROPERTY_HTTPACCESS_PROXYPASSWORD ="httpAccess.proxyPassword";
95      private static final String PROPERTY_HTTPACCESS_DOMAINNAME ="httpaccess.domainName";
96      
97      private static ConsumerManager _manager;
98      private static Logger _logger = Logger.getLogger( "openid" );
99  
100     /**
101      * Constructor
102      */
103     public OpenIDAuthentication(  )
104     {
105         super(  );
106 
107         // instantiate a ConsumerManager object
108         if ( _manager == null )
109         {
110             try
111             {
112 // init http proxy
113                 
114                 
115             	boolean bUsingProxy = false;
116                 ProxyProperties properties = new ProxyProperties(  );
117                 String strProxyHost = AppPropertiesService.getProperty( PROPERTY_HTTPACCESS_PROXYHOST );
118                 String strProxyPort = AppPropertiesService.getProperty( PROPERTY_HTTPACCESS_PROXYPORT );
119                 String strProxyUserName = AppPropertiesService.getProperty( PROPERTY_HTTPACCESS_PROXYUSERNAME );
120                 String strProxyPassword = AppPropertiesService.getProperty( PROPERTY_HTTPACCESS_PROXYPASSWORD );
121                 String strDomainName = AppPropertiesService.getProperty( PROPERTY_HTTPACCESS_DOMAINNAME );
122                 
123                 if ( StringUtils.isNotBlank( strProxyHost ) )
124                 {
125                 	properties.setProxyHostName( strProxyHost );
126                 	bUsingProxy = true;
127                 }
128                 if ( StringUtils.isNotBlank( strProxyPassword ) )
129                 {
130                 	properties.setPassword( strProxyPassword );
131                 }
132                 
133                 
134                 if ( StringUtils.isNotBlank( strProxyPort ) )
135                 {
136                 	try
137                 	{
138                 		int nProxyPort = Integer.parseInt( strProxyPort );
139                 		properties.setProxyPort( nProxyPort );
140                 	}
141                 	catch ( NumberFormatException nfe )
142                 	{
143                 		AppLogService.error( "Error retrieving proxy port : " + strProxyHost );
144                 	}
145                 }
146                 if ( StringUtils.isNotBlank( strProxyUserName ) )
147                 {
148                 	properties.setUserName( strProxyUserName );
149                 }
150                 if ( StringUtils.isNotBlank( strDomainName ) )
151                 {
152                 	properties.setDomain( strDomainName );
153                 }
154                 
155                 if ( bUsingProxy )
156                 {
157                 	HttpClientFactory.setProxyProperties( properties );
158                 }
159                 _manager = new ConsumerManager(  );
160             }
161             catch ( ConsumerException e )
162             {
163                 AppLogService.error( "Error instantiating OpenID ConsumerManager : " + e.getMessage(  ), e );
164             }
165         }
166     }
167 
168     /**
169      * Gets the Authentification service name
170      * @return The name of the authentication service
171      */
172     public String getAuthServiceName(  )
173     {
174         return AppPropertiesService.getProperty( PROPERTY_AUTH_SERVICE_NAME );
175     }
176 
177     /**
178      * Gets the Authentification type
179      * @param request The HTTP request
180      * @return The type of authentication
181      */
182     public String getAuthType( HttpServletRequest request )
183     {
184         return HttpServletRequest.BASIC_AUTH;
185     }
186 
187     /**
188      * This methods checks the login info in the LDAP repository
189      *
190      *
191      * @return A LuteceUser object corresponding to the login
192      * @param strUserName The username
193      * @param strUserPassword The password
194      * @param request The HttpServletRequest
195      * @throws LoginRedirectException This exception is used to redirect the authentication to the provider
196      * @throws LoginException The LoginException
197      */
198     public LuteceUser login( String strUserName, String strUserPassword, HttpServletRequest request )
199         throws LoginException, LoginRedirectException
200     {
201         String strRedirectUrl = getProviderRedirectUrl( request, strUserName );
202 
203         if ( strRedirectUrl != null )
204         {
205             throw new LoginRedirectException( strRedirectUrl );
206         }
207 
208         return null;
209     }
210 
211     /**
212      * This methods logout the user
213      * @param user The user
214      */
215     public void logout( LuteceUser user )
216     {
217     }
218 
219     /**
220      * This method returns an anonymous Lutece user
221      *
222      * @return An anonymous Lutece user
223      */
224     public LuteceUser getAnonymousUser(  )
225     {
226         return new OpenIDUser( LuteceUser.ANONYMOUS_USERNAME, this );
227     }
228 
229     /**
230      * Checks that the current user is associated to a given role
231      * @param user The user
232      * @param request The HTTP request
233      * @param strRole The role name
234      * @return Returns true if the user is associated to the role, otherwise false
235      */
236     public boolean isUserInRole( LuteceUser user, HttpServletRequest request, String strRole )
237     {
238         // Not used
239         return false;
240     }
241 
242     /**
243      * Build the http request to send to the provider to validate the authentication
244      * @param request The HTTP request
245      * @param strOpenID The user OpenID URL
246      * @return The URL
247      */
248     private String getProviderRedirectUrl( HttpServletRequest request, String strOpenID )
249     {
250         String strReturnUrl = getMessageUrl( request, MESSAGE_KEY_AUTHENTICATION_FAILED );
251 
252         try
253         {
254             // perform discovery on the user-supplied identifier
255             List discoveries = _manager.discover( strOpenID );
256 
257             // attempt to associate with the OpenID provider
258             // and retrieve one service endpoint for authentication
259             DiscoveryInformation discovered = _manager.associate( discoveries );
260 
261             // store the discovery information in the user's session
262             request.getSession(  ).setAttribute( "openid-disc", discovered );
263 
264             // obtain a AuthRequest message to be sent to the OpenID provider
265             AuthRequest authReq = _manager.authenticate( discovered, getReturnUrl( request ) );
266 
267             // Attribute Exchange example: fetching the 'email' attribute
268             FetchRequest fetch = FetchRequest.createFetchRequest(  );
269             fetch.addAttribute( ATTRIBUTE_FIRST_NAME, "http://schema.openid.net/namePerson/first", true );
270             fetch.addAttribute( ATTRIBUTE_LAST_NAME, "http://schema.openid.net/namePerson/last", true );
271             fetch.addAttribute( ATTRIBUTE_EMAIL, "http://schema.openid.net/contact/email", true );
272 
273             // attach the extension to the authentication request
274             authReq.addExtension( fetch );
275 
276             strReturnUrl = authReq.getDestinationUrl( true );
277         }
278         catch ( OpenIDException e )
279         {
280             _logger.error( "OpenId Error building authentication request : " + e.getMessage(  ), e );
281         }
282 
283         return strReturnUrl;
284     }
285 
286     /**
287      * The response URL that will be used by the provider to give its response :
288      * authentication validated or not. If OK the response will hold uesr's attributes.
289      * @param request The HTTP request
290      * @return The URL
291      */
292     private String getReturnUrl( HttpServletRequest request )
293     {
294         _logger.debug( "Callback URL : " + AppPathService.getBaseUrl( request ) + URL_CALLBACK );
295 
296         return AppPathService.getBaseUrl( request ) + URL_CALLBACK;
297     }
298 
299     /**
300      * processing the authentication response
301      * @param request The HTTP request
302      * @return The URL depending of the result
303      */
304     public String verifyResponse( HttpServletRequest request )
305     {
306         String strReturnUrl = getMessageUrl( request, MESSAGE_KEY_AUTHENTICATION_FAILED );
307 
308         _logger.debug( "Provider callback - host : " + request.getRemoteHost(  ) + " - IP : " +
309             request.getRemoteAddr(  ) );
310 
311         OpenIDUser user = null;
312 
313         try
314         {
315             // extract the parameters from the authentication response
316             // (which comes in as a HTTP request from the OpenID provider)
317             ParameterList response = new ParameterList( request.getParameterMap(  ) );
318 
319             // retrieve the previously stored discovery information
320             DiscoveryInformation discovered = (DiscoveryInformation) request.getSession(  ).getAttribute( "openid-disc" );
321 
322             // extract the receiving URL from the HTTP request
323             StringBuffer receivingURL = request.getRequestURL(  );
324             String queryString = request.getQueryString(  );
325 
326             if ( ( queryString != null ) && ( queryString.length(  ) > 0 ) )
327             {
328                 receivingURL.append( "?" ).append( request.getQueryString(  ) );
329             }
330 
331             // verify the response; ConsumerManager needs to be the same
332             // (static) instance used to place the authentication request
333             VerificationResult verification = _manager.verify( receivingURL.toString(  ), response, discovered );
334 
335             // examine the verification result and extract the verified identifier
336             Identifier verified = verification.getVerifiedId(  );
337             _logger.debug( "Authentication verification  : " + verified );
338 
339             if ( verified != null )
340             {
341                 user = new OpenIDUser( verified.getIdentifier(  ), this );
342 
343                 AuthSuccess authSuccess = (AuthSuccess) verification.getAuthResponse(  );
344 
345                 if ( authSuccess.hasExtension( AxMessage.OPENID_NS_AX ) )
346                 {
347                     _logger.debug( "Authentication successfull - identifier : " + verified.getIdentifier(  ) );
348 
349                     FetchResponse fetchResp = (FetchResponse) authSuccess.getExtension( AxMessage.OPENID_NS_AX );
350 
351                     for ( String strKey : (Set<String>) fetchResp.getAttributes(  ).keySet(  ) )
352                     {
353                         _logger.debug( "Attribute " + strKey + " - value : " +
354                             fetchResp.getAttributes(  ).get( strKey ) );
355                     }
356 
357                     String strFirstName = (String) fetchResp.getAttributes(  ).get( ATTRIBUTE_FIRST_NAME );
358                     String strLastName = (String) fetchResp.getAttributes(  ).get( ATTRIBUTE_LAST_NAME );
359                     List emails = fetchResp.getAttributeValues( ATTRIBUTE_EMAIL );
360                     String email = (String) emails.get( 0 );
361 
362                     user.setUserInfo( LuteceUser.NAME_GIVEN, strFirstName );
363                     user.setUserInfo( LuteceUser.NAME_FAMILY, strLastName );
364                     user.setUserInfo( LuteceUser.BUSINESS_INFO_ONLINE_EMAIL, email );
365                 }
366 
367                 SecurityService.getInstance(  ).registerUser( request, user );
368 
369                 strReturnUrl = AppPathService.getBaseUrl( request ) + AppPathService.getPortalUrl(  ); // success
370             }
371         }
372         catch ( OpenIDException e )
373         {
374             _logger.error( "OpenId Error in provider response : " + e.getMessage(  ), e );
375         }
376 
377         return strReturnUrl;
378     }
379 
380     /**
381      * Build the URL to display a message
382      * @param request The HTTP request
383      * @param strMessageKey The message key
384      * @return The URL to display the message
385      */
386     private String getMessageUrl( HttpServletRequest request, String strMessageKey )
387     {
388         UrlItem url = new UrlItem( AppPathService.getBaseUrl( request ) + AppPathService.getPortalUrl(  ) );
389         url.addParameter( OpenIDApp.PARAMETER_PAGE, OpenIDApp.PARAMETER_PAGE_VALUE );
390         url.addParameter( OpenIDApp.PARAMETER_ERROR, strMessageKey );
391 
392         return url.getUrl(  );
393     }
394 
395     /**
396      * 
397      *{@inheritDoc}
398      */
399 	public String getIconUrl()
400 	{
401 		return CONSTANT_PATH_ICON;
402 	}
403 
404 	/**
405 	 * 
406 	 *{@inheritDoc}
407 	 */
408 	public String getName()
409 	{
410 		return PLUGIN_NAME;
411 	}
412 
413 	/**
414 	 * 
415 	 *{@inheritDoc}
416 	 */
417 	public String getPluginName()
418 	{
419 		return PLUGIN_NAME;
420 	}
421 
422 	/**
423 	 * 
424 	 *{@inheritDoc}
425 	 */
426 	public boolean isMultiAuthenticationSupported()
427 	{
428 		return false;
429 	}
430 }