kafka-python 3.0.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 (373) hide show
  1. kafka/__init__.py +34 -0
  2. kafka/__main__.py +5 -0
  3. kafka/admin/__init__.py +29 -0
  4. kafka/admin/__main__.py +5 -0
  5. kafka/admin/_acls.py +355 -0
  6. kafka/admin/_cluster.py +359 -0
  7. kafka/admin/_configs.py +479 -0
  8. kafka/admin/_groups.py +754 -0
  9. kafka/admin/_partitions.py +595 -0
  10. kafka/admin/_topics.py +281 -0
  11. kafka/admin/_transactions.py +450 -0
  12. kafka/admin/_users.py +194 -0
  13. kafka/admin/client.py +373 -0
  14. kafka/benchmarks/__init__.py +0 -0
  15. kafka/benchmarks/consumer_performance.py +138 -0
  16. kafka/benchmarks/load_example.py +109 -0
  17. kafka/benchmarks/producer_encode_path.py +201 -0
  18. kafka/benchmarks/producer_performance.py +161 -0
  19. kafka/benchmarks/profile_protocol.py +138 -0
  20. kafka/benchmarks/protocol_old_vs_new.py +447 -0
  21. kafka/benchmarks/record_batch_compose.py +77 -0
  22. kafka/benchmarks/record_batch_read.py +82 -0
  23. kafka/benchmarks/varint_speed.py +426 -0
  24. kafka/cli/__init__.py +36 -0
  25. kafka/cli/admin/__init__.py +117 -0
  26. kafka/cli/admin/acls/__init__.py +9 -0
  27. kafka/cli/admin/acls/common.py +76 -0
  28. kafka/cli/admin/acls/create.py +19 -0
  29. kafka/cli/admin/acls/delete.py +23 -0
  30. kafka/cli/admin/acls/describe.py +16 -0
  31. kafka/cli/admin/cluster/__init__.py +14 -0
  32. kafka/cli/admin/cluster/describe.py +11 -0
  33. kafka/cli/admin/cluster/describe_quorum.py +11 -0
  34. kafka/cli/admin/cluster/features.py +52 -0
  35. kafka/cli/admin/cluster/log_dirs.py +43 -0
  36. kafka/cli/admin/cluster/versions.py +33 -0
  37. kafka/cli/admin/configs/__init__.py +10 -0
  38. kafka/cli/admin/configs/alter.py +43 -0
  39. kafka/cli/admin/configs/common.py +17 -0
  40. kafka/cli/admin/configs/describe.py +30 -0
  41. kafka/cli/admin/configs/list.py +16 -0
  42. kafka/cli/admin/configs/reset.py +20 -0
  43. kafka/cli/admin/groups/__init__.py +16 -0
  44. kafka/cli/admin/groups/alter_offsets.py +30 -0
  45. kafka/cli/admin/groups/delete.py +11 -0
  46. kafka/cli/admin/groups/delete_offsets.py +29 -0
  47. kafka/cli/admin/groups/describe.py +11 -0
  48. kafka/cli/admin/groups/list.py +28 -0
  49. kafka/cli/admin/groups/list_offsets.py +29 -0
  50. kafka/cli/admin/groups/remove_members.py +40 -0
  51. kafka/cli/admin/groups/reset_offsets.py +139 -0
  52. kafka/cli/admin/partitions/__init__.py +21 -0
  53. kafka/cli/admin/partitions/alter_reassignments.py +37 -0
  54. kafka/cli/admin/partitions/create.py +27 -0
  55. kafka/cli/admin/partitions/delete_records.py +31 -0
  56. kafka/cli/admin/partitions/describe.py +36 -0
  57. kafka/cli/admin/partitions/elect_leaders.py +53 -0
  58. kafka/cli/admin/partitions/list_offsets.py +88 -0
  59. kafka/cli/admin/partitions/list_reassignments.py +35 -0
  60. kafka/cli/admin/topics/__init__.py +10 -0
  61. kafka/cli/admin/topics/create.py +13 -0
  62. kafka/cli/admin/topics/delete.py +19 -0
  63. kafka/cli/admin/topics/describe.py +18 -0
  64. kafka/cli/admin/topics/list.py +11 -0
  65. kafka/cli/admin/transactions/__init__.py +17 -0
  66. kafka/cli/admin/transactions/abort.py +38 -0
  67. kafka/cli/admin/transactions/describe.py +24 -0
  68. kafka/cli/admin/transactions/describe_producers.py +29 -0
  69. kafka/cli/admin/transactions/find_hanging.py +26 -0
  70. kafka/cli/admin/transactions/list.py +37 -0
  71. kafka/cli/admin/users/__init__.py +8 -0
  72. kafka/cli/admin/users/alter_user_scram_credentials.py +34 -0
  73. kafka/cli/admin/users/describe_user_scram_credentials.py +15 -0
  74. kafka/cli/common.py +95 -0
  75. kafka/cli/consumer/__init__.py +63 -0
  76. kafka/cli/producer/__init__.py +57 -0
  77. kafka/cluster.py +824 -0
  78. kafka/codec.py +325 -0
  79. kafka/consumer/__init__.py +5 -0
  80. kafka/consumer/__main__.py +5 -0
  81. kafka/consumer/fetcher.py +2012 -0
  82. kafka/consumer/group.py +1347 -0
  83. kafka/consumer/subscription_state.py +897 -0
  84. kafka/coordinator/__init__.py +0 -0
  85. kafka/coordinator/assignors/__init__.py +0 -0
  86. kafka/coordinator/assignors/abstract.py +90 -0
  87. kafka/coordinator/assignors/cooperative_sticky.py +167 -0
  88. kafka/coordinator/assignors/range.py +81 -0
  89. kafka/coordinator/assignors/roundrobin.py +101 -0
  90. kafka/coordinator/assignors/sticky/StickyAssignorUserData.json +37 -0
  91. kafka/coordinator/assignors/sticky/__init__.py +0 -0
  92. kafka/coordinator/assignors/sticky/partition_movements.py +149 -0
  93. kafka/coordinator/assignors/sticky/sorted_set.py +63 -0
  94. kafka/coordinator/assignors/sticky/sticky_assignor.py +665 -0
  95. kafka/coordinator/assignors/sticky/user_data.py +8 -0
  96. kafka/coordinator/base.py +1215 -0
  97. kafka/coordinator/consumer.py +1224 -0
  98. kafka/coordinator/heartbeat.py +82 -0
  99. kafka/coordinator/subscription.py +34 -0
  100. kafka/errors.py +1004 -0
  101. kafka/future.py +166 -0
  102. kafka/metrics/__init__.py +13 -0
  103. kafka/metrics/compound_stat.py +33 -0
  104. kafka/metrics/dict_reporter.py +81 -0
  105. kafka/metrics/kafka_metric.py +36 -0
  106. kafka/metrics/measurable.py +27 -0
  107. kafka/metrics/measurable_stat.py +13 -0
  108. kafka/metrics/metric_config.py +33 -0
  109. kafka/metrics/metric_name.py +105 -0
  110. kafka/metrics/metrics.py +261 -0
  111. kafka/metrics/metrics_reporter.py +53 -0
  112. kafka/metrics/quota.py +41 -0
  113. kafka/metrics/stat.py +19 -0
  114. kafka/metrics/stats/__init__.py +15 -0
  115. kafka/metrics/stats/avg.py +24 -0
  116. kafka/metrics/stats/count.py +17 -0
  117. kafka/metrics/stats/histogram.py +99 -0
  118. kafka/metrics/stats/max_stat.py +17 -0
  119. kafka/metrics/stats/min_stat.py +19 -0
  120. kafka/metrics/stats/percentile.py +14 -0
  121. kafka/metrics/stats/percentiles.py +75 -0
  122. kafka/metrics/stats/rate.py +118 -0
  123. kafka/metrics/stats/sampled_stat.py +99 -0
  124. kafka/metrics/stats/sensor.py +136 -0
  125. kafka/metrics/stats/total.py +15 -0
  126. kafka/net/__init__.py +19 -0
  127. kafka/net/compat.py +165 -0
  128. kafka/net/connection.py +593 -0
  129. kafka/net/http_connect.py +144 -0
  130. kafka/net/inet.py +122 -0
  131. kafka/net/manager.py +451 -0
  132. kafka/net/metrics.py +149 -0
  133. kafka/net/sasl/__init__.py +32 -0
  134. kafka/net/sasl/abc.py +28 -0
  135. kafka/net/sasl/gssapi.py +95 -0
  136. kafka/net/sasl/msk.py +245 -0
  137. kafka/net/sasl/oauth.py +98 -0
  138. kafka/net/sasl/plain.py +42 -0
  139. kafka/net/sasl/scram.py +135 -0
  140. kafka/net/sasl/sspi.py +111 -0
  141. kafka/net/selector.py +644 -0
  142. kafka/net/socks5.py +262 -0
  143. kafka/net/transport.py +415 -0
  144. kafka/net/wakeup_notifier.py +72 -0
  145. kafka/partitioner/__init__.py +8 -0
  146. kafka/partitioner/abc.py +8 -0
  147. kafka/partitioner/default.py +89 -0
  148. kafka/partitioner/sticky.py +109 -0
  149. kafka/producer/__init__.py +5 -0
  150. kafka/producer/__main__.py +5 -0
  151. kafka/producer/future.py +101 -0
  152. kafka/producer/kafka.py +1123 -0
  153. kafka/producer/producer_batch.py +192 -0
  154. kafka/producer/record_accumulator.py +647 -0
  155. kafka/producer/sender.py +884 -0
  156. kafka/producer/transaction_manager.py +1326 -0
  157. kafka/protocol/__init__.py +0 -0
  158. kafka/protocol/admin/__init__.py +29 -0
  159. kafka/protocol/admin/acl.py +83 -0
  160. kafka/protocol/admin/acl.pyi +375 -0
  161. kafka/protocol/admin/client_quotas.py +14 -0
  162. kafka/protocol/admin/client_quotas.pyi +265 -0
  163. kafka/protocol/admin/cluster.py +31 -0
  164. kafka/protocol/admin/cluster.pyi +620 -0
  165. kafka/protocol/admin/configs.py +22 -0
  166. kafka/protocol/admin/configs.pyi +437 -0
  167. kafka/protocol/admin/groups.py +24 -0
  168. kafka/protocol/admin/groups.pyi +261 -0
  169. kafka/protocol/admin/topics.py +53 -0
  170. kafka/protocol/admin/topics.pyi +982 -0
  171. kafka/protocol/admin/transactions.py +18 -0
  172. kafka/protocol/admin/transactions.pyi +311 -0
  173. kafka/protocol/admin/users.py +14 -0
  174. kafka/protocol/admin/users.pyi +223 -0
  175. kafka/protocol/api_data.py +125 -0
  176. kafka/protocol/api_header.py +55 -0
  177. kafka/protocol/api_key.py +97 -0
  178. kafka/protocol/api_message.py +277 -0
  179. kafka/protocol/broker_version_data.py +246 -0
  180. kafka/protocol/consumer/__init__.py +13 -0
  181. kafka/protocol/consumer/fetch.py +16 -0
  182. kafka/protocol/consumer/fetch.pyi +298 -0
  183. kafka/protocol/consumer/group.py +38 -0
  184. kafka/protocol/consumer/group.pyi +824 -0
  185. kafka/protocol/consumer/metadata.py +30 -0
  186. kafka/protocol/consumer/metadata.pyi +89 -0
  187. kafka/protocol/consumer/offsets.py +75 -0
  188. kafka/protocol/consumer/offsets.pyi +288 -0
  189. kafka/protocol/data_container.py +166 -0
  190. kafka/protocol/frame.py +30 -0
  191. kafka/protocol/generate_stubs.py +468 -0
  192. kafka/protocol/metadata/__init__.py +10 -0
  193. kafka/protocol/metadata/api_versions.py +41 -0
  194. kafka/protocol/metadata/api_versions.pyi +128 -0
  195. kafka/protocol/metadata/find_coordinator.py +19 -0
  196. kafka/protocol/metadata/find_coordinator.pyi +105 -0
  197. kafka/protocol/metadata/metadata.py +34 -0
  198. kafka/protocol/metadata/metadata.pyi +160 -0
  199. kafka/protocol/old/__init__.py +0 -0
  200. kafka/protocol/old/abstract.py +17 -0
  201. kafka/protocol/old/add_offsets_to_txn.py +54 -0
  202. kafka/protocol/old/add_partitions_to_txn.py +71 -0
  203. kafka/protocol/old/admin.py +1086 -0
  204. kafka/protocol/old/api.py +205 -0
  205. kafka/protocol/old/api_versions.py +133 -0
  206. kafka/protocol/old/commit.py +355 -0
  207. kafka/protocol/old/consumer_protocol.py +36 -0
  208. kafka/protocol/old/end_txn.py +53 -0
  209. kafka/protocol/old/fetch.py +408 -0
  210. kafka/protocol/old/find_coordinator.py +72 -0
  211. kafka/protocol/old/group.py +451 -0
  212. kafka/protocol/old/init_producer_id.py +42 -0
  213. kafka/protocol/old/list_offsets.py +186 -0
  214. kafka/protocol/old/metadata.py +290 -0
  215. kafka/protocol/old/offset_for_leader_epoch.py +133 -0
  216. kafka/protocol/old/produce.py +247 -0
  217. kafka/protocol/old/sasl_authenticate.py +38 -0
  218. kafka/protocol/old/sasl_handshake.py +39 -0
  219. kafka/protocol/old/struct.py +87 -0
  220. kafka/protocol/old/txn_offset_commit.py +73 -0
  221. kafka/protocol/old/types.py +440 -0
  222. kafka/protocol/parser.py +191 -0
  223. kafka/protocol/producer/__init__.py +7 -0
  224. kafka/protocol/producer/produce.py +17 -0
  225. kafka/protocol/producer/produce.pyi +197 -0
  226. kafka/protocol/producer/transaction.py +30 -0
  227. kafka/protocol/producer/transaction.pyi +663 -0
  228. kafka/protocol/sasl.py +52 -0
  229. kafka/protocol/sasl.pyi +126 -0
  230. kafka/protocol/schemas/__init__.py +7 -0
  231. kafka/protocol/schemas/fields/__init__.py +7 -0
  232. kafka/protocol/schemas/fields/array.py +127 -0
  233. kafka/protocol/schemas/fields/base.py +156 -0
  234. kafka/protocol/schemas/fields/codecs/__init__.py +12 -0
  235. kafka/protocol/schemas/fields/codecs/encode_buffer.py +82 -0
  236. kafka/protocol/schemas/fields/codecs/tagged_fields.py +109 -0
  237. kafka/protocol/schemas/fields/codecs/types.py +505 -0
  238. kafka/protocol/schemas/fields/codegen.py +40 -0
  239. kafka/protocol/schemas/fields/simple.py +127 -0
  240. kafka/protocol/schemas/fields/struct.py +357 -0
  241. kafka/protocol/schemas/fields/struct_array.py +142 -0
  242. kafka/protocol/schemas/load_json.py +42 -0
  243. kafka/protocol/schemas/resources/AddOffsetsToTxnRequest.json +40 -0
  244. kafka/protocol/schemas/resources/AddOffsetsToTxnResponse.json +35 -0
  245. kafka/protocol/schemas/resources/AddPartitionsToTxnRequest.json +65 -0
  246. kafka/protocol/schemas/resources/AddPartitionsToTxnResponse.json +60 -0
  247. kafka/protocol/schemas/resources/AlterClientQuotasRequest.json +47 -0
  248. kafka/protocol/schemas/resources/AlterClientQuotasResponse.json +41 -0
  249. kafka/protocol/schemas/resources/AlterConfigsRequest.json +43 -0
  250. kafka/protocol/schemas/resources/AlterConfigsResponse.json +39 -0
  251. kafka/protocol/schemas/resources/AlterPartitionReassignmentsRequest.json +42 -0
  252. kafka/protocol/schemas/resources/AlterPartitionReassignmentsResponse.json +47 -0
  253. kafka/protocol/schemas/resources/AlterReplicaLogDirsRequest.json +41 -0
  254. kafka/protocol/schemas/resources/AlterReplicaLogDirsResponse.json +41 -0
  255. kafka/protocol/schemas/resources/AlterUserScramCredentialsRequest.json +45 -0
  256. kafka/protocol/schemas/resources/AlterUserScramCredentialsResponse.json +35 -0
  257. kafka/protocol/schemas/resources/ApiVersionsRequest.json +34 -0
  258. kafka/protocol/schemas/resources/ApiVersionsResponse.json +79 -0
  259. kafka/protocol/schemas/resources/ConsumerProtocolAssignment.json +42 -0
  260. kafka/protocol/schemas/resources/ConsumerProtocolSubscription.json +49 -0
  261. kafka/protocol/schemas/resources/CreateAclsRequest.json +46 -0
  262. kafka/protocol/schemas/resources/CreateAclsResponse.json +37 -0
  263. kafka/protocol/schemas/resources/CreatePartitionsRequest.json +47 -0
  264. kafka/protocol/schemas/resources/CreatePartitionsResponse.json +41 -0
  265. kafka/protocol/schemas/resources/CreateTopicsRequest.json +65 -0
  266. kafka/protocol/schemas/resources/CreateTopicsResponse.json +72 -0
  267. kafka/protocol/schemas/resources/DeleteAclsRequest.json +46 -0
  268. kafka/protocol/schemas/resources/DeleteAclsResponse.json +59 -0
  269. kafka/protocol/schemas/resources/DeleteGroupsRequest.json +30 -0
  270. kafka/protocol/schemas/resources/DeleteGroupsResponse.json +36 -0
  271. kafka/protocol/schemas/resources/DeleteRecordsRequest.json +42 -0
  272. kafka/protocol/schemas/resources/DeleteRecordsResponse.json +43 -0
  273. kafka/protocol/schemas/resources/DeleteTopicsRequest.json +43 -0
  274. kafka/protocol/schemas/resources/DeleteTopicsResponse.json +52 -0
  275. kafka/protocol/schemas/resources/DescribeAclsRequest.json +43 -0
  276. kafka/protocol/schemas/resources/DescribeAclsResponse.json +55 -0
  277. kafka/protocol/schemas/resources/DescribeClientQuotasRequest.json +37 -0
  278. kafka/protocol/schemas/resources/DescribeClientQuotasResponse.json +47 -0
  279. kafka/protocol/schemas/resources/DescribeClusterRequest.json +35 -0
  280. kafka/protocol/schemas/resources/DescribeClusterResponse.json +56 -0
  281. kafka/protocol/schemas/resources/DescribeConfigsRequest.json +42 -0
  282. kafka/protocol/schemas/resources/DescribeConfigsResponse.json +69 -0
  283. kafka/protocol/schemas/resources/DescribeGroupsRequest.json +38 -0
  284. kafka/protocol/schemas/resources/DescribeGroupsResponse.json +74 -0
  285. kafka/protocol/schemas/resources/DescribeLogDirsRequest.json +38 -0
  286. kafka/protocol/schemas/resources/DescribeLogDirsResponse.json +65 -0
  287. kafka/protocol/schemas/resources/DescribeProducersRequest.json +32 -0
  288. kafka/protocol/schemas/resources/DescribeProducersResponse.json +55 -0
  289. kafka/protocol/schemas/resources/DescribeQuorumRequest.json +39 -0
  290. kafka/protocol/schemas/resources/DescribeQuorumResponse.json +82 -0
  291. kafka/protocol/schemas/resources/DescribeTopicPartitionsRequest.json +40 -0
  292. kafka/protocol/schemas/resources/DescribeTopicPartitionsResponse.json +66 -0
  293. kafka/protocol/schemas/resources/DescribeTransactionsRequest.json +27 -0
  294. kafka/protocol/schemas/resources/DescribeTransactionsResponse.json +52 -0
  295. kafka/protocol/schemas/resources/DescribeUserScramCredentialsRequest.json +30 -0
  296. kafka/protocol/schemas/resources/DescribeUserScramCredentialsResponse.json +45 -0
  297. kafka/protocol/schemas/resources/ElectLeadersRequest.json +41 -0
  298. kafka/protocol/schemas/resources/ElectLeadersResponse.json +45 -0
  299. kafka/protocol/schemas/resources/EndTxnRequest.json +43 -0
  300. kafka/protocol/schemas/resources/EndTxnResponse.json +41 -0
  301. kafka/protocol/schemas/resources/FetchRequest.json +125 -0
  302. kafka/protocol/schemas/resources/FetchResponse.json +124 -0
  303. kafka/protocol/schemas/resources/FindCoordinatorRequest.json +43 -0
  304. kafka/protocol/schemas/resources/FindCoordinatorResponse.json +58 -0
  305. kafka/protocol/schemas/resources/HeartbeatRequest.json +39 -0
  306. kafka/protocol/schemas/resources/HeartbeatResponse.json +35 -0
  307. kafka/protocol/schemas/resources/IncrementalAlterConfigsRequest.json +44 -0
  308. kafka/protocol/schemas/resources/IncrementalAlterConfigsResponse.json +38 -0
  309. kafka/protocol/schemas/resources/InitProducerIdRequest.json +50 -0
  310. kafka/protocol/schemas/resources/InitProducerIdResponse.json +47 -0
  311. kafka/protocol/schemas/resources/JoinGroupRequest.json +63 -0
  312. kafka/protocol/schemas/resources/JoinGroupResponse.json +69 -0
  313. kafka/protocol/schemas/resources/LeaveGroupRequest.json +47 -0
  314. kafka/protocol/schemas/resources/LeaveGroupResponse.json +47 -0
  315. kafka/protocol/schemas/resources/ListConfigResourcesRequest.json +31 -0
  316. kafka/protocol/schemas/resources/ListConfigResourcesResponse.json +37 -0
  317. kafka/protocol/schemas/resources/ListGroupsRequest.json +36 -0
  318. kafka/protocol/schemas/resources/ListGroupsResponse.json +49 -0
  319. kafka/protocol/schemas/resources/ListOffsetsRequest.json +72 -0
  320. kafka/protocol/schemas/resources/ListOffsetsResponse.json +71 -0
  321. kafka/protocol/schemas/resources/ListPartitionReassignmentsRequest.json +34 -0
  322. kafka/protocol/schemas/resources/ListPartitionReassignmentsResponse.json +46 -0
  323. kafka/protocol/schemas/resources/ListTransactionsRequest.json +40 -0
  324. kafka/protocol/schemas/resources/ListTransactionsResponse.json +42 -0
  325. kafka/protocol/schemas/resources/MetadataRequest.json +56 -0
  326. kafka/protocol/schemas/resources/MetadataResponse.json +101 -0
  327. kafka/protocol/schemas/resources/OffsetCommitRequest.json +76 -0
  328. kafka/protocol/schemas/resources/OffsetCommitResponse.json +71 -0
  329. kafka/protocol/schemas/resources/OffsetDeleteRequest.json +39 -0
  330. kafka/protocol/schemas/resources/OffsetDeleteResponse.json +42 -0
  331. kafka/protocol/schemas/resources/OffsetFetchRequest.json +76 -0
  332. kafka/protocol/schemas/resources/OffsetFetchResponse.json +107 -0
  333. kafka/protocol/schemas/resources/OffsetForLeaderEpochRequest.json +52 -0
  334. kafka/protocol/schemas/resources/OffsetForLeaderEpochResponse.json +51 -0
  335. kafka/protocol/schemas/resources/ProduceRequest.json +73 -0
  336. kafka/protocol/schemas/resources/ProduceResponse.json +96 -0
  337. kafka/protocol/schemas/resources/RequestHeader.json +44 -0
  338. kafka/protocol/schemas/resources/ResponseHeader.json +26 -0
  339. kafka/protocol/schemas/resources/SaslAuthenticateRequest.json +29 -0
  340. kafka/protocol/schemas/resources/SaslAuthenticateResponse.json +34 -0
  341. kafka/protocol/schemas/resources/SaslHandshakeRequest.json +31 -0
  342. kafka/protocol/schemas/resources/SaslHandshakeResponse.json +32 -0
  343. kafka/protocol/schemas/resources/SyncGroupRequest.json +56 -0
  344. kafka/protocol/schemas/resources/SyncGroupResponse.json +46 -0
  345. kafka/protocol/schemas/resources/TxnOffsetCommitRequest.json +68 -0
  346. kafka/protocol/schemas/resources/TxnOffsetCommitResponse.json +47 -0
  347. kafka/protocol/schemas/resources/UpdateFeaturesRequest.json +43 -0
  348. kafka/protocol/schemas/resources/UpdateFeaturesResponse.json +39 -0
  349. kafka/protocol/schemas/resources/WriteTxnMarkersRequest.json +49 -0
  350. kafka/protocol/schemas/resources/WriteTxnMarkersResponse.json +45 -0
  351. kafka/protocol/schemas/resources/__init__.py +0 -0
  352. kafka/record/__init__.py +3 -0
  353. kafka/record/_crc32c.py +161 -0
  354. kafka/record/abc.py +144 -0
  355. kafka/record/default_records.py +782 -0
  356. kafka/record/legacy_records.py +587 -0
  357. kafka/record/memory_records.py +255 -0
  358. kafka/record/util.py +135 -0
  359. kafka/serializer/__init__.py +4 -0
  360. kafka/serializer/abstract.py +20 -0
  361. kafka/serializer/default.py +16 -0
  362. kafka/serializer/json.py +17 -0
  363. kafka/serializer/wrapper.py +21 -0
  364. kafka/structs.py +69 -0
  365. kafka/util.py +159 -0
  366. kafka/vendor/__init__.py +0 -0
  367. kafka/version.py +1 -0
  368. kafka_python-3.0.0.dist-info/METADATA +319 -0
  369. kafka_python-3.0.0.dist-info/RECORD +373 -0
  370. kafka_python-3.0.0.dist-info/WHEEL +5 -0
  371. kafka_python-3.0.0.dist-info/entry_points.txt +2 -0
  372. kafka_python-3.0.0.dist-info/licenses/LICENSE +202 -0
  373. kafka_python-3.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,95 @@
