pyrad 2.4__py3-none-any.whl → 2.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. docs/Makefile +20 -0
  2. docs/make.bat +36 -0
  3. docs/source/_static/logo.png +0 -0
  4. docs/source/api/client.rst +10 -0
  5. docs/source/api/dictionary.rst +10 -0
  6. docs/source/api/host.rst +7 -0
  7. docs/source/api/packet.rst +48 -0
  8. docs/source/api/proxy.rst +7 -0
  9. docs/source/api/server.rst +13 -0
  10. docs/source/conf.py +158 -0
  11. docs/source/index.rst +75 -0
  12. example/acct.py +41 -0
  13. example/auth.py +37 -0
  14. example/auth_async.py +164 -0
  15. example/client-coa.py +61 -0
  16. example/coa.py +40 -0
  17. example/dictionary +405 -0
  18. example/dictionary.freeradius +91 -0
  19. example/pyrad.log +0 -0
  20. example/server.py +68 -0
  21. example/server_async.py +117 -0
  22. example/status.py +26 -0
  23. pyrad/__init__.py +3 -3
  24. pyrad/client.py +14 -6
  25. pyrad/client_async.py +16 -13
  26. pyrad/dictfile.py +2 -5
  27. pyrad/dictionary.py +6 -7
  28. pyrad/host.py +1 -1
  29. pyrad/packet.py +145 -114
  30. pyrad/proxy.py +2 -2
  31. pyrad/server.py +3 -7
  32. pyrad/server_async.py +3 -4
  33. pyrad/tests/__init__.py +5 -0
  34. pyrad/tests/mock.py +145 -0
  35. pyrad/tests/test_bidict.py +56 -0
  36. pyrad/tests/test_client.py +183 -0
  37. pyrad/tests/test_dictionary.py +357 -0
  38. pyrad/tests/test_host.py +87 -0
  39. pyrad/tests/test_packet.py +679 -0
  40. pyrad/tests/test_proxy.py +96 -0
  41. pyrad/tests/test_server.py +323 -0
  42. pyrad/tests/test_tools.py +126 -0
  43. pyrad/tools.py +254 -158
  44. {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info}/METADATA +44 -20
  45. pyrad-2.5.0.dist-info/RECORD +51 -0
  46. {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info}/WHEEL +1 -1
  47. {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info/licenses}/LICENSE.txt +1 -1
  48. pyrad-2.5.0.dist-info/top_level.txt +3 -0
  49. pyrad-2.4.dist-info/RECORD +0 -19
  50. pyrad-2.4.dist-info/top_level.txt +0 -1
  51. {pyrad-2.4.dist-info → pyrad-2.5.0.dist-info}/zip-safe +0 -0
pyrad/tools.py CHANGED
@@ -1,236 +1,332 @@
1
1
  # tools.py
2
2
  #
3
3
  # Utility functions
4
- from netaddr import IPAddress
5
- from netaddr import IPNetwork
6
- import struct
7
- import six
8
4
  import binascii
5
+ import ipaddress
6
+ import struct
9
7
 
10
8
 
11
- def EncodeString(str):
12
- if len(str) > 253:
13
- raise ValueError('Can only encode strings of <= 253 characters')
14
- if isinstance(str, six.text_type):
15
- return str.encode('utf-8')
16
- else:
17
- return str
9
+ # -------------------------
10
+ # Encoding helpers
11
+ # -------------------------
18
12
 
