save progress

This commit is contained in:
Istvan Ruzman
2020-08-07 10:02:34 +02:00
parent 8a5fc3d357
commit 9d7d40806a
6 changed files with 85 additions and 23 deletions

View File

@@ -3,7 +3,7 @@
"""Implementation of a simple but extensible RADIUS Client""" """Implementation of a simple but extensible RADIUS Client"""
from typing import Optional, Union from typing import cast, Optional, Union
from ipaddress import IPv4Address, IPv6Address from ipaddress import IPv4Address, IPv6Address
import select import select
@@ -11,6 +11,7 @@ import socket
import time import time
import pyrad3.packet as P import pyrad3.packet as P
from pyrad3.dictionary import Dictionary
from pyrad3 import host from pyrad3 import host
SUPPORTED_SEND_TYPES = [ SUPPORTED_SEND_TYPES = [
@@ -41,7 +42,7 @@ class Client(host.Host):
self, self,
server: Union[str, IPv4Address, IPv6Address], server: Union[str, IPv4Address, IPv6Address],
secret: bytes, secret: bytes,
radius_dictionary: dict, radius_dictionary: Dictionary,
interface: Optional[str], interface: Optional[str],
**kwargs, **kwargs,
): ):
@@ -124,7 +125,8 @@ class Client(host.Host):
pass pass
# timed out: try the next attempt after increasing the acct delay time # timed out: try the next attempt after increasing the acct delay time
if packet.code == packet.AccountingRequest: if packet.code == P.Code.AccountingRequest:
packet = cast(P.AcctPacket, packet)
packet.increase_acct_delay_time(self.timeout) packet.increase_acct_delay_time(self.timeout)
raw_packet = packet.serialize() raw_packet = packet.serialize()

View File

