mongo 2.13.0 → 2.15.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (375) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +1 -4
  3. data.tar.gz.sig +0 -0
  4. data/README.md +4 -1
  5. data/Rakefile +46 -18
  6. data/lib/mongo.rb +32 -0
  7. data/lib/mongo/address.rb +1 -1
  8. data/lib/mongo/address/ipv4.rb +1 -1
  9. data/lib/mongo/address/ipv6.rb +1 -1
  10. data/lib/mongo/auth/aws/conversation.rb +1 -4
  11. data/lib/mongo/auth/base.rb +13 -7
  12. data/lib/mongo/auth/conversation_base.rb +32 -0
  13. data/lib/mongo/auth/cr/conversation.rb +6 -29
  14. data/lib/mongo/auth/gssapi/conversation.rb +4 -15
  15. data/lib/mongo/auth/ldap/conversation.rb +3 -14
  16. data/lib/mongo/auth/sasl_conversation_base.rb +1 -13
  17. data/lib/mongo/auth/scram_conversation_base.rb +7 -34
  18. data/lib/mongo/auth/user/view.rb +16 -9
  19. data/lib/mongo/auth/x509/conversation.rb +4 -25
  20. data/lib/mongo/background_thread.rb +11 -0
  21. data/lib/mongo/bulk_write.rb +38 -18
  22. data/lib/mongo/caching_cursor.rb +74 -0
  23. data/lib/mongo/client.rb +142 -16
  24. data/lib/mongo/cluster.rb +22 -31
  25. data/lib/mongo/cluster/reapers/cursor_reaper.rb +6 -2
  26. data/lib/mongo/cluster/sdam_flow.rb +14 -0
  27. data/lib/mongo/cluster/topology/single.rb +1 -1
  28. data/lib/mongo/collection.rb +58 -18
  29. data/lib/mongo/collection/view.rb +24 -20
  30. data/lib/mongo/collection/view/aggregation.rb +26 -5
  31. data/lib/mongo/collection/view/builder/find_command.rb +38 -18
  32. data/lib/mongo/collection/view/change_stream.rb +1 -1
  33. data/lib/mongo/collection/view/explainable.rb +27 -8
  34. data/lib/mongo/collection/view/iterable.rb +73 -13
  35. data/lib/mongo/collection/view/map_reduce.rb +2 -2
  36. data/lib/mongo/collection/view/readable.rb +57 -21
  37. data/lib/mongo/collection/view/writable.rb +29 -15
  38. data/lib/mongo/crypt/encryption_io.rb +6 -6
  39. data/lib/mongo/cursor.rb +18 -5
  40. data/lib/mongo/database.rb +28 -5
  41. data/lib/mongo/database/view.rb +2 -2
  42. data/lib/mongo/error.rb +11 -1
  43. data/lib/mongo/error/bulk_write_error.rb +17 -3
  44. data/lib/mongo/error/internal_driver_error.rb +22 -0
  45. data/lib/mongo/error/invalid_read_concern.rb +28 -0
  46. data/lib/mongo/error/operation_failure.rb +26 -7
  47. data/lib/mongo/error/parser.rb +65 -12
  48. data/lib/mongo/error/server_api_conflict.rb +23 -0
  49. data/lib/mongo/error/server_api_not_supported.rb +24 -0
  50. data/lib/mongo/error/server_certificate_revoked.rb +22 -0
  51. data/lib/mongo/error/unmet_dependency.rb +21 -0
  52. data/lib/mongo/error/unsupported_option.rb +14 -12
  53. data/lib/mongo/grid/fs_bucket.rb +37 -37
  54. data/lib/mongo/index/view.rb +21 -11
  55. data/lib/mongo/lint.rb +2 -1
  56. data/lib/mongo/logger.rb +3 -3
  57. data/lib/mongo/monitoring.rb +13 -4
  58. data/lib/mongo/monitoring/event/server_heartbeat_failed.rb +27 -16
  59. data/lib/mongo/monitoring/event/server_heartbeat_succeeded.rb +26 -15
  60. data/lib/mongo/operation.rb +4 -2
  61. data/lib/mongo/operation/aggregate/result.rb +9 -8
  62. data/lib/mongo/operation/collections_info.rb +18 -1
  63. data/lib/mongo/operation/collections_info/command.rb +5 -0
  64. data/lib/mongo/operation/collections_info/result.rb +18 -1
  65. data/lib/mongo/operation/context.rb +99 -0
  66. data/lib/mongo/operation/delete/bulk_result.rb +2 -0
  67. data/lib/mongo/operation/delete/result.rb +3 -0
  68. data/lib/mongo/operation/explain/command.rb +4 -0
  69. data/lib/mongo/operation/explain/legacy.rb +4 -0
  70. data/lib/mongo/operation/explain/op_msg.rb +6 -0
  71. data/lib/mongo/operation/explain/result.rb +3 -0
  72. data/lib/mongo/operation/find/legacy/result.rb +2 -0
  73. data/lib/mongo/operation/find/result.rb +13 -0
  74. data/lib/mongo/operation/get_more/result.rb +3 -0
  75. data/lib/mongo/operation/indexes.rb +15 -1
  76. data/lib/mongo/operation/indexes/result.rb +5 -0
  77. data/lib/mongo/operation/insert/bulk_result.rb +5 -0
  78. data/lib/mongo/operation/insert/command.rb +2 -2
  79. data/lib/mongo/operation/insert/legacy.rb +2 -2
  80. data/lib/mongo/operation/insert/op_msg.rb +2 -2
  81. data/lib/mongo/operation/insert/result.rb +5 -0
  82. data/lib/mongo/operation/list_collections/result.rb +9 -1
  83. data/lib/mongo/operation/map_reduce/result.rb +10 -0
  84. data/lib/mongo/operation/parallel_scan/result.rb +4 -0
  85. data/lib/mongo/operation/result.rb +37 -6
  86. data/lib/mongo/operation/shared/bypass_document_validation.rb +1 -0
  87. data/lib/mongo/operation/shared/causal_consistency_supported.rb +1 -0
  88. data/lib/mongo/operation/shared/executable.rb +25 -14
  89. data/lib/mongo/operation/shared/executable_no_validate.rb +2 -2
  90. data/lib/mongo/operation/shared/idable.rb +2 -1
  91. data/lib/mongo/operation/shared/limited.rb +1 -0
  92. data/lib/mongo/operation/shared/object_id_generator.rb +1 -0
  93. data/lib/mongo/operation/shared/op_msg_or_command.rb +1 -7
  94. data/lib/mongo/operation/shared/op_msg_or_find_command.rb +1 -7
  95. data/lib/mongo/operation/shared/polymorphic_operation.rb +39 -0
  96. data/lib/mongo/operation/shared/response_handling.rb +23 -23
  97. data/lib/mongo/operation/shared/result/aggregatable.rb +1 -0
  98. data/lib/mongo/operation/shared/sessions_supported.rb +14 -2
  99. data/lib/mongo/operation/shared/specifiable.rb +1 -0
  100. data/lib/mongo/operation/shared/write.rb +9 -18
  101. data/lib/mongo/operation/shared/write_concern_supported.rb +1 -0
  102. data/lib/mongo/operation/update/legacy/result.rb +7 -0
  103. data/lib/mongo/operation/update/result.rb +8 -0
  104. data/lib/mongo/operation/users_info/result.rb +3 -0
  105. data/lib/mongo/protocol/compressed.rb +51 -5
  106. data/lib/mongo/protocol/message.rb +31 -4
  107. data/lib/mongo/protocol/msg.rb +37 -12
  108. data/lib/mongo/protocol/query.rb +36 -0
  109. data/lib/mongo/query_cache.rb +272 -0
  110. data/lib/mongo/retryable.rb +9 -2
  111. data/lib/mongo/server.rb +12 -16
  112. data/lib/mongo/server/app_metadata.rb +52 -18
  113. data/lib/mongo/server/connection.rb +5 -0
  114. data/lib/mongo/server/connection_base.rb +16 -15
  115. data/lib/mongo/server/connection_common.rb +2 -2
  116. data/lib/mongo/server/connection_pool.rb +9 -4
  117. data/lib/mongo/server/description.rb +12 -1
  118. data/lib/mongo/server/description/features.rb +9 -8
  119. data/lib/mongo/server/monitor.rb +21 -2
  120. data/lib/mongo/server/monitor/app_metadata.rb +1 -1
  121. data/lib/mongo/server/monitor/connection.rb +12 -13
  122. data/lib/mongo/server/pending_connection.rb +26 -8
  123. data/lib/mongo/server/push_monitor.rb +12 -2
  124. data/lib/mongo/server_selector/base.rb +5 -1
  125. data/lib/mongo/session.rb +7 -3
  126. data/lib/mongo/session/session_pool.rb +4 -2
  127. data/lib/mongo/socket.rb +35 -8
  128. data/lib/mongo/socket/ocsp_cache.rb +97 -0
  129. data/lib/mongo/socket/ocsp_verifier.rb +368 -0
  130. data/lib/mongo/socket/ssl.rb +53 -24
  131. data/lib/mongo/srv/monitor.rb +7 -24
  132. data/lib/mongo/srv/resolver.rb +14 -10
  133. data/lib/mongo/timeout.rb +2 -0
  134. data/lib/mongo/uri.rb +21 -390
  135. data/lib/mongo/uri/options_mapper.rb +620 -0
  136. data/lib/mongo/uri/srv_protocol.rb +3 -2
  137. data/lib/mongo/utils.rb +27 -1
  138. data/lib/mongo/version.rb +1 -1
  139. data/spec/NOTES.aws-auth.md +12 -7
  140. data/spec/README.md +87 -2
  141. data/spec/integration/auth_spec.rb +25 -15
  142. data/spec/integration/bson_symbol_spec.rb +4 -2
  143. data/spec/integration/bulk_write_error_message_spec.rb +41 -0
  144. data/spec/integration/bulk_write_spec.rb +48 -0
  145. data/spec/integration/change_stream_spec.rb +5 -5
  146. data/spec/integration/client_authentication_options_spec.rb +92 -28
  147. data/spec/integration/client_side_encryption/auto_encryption_bulk_writes_spec.rb +6 -2
  148. data/spec/integration/command_monitoring_spec.rb +2 -2
  149. data/spec/integration/connection_pool_populator_spec.rb +4 -2
  150. data/spec/integration/connection_spec.rb +2 -0
  151. data/spec/integration/cursor_reaping_spec.rb +54 -18
  152. data/spec/integration/docs_examples_spec.rb +8 -1
  153. data/spec/integration/fork_reconnect_spec.rb +60 -2
  154. data/spec/integration/ocsp_connectivity_spec.rb +26 -0
  155. data/spec/integration/ocsp_verifier_cache_spec.rb +188 -0
  156. data/spec/integration/ocsp_verifier_spec.rb +340 -0
  157. data/spec/integration/operation_failure_code_spec.rb +1 -1
  158. data/spec/integration/operation_failure_message_spec.rb +90 -0
  159. data/spec/integration/query_cache_spec.rb +1045 -0
  160. data/spec/integration/query_cache_transactions_spec.rb +190 -0
  161. data/spec/integration/reconnect_spec.rb +1 -1
  162. data/spec/integration/retryable_writes/retryable_writes_40_and_newer_spec.rb +1 -0
  163. data/spec/integration/retryable_writes/shared/performs_legacy_retries.rb +2 -0
  164. data/spec/integration/sdam_error_handling_spec.rb +86 -1
  165. data/spec/integration/sdam_events_spec.rb +8 -7
  166. data/spec/integration/server_selection_spec.rb +36 -0
  167. data/spec/integration/size_limit_spec.rb +20 -19
  168. data/spec/integration/snappy_compression_spec.rb +25 -0
  169. data/spec/integration/srv_monitoring_spec.rb +39 -4
  170. data/spec/integration/srv_spec.rb +56 -0
  171. data/spec/integration/transactions_examples_spec.rb +23 -7
  172. data/spec/integration/zlib_compression_spec.rb +1 -1
  173. data/spec/integration/zstd_compression_spec.rb +26 -0
  174. data/spec/lite_spec_helper.rb +15 -5
  175. data/spec/mongo/address_spec.rb +16 -12
  176. data/spec/mongo/auth/ldap/conversation_spec.rb +1 -1
  177. data/spec/mongo/auth/ldap_spec.rb +5 -1
  178. data/spec/mongo/auth/scram_negotiation_spec.rb +1 -1
  179. data/spec/mongo/auth/scram_spec.rb +1 -1
  180. data/spec/mongo/auth/user_spec.rb +1 -1
  181. data/spec/mongo/auth/x509/conversation_spec.rb +3 -3
  182. data/spec/mongo/bulk_write_spec.rb +2 -2
  183. data/spec/mongo/caching_cursor_spec.rb +70 -0
  184. data/spec/mongo/client_construction_spec.rb +273 -35
  185. data/spec/mongo/client_encryption_spec.rb +16 -10
  186. data/spec/mongo/client_spec.rb +64 -0
  187. data/spec/mongo/cluster/topology/replica_set_spec.rb +1 -1
  188. data/spec/mongo/cluster/topology/sharded_spec.rb +1 -1
  189. data/spec/mongo/cluster/topology/single_spec.rb +15 -6
  190. data/spec/mongo/cluster/topology/unknown_spec.rb +1 -1
  191. data/spec/mongo/cluster/topology_spec.rb +1 -1
  192. data/spec/mongo/cluster_spec.rb +6 -18
  193. data/spec/mongo/collection/view/change_stream_resume_spec.rb +1 -1
  194. data/spec/mongo/collection/view/explainable_spec.rb +87 -4
  195. data/spec/mongo/collection/view/map_reduce_spec.rb +2 -0
  196. data/spec/mongo/collection/view/readable_spec.rb +50 -0
  197. data/spec/mongo/collection_crud_spec.rb +4357 -0
  198. data/spec/mongo/collection_ddl_spec.rb +534 -0
  199. data/spec/mongo/collection_spec.rb +5 -4787
  200. data/spec/mongo/crypt/auto_decryption_context_spec.rb +1 -1
  201. data/spec/mongo/crypt/auto_encryption_context_spec.rb +1 -1
  202. data/spec/mongo/crypt/binary_spec.rb +1 -6
  203. data/spec/mongo/crypt/binding/binary_spec.rb +1 -6
  204. data/spec/mongo/crypt/binding/context_spec.rb +2 -7
  205. data/spec/mongo/crypt/binding/helpers_spec.rb +1 -6
  206. data/spec/mongo/crypt/binding/mongocrypt_spec.rb +2 -7
  207. data/spec/mongo/crypt/binding/status_spec.rb +1 -6
  208. data/spec/mongo/crypt/binding/version_spec.rb +1 -6
  209. data/spec/mongo/crypt/data_key_context_spec.rb +1 -1
  210. data/spec/mongo/crypt/explicit_decryption_context_spec.rb +1 -1
  211. data/spec/mongo/crypt/explicit_encryption_context_spec.rb +1 -1
  212. data/spec/mongo/crypt/status_spec.rb +1 -6
  213. data/spec/mongo/database_spec.rb +174 -4
  214. data/spec/mongo/error/bulk_write_error_spec.rb +3 -3
  215. data/spec/mongo/error/no_server_available_spec.rb +1 -1
  216. data/spec/mongo/error/parser_spec.rb +37 -6
  217. data/spec/mongo/index/view_spec.rb +8 -2
  218. data/spec/mongo/logger_spec.rb +13 -11
  219. data/spec/mongo/monitoring/event/server_closed_spec.rb +1 -1
  220. data/spec/mongo/monitoring/event/server_heartbeat_failed_spec.rb +1 -1
  221. data/spec/mongo/monitoring/event/server_heartbeat_succeeded_spec.rb +1 -1
  222. data/spec/mongo/monitoring/event/server_opening_spec.rb +1 -1
  223. data/spec/mongo/monitoring/event/topology_changed_spec.rb +1 -1
  224. data/spec/mongo/monitoring/event/topology_closed_spec.rb +1 -1
  225. data/spec/mongo/monitoring/event/topology_opening_spec.rb +1 -1
  226. data/spec/mongo/operation/aggregate_spec.rb +2 -1
  227. data/spec/mongo/operation/collections_info_spec.rb +4 -1
  228. data/spec/mongo/operation/command_spec.rb +6 -3
  229. data/spec/mongo/operation/create_index_spec.rb +6 -3
  230. data/spec/mongo/operation/create_user_spec.rb +6 -3
  231. data/spec/mongo/operation/delete/bulk_spec.rb +9 -6
  232. data/spec/mongo/operation/delete/op_msg_spec.rb +3 -3
  233. data/spec/mongo/operation/delete_spec.rb +11 -7
  234. data/spec/mongo/operation/drop_index_spec.rb +6 -2
  235. data/spec/mongo/operation/find/legacy_spec.rb +3 -1
  236. data/spec/mongo/operation/get_more_spec.rb +3 -1
  237. data/spec/mongo/operation/indexes_spec.rb +5 -1
  238. data/spec/mongo/operation/insert/bulk_spec.rb +10 -7
  239. data/spec/mongo/operation/insert/command_spec.rb +2 -2
  240. data/spec/mongo/operation/insert/op_msg_spec.rb +3 -3
  241. data/spec/mongo/operation/insert_spec.rb +15 -12
  242. data/spec/mongo/operation/map_reduce_spec.rb +5 -2
  243. data/spec/mongo/operation/read_preference_op_msg_spec.rb +1 -1
  244. data/spec/mongo/operation/remove_user_spec.rb +6 -3
  245. data/spec/mongo/operation/result_spec.rb +1 -1
  246. data/spec/mongo/operation/update/bulk_spec.rb +9 -6
  247. data/spec/mongo/operation/update/command_spec.rb +2 -2
  248. data/spec/mongo/operation/update/op_msg_spec.rb +3 -3
  249. data/spec/mongo/operation/update_spec.rb +10 -7
  250. data/spec/mongo/operation/update_user_spec.rb +4 -1
  251. data/spec/mongo/protocol/compressed_spec.rb +26 -12
  252. data/spec/mongo/query_cache_middleware_spec.rb +55 -0
  253. data/spec/mongo/query_cache_spec.rb +280 -0
  254. data/spec/mongo/retryable_spec.rb +3 -2
  255. data/spec/mongo/server/app_metadata_shared.rb +2 -2
  256. data/spec/mongo/server/app_metadata_spec.rb +2 -0
  257. data/spec/mongo/server/connection_pool/populator_spec.rb +3 -1
  258. data/spec/mongo/server/connection_pool_spec.rb +8 -4
  259. data/spec/mongo/server/connection_spec.rb +39 -25
  260. data/spec/mongo/server/description_spec.rb +18 -0
  261. data/spec/mongo/server/monitor/connection_spec.rb +17 -7
  262. data/spec/mongo/server/monitor_spec.rb +9 -1
  263. data/spec/mongo/server_selector_spec.rb +2 -2
  264. data/spec/mongo/server_spec.rb +15 -2
  265. data/spec/mongo/socket/ssl_spec.rb +44 -4
  266. data/spec/mongo/socket_spec.rb +2 -2
  267. data/spec/mongo/tls_context_hooks_spec.rb +37 -0
  268. data/spec/mongo/uri/srv_protocol_spec.rb +64 -33
  269. data/spec/mongo/uri_option_parsing_spec.rb +11 -11
  270. data/spec/mongo/uri_spec.rb +68 -41
  271. data/spec/mongo/utils_spec.rb +39 -0
  272. data/spec/runners/auth.rb +3 -0
  273. data/spec/runners/change_streams/test.rb +1 -1
  274. data/spec/runners/connection_string.rb +31 -124
  275. data/spec/runners/crud/requirement.rb +40 -3
  276. data/spec/runners/crud/test_base.rb +0 -19
  277. data/spec/runners/crud/verifier.rb +8 -0
  278. data/spec/runners/server_selection.rb +1 -1
  279. data/spec/runners/transactions/operation.rb +13 -2
  280. data/spec/runners/transactions/test.rb +3 -2
  281. data/spec/runners/unified.rb +96 -0
  282. data/spec/runners/unified/assertions.rb +249 -0
  283. data/spec/runners/unified/change_stream_operations.rb +26 -0
  284. data/spec/runners/unified/crud_operations.rb +199 -0
  285. data/spec/runners/unified/ddl_operations.rb +96 -0
  286. data/spec/runners/unified/entity_map.rb +39 -0
  287. data/spec/runners/unified/error.rb +25 -0
  288. data/spec/runners/unified/event_subscriber.rb +91 -0
  289. data/spec/runners/unified/exceptions.rb +21 -0
  290. data/spec/runners/unified/grid_fs_operations.rb +55 -0
  291. data/spec/runners/unified/support_operations.rb +250 -0
  292. data/spec/runners/unified/test.rb +393 -0
  293. data/spec/runners/unified/test_group.rb +28 -0
  294. data/spec/runners/unified/using_hash.rb +31 -0
  295. data/spec/shared/LICENSE +20 -0
  296. data/spec/shared/bin/get-mongodb-download-url +17 -0
  297. data/spec/shared/lib/mrss/child_process_helper.rb +80 -0
  298. data/spec/shared/lib/mrss/cluster_config.rb +218 -0
  299. data/spec/shared/lib/mrss/constraints.rb +346 -0
  300. data/spec/shared/lib/mrss/docker_runner.rb +262 -0
  301. data/spec/shared/lib/mrss/lite_constraints.rb +175 -0
  302. data/spec/shared/lib/mrss/server_version_registry.rb +112 -0
  303. data/spec/shared/lib/mrss/spec_organizer.rb +149 -0
  304. data/spec/shared/lib/mrss/utils.rb +15 -0
  305. data/spec/shared/share/Dockerfile.erb +231 -0
  306. data/spec/shared/shlib/distro.sh +73 -0
  307. data/spec/shared/shlib/server.sh +290 -0
  308. data/spec/shared/shlib/set_env.sh +128 -0
  309. data/spec/solo/clean_exit_spec.rb +21 -0
  310. data/spec/spec_helper.rb +7 -2
  311. data/spec/spec_tests/cmap_spec.rb +7 -3
  312. data/spec/spec_tests/crud_unified_spec.rb +10 -0
  313. data/spec/spec_tests/data/change_streams/change-streams-errors.yml +0 -1
  314. data/spec/spec_tests/data/change_streams/change-streams.yml +0 -2
  315. data/spec/spec_tests/data/cmap/pool-checkout-connection.yml +6 -2
  316. data/spec/spec_tests/data/cmap/pool-create-min-size.yml +3 -0
  317. data/spec/spec_tests/data/connection_string/valid-warnings.yml +24 -0
  318. data/spec/spec_tests/data/crud_unified/estimatedDocumentCount.yml +267 -0
  319. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-4.9.yml +60 -0
  320. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount.yml → estimatedDocumentCount-pre4.9.yml} +2 -0
  321. data/spec/spec_tests/data/retryable_reads/estimatedDocumentCount-serverErrors-4.9.yml +146 -0
  322. data/spec/spec_tests/data/retryable_reads/{estimatedDocumentCount-serverErrors.yml → estimatedDocumentCount-serverErrors-pre4.9.yml} +2 -0
  323. data/spec/spec_tests/data/retryable_reads/listIndexNames.yml +1 -1
  324. data/spec/spec_tests/data/sdam_monitoring/discovered_standalone.yml +1 -3
  325. data/spec/spec_tests/data/sdam_monitoring/standalone.yml +2 -2
  326. data/spec/spec_tests/data/sdam_monitoring/standalone_repeated.yml +2 -2
  327. data/spec/spec_tests/data/sdam_monitoring/standalone_suppress_equal_description_changes.yml +2 -2
  328. data/spec/spec_tests/data/sdam_monitoring/standalone_to_rs_with_me_mismatch.yml +2 -2
  329. data/spec/spec_tests/data/unified/valid-fail/operation-failure.yml +31 -0
  330. data/spec/spec_tests/data/unified/valid-pass/poc-change-streams.yml +220 -0
  331. data/spec/spec_tests/data/unified/valid-pass/poc-command-monitoring.yml +102 -0
  332. data/spec/spec_tests/data/unified/valid-pass/poc-crud.yml +184 -0
  333. data/spec/spec_tests/data/unified/valid-pass/poc-gridfs.yml +155 -0
  334. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-reads.yml +193 -0
  335. data/spec/spec_tests/data/unified/valid-pass/poc-retryable-writes.yml +210 -0
  336. data/spec/spec_tests/data/unified/valid-pass/poc-sessions.yml +215 -0
  337. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-convenient-api.yml +235 -0
  338. data/spec/spec_tests/data/unified/valid-pass/poc-transactions-mongos-pin-auto.yml +169 -0
  339. data/spec/spec_tests/data/unified/valid-pass/poc-transactions.yml +170 -0
  340. data/spec/spec_tests/data/uri_options/auth-options.yml +25 -0
  341. data/spec/spec_tests/data/uri_options/compression-options.yml +7 -4
  342. data/spec/spec_tests/data/uri_options/read-preference-options.yml +24 -0
  343. data/spec/spec_tests/data/uri_options/ruby-connection-options.yml +1 -0
  344. data/spec/spec_tests/data/uri_options/tls-options.yml +160 -4
  345. data/spec/spec_tests/data/versioned_api/crud-api-version-1-strict.yml +416 -0
  346. data/spec/spec_tests/data/versioned_api/crud-api-version-1.yml +409 -0
  347. data/spec/spec_tests/data/versioned_api/runcommand-helper-no-api-version-declared.yml +67 -0
  348. data/spec/spec_tests/data/versioned_api/test-commands-deprecation-errors.yml +47 -0
  349. data/spec/spec_tests/data/versioned_api/test-commands-strict-mode.yml +44 -0
  350. data/spec/spec_tests/data/versioned_api/transaction-handling.yml +180 -0
  351. data/spec/spec_tests/dns_seedlist_discovery_spec.rb +9 -1
  352. data/spec/spec_tests/unified_spec.rb +15 -0
  353. data/spec/spec_tests/uri_options_spec.rb +47 -33
  354. data/spec/spec_tests/versioned_api_spec.rb +10 -0
  355. data/spec/stress/fork_reconnect_stress_spec.rb +1 -1
  356. data/spec/support/certificates/atlas-ocsp-ca.crt +28 -0
  357. data/spec/support/certificates/atlas-ocsp.crt +41 -0
  358. data/spec/support/client_registry_macros.rb +11 -2
  359. data/spec/support/common_shortcuts.rb +59 -0
  360. data/spec/support/constraints.rb +6 -253
  361. data/spec/support/matchers.rb +16 -0
  362. data/spec/support/ocsp +1 -0
  363. data/spec/support/session_registry.rb +52 -0
  364. data/spec/support/shared/session.rb +2 -2
  365. data/spec/support/spec_config.rb +68 -3
  366. data/spec/support/spec_setup.rb +48 -38
  367. data/spec/support/utils.rb +102 -4
  368. metadata +1087 -936
  369. metadata.gz.sig +0 -0
  370. data/lib/mongo/operation/shared/collections_info_or_list_collections.rb +0 -56
  371. data/lib/mongo/operation/shared/op_msg_or_list_indexes_command.rb +0 -47
  372. data/spec/support/child_process_helper.rb +0 -78
  373. data/spec/support/cluster_config.rb +0 -207
  374. data/spec/support/lite_constraints.rb +0 -141
  375. data/spec/support/spec_organizer.rb +0 -129
