Merge "BUG-2511 Fix XXE vulnerability in Netconf"
[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         try {
49             BUILDER_FACTORY.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
50             BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-general-entities", false);
51             BUILDER_FACTORY.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
52             BUILDER_FACTORY.setXIncludeAware(false);
53             BUILDER_FACTORY.setExpandEntityReferences(false);
54         } catch (ParserConfigurationException e) {
55             throw new ExceptionInInitializerError(e);
56         }
57         BUILDER_FACTORY.setNamespaceAware(true);
58         BUILDER_FACTORY.setCoalescing(true);
59         BUILDER_FACTORY.setIgnoringElementContentWhitespace(true);
60         BUILDER_FACTORY.setIgnoringComments(true);
61     }
62
63     public enum ErrorType {
64         transport, rpc, protocol, application;
65
66         public String getTagValue() {
67             return name();
68         }
69
70         public static ErrorType from( String text ) {
71             try {
72                 return valueOf( text );
73             }
74             catch( Exception e ) {
75                 return application;
76             }
77         }
78     }
79
80     public enum ErrorTag {
81         access_denied("access-denied"),
82         bad_attribute("bad-attribute"),
83         bad_element("bad-element"),
84         data_exists("data-exists"),
85         data_missing("data-missing"),
86         in_use("in-use"),
87         invalid_value("invalid-value"),
88         lock_denied("lock-denied"),
89         malformed_message("malformed-message"),
90         missing_attribute("missing-attribute"),
91         missing_element("missing-element"),
92         operation_failed("operation-failed"),
93         operation_not_supported("operation-not-supported"),
94         resource_denied("resource-denied"),
95         rollback_failed("rollback-failed"),
96         too_big("too-big"),
97         unknown_attribute("unknown-attribute"),
98         unknown_element("unknown-element"),
99         unknown_namespace("unknown-namespace");
100
101         private final String tagValue;
102
103         ErrorTag(final String tagValue) {
104             this.tagValue = tagValue;
105         }
106
107         public String getTagValue() {
108             return this.tagValue;
109         }
110
111         public static ErrorTag from( String text ) {
112             for( ErrorTag e: values() )
113             {
114                 if( e.getTagValue().equals( text ) ) {
115                     return e;
116                 }
117             }
118
119             return operation_failed;
120         }
121     }
122
123     public enum ErrorSeverity {
124         error, warning;
125
126         public String getTagValue() {
127             return name();
128         }
129
130         public static ErrorSeverity from( String text ) {
131             try {
132                 return valueOf( text );
133             }
134             catch( Exception e ) {
135                 return error;
136             }
137         }
138     }
139
140     private final ErrorType errorType;
141     private final ErrorTag errorTag;
142     private final ErrorSeverity errorSeverity;
143     private final Map<String, String> errorInfo;
144
145     public NetconfDocumentedException(String message) {
146         this(message,
147                 NetconfDocumentedException.ErrorType.application,
148                 NetconfDocumentedException.ErrorTag.invalid_value,
149                 NetconfDocumentedException.ErrorSeverity.error
150         );
151     }
152
153     public NetconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
154             final ErrorSeverity errorSeverity) {
155         this(message, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
156     }
157
158     public NetconfDocumentedException(final String message, final ErrorType errorType, final ErrorTag errorTag,
159             final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
160         super(message);
161         this.errorType = errorType;
162         this.errorTag = errorTag;
163         this.errorSeverity = errorSeverity;
164         this.errorInfo = errorInfo;
165     }
166
167     public NetconfDocumentedException(final String message, final Exception cause, final ErrorType errorType,
168             final ErrorTag errorTag, final ErrorSeverity errorSeverity) {
169         this(message, cause, errorType, errorTag, errorSeverity, Collections.<String, String> emptyMap());
170     }
171
172     public NetconfDocumentedException(final String message, final Exception cause, final ErrorType errorType,
173             final ErrorTag errorTag, final ErrorSeverity errorSeverity, final Map<String, String> errorInfo) {
174         super(message, cause);
175         this.errorType = errorType;
176         this.errorTag = errorTag;
177         this.errorSeverity = errorSeverity;
178         this.errorInfo = errorInfo;
179     }
180
181     public static <E extends Exception> NetconfDocumentedException wrap(E exception) throws NetconfDocumentedException {
182         final Map<String, String> errorInfo = new HashMap<>();
183         errorInfo.put(ErrorTag.operation_failed.name(), "Exception thrown");
184         throw new NetconfDocumentedException(exception.getMessage(), exception, ErrorType.application, ErrorTag.operation_failed,
185                 ErrorSeverity.error, errorInfo);
186     }
187     public static NetconfDocumentedException wrap(ValidationException e) throws NetconfDocumentedException {
188         final Map<String, String> errorInfo = new HashMap<>();
189         errorInfo.put(ErrorTag.operation_failed.name(), "Validation failed");
190         throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed,
191                 ErrorSeverity.error, errorInfo);
192     }
193
194     public static NetconfDocumentedException wrap(ConflictingVersionException e) throws NetconfDocumentedException {
195         final Map<String, String> errorInfo = new HashMap<>();
196         errorInfo.put(ErrorTag.operation_failed.name(), "Optimistic lock failed");
197         throw new NetconfDocumentedException(e.getMessage(), e, ErrorType.application, ErrorTag.operation_failed,
198                 ErrorSeverity.error, errorInfo);
199     }
200
201     public static NetconfDocumentedException fromXMLDocument( Document fromDoc ) {
202
203         ErrorType errorType = ErrorType.application;
204         ErrorTag errorTag = ErrorTag.operation_failed;
205         ErrorSeverity errorSeverity = ErrorSeverity.error;
206         Map<String, String> errorInfo = null;
207         String errorMessage = "";
208
209         Node rpcReply = fromDoc.getDocumentElement();
210
211         // FIXME: BUG? - we only handle one rpc-error.
212
213         NodeList replyChildren = rpcReply.getChildNodes();
214         for( int i = 0; i < replyChildren.getLength(); i++ ) {
215             Node replyChild = replyChildren.item( i );
216             if( RPC_ERROR.equals( replyChild.getNodeName() ) )
217             {
218                 NodeList rpcErrorChildren = replyChild.getChildNodes();
219                 for( int j = 0; j < rpcErrorChildren.getLength(); j++ )
220                 {
221                     Node rpcErrorChild = rpcErrorChildren.item( j );
222                     if( ERROR_TYPE.equals( rpcErrorChild.getNodeName() ) ) {
223                         errorType = ErrorType.from( rpcErrorChild.getTextContent() );
224                     }
225                     else if( ERROR_TAG.equals( rpcErrorChild.getNodeName() ) ) {
226                         errorTag = ErrorTag.from( rpcErrorChild.getTextContent() );
227                     }
228                     else if( ERROR_SEVERITY.equals( rpcErrorChild.getNodeName() ) ) {
229                         errorSeverity = ErrorSeverity.from( rpcErrorChild.getTextContent() );
230                     }
231                     else if( ERROR_MESSAGE.equals( rpcErrorChild.getNodeName() ) ) {
232                         errorMessage = rpcErrorChild.getTextContent();
233                     }
234                     else if( ERROR_INFO.equals( rpcErrorChild.getNodeName() ) ) {
235                         errorInfo = parseErrorInfo( rpcErrorChild );
236                     }
237                 }
238
239                 break;
240             }
241         }
242
243         return new NetconfDocumentedException( errorMessage, errorType, errorTag, errorSeverity, errorInfo );
244     }
245
246     private static Map<String, String> parseErrorInfo( Node node ) {
247         Map<String, String> infoMap = new HashMap<>();
248         NodeList children = node.getChildNodes();
249         for( int i = 0; i < children.getLength(); i++ ) {
250             Node child = children.item( i );
251             if( child.getNodeType() == Node.ELEMENT_NODE ) {
252                 infoMap.put( child.getNodeName(), child.getTextContent() );
253             }
254         }
255
256         return infoMap;
257     }
258
259     public ErrorType getErrorType() {
260         return this.errorType;
261     }
262
263     public ErrorTag getErrorTag() {
264         return this.errorTag;
265     }
266
267     public ErrorSeverity getErrorSeverity() {
268         return this.errorSeverity;
269     }
270
271     public Map<String, String> getErrorInfo() {
272         return this.errorInfo;
273     }
274
275     public Document toXMLDocument() {
276         Document doc = null;
277         try {
278             doc = BUILDER_FACTORY.newDocumentBuilder().newDocument();
279
280             Node rpcReply = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_REPLY_KEY );
281             doc.appendChild( rpcReply );
282
283             Node rpcError = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, RPC_ERROR );
284             rpcReply.appendChild( rpcError );
285
286             rpcError.appendChild( createTextNode( doc, ERROR_TYPE, getErrorType().getTagValue() ) );
287             rpcError.appendChild( createTextNode( doc, ERROR_TAG, getErrorTag().getTagValue() ) );
288             rpcError.appendChild( createTextNode( doc, ERROR_SEVERITY, getErrorSeverity().getTagValue() ) );
289             rpcError.appendChild( createTextNode( doc, ERROR_MESSAGE, getLocalizedMessage() ) );
290
291             Map<String, String> errorInfoMap = getErrorInfo();
292             if( errorInfoMap != null && !errorInfoMap.isEmpty() ) {
293                 /*
294                  * <error-info>
295                  *   <bad-attribute>message-id</bad-attribute>
296                  *   <bad-element>rpc</bad-element>
297                  * </error-info>
298                  */
299
300                 Node errorInfoNode = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, ERROR_INFO );
301                 errorInfoNode.setPrefix( rpcReply.getPrefix() );
302                 rpcError.appendChild( errorInfoNode );
303
304                 for ( Entry<String, String> entry : errorInfoMap.entrySet() ) {
305                     errorInfoNode.appendChild( createTextNode( doc, entry.getKey(), entry.getValue() ) );
306                 }
307             }
308         }
309         catch( ParserConfigurationException e ) {
310             LOG.error( "Error outputting to XML document", e ); // this shouldn't happen
311         }
312
313         return doc;
314     }
315
316     private Node createTextNode( Document doc, String tag, String textContent ) {
317         Node node = doc.createElementNS( URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, tag );
318         node.setTextContent( textContent );
319         return node;
320     }
321
322     @Override
323     public String toString() {
324         return "NetconfDocumentedException{" + "message=" + getMessage() + ", errorType=" + this.errorType
325                 + ", errorTag=" + this.errorTag + ", errorSeverity=" + this.errorSeverity + ", errorInfo="
326                 + this.errorInfo + '}';
327     }
328 }