@@ -9,14 +9,14 @@ Classes and Types to parse and represent a RADIUS dictionary.
from enum import IntEnum, Enum, auto from enum import IntEnum, Enum, auto
from dataclasses import dataclass from dataclasses import dataclass
from os.path import dirname, isabs, join, normpath from os.path import dirname, isabs, join, normpath
from typing import Dict, Generator, IO, List, Optional, Sequence, Tuple, Union from typing import cast, Dict, Generator, IO, List, Optional, Sequence, Tuple, Union
import logging import logging
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
INTEGER_TYPES = { INTEGER_TYPES: Dict[str, Tuple[int, int]] = {
"byte": (0, 255), "byte": (0, 255),
"short": (0, 2 ** 16 - 1), "short": (0, 2 ** 16 - 1),
"signed": (-(2 ** 31), 2 ** 31 - 1), "signed": (-(2 ** 31), 2 ** 31 - 1),
@@ -88,11 +88,11 @@ class Attribute: # pylint: disable=too-many-instance-attributes
name: str name: str
code: int code: int
datatype: Datatype datatype: Datatype
values: Dict[Union[int, str], Union[int, str]]
has_tag: bool = False has_tag: bool = False
encrypt: Encrypt = Encrypt(0) encrypt: Encrypt = Encrypt(0)
is_sub_attr: bool = False is_sub_attr: bool = False
# vendor = Dictionary # vendor = Dictionary
values: Dict[Union[int, str], Union[int, str]] = None
@dataclass @dataclass
@@ -180,7 +180,7 @@ class Dictionary:
def __init__(self, dictionary: str, __dictio: Optional[IO] = None): def __init__(self, dictionary: str, __dictio: Optional[IO] = None):
self.vendor: Dict[int, Vendor] = {} self.vendor: Dict[int, Vendor] = {}
self.vendor_lookup_id_by_name: Dict[str, int] = {} self.vendor_lookup_id_by_name: Dict[str, int] = {}
self.attrindex: Dict[Union[int, str], Attribute] = {} self.attrindex: Dict[Union[str, int, Tuple[int, ...]], Attribute] = {}
self.rfc_vendor = Vendor("RFC", 0, 1, 1, False, {}) self.rfc_vendor = Vendor("RFC", 0, 1, 1, False, {})
self.cur_vendor = self.rfc_vendor self.cur_vendor = self.rfc_vendor
if __dictio is not None: if __dictio is not None:
@@ -193,7 +193,7 @@ class Dictionary:
self, reader: Generator[Tuple[int, List[str]], None, None] self, reader: Generator[Tuple[int, List[str]], None, None]
): ):
"""Read and parse a (Free)RADIUS dictionary.""" """Read and parse a (Free)RADIUS dictionary."""
self.filestack = [] self.filestack: List[str] = []
for line_num, tokens in reader: for line_num, tokens in reader:
key = tokens[0] key = tokens[0]
if key == "ATTRIBUTE": if key == "ATTRIBUTE":
@@ -387,9 +387,9 @@ class Dictionary:
) )
has_tag, encrypt = self._parse_attribute_flags(tokens, line_num) has_tag, encrypt = self._parse_attribute_flags(tokens, line_num)
name, code, datatype = tokens[1:4] name, attr_code, datatype = tokens[1:4]
if datatype == "concat" and self.cur_vendor != self.rfc_vendor: if datatype in {"concat", "extended", "evs", "long-extended" } and self.cur_vendor != self.rfc_vendor:
raise ParseError( raise ParseError(
filename, filename,
'vendor attributes are not allowed to have the datatype "concat"', 'vendor attributes are not allowed to have the datatype "concat"',
@@ -397,10 +397,10 @@ class Dictionary:
) )
try: try:
codes = _parse_attribute_code(code) codes = _parse_attribute_code(attr_code)
except ValueError: except ValueError:
raise ParseError( raise ParseError(
filename, f'invalid attribute code {code}""', line_num filename, f'invalid attribute code {attr_code}""', line_num
) )
for code in codes: for code in codes:
@@ -434,17 +434,17 @@ class Dictionary:
name, name,
codes[-1], codes[-1],
attribute_type, attribute_type,
{},
has_tag, has_tag,
encrypt, encrypt,
len(codes) > 1, len(codes) > 1,
{},
) )
attrcode = codes[0] if len(codes) == 1 else tuple(codes) attrcode: Union[int, Tuple[int, ...]] = codes[0] if len(codes) == 1 else tuple(codes)
self.cur_vendor.attrs[attrcode] = attribute self.cur_vendor.attrs[attrcode] = attribute
if self.cur_vendor != self.rfc_vendor: if self.cur_vendor != self.rfc_vendor:
codes = tuple([26] + codes) codes = [26] + codes
attrcode = codes[0] if len(codes) == 1 else tuple(codes) attrcode = codes[0] if len(codes) == 1 else tuple(codes)
self.attrindex[attrcode] = attribute self.attrindex[attrcode] = attribute
self.attrindex[name] = attribute self.attrindex[name] = attribute
@@ -459,8 +459,8 @@ class Dictionary:
line_num, line_num,
) )
(attr_name, key, value) = tokens[1:] (attr_name, key, vvalue) = tokens[1:]
value = _parse_number(value) value = _parse_number(vvalue)
attribute = self.attrindex[attr_name] attribute = self.attrindex[attr_name]
try: try:

View File