@@ -63,6 +63,11 @@ module Mongo
63
63
  end
64
64
 
65
65
  result = handshake!(speculative_auth_doc: speculative_auth_doc)
66
+
67
+ if description.unknown?
68
+ raise Error::InternalDriverError, "Connection description cannot be unknown after successful handshake: #{description.inspect}"
69
+ end
70
+
66
71
  if speculative_auth_doc && (speculative_auth_result = result['speculativeAuthenticate'])
67
72
  unless description.features.scram_sha_1_enabled?
68
73
  raise Error::InvalidServerAuthResponse, "Speculative auth succeeded on a pre-3.0 server"
@@ -80,11 +85,15 @@ module Mongo
80
85
  speculative_auth_result: speculative_auth_result,
81
86
  )
82
87
  else
83
- raise NotImplementedError, "Speculative auth unexpectedly succeeded for mechanism #{speculative_auth_user.mechanism.inspect}"
88
+ raise Error::InternalDriverError, "Speculative auth unexpectedly succeeded for mechanism #{speculative_auth_user.mechanism.inspect}"
84
89
  end
85
90
  elsif !description.arbiter?
86
91
  authenticate!
87
92
  end
93
+
94
+ if description.unknown?
95
+ raise Error::InternalDriverError, "Connection description cannot be unknown after successful authentication: #{description.inspect}"
96
+ end
88
97
  end