13
+ def EncodeString(value):
14
+ """
15
+ Encode a RADIUS 'string' value to bytes (UTF-8).
16
+ Accepts: str -> bytes, bytes -> bytes
17
+ """
18
+ if value is None:
19
+ return b""
20
+ if isinstance(value, bytes):
21
+ if len(value) > 253:
22
+ raise ValueError("Can only encode strings of <= 253 characters")
23
+ return value
24
+ if isinstance(value, str):
25
+ if len(value) > 253:
26
+ raise ValueError("Can only encode strings of <= 253 characters")
27
+ return value.encode("utf-8")
28
+ raise TypeError("Can only encode str/bytes as string")
29
+
30
+
31
+ def EncodeOctets(value):
32
+ """
33
+ Encodes RADIUS attributes of type "octets" into a byte sequence.
34
+
35
+ Supported inputs:
36
+ - bytes / bytearray:
37
+ * If the value starts with b"0x", it is treated as a hex string and decoded.
38
+ * Otherwise the byte value is passed through unchanged.
39
+ - str:
40
+ * "0x..." → hexadecimal representation, decoded into bytes
41
+ * Decimal string (e.g. "65"):
42
+ - 0..255 → encoded as a single byte
43
+ - >255 → encoded as a minimal big-endian byte sequence
44
+ * Any other string is UTF-8 encoded
45
+
46
+ Constraints:
47
+ - The resulting byte sequence must not exceed 253 bytes
48
+ (RADIUS attribute size limit).
49
+
50
+ This behavior preserves compatibility with legacy pyrad dictionary
51
+ definitions and existing test cases.
52
+ """
53
+ if value is None:
54
+ return b""
55
+
56
+ if isinstance(value, (bytes, bytearray)):
57
+ b = bytes(value)
58
+ if b.startswith(b"0x"):
59
+ out = binascii.unhexlify(b[2:])
60
+ else:
61
+ out = b
62
+
63
+ if len(out) > 253:
64
+ raise ValueError("Can only encode strings of <= 253 characters")
65
+ return out
66
+
67
+ if isinstance(value, str):
68
+ s = value
69
+ if s.startswith("0x"):
70
+ out = binascii.unhexlify(s[2:])
71
+ elif s.isdecimal():
72
+ n = int(s)
73
+ if n < 0:
74
+ raise ValueError("Octet decimal value must be >= 0")
75
+ if n <= 255:
76
+ out = struct.pack("!B", n)
77
+ else:
78
+ byte_len = (n.bit_length() + 7) // 8
79
+ out = n.to_bytes(byte_len, "big")
80
+ else:
81
+ out = s.encode("utf-8")
82
+
83
+ if len(out) > 253:
84
+ raise ValueError("Can only encode strings of <= 253 characters")
85
+ return out
86
+
87
+ raise TypeError("Can only encode str/bytes as octets")
19
88
 
20
- def EncodeOctets(str):
21
- if len(str) > 253:
22
- raise ValueError('Can only encode strings of <= 253 characters')
23
89
 
24
- if str.startswith(b'0x'):
25
- hexstring = str.split(b'0x')[1]
26
- return binascii.unhexlify(hexstring)
27
- else:
28
- return str
90
+ def EncodeAddress(addr):
91
+ """
92
+ Encode a RADIUS 'ipaddr' value.
93
+ Traditionally IPv4, but accept IPv6 as well (robust for real-world use).
94
+ """
95
+ if not isinstance(addr, str):
96
+ raise TypeError("Address has to be a string")
97
+ return ipaddress.ip_address(addr).packed
29
98
 
30
99
 
31
- def EncodeAddress(addr):
32
- if not isinstance(addr, six.string_types):
33
- raise TypeError('Address has to be a string')
34
- return IPAddress(addr).packed
100
+ def EncodeIPv6Address(addr):
101
+ """
102
+ Encode a RADIUS 'ipv6addr' value to 16 bytes.
103
+ Accepts: str, IPv6Address
104
+ """
105
+ if isinstance(addr, ipaddress.IPv6Address):
106
+ return addr.packed
107
+ if not isinstance(addr, str):
108
+ raise TypeError("IPv6 Address has to be a string")
109
+ return ipaddress.IPv6Address(addr).packed
35
110
 
36
111
 
37
- def EncodeIPv6Prefix(addr):
38
- if not isinstance(addr, six.string_types):
39
- raise TypeError('IPv6 Prefix has to be a string')
40
- ip = IPNetwork(addr)
41
- return struct.pack('2B', *[0, ip.prefixlen]) + ip.ip.packed
112
+ def EncodeIPv6Prefix(value, default_prefixlen=128):
113
+ """
114
+ Encode a RADIUS 'ipv6prefix' value.
115
+
116
+ Accepts:
117
+ - "2001:db8::/64" (str)
118
+ - "2001:db8::" (str) -> uses default_prefixlen
119
+ - ipaddress.IPv6Network
120
+ - ipaddress.IPv6Address -> uses default_prefixlen
121
+ - netaddr.IPNetwork (duck-typed via .ip/.prefixlen)
122
+ """
123
+ # 1) string input
124
+ if isinstance(value, str):
125
+ if "/" in value:
126
+ net = ipaddress.ip_network(value, strict=False)
127
+ else:
128
+ addr = ipaddress.IPv6Address(value)
129
+ net = ipaddress.IPv6Network((addr, default_prefixlen), strict=False)
130
+
131
+ # 2) stdlib ipaddress objects
132
+ elif isinstance(value, ipaddress.IPv6Network):
133
+ net = value
134
+ elif isinstance(value, ipaddress.IPv6Address):
135
+ net = ipaddress.IPv6Network((value, default_prefixlen), strict=False)
136
+
137
+ # 3) netaddr fallback (duck typing)
138
+ elif hasattr(value, "ip") and hasattr(value, "prefixlen"):
139
+ # netaddr.IPNetwork uses .ip and .prefixlen
140
+ return struct.pack("2B", 0, int(value.prefixlen)) + value.ip.packed
42
141
 
