BUG-2511 Fix possible XXE vulnerability in restconf
[controller.git] / opendaylight / netconf / netconf-api / src / main / java / org / opendaylight / controller / netconf / api / NetconfDocumentedException.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8
9 package org.opendaylight.controller.netconf.api;
10
11 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.ERROR_INFO;
12 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.ERROR_MESSAGE;
13 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.ERROR_SEVERITY;
14 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.ERROR_TAG;
15 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.ERROR_TYPE;
16 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_ERROR;
17 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.RPC_REPLY_KEY;
18 import static org.opendaylight.controller.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
19
20 import java.util.Collections;
21 import java.util.HashMap;
22 import java.util.Map;
23 import java.util.Map.Entry;
24 import javax.xml.parsers.DocumentBuilderFactory;
25 import javax.xml.parsers.ParserConfigurationException;
26 import org.opendaylight.controller.config.api.ConflictingVersionException;
27 import org.opendaylight.controller.config.api.ValidationException;
28 import org.slf4j.Logger;
29 import org.slf4j.LoggerFactory;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Node;
32 import org.w3c.dom.NodeList;
33
34 /**
35  * Checked exception to communicate an error that needs to be sent to the
36  * netconf client.
37  */
38 public class NetconfDocumentedException extends Exception {
39
40     private static final long serialVersionUID = 1L;
41
42     private final static Logger LOG = LoggerFactory.getLogger( NetconfDocumentedException.class );
43
44     private static final DocumentBuilderFactory BUILDER_FACTORY;
45
46     static {
47         BUILDER_FACTORY = DocumentBuilderFactory.newInstance();
48         BUILDER_FACTORY.setNamespaceAware(true);
49         BUILDER_FACTORY.setCoalescing(true);
50         BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
51         BUILDER_FACTORY.setIgnoringComments(true);
52     }
53
54     public enum ErrorType {
55         transport, rpc, protocol, application;
56
57         public String getTagValue() {
58             return name();
59         }
60
61         public static ErrorType from( String text ) {
62             try {
63                 return valueOf( text );
64             }
65             catch( Exception e ) {
66                 return application;
67             }
68         }
69     }
70
71     public enum ErrorTag {
72         access_denied("access-denied"),
73         bad_attribute("bad-attribute"),
74         bad_element("bad-element"),
75         data_exists("data-exists"),
76         data_missing("data-missing"),
77         in_use("in-use"),
78         invalid_value("invalid-value"),
79         lock_denied("lock-denied"),
80         malformed_message("malformed-message"),
81         missing_attribute("missing-attribute"),
82         missing_element("missing-element"),
83         operation_failed("operation-failed"),
84         operation_not_supported("operation-not-supported"),
85         resource_denied("resource-denied"),
86         rollback_failed("rollback-failed"),
87         too_big("too-big"),
88         unknown_attribute("unknown-attribute"),
89         unknown_element("unknown-element"),
90         unknown_namespace("unknown-namespace");
91
92         private final String tagValue;
93
94         ErrorTag(final String tagValue) {
95             this.tagValue = tagValue;
96         }
97
98         public String getTagValue() {
99             return this.tagValue;
100         }
101
102         public static ErrorTag from( String text ) {
103             for( ErrorTag e: values() )
104             {
105                 if( e.getTagValue().equals( text ) ) {
106                     return e;
107                 }
108             }
109
110             return operation_failed;
111         }
112     }
113
114     public enum ErrorSeverity {
115         error, warning;
116
117         public String getTagValue() {
118             return name();
119         }
120
121         public static ErrorSeverity from( String text ) {
122             try {
123                 return valueOf( text );
124             }
125             catch( Exception e ) {
126                 return error;
127             }
128         }
129     }
130
131     private final ErrorType errorType;
132     private final ErrorTag errorTag;
133     private final ErrorSeverity errorSeverity;
134     private final Map<String, String> errorInfo;
135
136     public NetconfDocumentedException(String message) {
137         this(message,
138                 NetconfDocumentedException.ErrorType.application,
139                 NetconfDocumentedException.ErrorTag.invalid_value,
140                 NetconfDocumentedException.ErrorSeverity.error
141         );
142     }
143
144     public NetconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
145             final ErrorSeverity errorSeverity) {
146         this(message, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
147     }
148
149     public NetconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
150             final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
151         super(message);
152         this.errorType = errorType;
153         this.errorTag = errorTag;
154         this.errorSeverity = errorSeverity;
155         this.errorInfo = errorInfo;
156     }
157
158     public NetconfDocumentedException(final String message, final Exception cause, final ErrorType errorType,
159             final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
160         this(message, cause, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
161     }
162
163     public NetconfDocumentedException(final String message, final Exception cause, final ErrorType errorType,
164             final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
165         super(message, cause);
166         this.errorType = errorType;
167         this.errorTag = errorTag;
168         this.errorSeverity = errorSeverity;
169         this.errorInfo = errorInfo;
170     }
171
172     public static <E extends Exception> NetconfDocumentedException wrap(E exception) throws NetconfDocumentedException {
173         final Map<String, String> errorInfo = new HashMap<>();
174         errorInfo.put(ErrorTag.operation_failed.name(), "Exception thrown");
175         throw new NetconfDocumentedException(exception.getMessage(), exception, ErrorType.application, ErrorTag.operation_failed,
176                 ErrorSeverity.error, errorInfo);
177     }
178     public static NetconfDocumentedException wrap(ValidationException e) throws NetconfDocumentedException {
179         final Map<String, String> errorInfo = new HashMap<>();
180         errorInfo.put(ErrorTag.operation_failed.name(), "Validation failed");
181         throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed,
182                 ErrorSeverity.error, errorInfo);
183     }
184
185     public static NetconfDocumentedException wrap(ConflictingVersionException e) throws NetconfDocumentedException {
186         final Map<String, String> errorInfo = new HashMap<>();
187         errorInfo.put(ErrorTag.operation_failed.name(), "Optimistic lock failed");
188         throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed,
189                 ErrorSeverity.error, errorInfo);
190     }
191
192     public static NetconfDocumentedException fromXMLDocument( Document fromDoc ) {
193
194         ErrorType errorType = ErrorType.application;
195         ErrorTag errorTag = ErrorTag.operation_failed;
196         ErrorSeverity errorSeverity = ErrorSeverity.error;
197         Map<String, String> errorInfo = null;
198         String errorMessage = "";
199
200         Node rpcReply = fromDoc.getDocumentElement();
201
202         // FIXME: BUG? - we only handle one rpc-error.
203
204         NodeList replyChildren = rpcReply.getChildNodes();
205         for( int i = 0; i < replyChildren.getLength(); i++ ) {
206             Node replyChild = replyChildren.item( i );
207             if( RPC_ERROR.equals( replyChild.getNodeName() ) )
208             {
209                 NodeList rpcErrorChildren = replyChild.getChildNodes();
210                 for( int j = 0; j < rpcErrorChildren.getLength(); j++ )
211                 {
212                     Node rpcErrorChild = rpcErrorChildren.item( j );
213                     if( ERROR_TYPE.equals( rpcErrorChild.getNodeName() ) ) {
214                         errorType = ErrorType.from( rpcErrorChild.getTextContent() );
215                     }
216                     else if( ERROR_TAG.equals( rpcErrorChild.getNodeName() ) ) {
217                         errorTag = ErrorTag.from( rpcErrorChild.getTextContent() );
218                     }
219                     else if( ERROR_SEVERITY.equals( rpcErrorChild.getNodeName() ) ) {
220                         errorSeverity = ErrorSeverity.from( rpcErrorChild.getTextContent() );
221                     }
222                     else if( ERROR_MESSAGE.equals( rpcErrorChild.getNodeName() ) ) {
223                         errorMessage = rpcErrorChild.getTextContent();
224                     }
225                     else if( ERROR_INFO.equals( rpcErrorChild.getNodeName() ) ) {
226                         errorInfo = parseErrorInfo( rpcErrorChild );
227                     }
228                 }
229
230                 break;
231             }
232         }
233
234         return new NetconfDocumentedException( errorMessage, errorType, errorTag, errorSeverity, errorInfo );
235     }
236
237     private static Map<String, String> parseErrorInfo( Node node ) {
238         Map<String, String> infoMap = new HashMap<>();
239         NodeList children = node.getChildNodes();
240         for( int i = 0; i < children.getLength(); i++ ) {
241             Node child = children.item( i );
242             if( child.getNodeType() == Node.ELEMENT_NODE ) {
243                 infoMap.put( child.getNodeName(), child.getTextContent() );
244             }
245         }
246
247         return infoMap;
248     }
249
250     public ErrorType getErrorType() {
251         return this.errorType;
252     }
253
254     public ErrorTag getErrorTag() {
255         return this.errorTag;
256     }
257
258     public ErrorSeverity getErrorSeverity() {
259         return this.errorSeverity;
260     }
261
262     public Map<String, String> getErrorInfo() {
263         return this.errorInfo;
264     }
265
266     public Document toXMLDocument() {
267         Document doc = null;
268         try {
269             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
270
271             Node rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY );
272             doc.appendChild( rpcReply );
273
274             Node rpcError = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR );
275             rpcReply.appendChild( rpcError );
276
277             rpcError.appendChild( createTextNode( doc, ERROR_TYPE, getErrorType().getTagValue() ) );
278             rpcError.appendChild( createTextNode( doc, ERROR_TAG, getErrorTag().getTagValue() ) );
279             rpcError.appendChild( createTextNode( doc, ERROR_SEVERITY, getErrorSeverity().getTagValue() ) );
280             rpcError.appendChild( createTextNode( doc, ERROR_MESSAGE, getLocalizedMessage() ) );
281
282             Map<String, String> errorInfoMap = getErrorInfo();
283             if( errorInfoMap != null && !errorInfoMap.isEmpty() ) {
284                 /*
285                  * <error-info>
286                  *   <bad-attribute>message-id</bad-attribute>
287                  *   <bad-element>rpc</bad-element>
288                  * </error-info>
289                  */
290
291                 Node errorInfoNode = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO );
292                 errorInfoNode.setPrefix( rpcReply.getPrefix() );
293                 rpcError.appendChild( errorInfoNode );
294
295                 for ( Entry<String, String> entry : errorInfoMap.entrySet() ) {
296                     errorInfoNode.appendChild( createTextNode( doc, entry.getKey(), entry.getValue() ) );
297                 }
298             }
299         }
300         catch( ParserConfigurationException e ) {
301             LOG.error( "Error outputting to XML document", e ); // this shouldn't happen
302         }
303
304         return doc;
305     }
306
307     private Node createTextNode( Document doc, String tag, String textContent ) {
308         Node node = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag );
309         node.setTextContent( textContent );
310         return node;
311     }
312
313     @Override
314     public String toString() {
315         return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
316                 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
317                 + this.errorInfo + '}';
318     }
319 }