View Javadoc

1   /*
2    * The MIT License
3    *
4    * Original work sponsored and donated by National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
5    *
6    * Copyright (C) 2011 National Board of e-Health (NSI), Denmark (http://www.nsi.dk)
7    *
8    * Permission is hereby granted, free of charge, to any person obtaining a copy of
9    * this software and associated documentation files (the "Software"), to deal in
10   * the Software without restriction, including without limitation the rights to
11   * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12   * of the Software, and to permit persons to whom the Software is furnished to do
13   * so, subject to the following conditions:
14   *
15   * The above copyright notice and this permission notice shall be included in all
16   * copies or substantial portions of the Software.
17   *
18   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21   * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24   * SOFTWARE.
25   *
26   * $HeadURL$
27   * $Id$
28   */
29  package dk.sosi.seal.model;
30  
31  import dk.sosi.seal.model.constants.DSTags;
32  import dk.sosi.seal.model.constants.IDValues;
33  import dk.sosi.seal.model.constants.NameSpaces;
34  import dk.sosi.seal.model.constants.SAMLTags;
35  import dk.sosi.seal.modelbuilders.ModelBuildException;
36  import dk.sosi.seal.pki.Federation;
37  import dk.sosi.seal.xml.XmlUtil;
38  import org.apache.commons.codec.binary.Base64;
39  import org.w3c.dom.Document;
40  import org.w3c.dom.Element;
41  import org.w3c.dom.Node;
42  import org.xml.sax.SAXException;
43  
44  import javax.xml.transform.dom.DOMSource;
45  import javax.xml.validation.Schema;
46  import javax.xml.validation.Validator;
47  import java.io.*;
48  import java.util.Date;
49  import java.util.List;
50  import java.util.Properties;
51  import java.util.zip.Deflater;
52  import java.util.zip.GZIPInputStream;
53  import java.util.zip.GZIPOutputStream;
54  
55  /**
56   * The <code>IdentityToken</code> is a smaller ID representation than the regular <code>IDCard</code>.<br />
57   * The <code>IdentityToken</code> was introduced to allow transfering identity information within the limited constraints of an URL.<br />
58   * <br />
59   * Currently Internet Explorer 6/7/8/9 sets the limit for the URL representation to < 2000 characters.<br />
60   * Any part of an URL beyound 2000 characters will be cut off.<br />
61   * <br />
62   * All operations related to constructing, wrappring, etc. of the <code>IdentityToken</code> should be done through the <code>IDWSHFactory</code>.
63   * 
64   * @author ads
65   * @since 2.1
66   */
67  public class IdentityToken extends AbstractDOMInfoExtractor {
68  
69      private static final String UTF8 = "UTF-8";
70      private static final int BOM = 65279;
71  
72      private static Schema schema;
73  
74      /**
75       * Constructor for the <code>IdentityToken</code> class.
76       * 
77       * @param dom
78       *            DOM representation of the <code>IdentityToken</code> object.
79       */
80      /* pp */IdentityToken(Element dom) {
81          this.dom = dom;
82          validateSchema(dom);
83      }
84  
85      /**
86       * Constructor for the <code>IdentityToken</code> class.
87       * 
88       * @param urlString
89       *            URL <code>String</code> representation of the <code>IdentityToken</code>.
90       * @param federation
91       *            The federation used to check trust for the <code>IdentityToken</code>.
92       * @throws ModelException
93       *             Thrown if the conversion from the URL <code>String</code> to an <code>IdentityToken</code> object fails.
94       */
95      /* pp */IdentityToken(String urlString, Federation federation) throws ModelException {
96          try {
97  
98              byte[] unbased = Base64.decodeBase64(urlString);
99              byte[] uncompressed = decompress(unbased);
100             String string = new String(uncompressed, UTF8);
101 
102             // Strip eventual ByteOrderMark character
103             if (string.charAt(0) == BOM) {
104                 string = string.substring(1);
105             }
106 
107             Document doc = XmlUtil.readXml(new Properties(), string, false);
108             validateSchema(doc);
109             this.dom = (Element) doc.getFirstChild();
110             validateSignature(federation);
111             validateTimestamp();
112         } catch (UnsupportedEncodingException e) {
113             throw new RuntimeException("Invalid encoding", e);
114         }
115 
116     }
117 
118     /**
119      * Extract the <code>ID</code> attribute from the <code>saml:Assertion</code> value from the DOM.<br />
120      * 
121      * <pre>
122      *   &lt;saml:Assertion ... &gt; ID="..." ... &lt;/saml:Assertion&gt;
123      * </pre>
124      * 
125      * @return The value of the <code>saml:AudienceRestriction</code> tag.
126      */
127     public String getID() {
128         return getTag(SAMLTags.assertion).getAttribute(IDValues.ID);
129     }
130 
131     /**
132      * Extract the <code>saml:AudienceRestriction</code> value from the DOM.<br />
133      * 
134      * <pre>
135      *   &lt;saml:Conditions ... &gt;
136      *      &lt;saml:AudienceRestriction&gt;http://fmk-online.dk&lt;/saml:AudienceRestriction&gt;
137      *   &lt;/saml:Conditions&gt;
138      * </pre>
139      * 
140      * @return The value of the <code>saml:AudienceRestriction</code> tag.
141      */
142     public String getAudienceRestriction() {
143         Element ac = getTag(SAMLTags.assertion, SAMLTags.conditions, SAMLTags.audienceRestriction);
144         return ac.getTextContent().trim();
145     }
146 
147     /**
148      * Extract the <code>dk:healthcare:saml:attribute:UserAuthorizationCode</code> value from the DOM.<br />
149      * 
150      * <pre>
151      *  &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
152      *                     Name="dk:healthcare:saml:attribute:UserAuthorizationCode"&gt;
153      *      &lt;saml:AttributeValue xsi:type="xs:string"&gt;004PT&lt;/saml:AttributeValue&gt;
154      *  &lt;/saml:Attribute&gt;
155      * </pre>
156      * 
157      * @return The value of the <code>dk:healthcare:saml:attribute:UserAuthorizationCode</code> tag.
158      */
159     public String getAuthorizationCode() {
160         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
161         Element authorizationCodeElm = getFilteredElement(nl, "Name", "dk:healthcare:saml:attribute:UserAuthorizationCode");
162         if(authorizationCodeElm == null) {
163             return null;
164         }
165         return authorizationCodeElm.getTextContent().trim();
166     }
167 
168     /**
169      * Extract the <code>urn:oid:2.5.4.3</code>/CommonName value from the DOM.<br />
170      * 
171      * <pre>
172      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="urn:oid:2.5.4.3"
173      *       FriendlyName="CommonName"&gt;
174      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;Jens Sundbye Poulsen&lt;/saml:AttributeValue&gt;
175      *     &lt;/saml:Attribute&gt;
176      * </pre>
177      * 
178      * @return The value of the <code>urn:oid:2.5.4.3</code>/CommonName tag.
179      */
180     public String getCommonName() {
181         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
182         Element commonNameElm = getFilteredElement(nl, "Name", "urn:oid:2.5.4.3");
183         if(commonNameElm == null) {
184             throw new ModelException("Mandatory 'commonName' SAML attribute (urn:oid:2.5.4.3) is missing");
185         }
186         return commonNameElm.getTextContent().trim();
187     }
188 
189     /**
190      * Extract the <code>dk:gov:saml:attribute:CprNumberIdentifier</code> value from the DOM.<br />
191      * 
192      * <pre>
193      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
194      *       Name="dk:gov:saml:attribute:CprNumberIdentifier"&gt;
195      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;2702681273&lt;/saml:AttributeValue&gt;
196      *     &lt;/saml:Attribute&gt;
197      * </pre>
198      * 
199      * @return The value of the <code>dk:gov:saml:attribute:CprNumberIdentifier</code> tag.
200      */
201     public String getCpr() {
202         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
203         Element cprNoElm = getFilteredElement(nl, "Name", "dk:gov:saml:attribute:CprNumberIdentifier");
204         if(cprNoElm == null) {
205             return null;
206         }
207         return cprNoElm.getTextContent().trim();
208     }
209 
210     /**
211      * Extract the <code>dk:gov:saml:attribute:CvrNumberIdentifier</code> value from the DOM.<br />
212      * 
213      * <pre>
214      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
215      *         Name="dk:gov:saml:attribute:CvrNumberIdentifier"&gt;
216      *         &lt;saml:AttributeValue xsi:type="xs:string"&gt;20688092&lt;/saml:AttributeValue&gt;
217      *       &lt;/saml:Attribute&gt;
218      * </pre>
219      * 
220      * @return The value of the <code>dk:gov:saml:attribute:CvrNumberIdentifier</code> tag.
221      */
222     public String getCvrNumberIdentifier() {
223         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
224         Element cvrNumberElmElm = getFilteredElement(nl, "Name", "dk:gov:saml:attribute:CvrNumberIdentifier");
225         if(cvrNumberElmElm == null) {
226             return null;
227         }
228         return cvrNumberElmElm.getTextContent().trim();
229     }
230 
231     /**
232      * Retrieve the underlying DOM model of the <code>IdentityToken</code>.<br />
233      * <b>Warning</b>: The returned DOM model is a clone of the underlying model,<br />
234      * and hence all modifications done to the DOM model will <u>not</u> reflect back to the <code>IdentityToken</code>.
235      * 
236      * @return <code>Document</code> containing the DOM model.
237      */
238     public Document getDOM() {
239         Document doc = XmlUtil.createEmptyDocument();
240         Node tmp = doc.importNode(dom, true);
241         doc.appendChild(tmp);
242         return doc;
243     }
244 
245     /**
246      * Extract the <code>urn:oid:0.9.2342.19200300.100.1.3</code>/email value from the DOM.<br />
247      * 
248      * <pre>
249      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
250      *       Name="urn:oid:0.9.2342.19200300.100.1.3" FriendlyName="email"&gt;
251      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;jens@email.dk&lt;/saml:AttributeValue&gt;
252      *     &lt;/saml:Attribute&gt;
253      * </pre>
254      * 
255      * @return The value of the <code>urn:oid:0.9.2342.19200300.100.1.3</code>/email tag.
256      */
257     public String getEmail() {
258         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
259         Element emailElm = getFilteredElement(nl, "Name", "urn:oid:0.9.2342.19200300.100.1.3");
260         if(emailElm == null) {
261             throw new ModelException("Mandatory 'email' SAML attribute (urn:oid:0.9.2342.19200300.100.1.3) is missing");
262         }
263         return emailElm.getTextContent().trim();
264     }
265 
266     /**
267      * Extract the issuer part of the message.<br />
268      * 
269      * <pre>
270      *  &lt;saml:Assertion ... &gt;
271      *   &lt;saml:Issuer&gt;http://pan.certifikat.dk/sts/services/SecurityTokenService&lt;/saml:Issuer&gt;
272      *   ...
273      *  &lt;/saml:Assertion ... &gt;
274      * </pre>
275      * 
276      * @return The <code>IdentityTokenBuilder</code> instance.
277      */
278     public String getIssuer() {
279         Element ac = getTag(SAMLTags.assertion, SAMLTags.issuer);
280         return ac.getTextContent().trim();
281     }
282 
283     /**
284      * Extract the <code>saml:Conditions#NotBefore</code> value from the DOM.<br />
285      * 
286      * <pre>
287      *   &lt;saml:Conditions NotBefore="2011-07-23T15:32:12Z" ... &gt;
288      *      ...
289      *   &lt;/saml:Conditions&gt;
290      * </pre>
291      * 
292      * @return The value of the <code>saml:Conditions#NotBefore</code> tag.
293      */
294     public Date getNotBefore() throws ModelException {
295         Element ac = getTag(SAMLTags.assertion, SAMLTags.conditions);
296         return convertToDate(ac, "NotBefore");
297     }
298 
299     /**
300      * Extract the <code>saml:Conditions#NotOnOrAfter</code> value from the DOM.<br />
301      * 
302      * <pre>
303      *   &lt;saml:Conditions ... NotOnOrAfter="2011-07-23T15:37:12Z" &gt;
304      *      ...
305      *   &lt;/saml:Conditions&gt;
306      * </pre>
307      * 
308      * @return The value of the <code>saml:Conditions#NotOnOrAfter</code> tag.
309      */
310     public Date getNotOnOrAfter() {
311         Element ac = getTag(SAMLTags.assertion, SAMLTags.conditions);
312         return convertToDate(ac, "NotOnOrAfter");
313     }
314 
315     /**
316      * Extract the <code>urn:oid:2.5.4.10</code>/organizationName value from the DOM.<br />
317      * 
318      * <pre>
319      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="urn:oid:2.5.4.10"
320      *       FriendlyName="organizationName"&gt;
321      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;Lægehuset på bakken&lt;/saml:AttributeValue&gt;
322      *     &lt;/saml:Attribute&gt;
323      * </pre>
324      * 
325      * @return The value of the <code>urn:oid:2.5.4.10</code>/organizationName tag.
326      */
327     public String getOrganizationName() {
328         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
329         Element organizationNameElm = getFilteredElement(nl, "Name", "urn:oid:2.5.4.10");
330         if(organizationNameElm == null) {
331             return null;
332         }
333         return organizationNameElm.getTextContent().trim();
334     }
335 
336     /**
337      * Extract the <code>urn:oid:2.5.4.4</code>/surName value from the DOM.<br />
338      * 
339      * <pre>
340      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="urn:oid:2.5.4.4"
341      *       FriendlyName="surName"&gt;
342      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;Poulsen&lt;/saml:AttributeValue&gt;
343      *     &lt;/saml:Attribute&gt;
344      * </pre>
345      * 
346      * @return The value of the <code>urn:oid:2.5.4.4</code>/surName tag.
347      */
348     public String getSurName() {
349         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
350         Element surNameElm = getFilteredElement(nl, "Name", "urn:oid:2.5.4.4");
351         if(surNameElm == null) {
352             throw new ModelException("Mandatory 'surName' SAML attribute (urn:oid:2.5.4.4) is missing");
353         }
354         return surNameElm.getTextContent().trim();
355     }
356 
357     /**
358      * Extract the <code>dk:gov:saml:attribute:AssuranceLevel</code> value from the DOM.<br />
359      *
360      * <pre>
361      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:AssuranceLevel"&gt;
362      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;3&lt;/saml:AttributeValue&gt;
363      *     &lt;/saml:Attribute&gt;
364      * </pre>
365      *
366      * @return The value of the <code>dk:gov:saml:attribute:AssuranceLevel</code> tag.
367      */
368     public String getAssuranceLevel() {
369         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
370         Element assuranceLevelElm = getFilteredElement(nl, "Name", "dk:gov:saml:attribute:AssuranceLevel");
371         if(assuranceLevelElm == null) {
372             throw new ModelException("Mandatory 'assuranceLevel' SAML attribute (dk:gov:saml:attribute:AssuranceLevel) is missing");
373         }
374         return assuranceLevelElm.getTextContent().trim();
375     }
376 
377     /**
378      * Extract the <code>dk:gov:saml:attribute:AssuranceLevel</code> value from the DOM.<br />
379      *
380      * <pre>
381      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:SpecVer"&gt;
382      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;DK-SAML-2.0&lt;/saml:AttributeValue&gt;
383      *     &lt;/saml:Attribute&gt;
384      * </pre>
385      *
386      * @return The value of the <code>dk:gov:saml:attribute:SpecVer</code> tag.
387      */
388     public String getSpecVersion() {
389         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
390         Element specVersionElm = getFilteredElement(nl, "Name", "dk:gov:saml:attribute:SpecVer");
391         if(specVersionElm == null) {
392             throw new ModelException("Mandatory 'specVersion' SAML attribute (dk:gov:saml:attribute:SpecVer) is missing");
393         }
394         return specVersionElm.getTextContent().trim();
395     }
396 
397     /**
398      * Extract the <code>dk:gov:saml:attribute:ITSystemName</code> value from the DOM.<br />
399      *
400      * <pre>
401      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:ITSystemName"&gt;
402      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;Harmoni/EMS&lt;/saml:AttributeValue&gt;
403      *     &lt;/saml:Attribute&gt;
404      * </pre>
405      *
406      * @return The value of the <code>dk:gov:saml:attribute:ITSystemName</code> tag.
407      */
408     public String getITSystemName() {
409         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
410         Element itSystemElm = getFilteredElement(nl, "Name", "dk:healthcare:saml:attribute:ITSystemName");
411         if(itSystemElm == null) {
412             return null;
413         }
414         return itSystemElm.getTextContent().trim();
415     }
416 
417     /**
418      * Extract the <code>dk:gov:saml:attribute:UserEducationCode</code> value from the DOM.<br />
419      *
420      * <pre>
421      *     &lt;saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:UserEducationCode"&gt;
422      *       &lt;saml:AttributeValue xsi:type="xs:string"&gt;7170;/saml:AttributeValue&gt;
423      *     &lt;/saml:Attribute&gt;
424      * </pre>
425      *
426      * @return The value of the <code>dk:gov:saml:attribute:UserEducationCode</code> tag.
427      */
428     public String getUserEducationCode() {
429         List<Element> nl = getTags(SAMLTags.assertion, SAMLTags.attributeStatement, SAMLTags.attribute);
430         Element userEducationCodeElm = getFilteredElement(nl, "Name", "dk:healthcare:saml:attribute:UserEducationCode");
431         if(userEducationCodeElm == null) {
432             return null;
433         }
434         return userEducationCodeElm.getTextContent().trim();
435     }
436 
437     /**
438      * Extract the <code>AuthnInstant</code> value from the DOM, that is the time the user originally authenticated herself to the STS.<br />
439      *
440      * <pre>
441      *       &lt;saml:AuthnStatement AuthnInstant="2011-07-23T11:42:52Z"&gt;
442      *          &lt;saml:AuthnContext&gt;
443      *              &lt;saml:AuthnContextClassRef&gt;urn:oasis:names:tc:SAML:2.0:ac:classes:X509&lt;/saml:AuthnContextClassRef&gt;
444      *          &lt;/saml:AuthnContext&gt;
445      *       &lt;/saml:AuthnStatement&gt;
446      *
447      * @return The value of the <code>AuthnInstant</code> attribute.
448      */
449     public Date getUserAuthenticationInstant() {
450         final Element auth = getTag(SAMLTags.assertion, SAMLTags.authnStatement);
451         return convertToDate(auth, "AuthnInstant");
452     }
453 
454     /**
455      * Creates an <code>IdentityTokenURLBuilder</code> used for converting the <code>IdentityToken</code> into a URL.<br />
456      * See {@link IdentityTokenURLBuilder} for more usage information.
457      * 
458      * @return <code>IdentityTokenURLBuilder</code> instance used for constructing the final URL <code>String</code>.
459      * @throws ModelException
460      *             Thrown if the conversion fails.
461      */
462     public IdentityTokenURLBuilder createURLBuilder() throws ModelException {
463         return new IdentityTokenURLBuilder(XmlUtil.node2String(dom));
464     }
465 
466     /**
467      * Invoke this method to verify the validity of the <code>IdentityToken</code> against the {@link #getNotBefore()} and {@link #getNotOnOrAfter()} values.<br />
468      * 
469      * @throws ModelException
470      *             Thrown if the <code>IdentityToken</code> is invalid.
471      *
472      * @deprecated Use {@link @validateTimestamp()} instead
473      */
474     @Deprecated
475     public void validate() throws ModelException {
476         validateTimestamp();
477     }
478 
479     /**
480      * Invoke this method to verify the validity of the <code>IdentityToken</code> against the {@link #getNotBefore()} and {@link #getNotOnOrAfter()} values.<br />
481      *
482      * @throws ModelException
483      *             Thrown if the <code>IdentityToken</code> is invalid.
484      */
485     public void validateTimestamp() throws ModelException {
486         Date now = new Date();
487 
488         if(now.before(getNotBefore())) {
489             throw new ModelException("IdentityToken not valid yet - now: " + now + " IdentityToken validity start: " + getNotBefore());
490         }
491         if(!now.before(getNotOnOrAfter())) {
492             throw new ModelException("IdentityToken no longer valid - now: " + now + " IdentityToken validity end: " + getNotOnOrAfter());
493         }
494     }
495 
496     /**
497      * Checks the signature on the Identity Token.
498      * 
499      * An invocation of this method possibly involves retrieving the certificate used to sign the token from OCES operators LDAP
500      * 
501      * @param federation
502      *            The federation used to check trust for the <code>IdentityToken</code>.
503      */
504     public void validateSignature(Federation federation) {
505         final Node elmSignature = dom.getElementsByTagNameNS(NameSpaces.DSIG_SCHEMA, DSTags.SIGNATURE).item(0);
506         if(!SignatureUtil.validate(elmSignature, federation, null, true)) {
507             throw new ModelException("Signature on IdentityToken is invalid");
508         }
509     }
510 
511     private byte[] decompress(byte[] data) {
512         byte[] buffer = new byte[1024];
513         try {
514             ByteArrayOutputStream bos = new ByteArrayOutputStream();
515             BufferedInputStream bis = new BufferedInputStream(new GZIPInputStream(new ByteArrayInputStream(data)));
516 
517             int bytesRead;
518             while ((bytesRead = bis.read(buffer)) != -1) {
519                 bos.write(buffer, 0, bytesRead);
520             }
521 
522             return bos.toByteArray();
523         } catch (IOException e) {
524             throw new ModelException("Error during deflating", e);
525         }
526     }
527 
528     private static synchronized Schema getSchema() throws SAXException {
529         if (schema == null) {
530             schema = SchemaUtil.loadSchema("/idwsh/idt/saml.xsd");
531         }
532         return schema;
533     }
534 
535     private void validateSchema(Node node) {
536         try {
537             final Validator validator = getSchema().newValidator();
538             validator.validate(new DOMSource(node));
539         } catch (SAXException e) {
540             throw new ModelBuildException("Error validating IdentityToken", e);
541         } catch (IOException e) {
542             throw new ModelBuildException("Error validating IdentityToken", e);
543         }
544     }
545 
546     /**
547      * Helper class used for converting the <code>IdentityToken</code> into a <code>String</code> valid for usage in an URL.<br />
548      * The <code>IdentityToken</code> is first deflated using the <code>java.util.zip.Deflater</code> (see {@link Deflater}).<br />
549      * After deflation, the result is converted into a URL safe BASE64 encoded <code>String</code>.
550      */
551     public class IdentityTokenURLBuilder {
552 
553         private String xml;
554 
555         /* pp */IdentityTokenURLBuilder(String xml) {
556             this.xml = xml;
557         }
558 
559         /**
560          * Converts the <code>IdentityToken</code> to a <code>String</code> valid for usage in an URL.
561          * 
562          * @return The encoded <code>IdentityToken</code>.
563          */
564         public String encode() {
565             byte[] zipped = compress(xml);
566             return Base64.encodeBase64URLSafeString(zipped);
567         }
568 
569         private byte[] compress(String data) {
570             try {
571                 ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length());
572 
573                 BufferedOutputStream out = new BufferedOutputStream(new GZIPOutputStream(bos));
574                 out.write(data.getBytes(UTF8));
575                 out.close();
576 
577                 return bos.toByteArray();
578             } catch (IOException e) {
579                 throw new ModelException("Error during deflating", e);
580             }
581         }
582     }
583 }