pyrad 2.4__py3-none-any.whl → 2.5.1__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.1.dist-info}/METADATA +45 -22
  45. pyrad-2.5.1.dist-info/RECORD +51 -0
  46. {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info}/WHEEL +1 -1
  47. {pyrad-2.4.dist-info → pyrad-2.5.1.dist-info/licenses}/LICENSE.txt +1 -1
  48. pyrad-2.5.1.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.1.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,37 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: pyrad
3
- Version: 2.4
3
+ Version: 2.5.1
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
- 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
12
+ Classifier: Programming Language :: Python :: 3
17
13
  Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
18
20
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
19
21
  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
22
+ Requires-Python: >=3.8
23
+ Description-Content-Type: text/x-rst
24
+ License-File: LICENSE.txt
25
+ Requires-Dist: netaddr>=0.8.0
26
+ Requires-Dist: six>=1.16.0
27
+ Provides-Extra: test
28
+ Requires-Dist: pytest>=8; extra == "test"
29
+ Requires-Dist: mock; python_version < "3.10" and extra == "test"
30
+ Dynamic: license-file
31
+
32
+
33
+ .. image:: https://github.com/pyradius/pyrad/actions/workflows/python-test.yml/badge.svg?branch=master
34
+ :target: https://github.com/pyradius/pyrad/actions/workflows/python-test.yml
25
35
  .. image:: https://coveralls.io/repos/github/pyradius/pyrad/badge.svg?branch=master
26
36
  :target: https://coveralls.io/github/pyradius/pyrad?branch=master
27
37
  .. image:: https://img.shields.io/pypi/v/pyrad.svg
@@ -30,11 +40,13 @@ Requires-Dist: netaddr
30
40
  :target: https://pypi.python.org/pypi/pyrad
31
41
  .. image:: https://img.shields.io/pypi/dm/pyrad.svg
32
42
  :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
43
+ .. image:: https://readthedocs.org/projects/pyradius-pyrad/badge/?version=latest
44
+ :target: https://pyradius-pyrad.readthedocs.io/en/latest/?badge=latest
35
45
  :alt: Documentation Status
36
46
  .. image:: https://img.shields.io/pypi/l/pyrad.svg
37
47
  :target: https://pypi.python.org/pypi/pyrad
48
+ .. image:: https://img.shields.io/badge/Chat-darkgreen
49
+ :target: https://matrix.to/#/#pyradius:matrix.org
38
50
 
39
51
  Introduction
40
52
  ============
@@ -45,7 +57,6 @@ them and decoding responses.
45
57
 
46
58
  Here is an example of doing a authentication request::
47
59
 
48
- from __future__ import print_function
49
60
  from pyrad.client import Client
50
61
  from pyrad.dictionary import Dictionary
51
62
  import pyrad.packet
@@ -75,7 +86,7 @@ Here is an example of doing a authentication request::
75
86
  Requirements & Installation
76
87
  ===========================
77
88
 
78
- pyrad requires Python 2.7, or Python 3.6 or later
89
+ pyrad requires Python 3.8 or later
79
90
 
80
91
  Installing is simple; pyrad uses the standard distutils system for installing
81
92
  Python modules::
@@ -87,7 +98,11 @@ Author, Copyright, Availability
87
98
  ===============================
88
99
 
89
100
  pyrad was written by Wichert Akkerman <wichert@wiggy.net> and is maintained by
90
- Christian Giese (GIC-de) and Istvan Ruzman (Istvan91).
101
+ Christian Giese (GIC-de), Istvan Ruzman (Istvan91) and Stefan Lieberth (slieberth).
102
+
103
+ We’re looking for contributors to support the pyrad team! If you’re interested in
104
+ helping with development, testing, documentation, or other areas, please contact
105
+ us directly.
91
106
 
92
107
  This project is licensed under a BSD license.
93
108
 
@@ -99,4 +114,12 @@ https://pypi.org/project/pyrad/
99
114
  Bugs and wishes can be submitted in the pyrad issue tracker on github:
100
115
  https://github.com/pyradius/pyrad/issues
101
116
 
117
+ Related Projects & Forks
118
+ ========================
119
+
120
+ **pyrad2:** Noteworthy fork with experimental RadSec (RFC 6614) support. Targets Python 3.12+,
121
+ adds extensive type hints, boosts test coverage, and includes fresh bug fixes.
122
+ https://github.com/nicholasamorim/pyrad2
102
123
 
124
+ **pyrad-server:** Lab-grade RADIUS test server built on top of pyrad.
125
+ 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=Il5YNwrETYZXRY4VrPdB4-DxEZKQCpVAE8Hr8N67jxQ,5141
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=QJJWDc7i11_yGzrnVZhryqZRQD1dRHWvOyuCT33IdYI,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.1.dist-info/licenses/LICENSE.txt,sha256=JXj4emdUhaoO3l80a9HFY7c61ekO4yLbInhCcET9r_U,1660
47
+ pyrad-2.5.1.dist-info/METADATA,sha256=3hCzsV6ugSclJDUEw89fEFgpANelqjcJlkeEVlK-qBA,4727
48
+ pyrad-2.5.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
49
+ pyrad-2.5.1.dist-info/top_level.txt,sha256=DfCXLna0BEU1ycerXhff_P0tIEOQr8_XsWCw-re1Xg8,19
50
+ pyrad-2.5.1.dist-info/zip-safe,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
51
+ pyrad-2.5.1.dist-info/RECORD,,