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.wss4j.dom.message.token;
21  
22  import java.time.Instant;
23  import java.time.ZoneOffset;
24  import java.time.ZonedDateTime;
25  import java.time.format.DateTimeFormatter;
26  import java.time.format.DateTimeParseException;
27  import java.time.temporal.ChronoField;
28  
29  import org.apache.wss4j.common.bsp.BSPEnforcer;
30  import org.apache.wss4j.common.bsp.BSPRule;
31  import org.apache.wss4j.common.ext.WSSecurityException;
32  import org.apache.wss4j.common.util.DOM2Writer;
33  import org.apache.wss4j.common.util.DateUtil;
34  import org.apache.wss4j.common.util.WSCurrentTimeSource;
35  import org.apache.wss4j.common.util.WSTimeSource;
36  import org.apache.wss4j.common.util.XMLUtils;
37  import org.apache.wss4j.dom.WSConstants;
38  import org.w3c.dom.Document;
39  import org.w3c.dom.Element;
40  import org.w3c.dom.Node;
41  import org.w3c.dom.Text;
42  
43  /**
44   * Timestamp according to SOAP Message Security 1.0,
45   * chapter 10 / appendix A.2
46   */
47  public class Timestamp {
48  
49      private Element element;
50      private Instant created;
51      private Instant expires;
52      private String createdString;
53  
54      /**
55       * Constructs a <code>Timestamp</code> object and parses the
56       * <code>wsu:Timestamp</code> element to initialize it.
57       *
58       * @param timestampElement the <code>wsu:Timestamp</code> element that
59       *        contains the timestamp data
60       * @param bspEnforcer a BSPEnforcer instance to enforce BSP rules
61       */
62      public Timestamp(Element timestampElement, BSPEnforcer bspEnforcer) throws WSSecurityException {
63  
64          element = timestampElement;
65  
66          String strExpires = null;
67  
68          for (Node currentChild = element.getFirstChild();
69               currentChild != null;
70               currentChild = currentChild.getNextSibling()
71           ) {
72              if (Node.ELEMENT_NODE == currentChild.getNodeType()) {
73                  Element currentChildElement = (Element) currentChild;
74                  if (WSConstants.CREATED_LN.equals(currentChild.getLocalName())
75                      && WSConstants.WSU_NS.equals(currentChild.getNamespaceURI())) {
76                      if (createdString == null) {
77                          String valueType = currentChildElement.getAttributeNS(null, "ValueType");
78                          if (valueType != null && valueType.length() != 0) {
79                              // We can't have a ValueType attribute as per the BSP spec
80                              bspEnforcer.handleBSPRule(BSPRule.R3225);
81                          }
82                          createdString = ((Text)currentChildElement.getFirstChild()).getData();
83                      } else {
84                          // Test for multiple Created elements
85                          bspEnforcer.handleBSPRule(BSPRule.R3203);
86                      }
87                  } else if (WSConstants.EXPIRES_LN.equals(currentChild.getLocalName())
88                      && WSConstants.WSU_NS.equals(currentChild.getNamespaceURI())) {
89                      if (createdString == null) {
90                          // Created must appear before Expires
91                          bspEnforcer.handleBSPRule(BSPRule.R3221);
92                      }
93                      if (strExpires != null) {
94                          // We can't have multiple Expires elements
95                          bspEnforcer.handleBSPRule(BSPRule.R3224);
96                      } else {
97                          String valueType = currentChildElement.getAttributeNS(null, "ValueType");
98                          if (valueType != null && valueType.length() != 0) {
99                              // We can't have a ValueType attribute as per the BSP spec
100                             bspEnforcer.handleBSPRule(BSPRule.R3226);
101                         }
102                         strExpires = ((Text)currentChildElement.getFirstChild()).getData();
103                     }
104                 } else {
105                     bspEnforcer.handleBSPRule(BSPRule.R3222);
106                 }
107             }
108         }
109 
110         // We must have a Created element
111         if (createdString == null) {
112             bspEnforcer.handleBSPRule(BSPRule.R3203);
113         }
114 
115         // Parse the dates
116         if (createdString != null) {
117             try {
118                 ZonedDateTime createdDateTime = ZonedDateTime.parse(createdString);
119                 if (!ZoneOffset.UTC.equals(createdDateTime.getZone())) {
120                     bspEnforcer.handleBSPRule(BSPRule.R3217);
121                 }
122 
123                 created = createdDateTime.toInstant();
124             } catch (DateTimeParseException e) {
125                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
126             }
127 
128             if (created.getNano() > 0) {
129                 int milliseconds = created.get(ChronoField.MILLI_OF_SECOND);
130                 if (milliseconds * 1000000 != created.getNano()) {
131                     bspEnforcer.handleBSPRule(BSPRule.R3220);
132                 }
133             }
134         }
135 
136         if (strExpires != null) {
137             try {
138                 ZonedDateTime expiresDateTime = ZonedDateTime.parse(strExpires);
139                 if (!ZoneOffset.UTC.equals(expiresDateTime.getZone())) {
140                     bspEnforcer.handleBSPRule(BSPRule.R3223);
141                 }
142 
143                 expires = expiresDateTime.toInstant();
144             } catch (DateTimeParseException e) {
145                 throw new WSSecurityException(WSSecurityException.ErrorCode.INVALID_SECURITY, e);
146             }
147 
148             if (expires.getNano() > 0) {
149                 int milliseconds = expires.get(ChronoField.MILLI_OF_SECOND);
150                 if (milliseconds * 1000000 != expires.getNano()) {
151                     bspEnforcer.handleBSPRule(BSPRule.R3229);
152                 }
153             }
154         }
155     }
156 
157 
158     /**
159      * Constructs a <code>Timestamp</code> object according
160      * to the defined parameters.
161      *
162      * @param doc the SOAP envelope as <code>Document</code>
163      * @param ttl the time to live (validity of the security semantics) in seconds
164      */
165     public Timestamp(boolean milliseconds, Document doc, int ttl) {
166         this(milliseconds, doc, new WSCurrentTimeSource(), ttl);
167     }
168 
169     /**
170      * Constructs a <code>Timestamp</code> object according
171      * to the defined parameters.
172      *
173      * @param doc the SOAP envelope as <code>Document</code>
174      * @param ttl the time to live (validity of the security semantics) in seconds
175      */
176     public Timestamp(boolean milliseconds, Document doc, WSTimeSource timeSource, int ttl) {
177 
178         element =
179             doc.createElementNS(
180                 WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.TIMESTAMP_TOKEN_LN
181             );
182 
183         Element elementCreated =
184             doc.createElementNS(
185                 WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.CREATED_LN
186             );
187         created = timeSource.now();
188 
189         DateTimeFormatter formatter = DateUtil.getDateTimeFormatter(milliseconds);
190         elementCreated.appendChild(doc.createTextNode(created.atZone(ZoneOffset.UTC).format(formatter)));
191 
192         element.appendChild(elementCreated);
193         if (ttl != 0) {
194             expires = created.plusSeconds(ttl);
195 
196             Element elementExpires =
197                 doc.createElementNS(
198                     WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":" + WSConstants.EXPIRES_LN
199                 );
200             elementExpires.appendChild(doc.createTextNode(expires.atZone(ZoneOffset.UTC).format(formatter)));
201             element.appendChild(elementExpires);
202         }
203     }
204 
205     /**
206      * Add the WSU Namespace to this T. The namespace is not added by default for
207      * efficiency purposes.
208      */
209     public void addWSUNamespace() {
210         element.setAttributeNS(XMLUtils.XMLNS_NS, "xmlns:" + WSConstants.WSU_PREFIX, WSConstants.WSU_NS);
211     }
212 
213     /**
214      * Returns the dom element of this <code>Timestamp</code> object.
215      *
216      * @return the <code>wsse:UsernameToken</code> element
217      */
218     public Element getElement() {
219         return element;
220     }
221 
222     /**
223      * Returns the string representation of the token.
224      *
225      * @return a XML string representation
226      */
227     public String toString() {
228         return DOM2Writer.nodeToString(element);
229     }
230 
231     /**
232      * Get the time of creation.
233      *
234      * @return the "created" time
235      */
236     public Instant getCreated() {
237         return created;
238     }
239 
240     /**
241      * Get the time of creation as a String
242      *
243      * @return the time of creation as a String
244      */
245     public String getCreatedString() {
246         return createdString;
247     }
248 
249     /**
250      * Get the time of expiration.
251      *
252      * @return the "expires" time
253      */
254     public Instant getExpires() {
255         return expires;
256     }
257 
258     /**
259      * Set wsu:Id attribute of this timestamp
260      * @param id
261      */
262     public void setID(String id) {
263         element.setAttributeNS(WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":Id", id);
264     }
265 
266     /**
267      * @return the value of the wsu:Id attribute
268      */
269     public String getID() {
270         return element.getAttributeNS(WSConstants.WSU_NS, "Id");
271     }
272 
273     /**
274      * Return true if the current Timestamp is expired, meaning if the "Expires" value
275      * is before the current time. It returns false if there is no Expires value.
276      */
277     public boolean isExpired() {
278         if (expires != null) {
279             Instant rightNow = Instant.now();
280             return expires.isBefore(rightNow);
281         }
282         return false;
283     }
284 
285 
286     /**
287      * Return true if the "Created" value is before the current time minus the timeToLive
288      * argument, and if the Created value is not "in the future".
289      *
290      * @param timeToLive the value in seconds for the validity of the Created time
291      * @param futureTimeToLive the value in seconds for the future validity of the Created time
292      * @return true if the timestamp is before (now-timeToLive), false otherwise
293      */
294     public boolean verifyCreated(
295         int timeToLive,
296         int futureTimeToLive
297     ) {
298         return DateUtil.verifyCreated(created, timeToLive, futureTimeToLive);
299     }
300 
301 
302     @Override
303     public int hashCode() {
304         int result = 17;
305         if (created != null) {
306             result = 31 * result + created.hashCode();
307         }
308         if (expires != null) {
309             result = 31 * result + expires.hashCode();
310         }
311         return result;
312     }
313 
314     @Override
315     public boolean equals(Object object) {
316         if (!(object instanceof Timestamp)) {
317             return false;
318         }
319         Timestamp timestamp = (Timestamp)object;
320         if (!compare(timestamp.getCreated(), getCreated())) {
321             return false;
322         }
323         return compare(timestamp.getExpires(), getExpires());
324     }
325 
326     private boolean compare(Instant item1, Instant item2) {
327         if (item1 == null && item2 != null) {
328             return false;
329         } else if (item1 != null && !item1.equals(item2)) {
330             return false;
331         }
332         return true;
333     }
334 
335 }