89
98
 
90
99
  private
@@ -96,7 +105,7 @@ module Mongo
96
105
  # this particular connection.
97
106
  def handshake!(speculative_auth_doc: nil)
98
107
  unless socket
99
- raise Error::HandshakeError, "Cannot handshake because there is no usable socket (for #{address})"
108
+ raise Error::InternalDriverError, "Cannot handshake because there is no usable socket (for #{address})"
100
109
  end
101
110
 
102
111
  ismaster_doc = app_metadata.validated_document
@@ -104,21 +113,30 @@ module Mongo
104
113
  ismaster_doc = ismaster_doc.merge(speculativeAuthenticate: speculative_auth_doc)
105
114
  end
106
115
 
116
+ if server_api = options[:server_api]
117
+ ismaster_doc = ismaster_doc.merge(
118
+ Utils.transform_server_api(server_api)
119
+ )
120
+ end
121
+
107
122
  ismaster_command = Protocol::Query.new(Database::ADMIN, Database::COMMAND,
108
123
  ismaster_doc, :limit => -1)
109
124
 
110
- response = nil
125
+ doc = nil
111
126
  @server.handle_handshake_failure! do
112
127
  begin
113
128
  response = @server.round_trip_time_averager.measure do
114
129
  add_server_diagnostics do