142
+ else:
143
+ raise TypeError("IPv6 Prefix has to be a string, IPv6Network, IPv6Address, or netaddr IPNetwork")
43
144
 
44
- def EncodeIPv6Address(addr):
45
- if not isinstance(addr, six.string_types):
46
- raise TypeError('IPv6 Address has to be a string')
47
- return IPAddress(addr).packed
145
+ if getattr(net, "version", None) != 6:
146
+ raise ValueError("not an IPv6 prefix")
147
+
148
+ return struct.pack("2B", 0, net.prefixlen) + net.network_address.packed
48
149
 
49
150
 
50
- def EncodeAscendBinary(str):
151
+ def EncodeAscendBinary(orig_str):
51
152
  """
52
- Format: List of type=value pairs sperated by spaces.
53
-
54
- Example: 'family=ipv4 action=discard direction=in dst=10.10.255.254/32'
55
-
56
- Type:
57
- family ipv4(default) or ipv6
58
- action discard(default) or accept
59
- direction in(default) or out
60
- src source prefix (default ignore)
61
- dst destination prefix (default ignore)
62
- proto protocol number / next-header number (default ignore)
63
- sport source port (default ignore)
64
- dport destination port (default ignore)
65
- sportq source port qualifier (default 0)
66
- dportq destination port qualifier (default 0)
67
-
68
- Source/Destination Port Qualifier:
69
- 0 no compare
70
- 1 less than
71
- 2 equal to
72
- 3 greater than
73
- 4 not equal to
153
+ Ascend binary format encoder.
74
154
  """
75
-
76
155
  terms = {
77
- 'family': b'\x01',
78
- 'action': b'\x00',
79
- 'direction': b'\x01',
80
- 'src': b'\x00\x00\x00\x00',
81
- 'dst': b'\x00\x00\x00\x00',
82
- 'srcl': b'\x00',
83
- 'dstl': b'\x00',
84
- 'proto': b'\x00',
85
- 'sport': b'\x00\x00',
86
- 'dport': b'\x00\x00',
87
- 'sportq': b'\x00',
88
- 'dportq': b'\x00'
156
+ "family": b"\x01",
157
+ "action": b"\x00",
158
+ "direction": b"\x01",
159
+ "src": b"\x00\x00\x00\x00",
160
+ "dst": b"\x00\x00\x00\x00",
161
+ "srcl": b"\x00",
162
+ "dstl": b"\x00",
163
+ "proto": b"\x00",
164
+ "sport": b"\x00\x00",
165
+ "dport": b"\x00\x00",
166
+ "sportq": b"\x00",
167
+ "dportq": b"\x00",
89
168
  }
90
169
 
