webhook.slt 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395
  1. # Copyright Materialize, Inc. and contributors. All rights reserved.
  2. #
  3. # Use of this software is governed by the Business Source License
  4. # included in the LICENSE file at the root of this repository.
  5. #
  6. # As of the Change Date specified in that file, in accordance with
  7. # the Business Source License, use of this software will be governed
  8. # by the Apache License, Version 2.0.
  9. mode cockroach
  10. # Start from a pristine server
  11. reset-server
  12. # Create a cluster to install all of our webhook sources on.
  13. statement ok
  14. CREATE CLUSTER webhook_cluster REPLICAS (r1 (SIZE '1'));
  15. #
  16. # Happy Path, valid WEBHOOK sources
  17. #
  18. statement ok
  19. CREATE SOURCE webhook_bytes IN CLUSTER webhook_cluster FROM WEBHOOK
  20. BODY FORMAT BYTES
  21. query TTTT
  22. SHOW COLUMNS FROM webhook_bytes
  23. ----
  24. body false bytea (empty)
  25. query TT
  26. SELECT name, regexp_match(url, '(/api/webhook/.*)') FROM mz_internal.mz_webhook_sources
  27. ----
  28. webhook_bytes {/api/webhook/materialize/public/webhook_bytes}
  29. statement ok
  30. CREATE SOURCE "weird-name-(]%/'" IN CLUSTER webhook_cluster FROM WEBHOOK BODY FORMAT BYTES
  31. query TT
  32. SELECT name, regexp_match(url, '(/api/webhook/.*)') FROM mz_internal.mz_webhook_sources
  33. ----
  34. webhook_bytes {/api/webhook/materialize/public/webhook_bytes}
  35. weird-name-(]%/' {/api/webhook/materialize/public/weird-name-(]%25%2F'}
  36. statement ok
  37. DROP SOURCE "weird-name-(]%/'"
  38. query TT
  39. SELECT id, regexp_match(url, '(/api/webhook/.*)') FROM mz_internal.mz_webhook_sources
  40. ----
  41. u1 {/api/webhook/materialize/public/webhook_bytes}
  42. statement ok
  43. CREATE SOURCE webhook_bytes_include_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  44. BODY FORMAT BYTES
  45. INCLUDE HEADERS
  46. query TTTT
  47. SHOW COLUMNS FROM webhook_bytes_include_headers
  48. ----
  49. body false bytea (empty)
  50. headers false map (empty)
  51. statement ok
  52. CREATE SOURCE webhook_json IN CLUSTER webhook_cluster FROM WEBHOOK
  53. BODY FORMAT JSON
  54. query TTTT
  55. SHOW COLUMNS FROM webhook_json
  56. ----
  57. body false jsonb (empty)
  58. statement ok
  59. CREATE SOURCE webhook_json_include_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  60. BODY FORMAT JSON
  61. INCLUDE HEADERS
  62. query TTTT
  63. SHOW COLUMNS FROM webhook_json_include_headers
  64. ----
  65. body false jsonb (empty)
  66. headers false map (empty)
  67. statement ok
  68. CREATE SOURCE webhook_text IN CLUSTER webhook_cluster FROM WEBHOOK
  69. BODY FORMAT TEXT
  70. query TTTT
  71. SHOW COLUMNS FROM webhook_text
  72. ----
  73. body false text (empty)
  74. statement ok
  75. CREATE SOURCE webhook_text_include_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  76. BODY FORMAT TEXT
  77. INCLUDE HEADERS
  78. query TTTT
  79. SHOW COLUMNS FROM webhook_text_include_headers
  80. ----
  81. body false text (empty)
  82. headers false map (empty)
  83. statement ok
  84. CREATE MATERIALIZED VIEW mat_view_text IN CLUSTER webhook_cluster AS (
  85. SELECT body FROM webhook_text_include_headers
  86. );
  87. # Should fail because a source with this name already exists.
  88. statement error source "materialize.public.webhook_text_include_headers" already exists
  89. CREATE SOURCE webhook_text_include_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  90. BODY FORMAT TEXT
  91. INCLUDE HEADERS
  92. # Should succeed since we're specifying "if not exists".
  93. statement ok
  94. CREATE SOURCE IF NOT EXISTS webhook_text_include_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  95. BODY FORMAT TEXT
  96. INCLUDE HEADERS
  97. # Make sure we can parse a relatively complex validation expression.
  98. statement ok
  99. CREATE SOURCE webhook_bad_validation_expr IN CLUSTER webhook_cluster FROM WEBHOOK
  100. BODY FORMAT TEXT
  101. CHECK (
  102. WITH (HEADERS, BODY)
  103. decode(headers->'signature', 'base64') = hmac(headers->'timestamp' || '.' || body, 'key', 'sha256')
  104. )
  105. statement ok
  106. CREATE SOURCE webhook_buildkite IN CLUSTER webhook_cluster FROM WEBHOOK
  107. BODY FORMAT JSON
  108. CHECK (
  109. WITH (HEADERS, BODY BYTES)
  110. decode(split_part(headers->'x-buildkite-signature', 'signature=', 1), 'hex') = hmac(
  111. split_part(split_part(headers->'x-buildkite-signature', 'timestamp=', 1), ',', 1) || '.' || convert_from(body, 'utf-8'),
  112. 'test_key',
  113. 'sha256'
  114. )
  115. )
  116. statement error column "field_that_does_not_exist" does not exist
  117. CREATE SOURCE webhook_bad_validation_expr IN CLUSTER webhook_cluster FROM WEBHOOK
  118. BODY FORMAT TEXT
  119. CHECK (
  120. WITH (HEADERS)
  121. decode(headers->'signature', 'base64') = hmac(field_that_does_not_exist, 'key', 'sha256')
  122. )
  123. statement error CHECK must have type boolean, not type text
  124. CREATE SOURCE webhook_validation_returns_non_bool IN CLUSTER webhook_cluster FROM WEBHOOK
  125. BODY FORMAT TEXT
  126. CHECK ( 'foo' || 'bar' )
  127. statement error expression provided in CHECK does not reference any columns
  128. CREATE SOURCE webhook_json_with_validation IN CLUSTER webhook_cluster FROM WEBHOOK
  129. BODY FORMAT JSON
  130. CHECK ( 'a' = 'test' )
  131. statement error CHECK does not allow subqueries
  132. CREATE SOURCE webhook_validation_with_subquery IN CLUSTER webhook_cluster FROM WEBHOOK
  133. BODY FORMAT BYTES
  134. CHECK (
  135. WITH (HEADERS)
  136. headers->'signature' IN (select * from mz_tables)
  137. )
  138. # Some webhook providers (e.g. Stripe) suggest you should reject any request whose provided
  139. # timestamp is older than a certain threshold.
  140. statement ok
  141. CREATE SOURCE webhook_validation_with_now IN CLUSTER webhook_cluster FROM WEBHOOK
  142. BODY FORMAT BYTES
  143. CHECK (
  144. WITH (HEADERS)
  145. (headers->'event_ts'::text)::timestamp + INTERVAL '30s' >= now()
  146. )
  147. statement ok
  148. CREATE SOURCE webhook_validation_with_current_timestamp IN CLUSTER webhook_cluster FROM WEBHOOK
  149. BODY FORMAT BYTES
  150. CHECK (
  151. WITH (HEADERS)
  152. headers->'event_ts' = current_timestamp()::text
  153. )
  154. statement error expression provided in CHECK is not deterministic
  155. CREATE SOURCE webhook_validation_with_mz_now IN CLUSTER webhook_cluster FROM WEBHOOK
  156. BODY FORMAT BYTES
  157. CHECK (
  158. WITH (HEADERS)
  159. headers->'event_ts' = mz_now()::text
  160. )
  161. statement error expression provided in CHECK is not deterministic
  162. CREATE SOURCE webhook_validation_with_current_db IN CLUSTER webhook_cluster FROM WEBHOOK
  163. BODY FORMAT BYTES
  164. CHECK (
  165. WITH (HEADERS)
  166. headers->'database' = current_database()
  167. )
  168. statement error unknown cluster 'i_do_not_exist'
  169. CREATE SOURCE webhook_cluster_does_not_exist IN CLUSTER i_do_not_exist FROM WEBHOOK
  170. BODY FORMAT BYTES;
  171. # Dropping without cascade should fail since there are sources using it.
  172. statement error cannot drop cluster "webhook_cluster" because other objects depend on it
  173. DROP CLUSTER webhook_cluster;
  174. # Create a webhook source that uses secrets when validating.
  175. statement ok
  176. CREATE SECRET webhook_shared_secret AS 'test_key';
  177. statement ok
  178. CREATE SOURCE webhook_with_secret IN CLUSTER webhook_cluster FROM WEBHOOK
  179. BODY FORMAT BYTES
  180. CHECK (
  181. WITH ( HEADERS, SECRET webhook_shared_secret )
  182. headers->'signature' = webhook_shared_secret
  183. )
  184. statement ok
  185. CREATE SOURCE webhook_buildkite2 IN CLUSTER webhook_cluster FROM WEBHOOK
  186. BODY FORMAT JSON
  187. CHECK (
  188. WITH ( HEADERS, BODY, SECRET webhook_shared_secret )
  189. decode(split_part(headers->'x-buildkite-signature', 'signature=', 1), 'hex') = hmac(
  190. split_part(split_part(headers->'x-buildkite-signature', 'timestamp=', 1), ',', 1) || '.' || body,
  191. webhook_shared_secret,
  192. 'sha256'
  193. )
  194. )
  195. statement ok
  196. ALTER SOURCE webhook_buildkite2 RENAME TO webhook_buildkite2_renamed;
  197. statement ok
  198. SELECT * FROM webhook_buildkite2_renamed;
  199. statement error unknown catalog item 'webhook_buildkite2'
  200. SELECT * FROM webhook_buildkite2;
  201. statement ok
  202. CREATE SECRET other_secret AS 'another_one';
  203. statement ok
  204. CREATE SOURCE webhook_with_two_secrets IN CLUSTER webhook_cluster FROM WEBHOOK
  205. BODY FORMAT BYTES
  206. CHECK (
  207. WITH (
  208. HEADERS, BODY BYTES,
  209. SECRET webhook_shared_secret AS key,
  210. SECRET other_secret BYTES
  211. )
  212. headers->'signature' = key AND body = other_secret
  213. )
  214. statement error unknown catalog item 'non_existent_secret'
  215. CREATE SOURCE webhook_with_unknown_second_secret IN CLUSTER webhook_cluster FROM WEBHOOK
  216. BODY FORMAT BYTES
  217. CHECK (
  218. WITH (
  219. HEADERS, BODY,
  220. SECRET webhook_shared_secret AS key,
  221. SECRET non_existent_secret
  222. )
  223. headers->'signature' = key AND body = non_existent_secret
  224. )
  225. statement error column reference "other_secret" is ambiguous
  226. CREATE SOURCE webhook_with_duplicate_secret_names IN CLUSTER webhook_cluster FROM WEBHOOK
  227. BODY FORMAT BYTES
  228. CHECK (
  229. WITH (
  230. HEADERS, BODY,
  231. SECRET webhook_shared_secret AS other_secret,
  232. SECRET other_secret
  233. )
  234. headers->'signature' = other_secret AND body = other_secret
  235. )
  236. statement ok
  237. CREATE SOURCE webhook_with_headers_and_body_alias IN CLUSTER webhook_cluster FROM WEBHOOK
  238. BODY FORMAT TEXT
  239. CHECK (
  240. WITH (
  241. HEADERS AS h1 BYTES,
  242. HEADERS,
  243. BODY AS b1,
  244. BODY BYTES
  245. )
  246. headers->'signature' = convert_from(h1->'signature', 'utf-8') AND b1 = convert_from(body, 'utf-8')
  247. )
  248. statement error column reference "headers" is ambiguous
  249. CREATE SOURCE webhook_with_headers_duplicates IN CLUSTER webhook_cluster FROM WEBHOOK
  250. BODY FORMAT TEXT
  251. CHECK (
  252. WITH (HEADERS, HEADERS)
  253. HEADERS->'signature' = '42'
  254. )
  255. statement error column reference "body" is ambiguous
  256. CREATE SOURCE webhook_with_body_duplicates IN CLUSTER webhook_cluster FROM WEBHOOK
  257. BODY FORMAT TEXT
  258. CHECK (
  259. WITH (BODY, BODY)
  260. length(body) > 0
  261. )
  262. statement error column reference "x" is ambiguous
  263. CREATE SOURCE webhook_with_duplicate_aliases IN CLUSTER webhook_cluster FROM WEBHOOK
  264. BODY FORMAT TEXT
  265. CHECK (
  266. WITH (HEADERS as x, BODY as x)
  267. length(x) > 0
  268. )
  269. statement ok
  270. CREATE SOURCE webhook_text_with_mapped_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  271. BODY FORMAT TEXT
  272. INCLUDE HEADER 'x-special-header' AS "specialHeader"
  273. INCLUDE HEADER 'x-hash' AS hash BYTES
  274. INCLUDE HEADER 'content-type' AS content_type
  275. query TTTT
  276. SHOW COLUMNS FROM webhook_text_with_mapped_headers
  277. ----
  278. body false text (empty)
  279. specialHeader true text (empty)
  280. hash true bytea (empty)
  281. content_type true text (empty)
  282. statement ok
  283. CREATE SOURCE webhook_text_mapped_and_filtered_headers IN CLUSTER webhook_cluster FROM WEBHOOK
  284. BODY FORMAT TEXT
  285. INCLUDE HEADER 'x-hash' as hash BYTES
  286. INCLUDE HEADER 'content-type' as content_type
  287. INCLUDE HEADERS ('x-special-header')
  288. query TTTT
  289. SHOW COLUMNS FROM webhook_text_mapped_and_filtered_headers
  290. ----
  291. body false text (empty)
  292. headers false map (empty)
  293. hash true bytea (empty)
  294. content_type true text (empty)
  295. statement error column reference "header_a" is ambiguous
  296. CREATE SOURCE webhook_text_with_duplicate_header_alias IN CLUSTER webhook_cluster FROM WEBHOOK
  297. BODY FORMAT TEXT
  298. INCLUDE HEADER 'x-first-header' as header_a
  299. INCLUDE HEADER 'x-second-header' as header_a
  300. statement error column reference "body" is ambiguous
  301. CREATE SOURCE webhook_text_with_header_alias_as_body IN CLUSTER webhook_cluster FROM WEBHOOK
  302. BODY FORMAT TEXT
  303. INCLUDE HEADER 'x-my-header' as body
  304. # Try creating a webhook source in a compute cluster.
  305. statement ok
  306. CREATE CLUSTER cluster1 REPLICAS (r1 (SIZE '1'));
  307. statement ok
  308. CREATE MATERIALIZED VIEW mv1 IN CLUSTER cluster1 AS SELECT name FROM mz_objects;
  309. # Webhook on cluster alongside compute objects.
  310. statement ok
  311. CREATE SOURCE webhook_on_cluster1 IN CLUSTER cluster1 FROM WEBHOOK
  312. BODY FORMAT BYTES;
  313. # Make sure we report webhook sources as running.
  314. query TTTT
  315. SELECT name, type, status, error FROM mz_internal.mz_source_statuses WHERE name = 'webhook_bytes'
  316. ----
  317. webhook_bytes webhook running NULL
  318. statement ok
  319. DROP SOURCE webhook_bytes;
  320. query TTTT
  321. SELECT name, type, status, error FROM mz_internal.mz_source_statuses WHERE name = 'webhook_bytes'
  322. ----
  323. # Cleanup.
  324. statement ok
  325. DROP CLUSTER webhook_cluster CASCADE;
  326. statement ok
  327. DROP CLUSTER cluster1 CASCADE;