115
130
  socket.write(ismaster_command.serialize.to_s)
116
- Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE).documents.first
131
+ Protocol::Message.deserialize(socket, Protocol::Message::MAX_MESSAGE_SIZE)
117
132
  end
118
133
  end
134
+ result = Operation::Result.new([response])
135
+ result.validate!
136
+ doc = result.documents.first
119
137
  rescue => exc
120
138
  msg = "Failed to handshake with #{address}"
121
- Utils.warn_monitor_exception(msg, exc,
139
+ Utils.warn_bg_exception(msg, exc,
122
140
  logger: options[:logger],
123
141
  log_prefix: options[:log_prefix],
124
142
  bg_error_backtrace: options[:bg_error_backtrace],
@@ -127,9 +145,9 @@ module Mongo
127
145
  end
128
146
  end
129
147
 
130
- post_handshake(response, @server.round_trip_time_averager.average_round_trip_time)
148
+ post_handshake(doc, @server.round_trip_time_averager.average_round_trip_time)
131
149
 
132
- response
150
+ doc
133
151
  end
134
152
 
135
153
  # @param [ String | nil ] speculative_auth_client_nonce The client
@@ -158,7 +176,7 @@ module Mongo
158
176
  auth.login
159
177
  rescue => exc
160
178
  msg = "Failed to authenticate to #{address}"
161
- Utils.warn_monitor_exception(msg, exc,
179
+ Utils.warn_bg_exception(msg, exc,
162
180
  logger: options[:logger],
163
181
  log_prefix: options[:log_prefix],
164
182
  bg_error_backtrace: options[:bg_error_backtrace],
@@ -33,6 +33,9 @@ module Mongo
33
33
  if topology_version.nil?
34
34
  raise ArgumentError, 'Topology version must be provided but it was nil'
35
35
  end
36
+ unless options[:app_metadata]
37
+ raise ArgumentError, 'App metadata is required'
38
+ end
36
39
  @monitor = monitor
37
40
  @topology_version = topology_version
38
41
  @monitoring = monitoring
@@ -101,7 +104,7 @@ module Mongo
101
104
  end
102
105
  rescue Mongo::Error => exc
103
106
  msg = "Error running awaited ismaster on #{server.address}"
104
- Utils.warn_monitor_exception(msg, exc,
107
+ Utils.warn_bg_exception(msg, exc,
105
108
  logger: options[:logger],
106
109
  log_prefix: options[:log_prefix],
107
110
  bg_error_backtrace: options[:bg_error_backtrace],
@@ -139,7 +142,9 @@ module Mongo
139
142
  raise
140
143
  end
141
144
  @server_pushing = resp_msg.flags.include?(:more_to_come)
142
- result = resp_msg.documents.first
145
+ result = Operation::Result.new(resp_msg)
146
+ result.validate!
147
+ result.documents.first
143
148
  end
144
149
 
145
150
  def write_ismaster
@@ -147,6 +152,11 @@ module Mongo
147
152
  topologyVersion: topology_version.to_doc,
148
153
  maxAwaitTimeMS: monitor.heartbeat_interval * 1000,
149
154
  )
155
+ if server_api = options[:server_api]
156
+ payload.update(
157
+ Utils.transform_server_api(server_api)
158
+ )
159
+ end
150
160
 
151
161
  req_msg = Protocol::Msg.new([:exhaust_allowed], {}, payload)
152
162
  @lock.synchronize { @connection }.write_bytes(req_msg.serialize.to_s)
@@ -265,7 +265,11 @@ module Mongo
265
265
  end
266
266
  end
267
267
 
268
- msg = "No #{name} server is available in cluster: #{cluster.summary} " +
268
+ msg = "No #{name} server"
269
+ if is_a?(ServerSelector::Secondary) && !tag_sets.empty?
270
+ msg += " with tag sets: #{tag_sets}"
271
+ end
272
+ msg += " is available in cluster: #{cluster.summary} " +
269
273
  "with timeout=#{server_selection_timeout}, " +
270
274
  "LT=#{local_threshold_with_cluster(cluster)}"
271
275
  msg += server_selection_diagnostic_message(cluster)
data/lib/mongo/session.rb CHANGED
@@ -379,6 +379,7 @@ module Mongo
379
379
  rv = yield self
380
380
  rescue Exception => e
381
381
  if within_states?(STARTING_TRANSACTION_STATE, TRANSACTION_IN_PROGRESS_STATE)
382
+ log_warn("Aborting transaction due to #{e.class}: #{e}")
382
383
  abort_transaction
383
384
  transaction_in_progress = false
384
385
  end
@@ -443,7 +444,7 @@ module Mongo
443
444
  true
444
445
  ensure
445
446
  if transaction_in_progress
446
- log_warn('with_transaction callback altered with_transaction loop, aborting transaction')
447
+ log_warn('with_transaction callback broke out of with_transaction loop, aborting transaction')
447
448
  begin
448
449
  abort_transaction
449
450
  rescue Error::OperationFailure, Error::InvalidTransactionOperation
@@ -534,6 +535,7 @@ module Mongo
534
535
  #
535
536
  # @since 2.6.0
536
537
  def commit_transaction(options=nil)
538
+ QueryCache.clear
537
539
  check_if_ended!
538
540
  check_if_no_transaction!
539
541
 
@@ -580,7 +582,7 @@ module Mongo
580
582
  txn_num: txn_num,
581
583
  write_concern: write_concern,
582
584
  }
583
- Operation::Command.new(spec).execute(server, client: @client)
585
+ Operation::Command.new(spec).execute(server, context: Operation::Context.new(client: @client, session: self))
584
586
  end
585
587
  end
586
588
  ensure
@@ -602,6 +604,8 @@ module Mongo
602
604
  #
603
605
  # @since 2.6.0
604
606
  def abort_transaction
607
+ QueryCache.clear
608
+
605
609
  check_if_ended!
606
610
  check_if_no_transaction!
607
611
 
@@ -625,7 +629,7 @@ module Mongo
625
629
  db_name: 'admin',
626
630
  session: self,
627
631
  txn_num: txn_num
628
- ).execute(server, client: @client)
632
+ ).execute(server, context: Operation::Context.new(client: @client, session: self))
629
633
  end
630
634
  end
631
635
 
@@ -119,8 +119,10 @@ module Mongo
119
119
  },
120
120
  db_name: Database::ADMIN,
121
121
  )
