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.message.token;
21  
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSSecurityException;
24  import org.apache.ws.security.util.DOM2Writer;
25  import org.apache.ws.security.util.WSSecurityUtil;
26  import org.apache.ws.security.util.XmlSchemaDateFormat;
27  import org.w3c.dom.Document;
28  import org.w3c.dom.Element;
29  import org.w3c.dom.Node;
30  import org.w3c.dom.Text;
31  
32  import java.text.ParseException;
33  import java.text.SimpleDateFormat;
34  import java.text.DateFormat;
35  import java.util.ArrayList;
36  import java.util.Date;
37  import java.util.List;
38  import java.util.TimeZone;
39  
40  /**
41   * Timestamp according to SOAP Message Security 1.0,
42   * chapter 10 / appendix A.2
43   *
44   * @author Christof Soehngen (christof.soehngen@syracom.de)
45   */
46  public class Timestamp {
47      
48      private static final org.apache.commons.logging.Log LOG = 
49          org.apache.commons.logging.LogFactory.getLog(Timestamp.class);
50  
51      protected Element element = null;
52      protected List<Element> customElements = null;
53      protected Date createdDate;
54      protected Date expiresDate;
55      
56      /**
57       * Constructs a <code>Timestamp</code> object and parses the
58       * <code>wsu:Timestamp</code> element to initialize it.
59       *
60       * @param timestampElement the <code>wsu:Timestamp</code> element that
61       *        contains the timestamp data
62       */
63      public Timestamp(Element timestampElement) throws WSSecurityException {
64          this(timestampElement, true);
65      }
66      
67      /**
68       * Constructs a <code>Timestamp</code> object and parses the
69       * <code>wsu:Timestamp</code> element to initialize it.
70       *
71       * @param timestampElement the <code>wsu:Timestamp</code> element that
72       *        contains the timestamp data
73       * @param bspCompliant whether the Timestamp processing complies with the BSP spec
74       */
75      public Timestamp(Element timestampElement, boolean bspCompliant) throws WSSecurityException {
76  
77          element = timestampElement;
78          customElements = new ArrayList<Element>();
79  
80          String strCreated = null;
81          String strExpires = null;
82  
83          for (Node currentChild = element.getFirstChild();
84               currentChild != null;
85               currentChild = currentChild.getNextSibling()
86           ) {
87              if (Node.ELEMENT_NODE == currentChild.getNodeType()) {
88                  Element currentChildElement = (Element) currentChild;
89                  if (WSConstants.CREATED_LN.equals(currentChild.getLocalName()) &&
90                          WSConstants.WSU_NS.equals(currentChild.getNamespaceURI())) {
91                      if (strCreated == null) {
92                          String valueType = currentChildElement.getAttribute("ValueType");
93                          if (bspCompliant && valueType != null && !"".equals(valueType)) {
94                              // We can't have a ValueType attribute as per the BSP spec
95                              throw new WSSecurityException(
96                                  WSSecurityException.INVALID_SECURITY, "invalidTimestamp"
97                              );
98                          }
99                          strCreated = ((Text)currentChildElement.getFirstChild()).getData();
100                     } else {
101                         // Test for multiple Created elements
102                         throw new WSSecurityException(
103                             WSSecurityException.INVALID_SECURITY, "invalidTimestamp"
104                         );
105                     }
106                 } else if (WSConstants.EXPIRES_LN.equals(currentChild.getLocalName()) &&
107                         WSConstants.WSU_NS.equals(currentChild.getNamespaceURI())) {
108                     if (strExpires != null || (bspCompliant && strCreated == null)) {
109                         //
110                         // Created must appear before Expires and we can't have multiple Expires 
111                         // elements
112                         //
113                         throw new WSSecurityException(
114                             WSSecurityException.INVALID_SECURITY, "invalidTimestamp"
115                         ); 
116                     } else {
117                         String valueType = currentChildElement.getAttribute("ValueType");
118                         if (bspCompliant && valueType != null && !"".equals(valueType)) {
119                             // We can't have a ValueType attribute as per the BSP spec
120                             throw new WSSecurityException(
121                                 WSSecurityException.INVALID_SECURITY, "invalidTimestamp"
122                             );
123                         }
124                         strExpires = ((Text)currentChildElement.getFirstChild()).getData();
125                     }
126                 } else {
127                     if (bspCompliant) {
128                         throw new WSSecurityException(
129                             WSSecurityException.INVALID_SECURITY, "invalidTimestamp"
130                         );
131                     }
132                     customElements.add(currentChildElement);
133                 }
134             }
135         }
136         
137         // We must have a Created element
138         if (bspCompliant && strCreated == null) {
139             throw new WSSecurityException(
140                 WSSecurityException.INVALID_SECURITY, "invalidTimestamp"
141             );  
142         }
143 
144         // Parse the dates
145         DateFormat zulu = new XmlSchemaDateFormat();
146         if (bspCompliant) {
147             zulu.setLenient(false);
148         }
149         try {
150             if (LOG.isDebugEnabled()) {
151                 LOG.debug("Current time: " + zulu.format(new Date()));
152             }
153             if (strCreated != null) {
154                 createdDate = zulu.parse(strCreated);
155                 if (LOG.isDebugEnabled()) {
156                     LOG.debug("Timestamp created: " + zulu.format(createdDate));
157                 }
158             }
159             if (strExpires != null) {
160                 expiresDate = zulu.parse(strExpires);
161                 if (LOG.isDebugEnabled()) {
162                     LOG.debug("Timestamp expires: " + zulu.format(expiresDate));
163                 }
164             }
165         } catch (ParseException e) {
166             throw new WSSecurityException(
167                 WSSecurityException.INVALID_SECURITY, "invalidTimestamp", null, e
168             );
169         }
170     }
171 
172 
173     /**
174      * Constructs a <code>Timestamp</code> object according
175      * to the defined parameters.
176      *
177      * @param doc the SOAP envelope as <code>Document</code>
178      * @param ttl the time to live (validity of the security semantics) in seconds
179      */
180     public Timestamp(boolean milliseconds, Document doc, int ttl) {
181 
182         customElements = new ArrayList<Element>();
183         element = 
184             doc.createElementNS(
185                 WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.TIMESTAMP_TOKEN_LN
186             );
187 
188         DateFormat zulu = null;
189         if (milliseconds) {
190             zulu = new XmlSchemaDateFormat();
191         } else {
192             zulu = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
193             zulu.setTimeZone(TimeZone.getTimeZone("UTC"));
194         }
195         Element elementCreated =
196             doc.createElementNS(
197                 WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.CREATED_LN
198             );
199         createdDate = new Date();
200         elementCreated.appendChild(doc.createTextNode(zulu.format(createdDate)));
201         element.appendChild(elementCreated);
202         if (ttl != 0) {
203             expiresDate = new Date();
204             expiresDate.setTime(createdDate.getTime() + ((long)ttl * 1000L));
205 
206             Element elementExpires =
207                 doc.createElementNS(
208                     WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.EXPIRES_LN
209                 );
210             elementExpires.appendChild(doc.createTextNode(zulu.format(expiresDate)));
211             element.appendChild(elementExpires);
212         }
213     }
214     
215     /**
216      * Add the WSU Namespace to this T. The namespace is not added by default for
217      * efficiency purposes.
218      */
219     public void addWSUNamespace() {
220         WSSecurityUtil.setNamespace(element, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
221     }
222 
223     /**
224      * Returns the dom element of this <code>Timestamp</code> object.
225      *
226      * @return the <code>wsse:UsernameToken</code> element
227      */
228     public Element getElement() {
229         return element;
230     }
231 
232     /**
233      * Returns the string representation of the token.
234      *
235      * @return a XML string representation
236      */
237     public String toString() {
238         return DOM2Writer.nodeToString((Node) element);
239     }
240 
241     /**
242      * Get the time of creation.
243      *
244      * @return the "created" time
245      */
246     public Date getCreated() {
247         return createdDate;
248     }
249 
250     /**
251      * Get the time of expiration.
252      *
253      * @return the "expires" time
254      */
255     public Date getExpires() {
256         return expiresDate;
257     }
258 
259     /**
260      * Creates and adds a custom element to this Timestamp
261      */
262     public void addCustomElement(Document doc, Element customElement) {
263         customElements.add(customElement);
264         element.appendChild(customElement);
265     }
266 
267     /**
268      * Get the the custom elements from this Timestamp
269      *
270      * @return the list containing the custom elements.
271      */
272     public List<Element> getCustomElements() {
273         return customElements;
274     }
275     
276     /**
277      * Set wsu:Id attribute of this timestamp
278      * @param id
279      */
280     public void setID(String id) {
281         element.setAttributeNS(WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":Id", id);
282     }
283     
284     /**
285      * @return the value of the wsu:Id attribute
286      */
287     public String getID() {
288         return element.getAttributeNS(WSConstants.WSU_NS, "Id");
289     }
290     
291     /**
292      * Return true if the current Timestamp is expired, meaning if the "Expires" value
293      * is before the current time. It returns false if there is no Expires value.
294      */
295     public boolean isExpired() {
296         if (expiresDate != null) {
297             Date rightNow = new Date();
298             return expiresDate.before(rightNow);
299         }
300         return false;
301     }
302     
303     
304     /**
305      * Return true if the "Created" value is before the current time minus the timeToLive
306      * argument, and if the Created value is not "in the future".
307      * 
308      * @param timeToLive the value in seconds for the validity of the Created time
309      * @param futureTimeToLive the value in seconds for the future validity of the Created time
310      * @return true if the timestamp is before (now-timeToLive), false otherwise
311      */
312     public boolean verifyCreated(
313         int timeToLive,
314         int futureTimeToLive
315     ) {
316         Date validCreation = new Date();
317         long currentTime = validCreation.getTime();
318         if (futureTimeToLive > 0) {
319             validCreation.setTime(currentTime + ((long)futureTimeToLive * 1000L));
320         }
321         // Check to see if the created time is in the future
322         if (createdDate != null && createdDate.after(validCreation)) {
323             if (LOG.isDebugEnabled()) {
324                 LOG.debug("Validation of Timestamp: The message was created in the future!");
325             }
326             return false;
327         }
328         
329         // Calculate the time that is allowed for the message to travel
330         currentTime -= ((long)timeToLive * 1000L);
331         validCreation.setTime(currentTime);
332 
333         // Validate the time it took the message to travel
334         if (createdDate != null && createdDate.before(validCreation)) {
335             if (LOG.isDebugEnabled()) {
336                 LOG.debug("Validation of Timestamp: The message was created too long ago");
337             }
338             return false;
339         }
340 
341         if (LOG.isDebugEnabled()) {
342             LOG.debug("Validation of Timestamp: Everything is ok");
343         }
344         return true;
345     }
346 
347     
348     @Override
349     public int hashCode() {
350         int result = 17;
351         if (createdDate != null) {
352             result = 31 * result + createdDate.hashCode();
353         }
354         if (expiresDate != null) {
355             result = 31 * result + expiresDate.hashCode();
356         }
357         return result;
358     }
359     
360     @Override
361     public boolean equals(Object object) {
362         if (!(object instanceof Timestamp)) {
363             return false;
364         }
365         Timestamp timestamp = (Timestamp)object;
366         if (!compare(timestamp.getCreated(), getCreated())) {
367             return false;
368         }
369         if (!compare(timestamp.getExpires(), getExpires())) {
370             return false;
371         }
372         return true;
373     }
374     
375     private boolean compare(Date item1, Date item2) {
376         if (item1 == null && item2 != null) { 
377             return false;
378         } else if (item1 != null && !item1.equals(item2)) {
379             return false;
380         }
381         return true;
382     }
383     
384 }