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