122
- # end_sessions does not take a client as an argument
123
- op.execute(server, client: nil)
122
+ context = Operation::Context.new(options: {
123
+ server_api: server.options[:server_api],
124
+ })
125
+ op.execute(server, context: context)
124
126
  end
125
127
  rescue Mongo::Error, Error::AuthError
126
128
  end
data/lib/mongo/socket.rb CHANGED
@@ -15,6 +15,8 @@
15
15
  require 'mongo/socket/ssl'
16
16
  require 'mongo/socket/tcp'
17
17
  require 'mongo/socket/unix'
18
+ require 'mongo/socket/ocsp_verifier'
19
+ require 'mongo/socket/ocsp_cache'
18
20
 
19
21
  module Mongo
20
22
 
@@ -25,10 +27,11 @@ module Mongo
25
27
  class Socket
26
28
  include ::Socket::Constants
27
29
 
28
- # Error message for SSL related exceptions.
30
+ # Error message for TLS related exceptions.
29
31
  #
30
32
  # @since 2.0.0
31
- SSL_ERROR = 'MongoDB may not be configured with SSL support'.freeze
33
+ # @deprecated
34
+ SSL_ERROR = 'MongoDB may not be configured with TLS support'.freeze
32
35
 
33
36
  # Error message for timeouts on socket calls.