1
+ import struct
2
+
3
+ # needed for SASL_GSSAPI authentication:
4
+ try:
5
+ import gssapi
6
+ from gssapi.raw.misc import GSSError
7
+ except (ImportError, OSError):
8
+ #no gssapi available, will disable gssapi mechanism
9
+ gssapi = None
10
+ GSSError = None
11
+
12
+ from .abc import SaslMechanism
13
+
14
+
15
+ class SaslMechanismGSSAPI(SaslMechanism):
16
+ # Establish security context and negotiate protection level
17
+ # For reference RFC 2222, section 7.2.1
18
+
19
+ SASL_QOP_AUTH = 1
20
+ SASL_QOP_AUTH_INT = 2
21
+ SASL_QOP_AUTH_CONF = 4
22
+
23
+ def __init__(self, **config):
24
+ if gssapi is None:
25
+ raise RuntimeError('GSSAPI lib not found!')
26
+ if 'sasl_kerberos_name' not in config and 'sasl_kerberos_service_name' not in config:
27
+ raise ValueError('sasl_kerberos_service_name or sasl_kerberos_name required for GSSAPI sasl configuration')
28
+ self._is_done = False
29
+ self._is_authenticated = False
30
+ self.gssapi_name = None
31
+ if config.get('sasl_kerberos_name', None) is not None:
32
+ self.auth_id = str(config['sasl_kerberos_name'])
33
+ if isinstance(config['sasl_kerberos_name'], gssapi.Name):
34
+ self.gssapi_name = config['sasl_kerberos_name']
35
+ else:
36
+ kerberos_domain_name = config.get('sasl_kerberos_domain_name', '') or config.get('host', '')
37
+ self.auth_id = config['sasl_kerberos_service_name'] + '@' + kerberos_domain_name
38
+ if self.gssapi_name is None:
39
+ self.gssapi_name = gssapi.Name(self.auth_id, name_type=gssapi.NameType.hostbased_service).canonicalize(gssapi.MechType.kerberos)
40
+ self._client_ctx = gssapi.SecurityContext(name=self.gssapi_name, usage='initiate')
41
+ self._next_token = self._client_ctx.step(None)
42
+
43
+ def auth_bytes(self):
44
+ # GSSAPI Auth does not have a final broker->client message
45
+ # so mark is_done after the final auth_bytes are provided
46
+ # in practice we'll still receive a response when using SaslAuthenticate
47
+ # but not when using the prior unframed approach.
48
+ if self._is_authenticated:
49
+ self._is_done = True
50
+ return self._next_token or b''
51
+
52
+ def receive(self, auth_bytes):
53
+ if not self._client_ctx.complete:
54
+ # The server will send a token back. Processing of this token either
55
+ # establishes a security context, or it needs further token exchange.
56
+ # The gssapi will be able to identify the needed next step.
57
+ self._next_token = self._client_ctx.step(auth_bytes)
58
+ elif self._is_done:
59
+ # The final step of gssapi is send, so we do not expect any additional bytes
60
+ # however, allow an empty message to support SaslAuthenticate response
61
+ if auth_bytes != b'':
62
+ raise ValueError("Unexpected receive auth_bytes after sasl/gssapi completion")
63
+ else:
64
+ # unwraps message containing supported protection levels and msg size
65
+ msg = self._client_ctx.unwrap(auth_bytes).message
66
+ # Kafka currently doesn't support integrity or confidentiality security layers, so we
67
+ # simply set QoP to 'auth' only (first octet). We reuse the max message size proposed
68
+ # by the server
69
+ client_flags = self.SASL_QOP_AUTH
70
+ server_flags = struct.Struct('>b').unpack(msg[0:1])[0]
71
+ message_parts = [
72
+ struct.Struct('>b').pack(client_flags & server_flags),
73
+ msg[1:], # always agree to max message size from server
74
+ self.auth_id.encode('utf-8'),
75
+ ]
76
+ # add authorization identity to the response, and GSS-wrap
77
+ self._next_token = self._client_ctx.wrap(b''.join(message_parts), False).message
78
+ # We need to identify the last token in auth_bytes();
79
+ # we can't rely on client_ctx.complete because it becomes True after generating
80
+ # the second-to-last token (after calling .step(auth_bytes) for the final time)
81
+ # We could introduce an additional state variable (i.e., self._final_token),
82
+ # but instead we just set _is_authenticated. Since the plugin interface does
83
+ # not read is_authenticated() until after is_done() is True, this should be fine.
84
+ self._is_authenticated = True
85
+
86
+ def is_done(self):
87
+ return self._is_done
88
+
89
+ def is_authenticated(self):
90
+ return self._is_authenticated
91
+
92
+ def auth_details(self):
93
+ if not self.is_authenticated:
94
+ raise RuntimeError('Not authenticated yet!')
95
+ return 'Authenticated as %s to %s via SASL / GSSAPI' % (self._client_ctx.initiator_name, self._client_ctx.target_name)
kafka/net/sasl/msk.py ADDED
@@ -0,0 +1,245 @@
1
+ import datetime
2
+ import hashlib
3
+ import hmac
4
+ import json
5
+ import logging
6
+ import string
7
+ import urllib
8
+
9
+ # needed for AWS_MSK_IAM authentication:
10
+ try:
11
+ from botocore.session import Session as BotoSession
12
+ except ImportError:
13
+ # no botocore available, will disable AWS_MSK_IAM mechanism
14
+ BotoSession = None
15
+
16
+ from .abc import SaslMechanism
17
+ from kafka.errors import KafkaConfigurationError
18
+
19
+
20
+ log = logging.getLogger(__name__)
21
+
22
+
23
+ class SaslMechanismAwsMskIam(SaslMechanism):
24
+ def __init__(self, **config):
25
+ if BotoSession is None:
26
+ raise RuntimeError('AWS_MSK_IAM requires the "botocore" package')
27
+ if config.get('security_protocol', '') != 'SASL_SSL':
28
+ raise KafkaConfigurationError('AWS_MSK_IAM requires SASL_SSL')
29
+ if 'host' not in config:
30
+ raise KafkaConfigurationError('AWS_MSK_IAM requires host configuration')
31
+ self.host = config['host']
32
+ self._auth = None
33
+ self._is_done = False
34
+ self._is_authenticated = False
35
+
36
+ def _build_client(self):
37
+ session = BotoSession()
38
+ credentials = session.get_credentials().get_frozen_credentials()
39
+ if not session.get_config_variable('region'):
40
+ raise KafkaConfigurationError('Unable to determine region for AWS MSK cluster. Is AWS_DEFAULT_REGION set?')
41
+ return AwsMskIamClient(
42
+ host=self.host,
43
+ access_key=credentials.access_key,
44
+ secret_key=credentials.secret_key,
45
+ region=session.get_config_variable('region'),
46
+ token=credentials.token,
47
+ )
48
+
49
+ def auth_bytes(self):
50
+ client = self._build_client()
51
+ log.debug("Generating auth token for MSK scope: %s", client._scope)
52
+ return client.first_message()
53
+
54
+ def receive(self, auth_bytes):
55
+ self._is_done = True
56
+ self._is_authenticated = auth_bytes != b''
57
+ self._auth = auth_bytes.decode('utf-8')
58
+
59
+ def is_done(self):
60
+ return self._is_done
61
+
62
+ def is_authenticated(self):
63
+ return self._is_authenticated
64
+
65
+ def auth_details(self):
66
+ if not self.is_authenticated:
67
+ raise RuntimeError('Not authenticated yet!')
68
+ return 'Authenticated via SASL / AWS_MSK_IAM %s' % (self._auth,)
69
+
70
+
71
+ class AwsMskIamClient:
72
+ UNRESERVED_CHARS = string.ascii_letters + string.digits + '-._~'
73
+
74
+ def __init__(self, host, access_key, secret_key, region, token=None):
75
+ """
76
+ Arguments:
77
+ host (str): The hostname of the broker.
78
+ access_key (str): An AWS_ACCESS_KEY_ID.
79
+ secret_key (str): An AWS_SECRET_ACCESS_KEY.
80
+ region (str): An AWS_REGION.
81
+ token (Optional[str]): An AWS_SESSION_TOKEN if using temporary
82
+ credentials.
83
+ """
84
+ self.algorithm = 'AWS4-HMAC-SHA256'
85
+ self.expires = '900'
86
+ self.hashfunc = hashlib.sha256
87
+ self.headers = [
88
+ ('host', host)
89
+ ]
90
+ self.version = '2020_10_22'
91
+
92
+ self.service = 'kafka-cluster'
93
+ self.action = '{}:Connect'.format(self.service)
94
+
95
+ now = datetime.datetime.utcnow()
96
+ self.datestamp = now.strftime('%Y%m%d')
97
+ self.timestamp = now.strftime('%Y%m%dT%H%M%SZ')
98
+
99
+ self.host = host
100
+ self.access_key = access_key
101
+ self.secret_key = secret_key
102
+ self.region = region
103
+ self.token = token
104
+
105
+ @property
106
+ def _credential(self):
107
+ return '{0.access_key}/{0._scope}'.format(self)
108
+
109
+ @property
110
+ def _scope(self):
111
+ return '{0.datestamp}/{0.region}/{0.service}/aws4_request'.format(self)
112
+
113
+ @property
114
+ def _signed_headers(self):
115
+ """
116
+ Returns (str):
117
+ An alphabetically sorted, semicolon-delimited list of lowercase
118
+ request header names.
119
+ """
120
+ return ';'.join(sorted(k.lower() for k, _ in self.headers))
121
+
122
+ @property
123
+ def _canonical_headers(self):
124
+ """
125
+ Returns (str):
126
+ A newline-delited list of header names and values.
127
+ Header names are lowercased.
128
+ """
129
+ return '\n'.join(map(':'.join, self.headers)) + '\n'
130
+
131
+ @property
132
+ def _canonical_request(self):
133
+ """
134
+ Returns (str):
135
+ An AWS Signature Version 4 canonical request in the format:
136
+ <Method>\n
137
+ <Path>\n
138
+ <CanonicalQueryString>\n
139
+ <CanonicalHeaders>\n
140
+ <SignedHeaders>\n
141
+ <HashedPayload>
142
+ """
143
+ # The hashed_payload is always an empty string for MSK.
144
+ hashed_payload = self.hashfunc(b'').hexdigest()
145
+ return '\n'.join((
146
+ 'GET',
147
+ '/',
148
+ self._canonical_querystring,
149
+ self._canonical_headers,
150
+ self._signed_headers,
151
+ hashed_payload,
152
+ ))
153
+
154
+ @property
155
+ def _canonical_querystring(self):
156
+ """
157
+ Returns (str):
158
+ A '&'-separated list of URI-encoded key/value pairs.
159
+ """
160
+ params = []
161
+ params.append(('Action', self.action))
162
+ params.append(('X-Amz-Algorithm', self.algorithm))
163
+ params.append(('X-Amz-Credential', self._credential))
164
+ params.append(('X-Amz-Date', self.timestamp))
165
+ params.append(('X-Amz-Expires', self.expires))
166
+ if self.token:
167
+ params.append(('X-Amz-Security-Token', self.token))
168
+ params.append(('X-Amz-SignedHeaders', self._signed_headers))
169
+
170
+ return '&'.join(self._uriencode(k) + '=' + self._uriencode(v) for k, v in params)
171
+
172
+ @property
173
+ def _signing_key(self):
174
+ """
175
+ Returns (bytes):
176
+ An AWS Signature V4 signing key generated from the secret_key, date,
177
+ region, service, and request type.
178
+ """
179
+ key = self._hmac(('AWS4' + self.secret_key).encode('utf-8'), self.datestamp)
180
+ key = self._hmac(key, self.region)
181
+ key = self._hmac(key, self.service)
182
+ key = self._hmac(key, 'aws4_request')
183
+ return key
184
+
185
+ @property
186
+ def _signing_str(self):
187
+ """
188
+ Returns (str):
189
+ A string used to sign the AWS Signature V4 payload in the format:
190
+ <Algorithm>\n
191
+ <Timestamp>\n
192
+ <Scope>\n
193
+ <CanonicalRequestHash>
194
+ """
195
+ canonical_request_hash = self.hashfunc(self._canonical_request.encode('utf-8')).hexdigest()
196
+ return '\n'.join((self.algorithm, self.timestamp, self._scope, canonical_request_hash))
197
+
198
+ def _uriencode(self, msg):
199
+ """
200
+ Arguments:
201
+ msg (str): A string to URI-encode.
202
+
203
+ Returns (str):
204
+ The URI-encoded version of the provided msg, following the encoding
205
+ rules specified: https://github.com/aws/aws-msk-iam-auth#uriencode
206
+ """
207
+ return urllib.parse.quote(msg, safe=self.UNRESERVED_CHARS)
208
+
209
+ def _hmac(self, key, msg):
210
+ """
211
+ Arguments:
212
+ key (bytes): A key to use for the HMAC digest.
213
+ msg (str): A value to include in the HMAC digest.
214
+ Returns (bytes):
215
+ An HMAC digest of the given key and msg.
216
+ """
217
+ return hmac.new(key, msg.encode('utf-8'), digestmod=self.hashfunc).digest()
218
+
219
+ def first_message(self):
220
+ """
221
+ Returns (bytes):
222
+ An encoded JSON authentication payload that can be sent to the
223
+ broker.
224
+ """
225
+ signature = hmac.new(
226
+ self._signing_key,
227
+ self._signing_str.encode('utf-8'),
228
+ digestmod=self.hashfunc,
229
+ ).hexdigest()
230
+ msg = {
231
+ 'version': self.version,
232
+ 'host': self.host,
233
+ 'user-agent': 'kafka-python',
234
+ 'action': self.action,
235
+ 'x-amz-algorithm': self.algorithm,
236
+ 'x-amz-credential': self._credential,
237
+ 'x-amz-date': self.timestamp,
238
+ 'x-amz-signedheaders': self._signed_headers,
239
+ 'x-amz-expires': self.expires,
240
+ 'x-amz-signature': signature,
241
+ }
242
+ if self.token:
243
+ msg['x-amz-security-token'] = self.token
244
+
245
+ return json.dumps(msg, separators=(',', ':')).encode('utf-8')
@@ -0,0 +1,98 @@
1
+ from abc import ABC, abstractmethod
2
+ import logging
3
+
4
+ from .abc import SaslMechanism
5
+ from kafka.errors import KafkaConfigurationError
6
+
7
+
8
+ log = logging.getLogger(__name__)
9
+
10
+
11
+ class SaslMechanismOAuth(SaslMechanism):
12
+
13
+ def __init__(self, **config):
14
+ if 'sasl_oauth_token_provider' not in config:
15
+ raise KafkaConfigurationError('sasl_oauth_token_provider required for OAUTHBEARER sasl')
16
+ if not isinstance(config['sasl_oauth_token_provider'], AbstractTokenProvider):
17
+ raise KafkaConfigurationError('sasl_oauth_token_provider must implement kafka.net.sasl.oauth.AbstractTokenProvider')
18
+ self.token_provider = config['sasl_oauth_token_provider']
19
+ self._error = None
20
+ self._is_done = False
21
+ self._is_authenticated = False
22
+
23
+ def auth_bytes(self):
24
+ if self._error:
25
+ # Server should respond to this with SaslAuthenticate failure, which ends the auth process
26
+ return self._error
27
+ token = self.token_provider.token()
28
+ extensions = self._token_extensions()
29
+ return "n,,\x01auth=Bearer {}{}\x01\x01".format(token, extensions).encode('utf-8')
30
+
31
+ def receive(self, auth_bytes):
32
+ if auth_bytes != b'':
33
+ error = auth_bytes.decode('utf-8')
34
+ log.debug("Sending x01 response to server after receiving SASL OAuth error: %s", error)
35
+ self._error = b'\x01'
36
+ else:
37
+ self._is_done = True
38
+ self._is_authenticated = True
39
+
40
+ def is_done(self):
41
+ return self._is_done
42
+
43
+ def is_authenticated(self):
44
+ return self._is_authenticated
45
+
46
+ def _token_extensions(self):
47
+ """
48
+ Return a string representation of the OPTIONAL key-value pairs that can be sent with an OAUTHBEARER
49
+ initial request.
50
+ """
51
+ # Builds up a string separated by \x01 via a dict of key value pairs
52
+ extensions = self.token_provider.extensions()
53
+ msg = '\x01'.join(['{}={}'.format(k, v) for k, v in extensions.items()])
54
+ return '\x01' + msg if msg else ''
55
+
56
+ def auth_details(self):
57
+ if not self.is_authenticated:
58
+ raise RuntimeError('Not authenticated yet!')
59
+ return 'Authenticated via SASL / OAuth'
60
+
61
+
62
+ class AbstractTokenProvider(ABC):
63
+ """
64
+ A Token Provider must be used for the SASL OAuthBearer protocol.
65
+
66
+ The implementation should ensure token reuse so that multiple
67
+ calls at connect time do not create multiple tokens. The implementation
68
+ should also periodically refresh the token in order to guarantee
69
+ that each call returns an unexpired token. A timeout error should
70
+ be returned after a short period of inactivity so that the
71
+ broker can log debugging info and retry.
72
+
73
+ Token Providers MUST implement the token() method
74
+ """
75
+
76
+ def __init__(self, **config):
77
+ pass
78
+
79
+ @abstractmethod
80
+ def token(self):
81
+ """
82
+ Returns a (str) ID/Access Token to be sent to the Kafka
83
+ client.
84
+ """
85
+ pass
86
+
87
+ def extensions(self):
88
+ """
89
+ This is an OPTIONAL method that may be implemented.
90
+
91
+ Returns a map of key-value pairs that can
92
+ be sent with the SASL/OAUTHBEARER initial client request. If
93
+ not implemented, the values are ignored. This feature is only available
94
+ in Kafka >= 2.1.0.
95
+
96
+ All returned keys and values should be type str
97
+ """
98
+ return {}
@@ -0,0 +1,42 @@
1
+ import logging
2
+
3
+ from .abc import SaslMechanism
4
+ from kafka.errors import KafkaConfigurationError
5
+
6
+
7
+ log = logging.getLogger(__name__)
8
+
9
+
10
+ class SaslMechanismPlain(SaslMechanism):
11
+
12
+ def __init__(self, **config):
13
+ if config.get('security_protocol', '') == 'SASL_PLAINTEXT':
14
+ log.warning('Sending username and password in the clear')
15
+ if 'sasl_plain_username' not in config:
16
+ raise KafkaConfigurationError('sasl_plain_username required for PLAIN sasl')
17
+ if 'sasl_plain_password' not in config:
18
+ raise KafkaConfigurationError('sasl_plain_password required for PLAIN sasl')
19
+
20
+ self.username = config['sasl_plain_username']
21
+ self.password = config['sasl_plain_password']
22
+ self._is_done = False
23
+ self._is_authenticated = False
24
+
25
+ def auth_bytes(self):
26
+ # Send PLAIN credentials per RFC-4616
27
+ return bytes('\0'.join([self.username, self.username, self.password]).encode('utf-8'))
28
+
29
+ def receive(self, auth_bytes):
30
+ self._is_done = True
31
+ self._is_authenticated = auth_bytes == b''
32
+
33
+ def is_done(self):
34
+ return self._is_done
35
+
36
+ def is_authenticated(self):
37
+ return self._is_authenticated
38
+
39
+ def auth_details(self):
40
+ if not self.is_authenticated:
41
+ raise RuntimeError('Not authenticated yet!')
42
+ return 'Authenticated as %s via SASL / Plain' % self.username
@@ -0,0 +1,135 @@
1
+ import base64
2
+ import hashlib
3
+ import hmac
4
+ import logging
5
+ import uuid
6
+
7
+
8
+ from .abc import SaslMechanism
9
+ from kafka.errors import KafkaConfigurationError
10
+
11
+
12
+ log = logging.getLogger(__name__)
13
+
14
+
15
+ def xor_bytes(left, right):
16
+ return bytes(lb ^ rb for lb, rb in zip(left, right))
17
+
18
+
19
+ class SaslMechanismScram(SaslMechanism):
20
+ def __init__(self, **config):
21
+ if not config.get('sasl_plain_username', ''):
22
+ KafkaConfigurationError('sasl_plain_username required for SCRAM sasl')
23
+ if not config.get('sasl_plain_password', ''):
24
+ KafkaConfigurationError('sasl_plain_password required for SCRAM sasl')
25
+ if config.get('sasl_mechanism', '') not in ScramClient.MECHANISMS:
26
+ KafkaConfigurationError('Unrecognized SCRAM mechanism')
27
+ if config.get('security_protocol', '') == 'SASL_PLAINTEXT':
28
+ log.warning('Exchanging credentials in the clear during Sasl Authentication')
29
+
30
+ self.username = config['sasl_plain_username']
31
+ self.mechanism = config['sasl_mechanism']
32
+ self._scram_client = ScramClient(
33
+ config['sasl_plain_username'],
34
+ config['sasl_plain_password'],
35
+ config['sasl_mechanism']
36
+ )
37
+ self._state = 0
38
+
39
+ def auth_bytes(self):
40
+ if self._state == 0:
41
+ return self._scram_client.first_message()
42
+ elif self._state == 1:
43
+ return self._scram_client.final_message()
44
+ else:
45
+ raise ValueError('No auth_bytes for state: %s' % self._state)
46
+
47
+ def receive(self, auth_bytes):
48
+ if self._state == 0:
49
+ self._scram_client.process_server_first_message(auth_bytes)
50
+ elif self._state == 1:
51
+ self._scram_client.process_server_final_message(auth_bytes)
52
+ else:
53
+ raise ValueError('Cannot receive bytes in state: %s' % self._state)
54
+ self._state += 1
55
+ return self.is_done()
56
+
57
+ def is_done(self):
58
+ return self._state == 2
59
+
60
+ def is_authenticated(self):
61
+ # receive raises if authentication fails...?
62
+ return self._state == 2
63
+
64
+ def auth_details(self):
65
+ if not self.is_authenticated:
66
+ raise RuntimeError('Not authenticated yet!')
67
+ return 'Authenticated as %s via SASL / %s' % (self.username, self.mechanism)
68
+
69
+
70
+ class ScramClient:
71
+ MECHANISMS = {
72
+ 'SCRAM-SHA-256': hashlib.sha256,
73
+ 'SCRAM-SHA-512': hashlib.sha512
74
+ }
75
+
76
+ def __init__(self, user, password, mechanism):
77
+ self.nonce = str(uuid.uuid4()).replace('-', '').encode('utf-8')
78
+ self.auth_message = b''
79
+ self.salted_password = None
80
+ self.user = user.encode('utf-8')
81
+ self.password = password.encode('utf-8')
82
+ self.hashfunc = self.MECHANISMS[mechanism]
83
+ self.hashname = ''.join(mechanism.lower().split('-')[1:3])
84
+ self.stored_key = None
85
+ self.client_key = None
86
+ self.client_signature = None
87
+ self.client_proof = None
88
+ self.server_key = None
89
+ self.server_signature = None
90
+
91
+ def first_message(self):
92
+ client_first_bare = b'n=' + self.user + b',r=' + self.nonce
93
+ self.auth_message += client_first_bare
94
+ return b'n,,' + client_first_bare
95
+
96
+ def process_server_first_message(self, server_first_message):
97
+ self.auth_message += b',' + server_first_message
98
+ params = dict(pair.split('=', 1) for pair in server_first_message.decode('utf-8').split(','))
99
+ server_nonce = params['r'].encode('utf-8')
100
+ if not server_nonce.startswith(self.nonce):
101
+ raise ValueError("Server nonce, did not start with client nonce!")
102
+ self.nonce = server_nonce
103
+ self.auth_message += b',c=biws,r=' + self.nonce
104
+
105
+ salt = base64.b64decode(params['s'].encode('utf-8'))
106
+ try:
107
+ iterations = int(params['i'])
108
+ if iterations > 1000000:
109
+ raise ValueError('too many iterations')
110
+ except (TypeError, ValueError):
111
+ raise ValueError('Invalid value (not integer or too large) for Iteration count in server-first-message')
112
+ self.create_salted_password(salt, iterations)
113
+
114
+ self.client_key = self.hmac(self.salted_password, b'Client Key')
115
+ self.stored_key = self.hashfunc(self.client_key).digest()
116
+ self.client_signature = self.hmac(self.stored_key, self.auth_message)
117
+ self.client_proof = xor_bytes(self.client_key, self.client_signature)
118
+ self.server_key = self.hmac(self.salted_password, b'Server Key')
119
+ self.server_signature = self.hmac(self.server_key, self.auth_message)
120
+
121
+ def hmac(self, key, msg):
122
+ return hmac.new(key, msg, digestmod=self.hashfunc).digest()
123
+
124
+ def create_salted_password(self, salt, iterations):
125
+ self.salted_password = hashlib.pbkdf2_hmac(
126
+ self.hashname, self.password, salt, iterations
127
+ )
128
+
129
+ def final_message(self):
130
+ return b'c=biws,r=' + self.nonce + b',p=' + base64.b64encode(self.client_proof)
131
+
132
+ def process_server_final_message(self, server_final_message):
133
+ params = dict(pair.split('=', 1) for pair in server_final_message.decode('utf-8').split(','))
134
+ if self.server_signature != base64.b64decode(params['v'].encode('utf-8')):
135
+ raise ValueError("Server sent wrong signature!")