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.saml.authentication.engine;
35  
36  import fr.paris.lutece.plugins.mylutece.modules.saml.authentication.config.ConfigProperties;
37  import fr.paris.lutece.plugins.mylutece.modules.saml.authentication.config.Constants;
38  import fr.paris.lutece.plugins.mylutece.modules.saml.authentication.exceptions.CertificateValidationException;
39  import fr.paris.lutece.plugins.mylutece.modules.saml.authentication.exceptions.SAMLParsingException;
40  import fr.paris.lutece.plugins.mylutece.modules.saml.authentication.util.X509CertificateHelper;
41  import fr.paris.lutece.portal.service.security.LuteceUser;
42  import fr.paris.lutece.portal.service.util.AppLogService;
43  
44  import org.opensaml.saml2.core.Assertion;
45  import org.opensaml.saml2.core.Attribute;
46  import org.opensaml.saml2.core.AttributeStatement;
47  import org.opensaml.saml2.core.Audience;
48  import org.opensaml.saml2.core.AudienceRestriction;
49  import org.opensaml.saml2.core.Conditions;
50  import org.opensaml.saml2.core.Response;
51  import org.opensaml.saml2.core.Subject;
52  import org.opensaml.saml2.core.SubjectConfirmation;
53  import org.opensaml.saml2.core.SubjectConfirmationData;
54  import org.opensaml.saml2.core.validator.ResponseSchemaValidator;
55  import org.opensaml.saml2.metadata.RequestedAttribute;
56  
57  import org.opensaml.xml.XMLObject;
58  import org.opensaml.xml.schema.XSString;
59  import org.opensaml.xml.signature.KeyInfo;
60  import org.opensaml.xml.signature.Signature;
61  import org.opensaml.xml.signature.X509Data;
62  import org.opensaml.xml.validation.ValidationException;
63  
64  import java.io.IOException;
65  
66  import java.security.cert.CertificateException;
67  import java.security.cert.X509Certificate;
68  
69  import java.util.ArrayList;
70  import java.util.HashMap;
71  import java.util.Iterator;
72  import java.util.List;
73  import java.util.Map;
74  
75  
76  public class SAMLResponseManager
77  {
78      private Response response = null;
79  
80      /**
81       *
82       * @param response
83       * @throws SAMLParsingException
84       */
85      public SAMLResponseManager( Response response ) throws SAMLParsingException
86      {
87          initialize( response );
88      }
89  
90      /**
91       * Initialise le manager avec la Response. Valide le sch�ma et le contenu de
92       * la Response.
93       *
94       * @param response
95       * @throws SAMLParsingException
96       */
97      public void initialize( Response response ) throws SAMLParsingException
98      {
99          if ( response == null )
100         {
101             String message = "La Response ne devrait pas �tre nulle";
102             AppLogService.info( message );
103             throw new SAMLParsingException( message );
104         }
105 
106         this.response = response;
107 
108         // Validation Sch�ma
109         this.validateResponseSchema(  );
110 
111         // Validation Sch�ma "metier"
112         this.validateResponseContent(  );
113     }
114 
115     /**
116      * Validation du sch�ma Response
117      *
118      * @throws SAMLParsingException
119      */
120     private void validateResponseSchema(  ) throws SAMLParsingException
121     {
122         // XML Validation
123         ResponseSchemaValidator validator = new ResponseSchemaValidator(  );
124 
125         try
126         {
127             validator.validate( response );
128         }
129         catch ( ValidationException e )
130         {
131             String message = "Erreur lors de la validation du sch�ma de la Response : " + e.getLocalizedMessage(  );
132             AppLogService.info( message );
133             throw new SAMLParsingException( message );
134         }
135     }
136 
137     /**
138      * Validation du contenu "Metier" de la Response.
139      *
140      * @throws SAMLParsingException
141      */
142     private void validateResponseContent(  ) throws SAMLParsingException
143     {
144         // Response devrait contenir un attribut Destination
145         String destination = response.getDestination(  );
146 
147         if ( destination == null )
148         {
149             String message = "La Response devrait contenir un attribut Destination";
150             AppLogService.info( message );
151             throw new SAMLParsingException( message );
152         }
153 
154         // Response doit contenir une et une seule Assertion.  
155         List<Assertion> assertions = response.getAssertions(  );
156 
157         if ( assertions.size(  ) != 1 )
158         {
159             String message = "La Response devrait contenir une et une seule Assertion. Elle en contient " +
160                 assertions.size(  );
161             AppLogService.info( message );
162             throw new SAMLParsingException( message );
163         }
164 
165         // Assertion devrait contenir une Signature
166         Signature signature = assertions.get( 0 ).getSignature(  );
167 
168         if ( signature == null )
169         {
170             String message = "L'Assertion devrait contenir une Signature";
171             AppLogService.info( message );
172             throw new SAMLParsingException( message );
173         }
174 
175         // Signature devrait contenir un KeyInfo
176         KeyInfo keyInfo = signature.getKeyInfo(  );
177 
178         if ( keyInfo == null )
179         {
180             String message = "La Signature devrait contenir un KeyInfo";
181             AppLogService.info( message );
182             throw new SAMLParsingException( message );
183         }
184 
185         // KeyInfo devrait contenir un et un seul X509Data
186         List<X509Data> x509Datas = keyInfo.getX509Datas(  );
187 
188         if ( x509Datas.size(  ) != 1 )
189         {
190             String message = "Le KeyInfo devrait contenir un et un seul X509Data. Il en contient " +
191                 x509Datas.size(  );
192             AppLogService.info( message );
193             throw new SAMLParsingException( message );
194         }
195 
196         // X509Data devrait contenir un et un seul X509Certificate
197         List<org.opensaml.xml.signature.X509Certificate> x509Certificates = x509Datas.get( 0 ).getX509Certificates(  );
198 
199         if ( x509Certificates.size(  ) != 1 )
200         {
201             String message = "Le X509Data devrait contenir un et un seul X509Certificate. Il en contient " +
202                 x509Certificates.size(  );
203             AppLogService.info( message );
204             throw new SAMLParsingException( message );
205         }
206 
207         // Assertion doit contenir un Subject
208         Subject subject = assertions.get( 0 ).getSubject(  );
209 
210         if ( subject == null )
211         {
212             String message = "L'Assertion devrait contenir un Subject";
213             AppLogService.info( message );
214             throw new SAMLParsingException( message );
215         }
216 
217         // Subject doit contenir un SubjectConfirmation
218         List<SubjectConfirmation> subjectConfirmation = subject.getSubjectConfirmations(  );
219 
220         if ( subjectConfirmation.size(  ) != 1 )
221         {
222             String message = "Le Subject devrait contenir un et un seul SubjectConfirmations. Il en contient " +
223                 subjectConfirmation.size(  );
224             AppLogService.info( message );
225             throw new SAMLParsingException( message );
226         }
227 
228         // SubjectConfirmation doit contenir un SubjectConfirmationData		
229         SubjectConfirmationData subjectConfirmationData = subjectConfirmation.get( 0 ).getSubjectConfirmationData(  );
230 
231         if ( subjectConfirmationData == null )
232         {
233             String message = "Le SubjectConfirmation devrait contenir un SubjectConfirmationData";
234             AppLogService.info( message );
235             throw new SAMLParsingException( message );
236         }
237 
238         // SubjectConfirmationData doit contenir un Recipient, Address
239         if ( ( subjectConfirmationData.getRecipient(  ) == null ) || ( subjectConfirmationData.getAddress(  ) == null ) )
240         {
241             String message = "Le SubjectConfirmationData ne contient pas tous les attributs requis";
242             AppLogService.info( message );
243             throw new SAMLParsingException( message );
244         }
245 
246         // Assertion doit contenir un Conditions
247         Conditions conditions = assertions.get( 0 ).getConditions(  );
248 
249         if ( conditions == null )
250         {
251             String message = "L'Assertion devrait contenir un Conditions";
252             AppLogService.info( message );
253             throw new SAMLParsingException( message );
254         }
255 
256         // Conditions doit contenir un NotBefore et NotOnOrAfter
257         if ( ( conditions.getNotBefore(  ) == null ) || ( conditions.getNotOnOrAfter(  ) == null ) )
258         {
259             String message = "Conditions devrait contenir un NotBefore et un NotOnOrAfter";
260             AppLogService.info( message );
261             throw new SAMLParsingException( message );
262         }
263 
264         // Conditions doit contenir un AudienceRestriction
265         List<AudienceRestriction> audienceRestr = conditions.getAudienceRestrictions(  );
266 
267         if ( audienceRestr.size(  ) != 1 )
268         {
269             String message = "Conditions devrait contenir un et un seul AudienceRestrictions. Il en contient " +
270                 audienceRestr.size(  );
271             AppLogService.info( message );
272             throw new SAMLParsingException( message );
273         }
274 
275         // AudienceRestriction doit contenir un Audience
276         List<Audience> audience = audienceRestr.get( 0 ).getAudiences(  );
277 
278         if ( audience.size(  ) != 1 )
279         {
280             String message = "AudienceRestriction devrait contenir un et un seul Audience. Il en contient " +
281                 audience.size(  );
282             AppLogService.info( message );
283             throw new SAMLParsingException( message );
284         }
285 
286         // Assertion devrait contenir un et un seul AttributeStatement
287         List<AttributeStatement> attributesStatements = assertions.get( 0 ).getAttributeStatements(  );
288 
289         if ( attributesStatements.size(  ) != 1 )
290         {
291             String message = "L'Assertion devrait contenir un et un seul AttributeStatement. Elle en contient " +
292                 attributesStatements.size(  );
293             AppLogService.info( message );
294             throw new SAMLParsingException( message );
295         }
296     }
297 
298     /**
299      * Retourne la valeur de l'attribut. Suppose que l'attribut ne contienne
300      * qu'un seul AttributeValue. Sinon, Exception...
301      *
302      * @param attribute
303      * @return La valeur de l'attribut
304      * @throws SAMLParsingException si l'attribut est multivalu�
305      */
306     private String getAttributeValue( Attribute attribute )
307         throws SAMLParsingException
308     {
309         List<XMLObject> values = attribute.getAttributeValues(  );
310 
311         if ( values.size(  ) != 1 )
312         {
313             String message = "L'attribut devrait contenir une et une seule AttributeValue. Il en contient " +
314                 values.size(  );
315             AppLogService.info( message );
316             throw new SAMLParsingException( message );
317         }
318 
319         XSString stringValue = ( (XSString) values.get( 0 ) );
320 
321         return stringValue.getValue(  );
322     }
323 
324     /**
325      * Retourne la liste des valeurs de l'attribut attribute.
326      *
327      * @param attribute
328      * @return La liste des valeurs de l'attribut
329      */
330     private List<String> getAttributeValues( Attribute attribute )
331     {
332         List<String> result = new ArrayList<String>(  );
333         List<XMLObject> values = attribute.getAttributeValues(  );
334         Iterator<XMLObject> iter = values.listIterator(  );
335 
336         while ( iter.hasNext(  ) )
337         {
338             XSString stringValue = (XSString) iter.next(  );
339             result.add( stringValue.getValue(  ) );
340         }
341 
342         return result;
343     }
344 
345     /**
346      * Retourne la premi�re (et seule) Assertion
347      *
348      * @return
349      * @throws SAMLParsingException
350      */
351     public Assertion getAssertion(  )
352     {
353         return response.getAssertions(  ).get( 0 );
354     }
355 
356     /**
357      * Retourne le certificat de signature de l'assertion
358      *
359      * @return
360      * @throws CertificateValidationException
361      * @throws SAMLParsingException
362      */
363     public X509Certificate getSignatureCertificate(  )
364         throws SAMLParsingException
365     {
366         X509Certificate cert = null;
367         KeyInfo keyInfo = this.getAssertion(  ).getSignature(  ).getKeyInfo(  );
368         List<X509Data> x509Datas = keyInfo.getX509Datas(  );
369         List<org.opensaml.xml.signature.X509Certificate> x509Certificates = x509Datas.get( 0 ).getX509Certificates(  );
370         String b64ResponseCert = x509Certificates.get( 0 ).getValue(  );
371 
372         try
373         {
374             cert = X509CertificateHelper.buildX509Cert( b64ResponseCert );
375         }
376         catch ( CertificateException e )
377         {
378             String message = "Erreur lors de la recuperation du certificat de signature : " +
379                 e.getLocalizedMessage(  );
380             AppLogService.info( message );
381             throw new SAMLParsingException( message );
382         }
383         catch ( IOException e )
384         {
385             String message = "Erreur lors de la recuperation du certificat de signature : " +
386                 e.getLocalizedMessage(  );
387             AppLogService.info( message );
388             throw new SAMLParsingException( message );
389         }
390 
391         return cert;
392     }
393 
394     /**
395      * Retourne la liste des attributs de l'assertion
396      *
397      * @return
398      * @throws SAMLParsingException
399      */
400     public List<Attribute> getAssertionAttributes(  )
401     {
402         List<AttributeStatement> attributesStatements = getAssertion(  ).getAttributeStatements(  );
403 
404         return attributesStatements.get( 0 ).getAttributes(  );
405     }
406 
407     /**
408      * Retourne la liste des attributs de l'assertion valides vis-�-vis des
409      * requestedAttributes.
410      *
411      * @param requestedAttributes
412      * @return La liste des attributs
413      */
414     private List<Attribute> getFilteredAssertionAttributes( List<RequestedAttribute> requestedAttributes )
415     {
416         List<Attribute> attributes = this.getAssertionAttributes(  );
417 
418         List<Attribute> filteredAttributes = new ArrayList<Attribute>(  );
419         Iterator<Attribute> iter = attributes.listIterator(  );
420         Attribute attribute;
421 
422         while ( iter.hasNext(  ) )
423         {
424             attribute = iter.next(  );
425 
426             Iterator<RequestedAttribute> iterReq = requestedAttributes.listIterator(  );
427             RequestedAttribute reqAttribute;
428 
429             while ( iterReq.hasNext(  ) )
430             {
431                 reqAttribute = iterReq.next(  );
432 
433                 if ( reqAttribute.getName(  ).equals( attribute.getName(  ) ) )
434                 {
435                     filteredAttributes.add( attribute );
436 
437                     break;
438                 }
439             }
440         }
441 
442         return filteredAttributes;
443     }
444 
445     /**
446      * Retourne la Map des Name/Value des attributs monovalu�s de l'assertion valides
447      * vis-�-vis des requestedAttributes.
448      *
449      * @param requestedAttributes
450      * @return
451      */
452     public Map<String, String> getFilteredAssertionAttributesValues( List<RequestedAttribute> requestedAttributes )
453     {
454         List<Attribute> attributes = this.getFilteredAssertionAttributes( requestedAttributes );
455         Map<String, String> attributesValues = new HashMap<String, String>(  );
456 
457         Iterator<Attribute> iter = attributes.listIterator(  );
458         Attribute attribute;
459 
460         while ( iter.hasNext(  ) )
461         {
462             attribute = iter.next(  );
463             attributesValues.put( attribute.getName(  ), this.getAttributeValues( attribute ).get( 0 ) );
464         }
465 
466         return attributesValues;
467     }
468 
469     /**
470      * R�cupere la valeur de l'Attribut servant � construire le LuteceUserName
471      * dans l'Assertion.
472      *
473      * @return Le LuteceUserName si trouv�, la valeur par d�faut sinon.
474      * @throws SAMLParsingException
475      */
476     public String getLuteceUserName(  )
477     {
478         List<Attribute> attributes = this.getAssertionAttributes(  );
479         Iterator<Attribute> iter = attributes.listIterator(  );
480         Attribute attribute;
481 
482         while ( iter.hasNext(  ) )
483         {
484             attribute = iter.next(  );
485 
486             if ( attribute.getName(  )
487                               .equals( ConfigProperties.getInstance(  )
488                                                            .getProperty( Constants.LUTECE_USER_NAME_ATTRIBUTE_NAME_PROP ) ) )
489             {
490                 try
491                 {
492                     return this.getAttributeValue( attribute );
493                 }
494                 catch ( SAMLParsingException e )
495                 {
496                     String message = "L'Attribut contenant le nom du LuteceUser ne devrait pas �tre multivalu�";
497                     AppLogService.info( message );
498 
499                     return LuteceUser.ANONYMOUS_USERNAME;
500                 }
501             }
502         }
503 
504         return LuteceUser.ANONYMOUS_USERNAME;
505     }
506 
507     /**
508      * R�cupere les valeurs de l'Attribut servant � construire le LuteceUser
509      * Groups dans l'Assertion .
510      *
511      * @return Le LuteceUserName si trouv�, la valeur par d�faut sinon.
512      * @throws SAMLParsingException
513      */
514     public List<String> getLuteceUserGroups(  )
515     {
516         List<Attribute> attributes = this.getAssertionAttributes(  );
517         Iterator<Attribute> iter = attributes.listIterator(  );
518         Attribute attribute;
519 
520         while ( iter.hasNext(  ) )
521         {
522             attribute = iter.next(  );
523 
524             if ( attribute.getName(  )
525                               .equals( ConfigProperties.getInstance(  )
526                                                            .getProperty( Constants.LUTECE_USER_GROUPS_ATTRIBUTE_NAME_PROP ) ) )
527             {
528                 return this.getAttributeValues( attribute );
529             }
530         }
531 
532         return new ArrayList<String>(  );
533     }
534 
535     /**
536      *
537      * @return
538      */
539     public Response getResponse(  )
540     {
541         return response;
542     }
543 }