blob: 7f191ff169d974adcfb023b3ea928063a504a3f4 [file] [log] [blame]
Wei-Yu Chenad55cb82022-02-15 20:07:01 +08001# SPDX-FileCopyrightText: 2020 The Magma Authors.
2# SPDX-FileCopyrightText: 2022 Open Networking Foundation <support@opennetworking.org>
3#
4# SPDX-License-Identifier: BSD-3-Clause
5
Wei-Yu Chen49950b92021-11-08 19:19:18 +08006"""
Wei-Yu Chen49950b92021-11-08 19:19:18 +08007This file contains modifications of the core spyne functionality. This is done
8using child classes and function override to avoid modifying spyne code itself.
9Each function below is a modified version of the parent function. These
10modifications are required because:
111) Spyne is not fully python3-compliant
122) Not all parts of the TR-069 spec are possible through spyne APIs (e.g RPC
13 calls from server to client in HTTP responses)
143) Minor enhancements for debug-ability
15"""
16
17from lxml import etree
18from logger import EnodebdLogger as logger
19from spyne.application import Application
20from spyne.interface._base import Interface
21from spyne.protocol.soap import Soap11
22from spyne.protocol.xml import XmlDocument
23
24
25class Tr069Interface(Interface):
26 """ Modified base interface class. """
27
28 def reset_interface(self):
29 super(Tr069Interface, self).reset_interface()
30 # Replace default namespace prefix (may not strictly be
31 # required, but makes it easier to debug)
32 del self.nsmap['tns']
33 self.nsmap['cwmp'] = self.get_tns()
34 self.prefmap[self.get_tns()] = 'cwmp'
35 # To validate against the xsd:<types>, the namespace
36 # prefix is expected to be the same
37 del self.nsmap['xs']
38 self.nsmap['xsd'] = 'http://www.w3.org/2001/XMLSchema'
39 self.prefmap['http://www.w3.org/2001/XMLSchema'] = 'xsd'
40
41
42class Tr069Application(Application):
43 """ Modified spyne application. """
44
45 def __init__(
46 self, services, tns, name=None, in_protocol=None,
47 out_protocol=None, config=None,
48 ):
49 super(Tr069Application, self).__init__(
50 services, tns, name, in_protocol, out_protocol, config,
51 )
52 # Use modified interface class
53 self.interface = Tr069Interface(self)
54
55
56class Tr069Soap11(Soap11):
57 """ Modified SOAP protocol. """
58
59 def __init__(self, *args, **kwargs):
60 super(Tr069Soap11, self).__init__(*args, **kwargs)
61 # Disabling type resolution as a workaround for
62 # https://github.com/arskom/spyne/issues/567
63 self.parse_xsi_type = False
64 # Bug in spyne is cleaning up the default XSD namespace
65 # and causes validation issues on TR-069 clients
66 self.cleanup_namespaces = False
67
68 def create_in_document(self, ctx, charset=None):
69 """
70 In TR-069, the ACS (e.g Magma) is an HTTP server, but acts as a client
71 for SOAP messages. This is done by the CPE (e.g ENodeB) sending an
72 empty HTTP request, and the ACS responding with a SOAP request in the
73 HTTP response. This code replaces an empty HTTP request with a string
74 that gets decoded to a call to the 'EmptyHttp' RPC .
75 """
76
77 # Try cp437 as default to ensure that we dont get any decoding errors,
78 # since it uses 1-byte encoding and has a 'full' char map
79 if not charset:
80 charset = 'cp437'
81
82 # Convert from generator to bytes before doing comparison
83 # Re-encode to chosen charset to remove invalid characters
84 in_string = b''.join(ctx.in_string).decode(charset, 'ignore')
85 ctx.in_string = [in_string.encode(charset, 'ignore')]
86 if ctx.in_string == [b'']:
87 ctx.in_string = [
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +080088 b'<soapenv:Envelope xmlns:cwmp="urn:dslforum-org:cwmp-1-0" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">/n'
89 b' <soapenv:Body>/n'
Wei-Yu Chen49950b92021-11-08 19:19:18 +080090 b' <cwmp:EmptyHttp/>/n'
Wei-Yu Chen5cbdfbb2021-12-02 01:10:21 +080091 b' </soapenv:Body>/n'
92 b'</soapenv:Envelope>',
Wei-Yu Chen49950b92021-11-08 19:19:18 +080093 ]
94
95 super(Tr069Soap11, self).create_in_document(ctx, charset)
96
97 def decompose_incoming_envelope(self, ctx, message=XmlDocument.REQUEST):
98 """
99 For TR-069, the SOAP fault message (CPE->ACS) contains useful
100 information, and should not result in another fault response (ACS->CPE).
101 Strip the outer SOAP fault structure, so that the CWMP fault structure
102 is treated as a normal RPC call (to the 'Fault' function).
103 """
104 super(Tr069Soap11, self).decompose_incoming_envelope(ctx, message)
105
106 if ctx.in_body_doc.tag == '{%s}Fault' % self.ns_soap_env:
107 faultstring = ctx.in_body_doc.findtext('faultstring')
108 if not faultstring or 'CWMP fault' not in faultstring:
109 # Not a CWMP fault
110 return
111
112 # Strip SOAP fault structure, leaving inner CWMP fault structure
113 detail_elem = ctx.in_body_doc.find('detail')
114 if detail_elem is not None:
115 detail_children = list(detail_elem)
116 if len(detail_children):
117 if len(detail_children) > 1:
118 logger.warning(
119 "Multiple detail elements found in SOAP"
120 " fault - using first one",
121 )
122 ctx.in_body_doc = detail_children[0]
123 ctx.method_request_string = ctx.in_body_doc.tag
124 self.validate_body(ctx, message)
125
126 def get_call_handles(self, ctx):
127 """
128 Modified function to fix bug in receiving SOAP fault. In this case,
129 ctx.method_request_string is None, so 'startswith' errors out.
130 """
131 if ctx.method_request_string is None:
132 return []
133
134 return super(Tr069Soap11, self).get_call_handles(ctx)
135
136 def serialize(self, ctx, message):
137 # Workaround for issue https://github.com/magma/magma/issues/7869
138 # Updates to ctx.descriptor.out_message.Attributes.sub_name are taking
139 # effect on the descriptor. But when puled from _attrcache dictionary,
140 # it still has a stale value.
141 # Force repopulation of dictionary by deleting entry
142 # TODO Remove this code once we have a better fix
143 if (ctx.descriptor and ctx.descriptor.out_message in self._attrcache):
144 del self._attrcache[ctx.descriptor.out_message] # noqa: WPS529
145
146 super(Tr069Soap11, self).serialize(ctx, message)
147
148 # Keep XSD namespace
149 etree.cleanup_namespaces(ctx.out_document, keep_ns_prefixes=['xsd'])