91
- for t in str.split(' '):
92
- key, value = t.split('=')
93
- if key == 'family' and value == 'ipv6':
94
- terms[key] = b'\x03'
95
- if terms['src'] == b'\x00\x00\x00\x00':
96
- terms['src'] = 16 * b'\x00'
97
- if terms['dst'] == b'\x00\x00\x00\x00':
98
- terms['dst'] = 16 * b'\x00'
99
- elif key == 'action' and value == 'accept':
100
- terms[key] = b'\x01'
101
- elif key == 'direction' and value == 'out':
102
- terms[key] = b'\x00'
103
- elif key == 'src' or key == 'dst':
104
- ip = IPNetwork(value)
105
- terms[key] = ip.ip.packed
106
- terms[key+'l'] = struct.pack('B', ip.prefixlen)
107
- elif key == 'sport' or key == 'dport':
108
- terms[key] = struct.pack('!H', int(value))
109
- elif key == 'sportq' or key == 'dportq' or key == 'proto':
110
- terms[key] = struct.pack('B', int(value))
111
-
112
- trailer = 8 * b'\x00'
113
-
114
- result = b''.join((terms['family'], terms['action'], terms['direction'], b'\x00',
115
- terms['src'], terms['dst'], terms['srcl'], terms['dstl'], terms['proto'], b'\x00',
116
- terms['sport'], terms['dport'], terms['sportq'], terms['dportq'], b'\x00\x00', trailer))
117
- return result
118
-
119
-
120
- def EncodeInteger(num, format='!I'):
170
+ for t in orig_str.split(" "):
171
+ key, value = t.split("=")
172
+ if key == "family" and value == "ipv6":
173
+ terms[key] = b"\x03"
174
+ if terms["src"] == b"\x00\x00\x00\x00":
175
+ terms["src"] = 16 * b"\x00"
176
+ if terms["dst"] == b"\x00\x00\x00\x00":
177
+ terms["dst"] = 16 * b"\x00"
178
+ elif key == "action" and value == "accept":
179
+ terms[key] = b"\x01"
180
+ elif key == "action" and value == "redirect":
181
+ terms[key] = b"\x20"
182
+ elif key == "direction" and value == "out":
183
+ terms[key] = b"\x00"
184
+ elif key in ("src", "dst"):
185
+ net = ipaddress.ip_network(value, strict=False)
186
+ terms[key] = net.network_address.packed
187
+ terms[key + "l"] = struct.pack("B", net.prefixlen)
188
+ elif key in ("sport", "dport"):
189
+ terms[key] = struct.pack("!H", int(value))
190
+ elif key in ("sportq", "dportq", "proto"):
191
+ terms[key] = struct.pack("B", int(value))
192
+
193
+ trailer = 8 * b"\x00"
194
+ return b"".join((
195
+ terms["family"], terms["action"], terms["direction"], b"\x00",
196
+ terms["src"], terms["dst"], terms["srcl"], terms["dstl"],
197
+ terms["proto"], b"\x00", terms["sport"], terms["dport"],
198
+ terms["sportq"], terms["dportq"], b"\x00\x00", trailer
199
+ ))
200
+
201
+
202
+ def EncodeInteger(num, format="!I"):
121
203
  try:
122
204
  num = int(num)
123
- except:
124
- raise TypeError('Can not encode non-integer as integer')
205
+ except Exception:
206
+ raise TypeError("Can not encode non-integer as integer")
125
207
  return struct.pack(format, num)
126
208
 
127
- def EncodeInteger64(num, format='!Q'):
209
+
210
+ def EncodeInteger64(num, format="!Q"):
128
211
  try:
129
212
  num = int(num)
130
- except:
131
- raise TypeError('Can not encode non-integer as integer64')
213
+ except Exception:
214
+ raise TypeError("Can not encode non-integer as integer64")
132
215
  return struct.pack(format, num)
133
216
 
217
+
134
218
  def EncodeDate(num):
135
219
  if not isinstance(num, int):
136
- raise TypeError('Can not encode non-integer as date')
137
- return struct.pack('!I', num)
220
+ raise TypeError("Can not encode non-integer as date")
221
+ return struct.pack("!I", num)
138
222
 
139
223
 
140
- def DecodeString(str):
141
- try:
142
- return str.decode('utf-8')
143
- except:
144
- return str
224
+ # -------------------------
225
+ # Decoding helpers
226
+ # -------------------------
227
+
228
+ def DecodeString(value):
229
+ # Be tolerant: bytes -> utf-8 (replace), else passthrough
230
+ if isinstance(value, bytes):
231
+ return value.decode("utf-8", errors="replace")
232
+ return value
145
233
 
146
234
 
147
- def DecodeOctets(str):
148
- return str
235
+ def DecodeOctets(value):
236
+ return value
149
237
 
150
238
 
151
239
  def DecodeAddress(addr):
152
- return '.'.join(map(str, struct.unpack('BBBB', addr)))
240
+ return str(ipaddress.ip_address(addr))
153
241
 
154
242
 
155
243
  def DecodeIPv6Prefix(addr):
156
- addr = addr + b'\x00' * (18-len(addr))
157
- _, length, prefix = ':'.join(map('{0:x}'.format, struct.unpack('!BB'+'H'*8, addr))).split(":", 2)
158
- return str(IPNetwork("%s/%s" % (prefix, int(length, 16))))
244
+ # RADIUS IPv6-Prefix is: 2 bytes (reserved, prefixlen) + prefix bytes (0..16)
245
+ addr = addr + b"\x00" * (18 - len(addr))
246
+ _, length = struct.unpack("!BB", addr[:2])
247
+ prefix_bytes = addr[2:18]
248
+ prefix = ipaddress.IPv6Address(prefix_bytes)
249
+ return str(ipaddress.IPv6Network((prefix, int(length)), strict=False))
159
250
 
160
251
 
161
252
  def DecodeIPv6Address(addr):