34
37
  #
@@ -129,7 +132,7 @@ module Mongo
129
132
  sock_arr = [ @socket ]
130
133
  if Kernel::select(sock_arr, nil, sock_arr, 0)
131
134
  # The eof? call is supposed to return immediately since select
132
- # indicated the socket is readable. However, if @socket is an SSL
135
+ # indicated the socket is readable. However, if @socket is a TLS
133
136
  # socket, eof? can block anyway - see RUBY-2140.
134
137
  begin
135
138
  Timeout.timeout(0.1) do
@@ -152,7 +155,14 @@ module Mongo
152
155
  #
153
156
  # @since 2.0.0
154
157
  def close
155
- @socket.close rescue nil
158
+ begin
159
+ # Sometimes it seems the close call can hang for a long time
160
+ ::Timeout.timeout(5) do
161
+ @socket.close
162
+ end
163
+ rescue
164
+ # Silence all errors
165
+ end
156
166
  true
157
167
  end
158
168
 
@@ -324,8 +334,21 @@ module Mongo
324
334
  else
325
335
  select_args = [nil, [@socket], [@socket], select_timeout]
326
336
  end
327
- unless Kernel::select(*select_args)
328
- raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
337
+ rv = Kernel.select(*select_args)
338
+ if BSON::Environment.jruby?
339
+ # Ignore the return value of Kernel.select.
340
+ # On JRuby, select appears to return nil prior to timeout expiration
341
+ # (apparently due to a EAGAIN) which then causes us to fail the read
342
+ # even though we could have retried it.
343
+ # Check the deadline ourselves.
344
+ if deadline
345
+ select_timeout = deadline - Time.now
346
+ if select_timeout <= 0
347
+ raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data"
348
+ end
349
+ end
350
+ elsif rv.nil?
351
+ raise Errno::ETIMEDOUT, "Took more than #{_timeout} seconds to receive data (select call timed out)"
329
352
  end
330
353
  retry
331
354
  end
@@ -342,7 +365,7 @@ module Mongo
342
365
  end
343
366
 
344
367
  def read_buffer_size
345
- # Buffer size for non-SSL reads
368
+ # Buffer size for non-TLS reads
346
369
  # 64kb
347
370
  65536
348
371
  end
@@ -396,6 +419,10 @@ module Mongo
396
419
  set_option(sock, :TCP_KEEPCNT, DEFAULT_TCP_KEEPCNT)
397
420
  set_option(sock, :TCP_KEEPIDLE, DEFAULT_TCP_KEEPIDLE)
398
421
  rescue
422
+ # JRuby 9.2.13.0 and lower do not define TCP_KEEPINTVL etc. constants.
423
+ # JRuby 9.2.14.0 defines the constants but does not allow to get or
424
+ # set them with this error:
425
+ # Errno::ENOPROTOOPT: Protocol not available - Protocol not available
399
426
  end
400
427
 
401
428
  def set_option(sock, option, default)
@@ -420,7 +447,7 @@ module Mongo
420
447
  rescue IOError, SystemCallError => e
421
448
  raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})"
422
449
  rescue OpenSSL::SSL::SSLError => e
423
- raise Error::SocketError, "#{e.class}: #{e} (for #{human_address}) (#{SSL_ERROR})"
450
+ raise Error::SocketError, "#{e.class}: #{e} (for #{human_address})"
424
451
  end
425
452
  end
426
453
 
