def get_file(self, headers: OrderedDict, data: Optional[str], client_cert: SSLCertT) -> HttpResponse: # Ordinary file on filesystem username = CertHelpers.get_field(client_cert, "emailAddress") url_path = headers[":path"].split("?")[0] url_path_safe = "".join( filter(lambda c: c.isalpha() or c in "/-_.", url_path)).replace("..", "").strip("/") file_path = os.path.join(config.CFG.http["DOC_ROOT"], url_path_safe) if os.path.isdir(file_path): file_path = os.path.join(file_path, config.CFG.http["DOC_DEFAULT_NAME"]) ctype = mimetypes.guess_type( file_path)[0] or "application/octet-stream" try: fd = open(file_path, 'rb') response = fd.read() fd.close() except FileNotFoundError: warn("[{}] Cannot open requested file \"{}\"".format( username, file_path)) http_resp = HttpResponse.empty(HttpStatus.NotFound) else: info("[{}] Serving ordinary file {} of type \"{}\"".format( username, file_path, ctype)) http_resp = HttpResponse(HttpStatus.Ok, response, ctype) return http_resp
def zone_unset(self, input_args: JsonNodeT, username: str) -> JsonNodeT: try: usr_op_journal = self.op_journal[username] except KeyError: warn("zone_set: Op transaction not started") return usr_op_journal.append(KnotOp(KnotZoneCmd.UNSET, input_args))
def zone_commit_transaction(self, input_args: JsonNodeT, username: str) -> JsonNodeT: try: usr_op_journal = self.op_journal[username] except KeyError: warn("zone_commit_transaction: Nothing to commit") return # TODO del self.op_journal[username]
def run_conf_edit_handler(self, ii: InstanceRoute, ch: DataChange): try: sch_pth_list = list(filter(lambda n: isinstance(n, MemberName), ii)) if ch.change_type == ChangeType.CREATE: # Get target member name input_member_name_fq = tuple(ch.input_data.keys())[0] input_member_name_ns, input_member_name = input_member_name_fq.split( ":", maxsplit=1) # Append it to ii sch_pth_list.append(MemberName(input_member_name, None)) sch_pth = DataHelpers.ii2str(sch_pth_list) sn = self.get_schema_node(sch_pth) if sn is None: return h = CONF_DATA_HANDLES.get_handler(str(id(sn))) if h is not None: info("handler for actual data node triggered") if isinstance(h, ConfDataObjectHandler): if ch.change_type == ChangeType.CREATE: h.create(ii, ch) elif ch.change_type == ChangeType.REPLACE: h.replace(ii, ch) elif ch.change_type == ChangeType.DELETE: h.delete(ii, ch) if isinstance(h, ConfDataListHandler): if ch.change_type == ChangeType.CREATE: h.create_item(ii, ch) elif ch.change_type == ChangeType.REPLACE: h.replace_item(ii, ch) elif ch.change_type == ChangeType.DELETE: h.delete_item(ii, ch) else: sn = sn.parent while sn is not None: h = CONF_DATA_HANDLES.get_handler(str(id(sn))) if h is not None and isinstance(h, ConfDataObjectHandler): info( "handler for superior data node triggered, replace" ) # print(h.schema_path) # print(h.__class__.__name__) h.replace(ii, ch) if h is not None and isinstance(h, ConfDataListHandler): info( "handler for superior data node triggered, replace_item" ) h.replace_item(ii, ch) sn = sn.parent except NonexistentInstance: warn("Cannnot notify {}, parent container removed".format(ii))
def zone_commit_transaction(self, input_args: JsonNodeT, username: str) -> JsonNodeT: try: usr_op_journal = self.op_journal[username] except KeyError: warn("zone_commit_transaction: Nothing to commit") return # Connect to Knot socket and start zone transaction so.KNOT.knot_connect() so.KNOT.begin_zone() for knot_op in usr_op_journal: input_args = knot_op.op_input domain = input_args["dns-zone-rpcs:zone"] if knot_op.cmd == KnotZoneCmd.SET: rr_type = input_args["dns-zone-rpcs:type"][0] if rr_type == "SOA": rrdata = input_args["dns-zone-rpcs:SOA"] rr = SOARecord() rr.ttl = input_args["dns-zone-rpcs:ttl"] rr.mname = rrdata["mname"] rr.rname = rrdata["rname"] rr.serial = rrdata["serial"] rr.refresh = rrdata["refresh"] rr.retry = rrdata["retry"] rr.expire = rrdata["expire"] rr.minimum = rrdata["minimum"] so.KNOT.zone_add_record(domain, rr) elif rr_type == "A": rrdata = input_args["dns-zone-rpcs:A"] rr = ARecord(input_args["dns-zone-rpcs:owner"], input_args["dns-zone-rpcs:ttl"]) rr.address = rrdata["address"] so.KNOT.zone_add_record(domain, rr) elif rr_type == "AAAA": rrdata = input_args["dns-zone-rpcs:AAAA"] rr = AAAARecord(input_args["dns-zone-rpcs:owner"], input_args["dns-zone-rpcs:ttl"]) rr.address = rrdata["address"] so.KNOT.zone_add_record(domain, rr) elif rr_type == "MX": rrdata = input_args["dns-zone-rpcs:MX"] rr = MXRecord(input_args["dns-zone-rpcs:owner"], input_args["dns-zone-rpcs:ttl"]) rr.preference = rrdata["preference"] rr.exchange = rrdata["exchange"] so.KNOT.zone_add_record(domain, rr) elif rr_type == "CNAME": rrdata = input_args["dns-zone-rpcs:CNAME"] rr = CNAMERecord(input_args["dns-zone-rpcs:owner"], input_args["dns-zone-rpcs:ttl"]) rr.cname = rrdata["cname"] so.KNOT.zone_add_record(domain, rr) elif knot_op.cmd == KnotZoneCmd.UNSET: input_args = knot_op.op_input domain = input_args["dns-zone-rpcs:zone"] owner = input_args.get("dns-zone-rpcs:owner") try: rr_type = input_args["dns-zone-rpcs:type"][0] rrdata = input_args["dns-zone-rpcs:" + rr_type] except KeyError: rr_type = None rrdata = None if rrdata: if rr_type == "A": rr = ARecord(input_args["dns-zone-rpcs:owner"], 0) rr.address = rrdata["address"] elif rr_type == "AAAA": rr = AAAARecord(input_args["dns-zone-rpcs:owner"], 0) rr.address = rrdata["address"] elif rr_type == "MX": rr = MXRecord(input_args["dns-zone-rpcs:owner"], 0) rr.preference = rrdata["preference"] rr.exchange = rrdata["exchange"] elif rr_type == "CNAME": rr = CNAMERecord(input_args["dns-zone-rpcs:owner"], 0) rr.cname = rrdata["cname"] else: rr = None selector = rr.rrdata_format() if rr is not None else None else: selector = None so.KNOT.zone_del_record(domain, owner, rr_type, selector) so.KNOT.commit() so.KNOT.knot_disconnect() del self.op_journal[username]
def zone_abort_transaction(self, input_args: JsonNodeT, username: str) -> JsonNodeT: try: del self.op_journal[username] except KeyError: warn("zone_abort_transaction: Nothing to abort")
def data_received(self, data: bytes): events = self.conn.receive_data(data) for event in events: if isinstance(event, RequestReceived): # Store request headers headers = OrderedDict(event.headers) request_data = RequestData(headers, BytesIO()) self.stream_data[event.stream_id] = request_data elif isinstance(event, DataReceived): # Store incoming data try: stream_data = self.stream_data[event.stream_id] except KeyError: self.conn.reset_stream(event.stream_id, error_code=H2ErrorCodes.PROTOCOL_ERROR) else: # Check if incoming data are not excessively large if (stream_data.data.tell() + len(event.data)) < (config.CFG.http["UPLOAD_SIZE_LIMIT"] * 1048576): stream_data.data.write(event.data) self.conn.acknowledge_received_data(len(event.data), event.stream_id) else: stream_data.data_overflow = True self.conn.reset_stream(event.stream_id, error_code=H2ErrorCodes.ENHANCE_YOUR_CALM) elif isinstance(event, StreamEnded): # Process request try: request_data = self.stream_data.pop(event.stream_id) except KeyError: self.send_response( HttpResponse.error(HttpStatus.BadRequest, RestconfErrType.Transport, ERRTAG_MALFORMED), event.stream_id ) else: if request_data.data_overflow: self.send_response( HttpResponse.error(HttpStatus.ReqTooLarge, RestconfErrType.Transport, ERRTAG_REQLARGE), event.stream_id ) else: headers = request_data.headers http_method = headers[":method"] if http_method in ("GET", "DELETE", "OPTIONS", "HEAD"): self.run_request_handler(headers, event.stream_id, None) elif http_method in ("PUT", "POST"): body = request_data.data.getvalue().decode("utf-8") self.run_request_handler(headers, event.stream_id, body) else: warn("Unknown http method \"{}\"".format(headers[":method"])) self.send_response( HttpResponse.error( HttpStatus.MethodNotAllowed, RestconfErrType.Transport, ERRTAG_OPNOTSUPPORTED ), event.stream_id ) elif isinstance(event, RemoteSettingsChanged): changed_settings = {} for s in event.changed_settings.items(): changed_settings[s[0]] = s[1].new_value self.conn.update_settings(changed_settings) elif isinstance(event, WindowUpdated): try: debug_srv("str {} nw={}".format(event.stream_id, self.conn.local_flow_control_window(event.stream_id))) self.send_response_continue(event.stream_id) except (ProtocolError, KeyError) as e: debug_srv("wupexception strid={}: {}".format(event.stream_id, str(e))) elif isinstance(event, ConnectionTerminated): self.transport.close() dts = self.conn.data_to_send() if dts: self.transport.write(dts)
def get_node_rpc(self, rpc: RpcInfo, staging=False) -> InstanceNode: ii = self.parse_ii(rpc.path, rpc.path_format) if staging: try: root = self.get_data_root_staging(rpc.username) except StagingDataException: # root = self._data info("Starting transaction for user \"{}\"".format( rpc.username)) self.make_user_journal(rpc.username, None) root = self.get_data_root_staging(rpc.username) else: root = self._data yl_data_request = False if (len(ii) > 0) and (isinstance(ii[0], MemberName)): # Not getting root ns_first = ii[0].namespace if (ns_first == "ietf-netconf-acm") and ( rpc.username not in CONFIG_NACM["ALLOWED_USERS"]): raise NacmForbiddenError(rpc.username + " not allowed to access NACM data") elif ns_first == "ietf-yang-library": root = self._yang_lib_data yl_data_request = True else: # Root node requested # Remove NACM data if user is not NACM privieged if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: try: root = root.delete_item("ietf-netconf-acm:nacm") except NonexistentInstance: pass # Append YANG library data for member_name, member_val in self._yang_lib_data.value.items(): root = root.put_member(member_name, member_val).top() # Resolve schema node of the desired data node sch_pth_list = filter(lambda isel: isinstance(isel, MemberName), ii) sch_pth = DataHelpers.ii2str(sch_pth_list) sn = self.get_schema_node(sch_pth) state_roots = sn.state_roots() # Check if URL points to state data or node that contains state data if state_roots and not yl_data_request: debug_data("State roots: {}".format(state_roots)) n = None for state_root_sch_pth in state_roots: state_root_sn = self._dm.get_data_node(state_root_sch_pth) # Check if the desired node is child of the state root sni = sn is_child = False while sni: if sni is state_root_sn: is_child = True break sni = sni.parent if is_child: # Direct request for the state data sdh = STATE_DATA_HANDLES.get_handler(state_root_sch_pth) if sdh is not None: if isinstance(sdh, StateDataContainerHandler): state_handler_val = sdh.generate_node( ii, rpc.username, staging) state_root_n = sdh.schema_node.orphan_instance( state_handler_val) elif isinstance(sdh, StateDataListHandler): if (sn is sdh.schema_node) and isinstance( ii[-1], MemberName): state_handler_val = sdh.generate_list( ii, rpc.username, staging) state_root_n = sdh.schema_node.orphan_instance( state_handler_val) else: state_handler_val = sdh.generate_item( ii, rpc.username, staging) state_root_n = sdh.schema_node.orphan_entry( state_handler_val) # Select desired subnode from handler-generated content ii_prefix, ii_rel = sdh.schema_node.split_instance_route( ii) n = state_root_n.goto(ii_rel) # There should be only one state root, no need to continue if len(state_roots) != 1: warn( "URI points to directly to state data, but more state roots found" ) break else: raise NoHandlerForStateDataError(rpc.path) else: # Request for config data containing state data n = root.goto(ii) def _fill_state_roots(node: InstanceNode) -> InstanceNode: if isinstance(node.value, ObjectValue): if node.schema_node is state_root_sn.parent: ii_gen = DataHelpers.node_get_ii(node) sdh = STATE_DATA_HANDLES.get_handler( state_root_sch_pth) if sdh is not None: try: if isinstance( sdh, StateDataContainerHandler): state_handler_val = sdh.generate_node( ii_gen, rpc.username, staging) elif isinstance( sdh, StateDataListHandler): state_handler_val = sdh.generate_list( ii_gen, rpc.username, staging) except Exception as e: error( "Error occured in state data generator (sn: {})" .format(state_root_sch_pth)) error(epretty(e)) error( "This state node will be omitted.") else: if state_root_sn.ns == state_root_sn.parent.ns: nm_name = state_root_sn.qual_name[ 0] else: nm_name = state_root_sn.qual_name[ 1] + ":" + state_root_sn.qual_name[ 0] # print("nm={}".format(nm_name)) node = node.put_member( nm_name, state_handler_val, raw=True).up() else: for key in node: member = node[key] node = _fill_state_roots(member).up() elif isinstance(node.value, ArrayValue): i = 0 arr_len = len(node.value) while i < arr_len: node = _fill_state_roots(node[i]).up() i += 1 return node n = _fill_state_roots(n) root = n.top() else: # No state data in requested node n = root.goto(ii) # Process "with-defaults" query parameter try: with_defs = rpc.qs["with-defaults"][0] except (IndexError, KeyError): with_defs = None if with_defs == "report-all": n = n.add_defaults() # Evaluate NACM if required if self.nacm and not rpc.skip_nacm_check: nrpc = self.nacm.get_user_rules(rpc.username) if nrpc.check_data_node_permission( root, ii, Permission.NACM_ACCESS_READ) == Action.DENY: raise NacmForbiddenError() else: # Prune nodes that should not be accessible to user n = nrpc.prune_data_tree(n, root, ii, Permission.NACM_ACCESS_READ) # Process "depth" query parameter try: max_depth_str = rpc.qs["depth"][0] if max_depth_str == "unbounded": max_depth = None else: max_depth = int(max_depth_str) - 1 if (max_depth < 0) or (max_depth > 65535): raise ValueError() except (IndexError, KeyError): max_depth = None except ValueError: raise ValueError("Invalid value of query param \"depth\"") if max_depth is not None: def _tree_limit_depth(node: InstanceNode, depth: int) -> InstanceNode: if isinstance(node.value, ObjectValue): if depth > max_depth: node.value = ObjectValue({}) else: for child_key in sorted(node.value.keys()): m = node[child_key] node = _tree_limit_depth(m, depth + 1).up() elif isinstance(node.value, ArrayValue): depth -= 1 for i in range(len(node.value)): e = node[i] node = _tree_limit_depth(e, depth + 1).up() return node n = _tree_limit_depth(n, 1) # Return result return n