162
- addr = addr + b'\x00' * (16-len(addr))
163
- prefix = ':'.join(map('{0:x}'.format, struct.unpack('!'+'H'*8, addr)))
164
- return str(IPAddress(prefix))
253
+ addr = addr + b"\x00" * (16 - len(addr))
254
+ return str(ipaddress.IPv6Address(addr))
165
255
 
166
256
 
167
- def DecodeAscendBinary(str):
168
- return str
257
+ def DecodeAscendBinary(value):
258
+ return value
169
259
 
170
260
 
171
- def DecodeInteger(num, format='!I'):
172
- return (struct.unpack(format, num))[0]
261
+ def DecodeInteger(num, format="!I"):
262
+ return struct.unpack(format, num)[0]
263
+
264
+
265
+ def DecodeInteger64(num, format="!Q"):
266
+ return struct.unpack(format, num)[0]
173
267
 
174
- def DecodeInteger64(num, format='!Q'):
175
- return (struct.unpack(format, num))[0]
176
268
 
177
269
  def DecodeDate(num):
178
- return (struct.unpack('!I', num))[0]
270
+ return struct.unpack("!I", num)[0]
271
+
179
272
 
273
+ # -------------------------
274
+ # Attribute encode/decode dispatch
275
+ # -------------------------
180
276
 
181
277
  def EncodeAttr(datatype, value):
182
- if datatype == 'string':
278
+ if datatype == "string":
183
279
  return EncodeString(value)
184
- elif datatype == 'octets':
280
+ elif datatype == "octets":
185
281
  return EncodeOctets(value)
186
- elif datatype == 'integer':
282
+ elif datatype == "integer":
187
283
  return EncodeInteger(value)
188
- elif datatype == 'ipaddr':
284
+ elif datatype == "ipaddr":
189
285
  return EncodeAddress(value)
190
- elif datatype == 'ipv6prefix':
286
+ elif datatype == "ipv6prefix":
191
287
  return EncodeIPv6Prefix(value)
192
- elif datatype == 'ipv6addr':
288
+ elif datatype == "ipv6addr":
193
289
  return EncodeIPv6Address(value)
194
- elif datatype == 'abinary':
290
+ elif datatype == "abinary":
195
291
  return EncodeAscendBinary(value)
196
- elif datatype == 'signed':
197
- return EncodeInteger(value, '!i')
198
- elif datatype == 'short':
199
- return EncodeInteger(value, '!H')
200
- elif datatype == 'byte':
201
- return EncodeInteger(value, '!B')
202
- elif datatype == 'date':
292
+ elif datatype == "signed":
293
+ return EncodeInteger(value, "!i")
294
+ elif datatype == "short":
295
+ return EncodeInteger(value, "!H")
296
+ elif datatype == "byte":
297
+ return EncodeInteger(value, "!B")
298
+ elif datatype == "date":
203
299
  return EncodeDate(value)
204
- elif datatype == 'integer64':
300
+ elif datatype == "integer64":
205
301
  return EncodeInteger64(value)
206
302
  else:
207
- raise ValueError('Unknown attribute type %s' % datatype)
303
+ raise ValueError("Unknown attribute type %s" % datatype)
208
304
 
209
305
 
210
306
  def DecodeAttr(datatype, value):
211
- if datatype == 'string':
307
+ if datatype == "string":
212
308
  return DecodeString(value)
213
- elif datatype == 'octets':
309
+ elif datatype == "octets":
214
310
  return DecodeOctets(value)
215
- elif datatype == 'integer':
311
+ elif datatype == "integer":
216
312
  return DecodeInteger(value)
217
- elif datatype == 'ipaddr':
313
+ elif datatype == "ipaddr":
218
314
  return DecodeAddress(value)
219
- elif datatype == 'ipv6prefix':
315
+ elif datatype == "ipv6prefix":
220
316
  return DecodeIPv6Prefix(value)
221
- elif datatype == 'ipv6addr':
317
+ elif datatype == "ipv6addr":
222
318
  return DecodeIPv6Address(value)
223
- elif datatype == 'abinary':
319
+ elif datatype == "abinary":
224
320
  return DecodeAscendBinary(value)
225
- elif datatype == 'signed':
226
- return DecodeInteger(value, '!i')
227
- elif datatype == 'short':
228
- return DecodeInteger(value, '!H')
229
- elif datatype == 'byte':
230
- return DecodeInteger(value, '!B')
231
- elif datatype == 'date':
321
+ elif datatype == "signed":
322
+ return DecodeInteger(value, "!i")
323
+ elif datatype == "short":
324
+ return DecodeInteger(value, "!H")
325
+ elif datatype == "byte":
326
+ return DecodeInteger(value, "!B")
327
+ elif datatype == "date":
232
328
  return DecodeDate(value)