@@ -281,6 +281,12 @@ class AcctPacket(Packet):
**attributes **attributes
) )
def increase_acct_delay_time(self, delay_time: float):
try:
self['Acct-Delay-Time'] += int(delay_time)
except KeyError:
pass
class CoAPacket(Packet): class CoAPacket(Packet):
def __init__( def __init__(

View File

@@ -4,7 +4,7 @@
"""Collections of functions to en- and decode RADIUS Attributes""" """Collections of functions to en- and decode RADIUS Attributes"""
from typing import Union from typing import Union
from ipaddress import IPv4Address, IPv6Address, IPv6Network, ip_network, ip_address from ipaddress import IPv4Address, IPv4Network, IPv6Address, IPv6Network, ip_network, ip_address
import struct import struct
@@ -30,10 +30,16 @@ def encode_address(addr: Union[str, IPv4Address]) -> bytes:
return IPv4Address(addr).packed return IPv4Address(addr).packed
def encode_ipv6_prefix(addr: Union[str, IPv6Network]) -> bytes: def encode_network(network: Union[str, IPv4Network]) -> bytes:
"""Encode a RADIUS value of type ipv4prefix"""
address = IPv4Network(network)
return struct.pack("2B", 0, address.prefixlen) + address.network_address.packed
def encode_ipv6_prefix(network: Union[str, IPv6Network]) -> bytes:
"""Encode a RADIUS value of type ipv6prefix""" """Encode a RADIUS value of type ipv6prefix"""
address = IPv6Network(addr) address = IPv6Network(network)
return struct.pack("2B", *[0, address.prefixlen]) + address.network_address.packed return struct.pack("2B", 0, address.prefixlen) + address.network_address.packed.rstrip(b'\0')
def encode_ipv6_address(addr: Union[str, IPv6Address]) -> bytes: def encode_ipv6_address(addr: Union[str, IPv6Address]) -> bytes:

View File

@@ -92,7 +92,7 @@ def parse_vendor_attributes(
if len(vendor_value) < 4: if len(vendor_value) < 4:
raise PacketError raise PacketError
vendor_id = int.from_bytes(vendor_value[:4], "big") vendor_id = int.from_bytes(vendor_value[:4], "big")
vendor_dict = rad_dict.vendor_by_id[vendor_id] vendor_dict = rad_dict.vendor[vendor_id]
vendor_name = vendor_dict.name vendor_name = vendor_dict.name
attributes = [] attributes = []
@@ -122,7 +122,7 @@ def parse_vendor_attributes(
def parse_key(rad_dict: Dictionary, key_id: int) -> Union[str, int]: def parse_key(rad_dict: Dictionary, key_id: int) -> Union[str, int]:
"""Parse the key in the Dictionary Context""" """Parse the key in the Dictionary Context"""
try: try:
return rad_dict.attrs[key_id].name return rad_dict.attrindex[key_id].name
except KeyError: except KeyError:
return key_id return key_id

View File

@@ -188,3 +188,51 @@ def test_value_number_out_of_limit(value_num, attr_type):
) )
with pytest.raises(ParseError): with pytest.raises(ParseError):
Dictionary("", dictionary) Dictionary("", dictionary)
@pytest.mark.parametrize("datatype", [
"string", "octets", "abinary", "byte", "short",
"integer", "signed", "integer64", "ipaddr",
"ipv4prefix", "ipv6addr", "ipv6prefix", "combo-ip",
"ifid", "ether", "concat", "tlv", "extended",
"long-extended", "evs",
])
def test_all_datatypes_rfc_space(datatype):
dictionary = StringIO(
f"ATTRIBUTE TEST-ATTRIBUTE 1 {datatype}\n"
)
Dictionary("", dictionary)
@pytest.mark.parametrize("datatype", [
"string", "octets", "abinary", "byte", "short",
"integer", "signed", "integer64", "ipaddr",
"ipv4prefix", "ipv6addr", "ipv6prefix", "combo-ip",
"ifid", "ether", "tlv",
])
def test_valid_datatypes_in_vendor_space(datatype):
dictionary = StringIO(
"VENDOR TEST 1234\n"
"BEGIN-VENDOR TEST\n"
f"ATTRIBUTE TEST-ATTRIBUTE 1 {datatype}\n"
"END-VENDOR TEST\n"
)
Dictionary("", dictionary)
@pytest.mark.parametrize("datatype", [
"concat", "extended", "long-extended", "evs",
])
def test_invalid_datatypes_in_vendor_space(datatype):
dictionary = StringIO(
"VENDOR TEST 1234\n"
"BEGIN-VENDOR TEST\n"
f"ATTRIBUTE TEST-ATTRIBUTE 1 {datatype}\n"
"END-VENDOR TEST\n"
)
with pytest.raises(ParseError):
Dictionary("", dictionary)