def invoke_action_rpc(self, root: InstanceNode, rpc: RpcInfo) -> JsonNodeT: ii = self.parse_ii(rpc.path, rpc.path_format) node_ii = ii[0:-1] n = root.goto(node_ii) # Evaluate NACM if self.nacm and not rpc.skip_nacm_check: nrpc = self.nacm.get_user_rules(rpc.username) if nrpc.check_data_node_permission( root, node_ii, Permission.NACM_ACCESS_EXEC) == Action.DENY: raise NacmForbiddenError( "Invocation of \"{}\" operation denied for user \"{}\"". format(rpc.op_name, rpc.username)) ii_an = ii[-1] node_sn = n.schema_node sn = node_sn.get_child(ii_an.name, ii_an.namespace) action_handler = self.handlers.action.get_handler(id(sn)) if action_handler is None: raise NoHandlerForOpError(rpc.path) # Get operation input schema sn_input = sn.get_child("input") # Input arguments are expected, this will validate them op_input_args = sn_input.from_raw( rpc.op_input_args) if sn_input.children else None try: ret_data = action_handler(ii, op_input_args, rpc.username) except Exception as e: raise OpHandlerFailedError(epretty(e)) return ret_data
def update_node_rpc(self, root: InstanceNode, rpc: RpcInfo, value: Any) -> InstanceNode: ii = self.parse_ii(rpc.path, rpc.path_format) n = root.goto(ii) # Deny any changes of NACM data for non-privileged users 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 modify NACM data") else: # Replacing root node # Check if NACM data are present in the datastore nacm_val = n.value.get("ietf-netconf-acm:nacm") if (nacm_val is not None) and (rpc.username not in CONFIG_NACM["ALLOWED_USERS"]): raise NacmForbiddenError(rpc.username + " not allowed to modify NACM data") # Evaluate NACM if self.nacm: nrpc = self.nacm.get_user_rules(rpc.username) if nrpc.check_data_node_permission(root, ii, Permission.NACM_ACCESS_UPDATE) == Action.DENY: raise NacmForbiddenError() new_n = n.update(value, raw=True) return new_n.top()
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, ContainerNodeHandlerBase): state_handler_val = sdh.generate_node(ii_gen, rpc.username, staging) elif isinstance(sdh, ListNodeHandlerBase): 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
def _prune_data_tree(self, node: InstanceNode, root: InstanceNode, ii: InstanceRoute, access: Permission) -> InstanceNode: if isinstance(node.value, ObjectValue): # print("obj: {}".format(node.value)) nsel = MemberName(name="", ns=None) mii = ii + [nsel] for child_key in node.value.keys(): key_splitted = child_key.split(":", maxsplit=1) if len(key_splitted) > 1: nsel.namespace, nsel.name = key_splitted else: nsel.namespace, nsel.name = (None, key_splitted[0]) m = nsel.goto_step(node) # debug_nacm("checking mii {}".format(mii)) if self.check_data_node_permission(root, mii, access) == Action.DENY: # debug_nacm("Pruning node {} {}".format(id(node.value[child_key]), node.value[child_key])) debug_nacm("Pruning node {}".format( DataHelpers.ii2str(mii))) node = node.delete_item(child_key) else: node = self._prune_data_tree(m, root, mii, access).up() elif isinstance(node.value, ArrayValue): # print("array: {}".format(node.value)) nsel = EntryIndex(0) eii = ii + [nsel] i = 0 arr_len = len(node.value) while i < arr_len: nsel.index = i e = nsel.goto_step(node) # debug_nacm("checking eii {}".format(eii)) if self.check_data_node_permission(root, eii, access) == Action.DENY: # debug_nacm("Pruning node {} {}".format(id(node.value[i]), node.value[i])) debug_nacm("Pruning node {}".format( DataHelpers.ii2str(eii))) node = node.delete_item(i) arr_len -= 1 else: i += 1 node = self._prune_data_tree(e, root, eii, access).up() return node
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): if depth > max_depth: node.value = ArrayValue([]) else: for i in range(len(node.value)): e = node[i] node = _tree_limit_depth(e, depth + 1).up() return node
def delete_node_rpc(self, root: InstanceNode, rpc: RpcInfo) -> Tuple[InstanceNode, bool]: ii = self.parse_ii(rpc.path, rpc.path_format) n = root.goto(ii) # Deny any changes of NACM data for non-privileged users nacm_changed = False if (len(ii) > 0) and (isinstance(ii[0], MemberName)): # Not getting root ns_first = ii[0].namespace if ns_first == "ietf-netconf-acm": nacm_changed = True if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: raise NacmForbiddenError( rpc.username + " not allowed to modify NACM data") else: # Deleting root node # Check if NACM data are present in the datastore nacm_val = n.value.get("ietf-netconf-acm:nacm") if nacm_val is not None: nacm_changed = True if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: raise NacmForbiddenError( rpc.username + " not allowed to modify NACM data") # Evaluate NACM 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_DELETE) == Action.DENY: raise NacmForbiddenError() if len(ii) == 0: # Deleting entire datastore new_n = RootNode(ObjectValue({}), root.schema_node, datetime.now()) else: n_parent = n.up() last_isel = ii[-1] if isinstance(n_parent.value, ArrayValue): if isinstance(last_isel, EntryIndex): new_n = n_parent.delete_item(last_isel.index) elif isinstance(last_isel, EntryKeys): new_n = n_parent.delete_item(n.index) else: raise ValueError("Unknown node selector") elif isinstance(n_parent.value, ObjectValue): new_n = n_parent.delete_item( last_isel.namespace + ":" + last_isel.name if last_isel.namespace else last_isel.name) else: raise InstanceValueError(rpc.path, "Invalid target node type") return new_n.top(), nacm_changed
def update_node_rpc(self, root: InstanceNode, rpc: RpcInfo, value: Any) -> Tuple[InstanceNode, bool]: ii = self.parse_ii(rpc.path, rpc.path_format) # Get target member name input_member_keys = tuple(value.keys()) if len(input_member_keys) != 1: raise ValueError( "Received json object must contain exactly one member") input_member_name_fq = input_member_keys[0] try: input_member_ns, input_member_name = input_member_name_fq.split( ":", maxsplit=1) except ValueError: raise ValueError( "Input object name must me in fully-qualified format") input_member_value = value[input_member_name_fq] n = root.goto(ii) # Deny any changes of NACM data for non-privileged users nacm_changed = False if (len(ii) > 0) and (isinstance(ii[0], MemberName)): # Not getting root ns_first = ii[0].namespace if ns_first == "ietf-netconf-acm": nacm_changed = True if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: raise NacmForbiddenError( rpc.username + " not allowed to modify NACM data") else: # Replacing root node # Check if NACM data are present in the datastore nacm_val = n.value.get("ietf-netconf-acm:nacm") if nacm_val is not None: nacm_changed = True if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: raise NacmForbiddenError( rpc.username + " not allowed to modify NACM data") # Evaluate NACM 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_UPDATE) == Action.DENY: raise NacmForbiddenError() new_n = n.update(input_member_value, raw=True) new_n.validate(ValidationScope.syntax) return new_n.top(), nacm_changed
def create_node_rpc(self, root: InstanceNode, rpc: RpcInfo, value: Any) -> Tuple[InstanceNode, bool]: ii = self.parse_ii(rpc.path, rpc.path_format) # Get target member name input_member_keys = tuple(value.keys()) if len(input_member_keys) != 1: raise ValueError( "Received json object must contain exactly one member") input_member_name_fq = input_member_keys[0] try: input_member_ns, input_member_name = input_member_name_fq.split( ":", maxsplit=1) except ValueError: raise ValueError( "Input object name must me in fully-qualified format") input_member_value = value[input_member_name_fq] # Deny any changes of NACM data for non-privileged users nacm_changed = False if (len(ii) > 0) and (isinstance(ii[0], MemberName)): # Not getting root ns_first = ii[0].namespace if ns_first == "ietf-netconf-acm": nacm_changed = True if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: raise NacmForbiddenError( rpc.username + " not allowed to modify NACM data") else: # Editing root node if input_member_ns == "ietf-netconf-acm": nacm_changed = True if rpc.username not in CONFIG_NACM["ALLOWED_USERS"]: raise NacmForbiddenError( rpc.username + " not allowed to modify NACM data") # Evaluate NACM 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_CREATE) == Action.DENY: raise NacmForbiddenError() n = root.goto(ii) # Get target schema node sn = n.schema_node # type: InternalNode member_sn = sn.get_child(input_member_name, input_member_ns) if member_sn is None: raise ValueError("Received json object contains unknown member") # Check if target member already exists if sn.ns == member_sn.ns: try: existing_member = n[input_member_name] except NonexistentInstance: existing_member = None else: try: existing_member = n[input_member_name_fq] except NonexistentInstance: existing_member = None # Get query parameters insert = rpc.qs.get("insert", [None])[0] point = rpc.qs.get("point", [None])[0] if isinstance(member_sn, ListNode): # Append received node to list # Create list if necessary if existing_member is None: new_member_name = input_member_name if n.namespace == input_member_ns else input_member_name_fq existing_member = n.put_member(new_member_name, ArrayValue([])) # Get ListNode key names list_node_keys = member_sn.keys # Key names in the form [(key, ns), ] if insert == "first": # Optimization if len(existing_member.value) > 0: list_entry_first = existing_member[0] # type: ArrayEntry new_member = list_entry_first.insert_before( input_member_value, raw=True).up() else: new_member = existing_member.update([input_member_value], raw=True) elif (insert == "last") or (insert is None): # Optimization if len(existing_member.value) > 0: list_entry_last = existing_member[-1] # type: ArrayEntry new_member = list_entry_last.insert_after( input_member_value, raw=True).up() else: new_member = existing_member.update([input_member_value], raw=True) elif (insert == "before") and (point is not None): point_keys_val = point.split( "," ) # List key values passed in the "point" query argument if len(list_node_keys) != len(point_keys_val): raise ValueError( "Invalid number of keys passed in 'point' query: {} ({} expected)" .format(len(point_keys_val), len(list_node_keys))) entry_keys = dict( map(lambda i: (list_node_keys[i], point_keys_val[i]), range(len(list_node_keys)))) entry_sel = EntryKeys(entry_keys) point_list_entry = entry_sel.goto_step( existing_member) # type: ArrayEntry new_member = point_list_entry.insert_before(input_member_value, raw=True).up() elif (insert == "after") and (point is not None): point_keys_val = point.split( "," ) # List key values passed in the "point" query argument if len(list_node_keys) != len(point_keys_val): raise ValueError( "Invalid number of keys passed in 'point' query: {} ({} expected)" .format(len(point_keys_val), len(list_node_keys))) entry_keys = dict( map(lambda i: (list_node_keys[i], point_keys_val[i]), range(len(list_node_keys)))) entry_sel = EntryKeys(entry_keys) point_list_entry = entry_sel.goto_step( existing_member) # type: ArrayEntry new_member = point_list_entry.insert_after(input_member_value, raw=True).up() else: raise ValueError("Invalid 'insert'/'point' query values") elif isinstance(member_sn, LeafListNode): # Append received node to leaf list # Create leaf list if necessary if existing_member is None: new_member_name = input_member_name if n.namespace == input_member_ns else input_member_name_fq existing_member = n.put_member(new_member_name, ArrayValue([])) # Convert input data from List/Dict to ArrayValue/ObjectValue new_value_item = member_sn.entry_from_raw(input_member_value) if insert == "first": new_member = existing_member.update( ArrayValue([new_value_item] + existing_member.value)) elif (insert == "last") or (insert is None): new_member = existing_member.update( ArrayValue(existing_member.value + [new_value_item])) else: raise ValueError("Invalid 'insert' query value") else: # Create new container member if existing_member is None: # Create new node (object member) new_member_name = input_member_name if n.namespace == input_member_ns else input_member_name_fq new_member = n.put_member(new_member_name, input_member_value, raw=True) else: # Data node already exists raise InstanceAlreadyPresent( "Member \"{}\" already present in \"{}\"".format( input_member_name, ii)) return new_member.top(), nacm_changed