233
- elif datatype == 'integer64':
329
+ elif datatype == "integer64":
234
330
  return DecodeInteger64(value)
235
331
  else:
236
- raise ValueError('Unknown attribute type %s' % datatype)
332
+ raise ValueError("Unknown attribute type %s" % datatype)
@@ -1,27 +1,38 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyrad
3
- Version: 2.4
3
+ Version: 2.5.0
4
4
  Summary: RADIUS tools
5
- Home-page: https://github.com/pyradius/pyrad
6
- Author: Istvan Ruzman, Christian Giese
7
- Author-email: istvan@ruzman.eu, developer@gicnet.de
8
- License: BSD
9
- Keywords: radius,authentication
10
- Platform: UNKNOWN
5
+ Author-email: Istvan Ruzman <istvan@ruzman.eu>, Christian Giese <gic@gicnet.de>, Stefan Lieberth <stefan@lieberth.net>
6
+ License: BSD-3-Clause
7
+ Project-URL: Repository, https://github.com/pyradius/pyrad
8
+ Project-URL: Documentation, https://pyradius-pyrad.readthedocs.io
9
+ Keywords: radius,authentication,AAA,accounting,authorization,RADIUS
11
10
  Classifier: Development Status :: 6 - Mature
12
11
  Classifier: Intended Audience :: Developers
13
12
  Classifier: License :: OSI Approved :: BSD License
14
- Classifier: Programming Language :: Python :: 2.7
15
- Classifier: Programming Language :: Python :: 3.6
16
- Classifier: Programming Language :: Python :: 3.7
13
+ Classifier: Programming Language :: Python :: 3
17
14
  Classifier: Programming Language :: Python :: 3.8
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
18
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
22
  Classifier: Topic :: System :: Systems Administration :: Authentication/Directory
20
- Requires-Dist: six
21
- Requires-Dist: netaddr
22
-
23
- .. image:: https://travis-ci.org/pyradius/pyrad.svg?branch=master
24
- :target: https://travis-ci.org/pyradius/pyrad
23
+ Requires-Python: >=3.8
24
+ Description-Content-Type: text/x-rst
25
+ License-File: LICENSE.txt
26
+ Requires-Dist: netaddr>=0.8.0
27
+ Requires-Dist: six>=1.16.0
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=8; extra == "test"
30
+ Requires-Dist: mock; python_version < "3.10" and extra == "test"
31
+ Dynamic: license-file
32
+
33
+
34
+ .. image:: https://github.com/pyradius/pyrad/actions/workflows/python-test.yml/badge.svg?branch=master
35
+ :target: https://github.com/pyradius/pyrad/actions/workflows/python-test.yml
25
36
  .. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
26
37
  :target: https://coveralls.io/github/pyradius/pyrad?branch=master
27
38
  .. image:: https://img.shields.io/pypi/v/pyrad.svg
@@ -30,11 +41,13 @@ Requires-Dist: netaddr
30
41
  :target: https://pypi.python.org/pypi/pyrad
31
42
  .. image:: https://img.shields.io/pypi/dm/pyrad.svg
32
43
  :target: https://pypi.python.org/pypi/pyrad
33
- .. image:: https://readthedocs.org/projects/pyrad/badge/?version=latest
34
- :target: http://pyrad.readthedocs.io/en/latest/?badge=latest
44
+ .. image:: https://readthedocs.org/projects/pyradius-pyrad/badge/?version=latest
45
+ :target: https://pyradius-pyrad.readthedocs.io/en/latest/?badge=latest
35
46
  :alt: Documentation Status
36
47
  .. image:: https://img.shields.io/pypi/l/pyrad.svg
37
48
  :target: https://pypi.python.org/pypi/pyrad
49
+ .. image:: https://img.shields.io/badge/Chat-darkgreen
50
+ :target: https://matrix.to/#/#pyradius:matrix.org
38
51
 
39
52
  Introduction
40
53
  ============
@@ -45,7 +58,6 @@ them and decoding responses.
45
58
 
46
59
  Here is an example of doing a authentication request::
47
60
 
48
- from __future__ import print_function
49
61
  from pyrad.client import Client
50
62
  from pyrad.dictionary import Dictionary
51
63
  import pyrad.packet
@@ -75,7 +87,7 @@ Here is an example of doing a authentication request::
75
87
  Requirements & Installation
76
88
  ===========================
77
89
 
78
- pyrad requires Python 2.7, or Python 3.6 or later
90
+ pyrad requires Python 3.8 or later
79
91
 