@@ -0,0 +1,97 @@
1
+ # Copyright (C) 2020 MongoDB Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Mongo
16
+ class Socket
17
+
18
+ # This module caches OCSP responses for their indicated validity time.
19
+ #
20
+ # The key is the CertificateId used for the OCSP request.
21
+ # The value is the SingleResponse on Ruby 2.4+, or the OpenStruct
22
+ # emulation of it on Ruby 2.3.
23
+ #
24
+ # @api private
25
+ module OcspCache
26
+ module_function def set(cert_id, response)
27
+ delete(cert_id)
28
+ responses << response
29
+ end
30
+
31
+ # Retrieves a cached SingleResponse for the specified CertificateId.
32
+ #
33
+ # This method may return expired responses if they are revoked.
34
+ # Such responses were valid when they were first received.
35
+ #
36
+ # This method may also return responses that are valid but that may
37
+ # expire by the time caller uses them. The caller should not perform
38
+ # update time checks on the returned response.
39
+ #
40
+ # @return [ OpenSSL::OCSP::SingleResponse | OpenStruct ] The previously
41
+ # retrieved response.
42
+ module_function def get(cert_id)
43
+ resp = responses.detect do |resp|
44
+ resp.certid.cmp(cert_id)
45
+ end
46
+ if resp
47
+ # Only expire responses with good status.
48
+ # Once a certificate is revoked, it should stay revoked forever,
49
+ # hence we should be able to cache revoked responses indefinitely.
50
+ if resp.cert_status == OpenSSL::OCSP::V_CERTSTATUS_GOOD &&
51
+ resp.next_update < Time.now
52
+ then
53
+ responses.delete(resp)
54
+ resp = nil
55
+ end
56
+ end
57
+
58
+ # If we have connected to a server and cached the OCSP response for it,
59
+ # and then never connect to that server again, the cached OCSP response
60
+ # is going to remain in memory indefinitely. Periodically remove all
61
+ # expired OCSP responses, not just the ones matching the certificate id
62
+ # we are querying by.
63
+ if rand < 0.01
64
+ responses.delete_if do |resp|
65
+ resp.next_update < Time.now
66
+ end
67
+ end
68
+
69
+ resp
70
+ end
71
+
72
+ module_function def delete(cert_id)
73
+ responses.delete_if do |resp|
74
+ resp.certid.cmp(cert_id)
75
+ end
76
+ end
77
+
78
+ # Clears the driver's OCSP response cache.
79
+ #
80
+ # @note Use Mongo.clear_ocsp_cache from applications instead of invoking
81
+ # this method directly.
82
+ module_function def clear
83
+ responses.replace([])
84
+ end
85
+
86
+ private
87
+
88
+ LOCK = Mutex.new
89
+
90
+ module_function def responses
91
+ LOCK.synchronize do
92
+ @responses ||= []
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,368 @@
1
+ # Copyright (C) 2020 MongoDB Inc.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ module Net
16
+ autoload :HTTP, 'net/http'
17
+ end
18
+
19
+ module Mongo
20
+ class Socket
21
+
22
+ # OCSP endpoint verifier.
23
+ #
24
+ # After a TLS connection is established, this verifier inspects the
25
+ # certificate presented by the server, and if the certificate contains
26
+ # an OCSP URI, performs the OCSP status request to the specified URI
27
+ # (following up to 5 redirects) to verify the certificate status.
28
+ #
29
+ # @see https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL/OCSP.html
30
+ #
31
+ # @api private
32
+ class OcspVerifier
33
+ include Loggable
34
+
35
+ # @param [ String ] host_name The host name being verified, for
36
+ # diagnostic output.
37
+ # @param [ OpenSSL::X509::Certificate ] cert The certificate presented by
38
+ # the server at host_name.
39
+ # @param [ OpenSSL::X509::Certificate ] ca_cert The CA certificate
40
+ # presented by the server or resolved locally from the server
41
+ # certificate.
42
+ # @param [ OpenSSL::X509::Store ] cert_store The certificate store to
43
+ # use for verifying OCSP response. This should be the same store as
44
+ # used in SSLContext used with the SSLSocket that we are verifying the
45
+ # certificate for. This must NOT be the CA certificate provided by
46
+ # the server (i.e. anything taken out of peer_cert) - otherwise the
47
+ # server would dictate which CA authorities the client trusts.
48
+ def initialize(host_name, cert, ca_cert, cert_store, **opts)
49
+ @host_name = host_name
50
+ @cert = cert
51
+ @ca_cert = ca_cert
52
+ @cert_store = cert_store
53
+ @options = opts
54
+ end
55
+
56
+ attr_reader :host_name
57
+ attr_reader :cert
58
+ attr_reader :ca_cert
59
+ attr_reader :cert_store
60
+ attr_reader :options
61
+
62
+ def timeout
63
+ options[:timeout] || 5
64
+ end
65
+
66
+ # @return [ Array<String> ] OCSP URIs in the specified server certificate.
67
+ def ocsp_uris
68
+ @ocsp_uris ||= begin
69
+ # https://tools.ietf.org/html/rfc3546#section-2.3
70
+ # prohibits multiple extensions with the same oid.
71
+ ext = cert.extensions.detect do |ext|
72
+ ext.oid == 'authorityInfoAccess'
73
+ end
74
+
75
+ if ext
76
+ # Our test certificates have multiple OCSP URIs.
77
+ ext.value.split("\n").select do |line|
78
+ line.start_with?('OCSP - URI:')
79
+ end.map do |line|
80
+ line.split(':', 2).last
81
+ end
82
+ else
83
+ []
84
+ end
85
+ end
86
+ end
87
+
88
+ def cert_id
89
+ @cert_id ||= OpenSSL::OCSP::CertificateId.new(
90
+ cert,
91
+ ca_cert,
92
+ OpenSSL::Digest::SHA1.new,
93
+ )
94
+ end
95
+
96
+ def verify_with_cache
97
+ handle_exceptions do
98
+ return false if ocsp_uris.empty?
99
+
100
+ resp = OcspCache.get(cert_id)
101
+ if resp
102
+ return return_ocsp_response(resp)
103
+ end
104
+
105
+ resp, errors = do_verify
106
+
107
+ if resp
108
+ OcspCache.set(cert_id, resp)
109
+ end
110
+
111
+ return_ocsp_response(resp, errors)
112
+ end
113
+ end
114
+
115
+ # @return [ true | false ] Whether the certificate was verified.
116
+ #
117
+ # @raise [ Error::ServerCertificateRevoked ] If the certificate was
118
+ # definitively revoked.
119
+ def verify
120
+ handle_exceptions do
121
+ return false if ocsp_uris.empty?
122
+
123
+ resp, errors = do_verify
124
+ return_ocsp_response(resp, errors)
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def do_verify
131
+ # This synchronized array contains definitive pass/fail responses
132
+ # obtained from the responders. We'll take the first one but due to
133
+ # concurrency multiple responses may be produced and queued.
134
+ @resp_queue = Queue.new
135
+
136
+ # This synchronized array contains strings, one per responder, that
137
+ # explain why each responder hasn't produced a definitive response.
138
+ # These are concatenated and logged if none of the responders produced
139
+ # a definitive respnose, or if the main thread times out waiting for
140
+ # a definitive response (in which case some of the worker threads'
141
+ # diagnostics may be logged and some may not).
142
+ @resp_errors = Queue.new
143
+
144
+ @req = OpenSSL::OCSP::Request.new
145
+ @req.add_certid(cert_id)
146
+ @req.add_nonce
147
+ @serialized_req = @req.to_der
148
+
149
+ @outstanding_requests = ocsp_uris.count
150
+ @outstanding_requests_lock = Mutex.new
151
+
152
+ threads = ocsp_uris.map do |uri|
153
+ Thread.new do
154
+ verify_one_responder(uri)
155
+ end
156
+ end
157
+
158
+ resp = begin
159
+ ::Timeout.timeout(timeout) do
160
+ @resp_queue.shift
161
+ end
162
+ rescue ::Timeout::Error
163
+ nil
164
+ end
165
+
166
+ threads.map(&:kill)
167
+ threads.map(&:join)
168
+
169
+ [resp, @resp_errors]
170
+ end
171
+
172
+ def verify_one_responder(uri)
173
+ original_uri = uri
174
+ redirect_count = 0
175
+ http_response = nil
176
+ loop do
177
+ http_response = begin
178
+ uri = URI(uri)
179
+ Net::HTTP.start(uri.hostname, uri.port) do |http|
180
+ path = uri.path
181
+ if path.empty?
182
+ path = '/'
183
+ end
184
+ http.post(path, @serialized_req,
185
+ 'content-type' => 'application/ocsp-request')
186
+ end
187
+ rescue IOError, SystemCallError => e
188
+ @resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed: #{e.class}: #{e}"
189
+ return false
190
+ end
191
+
192
+ code = http_response.code.to_i
193
+ if (300..399).include?(code)
194
+ redirected_uri = http_response.header['location']
195
+ uri = ::URI.join(uri, redirected_uri)
196
+ redirect_count += 1
197
+ if redirect_count > 5
198
+ @resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed: too many redirects (6)"
199
+ return false
200
+ end
201
+ next
202
+ end
203
+
204
+ if code >= 400
205
+ @resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed with HTTP status code #{http_response.code}" + report_response_body(http_response.body)
206
+ return false
207
+ end
208
+
209
+ if code != 200
210
+ # There must be a body provided with the response, if one isn't
211
+ # provided the response cannot be verified.
212
+ @resp_errors << "OCSP request to #{report_uri(original_uri, uri)} failed with unexpected HTTP status code #{http_response.code}" + report_response_body(http_response.body)
213
+ return false
214
+ end
215
+
216
+ break
217
+ end
218
+
219
+ resp = OpenSSL::OCSP::Response.new(http_response.body).basic
220
+ unless resp.verify([ca_cert], cert_store)
221
+ # Ruby's OpenSSL binding discards error information - see
222
+ # https://github.com/ruby/openssl/issues/395
223
+ @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} failed signature verification; set `OpenSSL.debug = true` to see why"
224
+ return false
225
+ end
226
+
227
+ if @req.check_nonce(resp) == 0
228
+ @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} included invalid nonce"
229
+ return false
230
+ end
231
+
232
+ if resp.respond_to?(:find_response)
233
+ # Ruby 2.4+
234
+ resp = resp.find_response(cert_id)
235
+ # TODO make a new class instead of patching the stdlib one?
236
+ resp.instance_variable_set('@uri', uri)
237
+ resp.instance_variable_set('@original_uri', original_uri)
238
+ class << resp
239
+ attr_reader :uri, :original_uri
240
+ end
241
+ else
242
+ # Ruby 2.3
243
+ found = nil
244
+ resp.status.each do |_cert_id, cert_status, revocation_reason, revocation_time, this_update, next_update, extensions|
245
+ if _cert_id.cmp(cert_id)
246
+ found = OpenStruct.new(
247
+ cert_status: cert_status,
248
+ certid: _cert_id,
249
+ next_update: next_update,
250
+ this_update: this_update,
251
+ revocation_reason: revocation_reason,
252
+ revocation_time: revocation_time,
253
+ extensions: extensions,
254
+ uri: uri,
255
+ original_uri: original_uri,
256
+ )
257
+ class << found
258
+ # Unlike the stdlib method, this one doesn't accept
259
+ # any arguments.
260
+ def check_validity
261
+ now = Time.now
262
+ this_update <= now && next_update >= now
263
+ end
264
+ end
265
+ break
266
+ end
267
+ end
268
+ resp = found
269
+ end
270
+
271
+ unless resp
272
+ @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} did not include information about the requested certificate"
273
+ return false
274
+ end
275
+
276
+ unless resp.check_validity
277
+ @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} was invalid: this_update was in the future or next_update time has passed"
278
+ return false
279
+ end
280
+
281
+ unless [
282
+ OpenSSL::OCSP::V_CERTSTATUS_GOOD,
283
+ OpenSSL::OCSP::V_CERTSTATUS_REVOKED,
284
+ ].include?(resp.cert_status)
285
+ @resp_errors << "OCSP response from #{report_uri(original_uri, uri)} had a non-definitive status: #{resp.cert_status}"
286
+ return false
287
+ end
288
+
289
+ # Note this returns the redirected URI
290
+ @resp_queue << resp
291
+ rescue => exc
292
+ Utils.warn_bg_exception("Error performing OCSP verification for '#{host_name}' via '#{uri}'", exc,
293
+ logger: options[:logger],
294
+ log_prefix: options[:log_prefix],
295
+ bg_error_backtrace: options[:bg_error_backtrace],
296
+ )
297
+ false
298
+ ensure
299
+ @outstanding_requests_lock.synchronize do
300
+ @outstanding_requests -= 1
301
+ if @outstanding_requests == 0
302
+ @resp_queue << nil
303
+ end
304
+ end
305
+ end
306
+
307
+ def return_ocsp_response(resp, errors = nil)
308
+ if resp
309
+ if resp.cert_status == OpenSSL::OCSP::V_CERTSTATUS_REVOKED
310
+ raise_revoked_error(resp)
311
+ end
312
+ true
313
+ else
314
+ reasons = []
315
+ errors.length.times do
316
+ reasons << errors.shift
317
+ end
318
+ if reasons.empty?
319
+ msg = "No responses from responders: #{ocsp_uris.join(', ')} within #{timeout} seconds"
320
+ else
321
+ msg = "For responders #{ocsp_uris.join(', ')} with a timeout of #{timeout} seconds: #{reasons.join(', ')}"
322
+ end
323
+ log_warn("TLS certificate of '#{host_name}' could not be definitively verified via OCSP: #{msg}")
324
+ false
325
+ end
326
+ end
327
+
328
+ def handle_exceptions
329
+ begin
330
+ yield
331
+ rescue Error::ServerCertificateRevoked
332
+ raise
333
+ rescue => exc
334
+ Utils.warn_bg_exception(
335
+ "Error performing OCSP verification for '#{host_name}'",
336
+ exc,
337
+ **options)
338
+ false
339
+ end
340
+ end
341
+
342
+ def raise_revoked_error(resp)
343
+ if resp.uri == resp.original_uri
344
+ redirect = ''
345
+ else
346
+ redirect = " (redirected from #{resp.original_uri})"
347
+ end
348
+ raise Error::ServerCertificateRevoked, "TLS certificate of '#{host_name}' has been revoked according to '#{resp.uri}'#{redirect} for reason '#{resp.revocation_reason}' at '#{resp.revocation_time}'"
349
+ end
350
+
351
+ def report_uri(original_uri, uri)
352
+ if URI(uri) == URI(original_uri)
353
+ uri
354
+ else
355
+ "#{original_uri} (redirected to #{uri})"
356
+ end
357
+ end
358
+
359
+ def report_response_body(body)
360
+ if body
361
+ ": #{body}"
362
+ else
363
+ ''
364
+ end
365
+ end
366
+ end
367
+ end
368
+ end