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 * <saml:Assertion ... > ID="..." ... </saml:Assertion>
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 * <saml:Conditions ... >
136 * <saml:AudienceRestriction>http://fmk-online.dk</saml:AudienceRestriction>
137 * </saml:Conditions>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
152 * Name="dk:healthcare:saml:attribute:UserAuthorizationCode">
153 * <saml:AttributeValue xsi:type="xs:string">004PT</saml:AttributeValue>
154 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="urn:oid:2.5.4.3"
173 * FriendlyName="CommonName">
174 * <saml:AttributeValue xsi:type="xs:string">Jens Sundbye Poulsen</saml:AttributeValue>
175 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
194 * Name="dk:gov:saml:attribute:CprNumberIdentifier">
195 * <saml:AttributeValue xsi:type="xs:string">2702681273</saml:AttributeValue>
196 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"
215 * Name="dk:gov:saml:attribute:CvrNumberIdentifier">
216 * <saml:AttributeValue xsi:type="xs:string">20688092</saml:AttributeValue>
217 * </saml:Attribute>
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 * <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">
251 * <saml:AttributeValue xsi:type="xs:string">jens@email.dk</saml:AttributeValue>
252 * </saml:Attribute>
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 * <saml:Assertion ... >
271 * <saml:Issuer>http://pan.certifikat.dk/sts/services/SecurityTokenService</saml:Issuer>
272 * ...
273 * </saml:Assertion ... >
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 * <saml:Conditions NotBefore="2011-07-23T15:32:12Z" ... >
288 * ...
289 * </saml:Conditions>
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 * <saml:Conditions ... NotOnOrAfter="2011-07-23T15:37:12Z" >
304 * ...
305 * </saml:Conditions>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="urn:oid:2.5.4.10"
320 * FriendlyName="organizationName">
321 * <saml:AttributeValue xsi:type="xs:string">Lægehuset på bakken</saml:AttributeValue>
322 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="urn:oid:2.5.4.4"
341 * FriendlyName="surName">
342 * <saml:AttributeValue xsi:type="xs:string">Poulsen</saml:AttributeValue>
343 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:AssuranceLevel">
362 * <saml:AttributeValue xsi:type="xs:string">3</saml:AttributeValue>
363 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:SpecVer">
382 * <saml:AttributeValue xsi:type="xs:string">DK-SAML-2.0</saml:AttributeValue>
383 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:ITSystemName">
402 * <saml:AttributeValue xsi:type="xs:string">Harmoni/EMS</saml:AttributeValue>
403 * </saml:Attribute>
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 * <saml:Attribute NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic" Name="dk:gov:saml:attribute:UserEducationCode">
422 * <saml:AttributeValue xsi:type="xs:string">7170;/saml:AttributeValue>
423 * </saml:Attribute>
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 * <saml:AuthnStatement AuthnInstant="2011-07-23T11:42:52Z">
442 * <saml:AuthnContext>
443 * <saml:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:X509</saml:AuthnContextClassRef>
444 * </saml:AuthnContext>
445 * </saml:AuthnStatement>
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 }