View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. 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,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.saml.ext.builder;
21  
22  import org.apache.ws.security.WSSecurityException;
23  import org.apache.ws.security.saml.ext.OpenSAMLUtil;
24  import org.apache.ws.security.saml.ext.bean.ActionBean;
25  import org.apache.ws.security.saml.ext.bean.AttributeBean;
26  import org.apache.ws.security.saml.ext.bean.AttributeStatementBean;
27  import org.apache.ws.security.saml.ext.bean.AuthDecisionStatementBean;
28  import org.apache.ws.security.saml.ext.bean.AuthenticationStatementBean;
29  import org.apache.ws.security.saml.ext.bean.ConditionsBean;
30  import org.apache.ws.security.saml.ext.bean.KeyInfoBean;
31  import org.apache.ws.security.saml.ext.bean.SubjectBean;
32  import org.apache.ws.security.saml.ext.bean.SubjectLocalityBean;
33  import org.apache.ws.security.util.UUIDGenerator;
34  
35  import org.joda.time.DateTime;
36  import org.opensaml.Configuration;
37  import org.opensaml.common.SAMLObjectBuilder;
38  import org.opensaml.common.SAMLVersion;
39  
40  import org.opensaml.saml1.core.Action;
41  import org.opensaml.saml1.core.Assertion;
42  import org.opensaml.saml1.core.Attribute;
43  import org.opensaml.saml1.core.AttributeStatement;
44  import org.opensaml.saml1.core.AttributeValue;
45  import org.opensaml.saml1.core.Audience;
46  import org.opensaml.saml1.core.AudienceRestrictionCondition;
47  import org.opensaml.saml1.core.AuthenticationStatement;
48  import org.opensaml.saml1.core.AuthorizationDecisionStatement;
49  import org.opensaml.saml1.core.Conditions;
50  import org.opensaml.saml1.core.ConfirmationMethod;
51  import org.opensaml.saml1.core.DecisionTypeEnumeration;
52  import org.opensaml.saml1.core.Evidence;
53  import org.opensaml.saml1.core.NameIdentifier;
54  import org.opensaml.saml1.core.Subject;
55  import org.opensaml.saml1.core.SubjectConfirmation;
56  import org.opensaml.saml1.core.SubjectLocality;
57  
58  import org.opensaml.xml.XMLObject;
59  import org.opensaml.xml.XMLObjectBuilderFactory;
60  import org.opensaml.xml.schema.XSString;
61  import org.opensaml.xml.schema.impl.XSStringBuilder;
62  import org.opensaml.xml.security.x509.BasicX509Credential;
63  import org.opensaml.xml.security.x509.X509KeyInfoGeneratorFactory;
64  import org.opensaml.xml.signature.KeyInfo;
65  
66  import java.util.ArrayList;
67  import java.util.List;
68  
69  /**
70   * Class SAML1ComponentBuilder provides builder methods that can be used
71   * to construct SAML v1.1 statements using the OpenSaml library.
72   * <p/>
73   * Created on May 18, 2009
74   */
75  public final class SAML1ComponentBuilder {
76      
77      private static volatile SAMLObjectBuilder<Assertion> assertionV1Builder;
78      
79      private static volatile SAMLObjectBuilder<Conditions> conditionsV1Builder;
80      
81      private static volatile SAMLObjectBuilder<AudienceRestrictionCondition> audienceRestrictionV1Builder;
82      
83      private static volatile SAMLObjectBuilder<Audience> audienceV1Builder;
84      
85      private static volatile SAMLObjectBuilder<AuthenticationStatement> authenticationStatementV1Builder;
86      
87      private static volatile SAMLObjectBuilder<Subject> subjectV1Builder;
88      
89      private static volatile SAMLObjectBuilder<NameIdentifier> nameIdentifierV1Builder;
90      
91      private static volatile SAMLObjectBuilder<SubjectConfirmation> 
92          subjectConfirmationV1Builder;
93      
94      private static volatile SAMLObjectBuilder<ConfirmationMethod> confirmationMethodV1Builder;
95      
96      private static volatile SAMLObjectBuilder<AttributeStatement> 
97          attributeStatementV1Builder;
98      
99      private static volatile SAMLObjectBuilder<Attribute> attributeV1Builder;
100     
101     private static volatile XSStringBuilder stringBuilder;
102     
103     private static volatile SAMLObjectBuilder<AuthorizationDecisionStatement> 
104         authorizationDecisionStatementV1Builder;
105     
106     private static volatile SAMLObjectBuilder<Action> actionElementV1Builder;
107     
108     private static volatile XMLObjectBuilderFactory builderFactory = Configuration.getBuilderFactory();
109     
110     private static volatile SAMLObjectBuilder<SubjectLocality> subjectLocalityBuilder;
111 
112     private SAML1ComponentBuilder() {
113         // Complete
114     }
115     
116     /**
117      * Create a new SAML 1.1 assertion
118      *
119      * @param issuer of type String
120      * @return A SAML 1.1 assertion
121      */
122     @SuppressWarnings("unchecked")
123     public static Assertion createSamlv1Assertion(String issuer) {
124         if (assertionV1Builder == null) {
125             assertionV1Builder = (SAMLObjectBuilder<Assertion>) 
126                 builderFactory.getBuilder(Assertion.DEFAULT_ELEMENT_NAME);
127             if (assertionV1Builder == null) {
128                 throw new IllegalStateException(
129                     "OpenSaml engine not initialized. Please make sure to initialize the OpenSaml "
130                     + "engine prior using it"
131                 );
132             }
133         }
134         Assertion assertion = 
135             assertionV1Builder.buildObject(
136                 Assertion.DEFAULT_ELEMENT_NAME, 
137                 Assertion.TYPE_NAME
138             );
139         assertion.setVersion(SAMLVersion.VERSION_11);
140         assertion.setIssuer(issuer);
141         assertion.setIssueInstant(new DateTime()); // now
142         assertion.setID("_" + UUIDGenerator.getUUID());
143         return assertion;
144     }
145 
146 
147     /**
148      * Create a SAML Subject from a SubjectBean instance
149      *
150      * @param subjectBean A SubjectBean instance
151      * @return A Saml 1.1 subject
152      */
153     @SuppressWarnings("unchecked")
154     public static Subject createSaml1v1Subject(SubjectBean subjectBean) 
155         throws org.opensaml.xml.security.SecurityException, WSSecurityException {
156         if (subjectV1Builder == null) {
157             subjectV1Builder = (SAMLObjectBuilder<Subject>) 
158                 builderFactory.getBuilder(Subject.DEFAULT_ELEMENT_NAME);
159         }
160         if (nameIdentifierV1Builder == null) {
161             nameIdentifierV1Builder = (SAMLObjectBuilder<NameIdentifier>)
162                 builderFactory.getBuilder(NameIdentifier.DEFAULT_ELEMENT_NAME);
163         }
164         if (subjectConfirmationV1Builder == null) {
165             subjectConfirmationV1Builder = (SAMLObjectBuilder<SubjectConfirmation>)
166                 builderFactory.getBuilder(SubjectConfirmation.DEFAULT_ELEMENT_NAME);
167             
168         }
169         if (confirmationMethodV1Builder == null) {
170             confirmationMethodV1Builder = (SAMLObjectBuilder<ConfirmationMethod>)
171                 builderFactory.getBuilder(ConfirmationMethod.DEFAULT_ELEMENT_NAME);
172         }
173         
174         Subject subject = subjectV1Builder.buildObject();
175         NameIdentifier nameIdentifier = nameIdentifierV1Builder.buildObject();
176         SubjectConfirmation subjectConfirmation = subjectConfirmationV1Builder.buildObject();
177         ConfirmationMethod confirmationMethod = confirmationMethodV1Builder.buildObject();
178         
179         nameIdentifier.setNameQualifier(subjectBean.getSubjectNameQualifier());
180         nameIdentifier.setNameIdentifier(subjectBean.getSubjectName());
181         nameIdentifier.setFormat(subjectBean.getSubjectNameIDFormat());
182         String confirmationMethodStr = subjectBean.getSubjectConfirmationMethod();
183         
184         if (confirmationMethodStr == null) {
185             confirmationMethodStr = SAML1Constants.CONF_SENDER_VOUCHES;
186         }
187         
188         confirmationMethod.setConfirmationMethod(confirmationMethodStr);
189         subjectConfirmation.getConfirmationMethods().add(confirmationMethod);
190         if (subjectBean.getKeyInfo() != null) {
191             KeyInfo keyInfo = createKeyInfo(subjectBean.getKeyInfo());
192             subjectConfirmation.setKeyInfo(keyInfo);
193         }
194         subject.setNameIdentifier(nameIdentifier);
195         subject.setSubjectConfirmation(subjectConfirmation);
196         
197         return subject;
198     }
199     
200     /**
201      * Create an Opensaml KeyInfo object from the parameters
202      * @param keyInfo the KeyInfo bean from which to extract security credentials
203      * @return the KeyInfo object
204      * @throws org.opensaml.xml.security.SecurityException
205      */
206     public static KeyInfo createKeyInfo(KeyInfoBean keyInfo) 
207         throws org.opensaml.xml.security.SecurityException, WSSecurityException {
208         if (keyInfo.getElement() != null) {
209             return (KeyInfo)OpenSAMLUtil.fromDom(keyInfo.getElement());
210         } else {
211             // Set the certificate or public key
212             BasicX509Credential keyInfoCredential = new BasicX509Credential();
213             if (keyInfo.getCertificate() != null) {
214                 keyInfoCredential.setEntityCertificate(keyInfo.getCertificate());
215             } else if (keyInfo.getPublicKey() != null) {
216                 keyInfoCredential.setPublicKey(keyInfo.getPublicKey());
217             }
218             
219             // Configure how to emit the certificate
220             X509KeyInfoGeneratorFactory kiFactory = new X509KeyInfoGeneratorFactory();
221             KeyInfoBean.CERT_IDENTIFIER certIdentifier = keyInfo.getCertIdentifer();
222             switch (certIdentifier) {
223                 case X509_CERT: {
224                     kiFactory.setEmitEntityCertificate(true);
225                     break;
226                 }
227                 case KEY_VALUE: {
228                     kiFactory.setEmitPublicKeyValue(true);
229                     break;
230                 }
231                 case X509_ISSUER_SERIAL: {
232                     kiFactory.setEmitX509IssuerSerial(true);
233                 }
234             }
235             return kiFactory.newInstance().generate(keyInfoCredential);
236         }
237     }
238 
239     /**
240      * Create a Conditions object
241      *
242      * @param conditionsBean A ConditionsBean object
243      * @return a Conditions object
244      */
245     @SuppressWarnings("unchecked")
246     public static Conditions createSamlv1Conditions(ConditionsBean conditionsBean) {
247         if (conditionsV1Builder == null) {
248             conditionsV1Builder = (SAMLObjectBuilder<Conditions>) 
249                 builderFactory.getBuilder(Conditions.DEFAULT_ELEMENT_NAME);
250             
251         }
252         Conditions conditions = conditionsV1Builder.buildObject();
253         
254         if (conditionsBean == null) {
255             DateTime newNotBefore = new DateTime();
256             conditions.setNotBefore(newNotBefore);
257             conditions.setNotOnOrAfter(newNotBefore.plusMinutes(5));
258             return conditions;
259         }
260         
261         int tokenPeriodMinutes = conditionsBean.getTokenPeriodMinutes();
262         DateTime notBefore = conditionsBean.getNotBefore();
263         DateTime notAfter = conditionsBean.getNotAfter();
264         
265         if (notBefore != null && notAfter != null) {
266             if (notBefore.isAfter(notAfter)) {
267                 throw new IllegalStateException(
268                     "The value of notBefore may not be after the value of notAfter"
269                 );
270             }
271             conditions.setNotBefore(notBefore);
272             conditions.setNotOnOrAfter(notAfter);
273         } else {
274             DateTime newNotBefore = new DateTime();
275             conditions.setNotBefore(newNotBefore);
276             if (tokenPeriodMinutes <= 0) {
277                 tokenPeriodMinutes = 5;
278             }
279             conditions.setNotOnOrAfter(newNotBefore.plusMinutes(tokenPeriodMinutes));
280         }
281         
282         if (conditionsBean.getAudienceURI() != null) {
283             AudienceRestrictionCondition audienceRestriction = 
284                 createSamlv1AudienceRestriction(conditionsBean.getAudienceURI());
285             conditions.getAudienceRestrictionConditions().add(audienceRestriction);
286         }
287         
288         return conditions;
289     }
290     
291     /**
292      * Create an AudienceRestrictionCondition object
293      *
294      * @param audienceURI of type String
295      * @return an AudienceRestrictionCondition object
296      */
297     @SuppressWarnings("unchecked")
298     public static AudienceRestrictionCondition 
299     createSamlv1AudienceRestriction(String audienceURI) {
300         if (audienceRestrictionV1Builder == null) {
301             audienceRestrictionV1Builder = (SAMLObjectBuilder<AudienceRestrictionCondition>) 
302                 builderFactory.getBuilder(AudienceRestrictionCondition.DEFAULT_ELEMENT_NAME);
303         }
304         if (audienceV1Builder == null) {
305             audienceV1Builder = (SAMLObjectBuilder<Audience>) 
306                 builderFactory.getBuilder(Audience.DEFAULT_ELEMENT_NAME);
307         }
308        
309         AudienceRestrictionCondition audienceRestriction = 
310             audienceRestrictionV1Builder.buildObject();
311         Audience audience = audienceV1Builder.buildObject();
312         audience.setUri(audienceURI);
313         audienceRestriction.getAudiences().add(audience);
314         return audienceRestriction;
315     }
316 
317     /**
318      * Create SAML 1.1 authentication statement(s)
319      *
320      * @param authBeans A list of AuthenticationStatementBean objects
321      * @return a list of SAML 1.1 authentication statement(s)
322      */
323     @SuppressWarnings("unchecked")
324     public static List<AuthenticationStatement> createSamlv1AuthenticationStatement(
325         List<AuthenticationStatementBean> authBeans
326     ) throws org.opensaml.xml.security.SecurityException, WSSecurityException {
327         List<AuthenticationStatement> authenticationStatements = 
328             new ArrayList<AuthenticationStatement>();
329         
330         if (authenticationStatementV1Builder == null) {
331             authenticationStatementV1Builder = (SAMLObjectBuilder<AuthenticationStatement>) 
332                 builderFactory.getBuilder(AuthenticationStatement.DEFAULT_ELEMENT_NAME);
333         }
334         if (subjectLocalityBuilder == null) {
335             subjectLocalityBuilder = (SAMLObjectBuilder<SubjectLocality>) 
336                 builderFactory.getBuilder(SubjectLocality.DEFAULT_ELEMENT_NAME);
337         }
338 
339         if (authBeans != null && authBeans.size() > 0) {
340             for (AuthenticationStatementBean statementBean : authBeans) {
341                 AuthenticationStatement authenticationStatement = 
342                     authenticationStatementV1Builder.buildObject(
343                         AuthenticationStatement.DEFAULT_ELEMENT_NAME, 
344                         AuthenticationStatement.TYPE_NAME
345                     );
346                 Subject authSubject = 
347                     SAML1ComponentBuilder.createSaml1v1Subject(statementBean.getSubject());
348                 authenticationStatement.setSubject(authSubject);
349 
350                 if (statementBean.getAuthenticationInstant() != null) {
351                     authenticationStatement.setAuthenticationInstant(
352                         statementBean.getAuthenticationInstant()
353                     );
354                 } else {
355                     authenticationStatement.setAuthenticationInstant(new DateTime());
356                 }
357 
358                 authenticationStatement.setAuthenticationMethod(
359                     transformAuthenticationMethod(statementBean.getAuthenticationMethod())
360                 );
361                 
362                 SubjectLocalityBean subjectLocalityBean = statementBean.getSubjectLocality();
363                 if (subjectLocalityBean != null) {
364                     SubjectLocality subjectLocality = subjectLocalityBuilder.buildObject();
365                     subjectLocality.setDNSAddress(subjectLocalityBean.getDnsAddress());
366                     subjectLocality.setIPAddress(subjectLocalityBean.getIpAddress());
367 
368                     authenticationStatement.setSubjectLocality(subjectLocality);
369                 }
370                 
371                 authenticationStatements.add(authenticationStatement);
372             }
373         }
374 
375         return authenticationStatements;
376     }
377 
378     /**
379      * Method transformAuthenticationMethod transforms the user-supplied authentication method 
380      * value into one of the supported specification-compliant values.
381      *
382      * @param sourceMethod of type String
383      * @return String
384      */
385     private static String transformAuthenticationMethod(String sourceMethod) {
386         String transformedMethod = "";
387 
388         if ("Password".equals(sourceMethod)) {
389             transformedMethod = SAML1Constants.AUTH_METHOD_PASSWORD;
390         } else if (sourceMethod != null && !"".equals(sourceMethod)) {
391             return sourceMethod;
392         }
393 
394         return transformedMethod;
395     }
396 
397     /**
398      * Create SAML 1.1 attribute statement(s)
399      *
400      * @param attributeData A list of AttributeStatementBean instances
401      * @return a list of SAML 1.1 attribute statement(s)
402      */
403     @SuppressWarnings("unchecked")
404     public static List<AttributeStatement> createSamlv1AttributeStatement(
405         List<AttributeStatementBean> attributeData
406     ) throws org.opensaml.xml.security.SecurityException, WSSecurityException {
407         if (attributeStatementV1Builder == null) {
408             attributeStatementV1Builder = (SAMLObjectBuilder<AttributeStatement>) 
409                 builderFactory.getBuilder(AttributeStatement.DEFAULT_ELEMENT_NAME);
410         }
411 
412         List<AttributeStatement> attributeStatements = new ArrayList<AttributeStatement>();
413 
414         if (attributeData != null && attributeData.size() > 0) {
415             for (AttributeStatementBean statementBean : attributeData) {
416                 // Create the attribute statementBean and set the subject
417                 AttributeStatement attributeStatement = attributeStatementV1Builder.buildObject();
418                 Subject attributeSubject = 
419                     SAML1ComponentBuilder.createSaml1v1Subject(statementBean.getSubject());
420                 attributeStatement.setSubject(attributeSubject);
421                 // Add the individual attributes
422                 for (AttributeBean values : statementBean.getSamlAttributes()) {
423                     List<?> attributeValues = values.getAttributeValues();
424                     if (attributeValues == null || attributeValues.isEmpty()) {
425                         attributeValues = values.getCustomAttributeValues();
426                     }
427                     
428                     Attribute samlAttribute = 
429                         createSamlv1Attribute(
430                             values.getSimpleName(),
431                             values.getQualifiedName(), 
432                             attributeValues
433                         );
434                     attributeStatement.getAttributes().add(samlAttribute);
435                 }
436                 // Add the completed attribute statementBean to the collection
437                 attributeStatements.add(attributeStatement);
438             }
439         }
440 
441         return attributeStatements;
442     }
443 
444     /**
445      * Create a SAML 1.1 attribute
446      *
447      * @param attributeName the Attribute Name
448      * @param attributeUrn the Attribute Qualified Name
449      * @param values the Attribute Values
450      * @return a SAML 1.1 attribute
451      */
452     @SuppressWarnings("unchecked")
453     public static Attribute createSamlv1Attribute(
454         String attributeName, 
455         String attributeUrn,
456         List<?> values
457     ) {
458         if (attributeV1Builder == null) {
459             attributeV1Builder = (SAMLObjectBuilder<Attribute>) 
460                 builderFactory.getBuilder(Attribute.DEFAULT_ELEMENT_NAME);
461         }
462         if (stringBuilder == null) {
463             stringBuilder = (XSStringBuilder)builderFactory.getBuilder(XSString.TYPE_NAME);
464         }
465 
466         Attribute attribute = attributeV1Builder.buildObject();
467         attribute.setAttributeName(attributeName);
468         attribute.setAttributeNamespace(attributeUrn);
469         
470         for (Object value : values) {
471             if (value instanceof String) {
472                 XSString attribute1 = 
473                     stringBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME, XSString.TYPE_NAME);
474                 attribute1.setValue((String)value);
475                 attribute.getAttributeValues().add(attribute1);
476             } else if (value instanceof XMLObject) {
477                 attribute.getAttributeValues().add((XMLObject)value);
478             }
479         }
480 
481         return attribute;
482     }
483 
484     /**
485      * Create SAML 1.1 Authorization Decision Statement(s)
486      *
487      * @param decisionData        of type List<AuthDecisionStatementBean>
488      * @return a list of SAML 1.1 Authorization Decision Statement(s)
489      */
490     @SuppressWarnings("unchecked")
491     public static List<AuthorizationDecisionStatement> createSamlv1AuthorizationDecisionStatement(
492             List<AuthDecisionStatementBean> decisionData) 
493         throws org.opensaml.xml.security.SecurityException, WSSecurityException {
494         List<AuthorizationDecisionStatement> authDecisionStatements = 
495                 new ArrayList<AuthorizationDecisionStatement>();
496         if (authorizationDecisionStatementV1Builder == null) {
497             authorizationDecisionStatementV1Builder = 
498                 (SAMLObjectBuilder<AuthorizationDecisionStatement>) 
499                     builderFactory.getBuilder(AuthorizationDecisionStatement.DEFAULT_ELEMENT_NAME);
500             
501         }
502 
503         if (decisionData != null && decisionData.size() > 0) {
504             for (AuthDecisionStatementBean decisionStatementBean : decisionData) {
505                 AuthorizationDecisionStatement authDecision = 
506                     authorizationDecisionStatementV1Builder.buildObject();
507                 Subject authDecisionSubject = 
508                     SAML1ComponentBuilder.createSaml1v1Subject(decisionStatementBean.getSubject());
509                 authDecision.setSubject(authDecisionSubject);
510 
511                 authDecision.setResource(decisionStatementBean.getResource());
512                 authDecision.setDecision(transformDecisionType(decisionStatementBean.getDecision()));
513 
514                 for (ActionBean actionBean : decisionStatementBean.getActions()) {
515                     Action actionElement = createSamlv1Action(actionBean);
516                     authDecision.getActions().add(actionElement);
517                 }
518                 
519                 if (decisionStatementBean.getEvidence() instanceof Evidence) {                                    
520                     authDecision.setEvidence((Evidence)decisionStatementBean.getEvidence());
521                 }
522                 
523                 authDecisionStatements.add(authDecision);
524             }
525         }
526 
527         return authDecisionStatements;
528     }
529 
530     /**
531      * Create an Action object
532      *
533      * @param actionBean of type SamlAction
534      * @return an Action object
535      */
536     @SuppressWarnings("unchecked")
537     public static Action createSamlv1Action(ActionBean actionBean) {
538         if (actionElementV1Builder == null) {
539             actionElementV1Builder = (SAMLObjectBuilder<Action>)
540                 builderFactory.getBuilder(Action.DEFAULT_ELEMENT_NAME);
541         }
542 
543         Action actionElement = actionElementV1Builder.buildObject();
544         actionElement.setNamespace(actionBean.getActionNamespace());
545         actionElement.setContents(actionBean.getContents());
546 
547         return actionElement;
548     }
549 
550     /**
551      * Transform a DecisionType
552      *
553      * @param decision of type Decision
554      * @return DecisionTypeEnumeration
555      */
556     private static DecisionTypeEnumeration transformDecisionType(
557         AuthDecisionStatementBean.Decision decision
558     ) {
559         DecisionTypeEnumeration decisionTypeEnum = DecisionTypeEnumeration.DENY;
560         if (decision.equals(AuthDecisionStatementBean.Decision.PERMIT)) {
561             decisionTypeEnum = DecisionTypeEnumeration.PERMIT;
562         } else if (decision.equals(AuthDecisionStatementBean.Decision.INDETERMINATE)) {
563             decisionTypeEnum = DecisionTypeEnumeration.INDETERMINATE;
564         }
565 
566         return decisionTypeEnum;
567     }
568 
569 }