80
92
  Installing is simple; pyrad uses the standard distutils system for installing
81
93
  Python modules::
@@ -89,6 +101,10 @@ Author, Copyright, Availability
89
101
  pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by
90
102
  Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
91
103
 
104
+ We’re looking for contributors to support the pyrad team! If you’re interested in
105
+ helping with development, testing, documentation, or other areas, please contact
106
+ us directly.
107
+
92
108
  This project is licensed under a BSD license.
93
109
 
94
110
  Copyright and license information can be found in the LICENSE.txt file.
@@ -99,4 +115,12 @@ https://pypi.org/project/pyrad/
99
115
  Bugs and wishes can be submitted in the pyrad issue tracker on github:
100
116
  https://github.com/pyradius/pyrad/issues
101
117
 
118
+ Related Projects & Forks
119
+ ========================
120
+
121
+ **pyrad2:** Noteworthy fork with experimental RadSec (RFC 6614) support. Targets Python 3.12+,
122
+ adds extensive type hints, boosts test coverage, and includes fresh bug fixes.
123
+ https://github.com/nicholasamorim/pyrad2
102
124
 
125
+ **pyrad-server:** Lab-grade RADIUS test server built on top of pyrad.
126
+ https://github.com/slieberth/pyrad-server
@@ -0,0 +1,51 @@
1
+ docs/Makefile,sha256=De9yQi78Rqt6niypd7CgZSkhyHFEx5cXhh6ZL3YT1Yc,606
2
+ docs/make.bat,sha256=MIkBA-XhK2GzLus-jttyGfmQMlVYtQT23DOTPKnICw8,813
3
+ docs/source/conf.py,sha256=BmMQb6oxGNAzfGGDT6WZ4MYrd_h0kf5-00Iasu2F-Uk,5137
4
+ docs/source/index.rst,sha256=i_Fb3-3lEDGp8iMYdgQphqexeoiGY0WREO-btdlnelA,1650
5
+ docs/source/_static/logo.png,sha256=xr7ETIziytN2Sl9WkOCnAKWwikXJYwvpIB7stu5a6iE,1247
6
+ docs/source/api/client.rst,sha256=Bg2tB-hlaRtiZleaJXEOrA0weDi0w25lS0xzw_k0xKE,181
7
+ docs/source/api/dictionary.rst,sha256=-dQM3gTaZjcEVMe0RlDZ5TgtBhnk8A927TlrZqvjFGQ,210
8
+ docs/source/api/host.rst,sha256=R1IUSCWpwfW7xha-Z18xyaq3kDhlsmxxX9vnJx1fPsw,153
9
+ docs/source/api/packet.rst,sha256=anx-kgMZN27cp0oMhQIgnts5HvUFInK-UsvCDRD0L40,1013
10
+ docs/source/api/proxy.rst,sha256=pgdVrBb1vMqFrjV3Z27WVl3LReIzbPDoe_SVXHMZctY,135
11
+ docs/source/api/server.rst,sha256=R8e3frO_Dc7Jw1EmB5BthTLS_xnRfA-nsCSHccEG44g,234
12
+ example/acct.py,sha256=X9O3YKdR-pEEoziyfJR9b1Dhh8kfDsrwm-WUji4PlT4,1239
13
+ example/auth.py,sha256=GubmBRDEP955YoA-vK5He-35B266-sOKx0kOfhlBU98,1051
14
+ example/auth_async.py,sha256=J18gwQHscjeVhQT0DVjTJQTZ546REypnsM98vzul_C8,4683
15
+ example/client-coa.py,sha256=OR2vwLvGegPUaeHCYMjo59xBi-SIQ8MWwBKZH2EqPKE,1811
16
+ example/coa.py,sha256=avdyiA4zdBZDqzGAfvlJ5_f8HWy9ggKp0llnwj5u63g,954
17
+ example/dictionary,sha256=KnfpaTXwdFGAxgoVoNDMWGGj0teZ8xJCLOWo2bXpIG4,13085
18
+ example/dictionary.freeradius,sha256=Sfo8911YmOex3vNyF42opvD4C2C-NoK3Az6wRobmGXU,3851
19
+ example/pyrad.log,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
+ example/server.py,sha256=kvXl_7HpW2xXy5U-LU-9VRSVJubeHll6XSnbms2M0rs,1993
21
+ example/server_async.py,sha256=va4bEKX796ePDrM_pAERAXNaSUsxrelLoa3fzp2n4TY,3442
22
+ example/status.py,sha256=jyUgdW_sUdUgjyChnKEQJQf-bnLr6KXC3479mi5-q9Q,724
23
+ pyrad/__init__.py,sha256=UHLdGJEv8IRKt2TsjbJsxYO3BB-grtobpuNp0iXftGo,1537
24
+ pyrad/bidict.py,sha256=wgSr6odl-sYqkHrAFvCqRPjKOPpck4tZ-YxXivmPLsQ,870
25
+ pyrad/client.py,sha256=scOSXMs1Oee7m7w5xdwHnCoxpeBD298tgKDgxsSRhj4,8498
26
+ pyrad/client_async.py,sha256=XIi9tEo3sfmmbG33xs3e9YDa6GGIiAMDC4LqS0VFNno,14758
27
+ pyrad/curved.py,sha256=PbtHrJjk9S5BBSF9jmDvOMZKkaJcbAaICodTQpqDbBg,2204
28
+ pyrad/dictfile.py,sha256=ymcfT95v28vnsytcnP4LnsBje9RBw36mtxUMAZOwBQs,2858
29
+ pyrad/dictionary.py,sha256=KjWQTigR6HqOYN3U1hCK5I02LHqyxLNH35p9KIDXZQw,14375
30
+ pyrad/host.py,sha256=7e8d4mFvNTapNXmxyh62b7ddCLDFjc2hEc-5MsDUMe4,3628
31
+ pyrad/packet.py,sha256=QW22xekqdLvaoC4DwiEy0DLLZyOO-od7BEsZRLx9ykI,34546
32
+ pyrad/proxy.py,sha256=T3vBt8q3T4qbCO8JEXa90DOojbH9vC-YOID8kIJ-HO8,2510
33
+ pyrad/server.py,sha256=GriMqeMk0l16Nd3-yWzbE2eqkYD8X_KE7zOXiK6pvqs,12602
34
+ pyrad/server_async.py,sha256=mQ--rt1se-pQVWLgoaXxyzds6AdzRV8LNax5Wvn4mNw,11889
35
+ pyrad/tools.py,sha256=3AN3YUqWmtMf_k8glIVMu2LI7rfriRV_06-1yQhFf0s,10094
36
+ pyrad/tests/__init__.py,sha256=E-zlu3gcB6Pm97MjuIlChVLyKBqHcP8V9aKsRU1NG5I,104
37
+ pyrad/tests/mock.py,sha256=VJUVUgfpiPI5fCc9ILg_ZLeSPpMZjSnwD6C7NG39QJw,3349
38
+ pyrad/tests/test_bidict.py,sha256=eAeOcxs3AoCOzE5M-zRuPIb2p8YBGkDzmGXtQ9IxodU,2257
39
+ pyrad/tests/test_client.py,sha256=PK4-rvfL-EcSecIM9zKjQLViKpXjshSGIkgYVG9HjeA,6724
40
+ pyrad/tests/test_dictionary.py,sha256=S3LzstjbmyqLYZgHTmxwxCRQzefV0XLs_cWP5AwU6zg,13618
41
+ pyrad/tests/test_host.py,sha256=IOqxVXNLB8znQQkpxvRMYSja_oo2lVwQ_IRjfS_IIZw,2597
42
+ pyrad/tests/test_packet.py,sha256=7d7_NObiEZeiGArz9uVXY8qZkzv_F8z8Saz41TlbK0s,27085
43
+ pyrad/tests/test_proxy.py,sha256=JVhVCDqa4waixtNw82kMsDzVeiKgIeUU5fLf8mcp7E8,2981
44
+ pyrad/tests/test_server.py,sha256=UZo5RCyYJcYvuKvFlE0e0tWs4hjnSVVbJTqJ9YciVgA,11383
45
+ pyrad/tests/test_tools.py,sha256=PKc5tPzyartVCE4OIMzgkIIdeSIK3luQFPlrO3ivVFM,6617
46
+ pyrad-2.5.0.dist-info/licenses/LICENSE.txt,sha256=JXj4emdUhaoO3l80a9HFY7c61ekO4yLbInhCcET9r_U,1660
47
+ pyrad-2.5.0.dist-info/METADATA,sha256=FP3FxyfhjQeD8c3DkRhNnKP4Y8AhiQ--Yv7nFk7U-4c,4748
48
+ pyrad-2.5.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
49
+ pyrad-2.5.0.dist-info/top_level.txt,sha256=DfCXLna0BEU1ycerXhff_P0tIEOQr8_XsWCw-re1Xg8,19
50
+ pyrad-2.5.0.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
51
+ pyrad-2.5.0.dist-info/RECORD,,