1package cloudflare
2
3import (
4	"context"
5	"fmt"
6	"net/http"
7	"testing"
8
9	"github.com/stretchr/testify/assert"
10)
11
12var firewallRulePageOpts = PaginationOptions{
13	PerPage: 25,
14	Page:    1,
15}
16
17func TestFirewallRules(t *testing.T) {
18	setup()
19	defer teardown()
20
21	handler := func(w http.ResponseWriter, r *http.Request) {
22		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
23		w.Header().Set("content-type", "application/json")
24		fmt.Fprintf(w, `{
25			"result":[
26				{
27					"id":"2ae338944d6143383c3cf05a7c80d984",
28					"paused":false,
29					"description":"allow uploads without waf",
30					"action":"bypass",
31					"products": ["waf"],
32					"priority":null,
33					"filter":{
34						"id":"74217d7bd5ab435e84b1bd473bf4fb9f",
35						"expression":"http.request.uri.path matches \"^/upload$\"",
36						"paused":false,
37						"description":"/upload"
38					}
39				},
40				{
41					"id":"4ae338944d6143378c3cf05a7c77d983",
42					"paused":false,
43					"description":"allow API traffic without challenge",
44					"action":"allow",
45					"priority":null,
46					"filter":{
47						"id":"14217d7bd5ab435e84b1bd468bf4fb9f",
48						"expression":"http.request.uri.path matches \"^/api/.*$\"",
49						"paused":false,
50						"description":"/api"
51					}
52				},
53				{
54					"id":"f2d427378e7542acb295380d352e2ebd",
55					"paused":false,
56					"description":"do not challenge login from office",
57					"action":"allow",
58					"priority":null,
59					"filter":{
60						"id":"b7ff25282d394be7b945e23c7106ce8a",
61						"expression":"(http.request.uri.path ~ \"^.*/xmlrpc.php$\"",
62						"paused":false,
63						"description":"wordpress xmlrpc"
64					}
65				},
66				{
67					"id":"cbf4b7a5a2a24e59a03044d6d44ceb09",
68					"paused":false,
69					"description":"challenge login",
70					"action":"challenge",
71					"priority":null,
72					"filter":{
73						"id":"c218c536b2bd406f958f278cf0fa8c0f",
74						"expression":"(http.request.uri.path ~ \"^.*/wp-login.php$\"",
75						"paused":false,
76						"description":"Login"
77					}
78				},
79				{
80					"id":"52161eb6af4241bb9d4b32394be72fdf",
81					"paused":false,
82					"description":"JS challenge site",
83					"action":"js_challenge",
84					"priority":null,
85					"filter":{
86						"id":"f2a64520581a4209aab12187a0081364",
87						"expression":"not http.request.uri.path matches \"^/api/.*$\"",
88						"paused":false,
89						"description":"not /api"
90					}
91				}
92			],
93			"success":true,
94			"errors":null,
95			"messages":null,
96			"result_info":{
97				"page":1,
98				"per_page":25,
99				"count":5,
100				"total_count":5
101			}
102		}
103		`)
104	}
105
106	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
107	want := []FirewallRule{
108		{
109			ID:          "2ae338944d6143383c3cf05a7c80d984",
110			Paused:      false,
111			Description: "allow uploads without waf",
112			Action:      "bypass",
113			Priority:    nil,
114			Products:    []string{"waf"},
115			Filter: Filter{
116				ID:          "74217d7bd5ab435e84b1bd473bf4fb9f",
117				Expression:  "http.request.uri.path matches \"^/upload$\"",
118				Paused:      false,
119				Description: "/upload",
120			},
121		},
122		{
123			ID:          "4ae338944d6143378c3cf05a7c77d983",
124			Paused:      false,
125			Description: "allow API traffic without challenge",
126			Action:      "allow",
127			Priority:    nil,
128			Filter: Filter{
129				ID:          "14217d7bd5ab435e84b1bd468bf4fb9f",
130				Expression:  "http.request.uri.path matches \"^/api/.*$\"",
131				Paused:      false,
132				Description: "/api",
133			},
134		},
135		{
136			ID:          "f2d427378e7542acb295380d352e2ebd",
137			Paused:      false,
138			Description: "do not challenge login from office",
139			Action:      "allow",
140			Priority:    nil,
141			Filter: Filter{
142				ID:          "b7ff25282d394be7b945e23c7106ce8a",
143				Expression:  "(http.request.uri.path ~ \"^.*/xmlrpc.php$\"",
144				Paused:      false,
145				Description: "wordpress xmlrpc",
146			},
147		},
148		{
149			ID:          "cbf4b7a5a2a24e59a03044d6d44ceb09",
150			Paused:      false,
151			Description: "challenge login",
152			Action:      "challenge",
153			Priority:    nil,
154			Filter: Filter{
155				ID:          "c218c536b2bd406f958f278cf0fa8c0f",
156				Expression:  "(http.request.uri.path ~ \"^.*/wp-login.php$\"",
157				Paused:      false,
158				Description: "Login",
159			},
160		},
161		{
162			ID:          "52161eb6af4241bb9d4b32394be72fdf",
163			Paused:      false,
164			Description: "JS challenge site",
165			Action:      "js_challenge",
166			Priority:    nil,
167			Filter: Filter{
168				ID:          "f2a64520581a4209aab12187a0081364",
169				Expression:  "not http.request.uri.path matches \"^/api/.*$\"",
170				Paused:      false,
171				Description: "not /api",
172			},
173		},
174	}
175
176	actual, err := client.FirewallRules(context.Background(), "d56084adb405e0b7e32c52321bf07be6", firewallRulePageOpts)
177
178	if assert.NoError(t, err) {
179		assert.Equal(t, want, actual)
180	}
181}
182
183func TestFirewallRule(t *testing.T) {
184	setup()
185	defer teardown()
186
187	handler := func(w http.ResponseWriter, r *http.Request) {
188		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
189		w.Header().Set("content-type", "application/json")
190		fmt.Fprintf(w, `{
191			"result":{
192				"id":"f2d427378e7542acb295380d352e2ebd",
193				"paused":false,
194				"description":"do not challenge login from office",
195				"action":"allow",
196				"priority":null,
197				"filter":{
198					"id":"b7ff25282d394be7b945e23c7106ce8a",
199					"expression":"ip.src in {127.0.0.1} ~ \"^.*/login.php$\")",
200					"paused":false,
201					"description":"Login from office"
202				}
203			},
204			"success":true,
205			"errors":null,
206			"messages":null
207		}
208		`)
209	}
210
211	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules/f2d427378e7542acb295380d352e2ebd", handler)
212	want := FirewallRule{
213		ID:          "f2d427378e7542acb295380d352e2ebd",
214		Paused:      false,
215		Description: "do not challenge login from office",
216		Action:      "allow",
217		Priority:    nil,
218		Filter: Filter{
219			ID:          "b7ff25282d394be7b945e23c7106ce8a",
220			Expression:  "ip.src in {127.0.0.1} ~ \"^.*/login.php$\")",
221			Paused:      false,
222			Description: "Login from office",
223		},
224	}
225
226	actual, err := client.FirewallRule(context.Background(), "d56084adb405e0b7e32c52321bf07be6", "f2d427378e7542acb295380d352e2ebd")
227
228	if assert.NoError(t, err) {
229		assert.Equal(t, want, actual)
230	}
231}
232
233func TestCreateSingleFirewallRule(t *testing.T) {
234	setup()
235	defer teardown()
236
237	handler := func(w http.ResponseWriter, r *http.Request) {
238		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
239		w.Header().Set("content-type", "application/json")
240		fmt.Fprintf(w, `{
241			"result":[
242				{
243					"id":"f2d427378e7542acb295380d352e2ebd",
244					"paused":false,
245					"description":"do not challenge login from office",
246					"action":"allow",
247					"priority":null,
248					"filter":{
249						"id":"b7ff25282d394be7b945e23c7106ce8a",
250						"expression":"ip.src in {127.0.0.0/24}",
251						"paused":false,
252						"description":"Login from office"
253					}
254				}
255			],
256			"success":true,
257			"errors":null,
258			"messages":null
259		}
260		`)
261	}
262
263	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
264	want := []FirewallRule{
265		{
266			ID:          "f2d427378e7542acb295380d352e2ebd",
267			Paused:      false,
268			Description: "do not challenge login from office",
269			Action:      "allow",
270			Priority:    nil,
271			Filter: Filter{
272				ID:          "b7ff25282d394be7b945e23c7106ce8a",
273				Expression:  "ip.src in {127.0.0.0/24}",
274				Paused:      false,
275				Description: "Login from office",
276			},
277		},
278	}
279
280	actual, err := client.CreateFirewallRules(context.Background(), "d56084adb405e0b7e32c52321bf07be6", want)
281
282	if assert.NoError(t, err) {
283		assert.Equal(t, want, actual)
284	}
285}
286
287func TestCreateMultipleFirewallRules(t *testing.T) {
288	setup()
289	defer teardown()
290
291	handler := func(w http.ResponseWriter, r *http.Request) {
292		assert.Equal(t, http.MethodPost, r.Method, "Expected method 'POST', got %s", r.Method)
293		w.Header().Set("content-type", "application/json")
294		fmt.Fprintf(w, `{
295			"result":[
296				{
297					"id":"f2d427378e7542acb295380d352e2ebd",
298					"paused":false,
299					"description":"do not challenge login from office",
300					"action":"allow",
301					"priority":null,
302					"filter":{
303						"id":"b7ff25282d394be7b945e23c7106ce8a",
304						"expression":"ip.src in {127.0.0.0/24}",
305						"paused":false,
306						"description":"Login from office"
307					}
308				},
309				{
310					"id":"cbf4b7a5a2a24e59a03044d6d44ceb09",
311					"paused":false,
312					"description":"challenge login",
313					"action":"challenge",
314					"priority":null,
315					"filter":{
316						"id":"c218c536b2bd406f958f278cf0fa8c0f",
317						"expression":"(http.request.uri.path ~ \"^.*/wp-login.php$\")",
318						"paused":false,
319						"description":"Login"
320					}
321				}
322			],
323			"success":true,
324			"errors":null,
325			"messages":null
326		}
327		`)
328	}
329
330	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
331	want := []FirewallRule{
332		{
333			ID:          "f2d427378e7542acb295380d352e2ebd",
334			Paused:      false,
335			Description: "do not challenge login from office",
336			Action:      "allow",
337			Priority:    nil,
338			Filter: Filter{
339				ID:          "b7ff25282d394be7b945e23c7106ce8a",
340				Expression:  "ip.src in {127.0.0.0/24}",
341				Paused:      false,
342				Description: "Login from office",
343			},
344		},
345		{
346			ID:          "cbf4b7a5a2a24e59a03044d6d44ceb09",
347			Paused:      false,
348			Description: "challenge login",
349			Action:      "challenge",
350			Priority:    nil,
351			Filter: Filter{
352				ID:          "c218c536b2bd406f958f278cf0fa8c0f",
353				Expression:  "(http.request.uri.path ~ \"^.*/wp-login.php$\")",
354				Paused:      false,
355				Description: "Login",
356			},
357		},
358	}
359
360	actual, err := client.CreateFirewallRules(context.Background(), "d56084adb405e0b7e32c52321bf07be6", want)
361
362	if assert.NoError(t, err) {
363		assert.Equal(t, want, actual)
364	}
365}
366
367func TestUpdateFirewallRuleWithMissingID(t *testing.T) {
368	setup()
369	defer teardown()
370
371	want := FirewallRule{
372		ID:          "",
373		Paused:      false,
374		Description: "challenge site",
375		Action:      "challenge",
376		Priority:    nil,
377		Filter: Filter{
378			ID:          "f2a64520581a4209aab12187a0081364",
379			Expression:  "not http.request.uri.path matches \"^/api/.*$\"",
380			Paused:      false,
381			Description: "not /api",
382		},
383	}
384
385	_, err := client.UpdateFirewallRule(context.Background(), "d56084adb405e0b7e32c52321bf07be6", want)
386	assert.EqualError(t, err, "firewall rule ID cannot be empty")
387}
388
389func TestUpdateSingleFirewallRule(t *testing.T) {
390	setup()
391	defer teardown()
392
393	handler := func(w http.ResponseWriter, r *http.Request) {
394		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
395		w.Header().Set("content-type", "application/json")
396		fmt.Fprintf(w, `{
397			"result":{
398				"id":"52161eb6af4241bb9d4b32394be72fdf",
399				"paused":false,
400				"description":"challenge site",
401				"action":"challenge",
402				"priority":null,
403				"filter":{
404					"id":"f2a64520581a4209aab12187a0081364",
405					"expression":"not http.request.uri.path matches \"^/api/.*$\"",
406					"paused":false,
407					"description":"not /api"
408				}
409			},
410			"success":true,
411			"errors":null,
412			"messages":null
413		}
414		`)
415	}
416
417	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules/52161eb6af4241bb9d4b32394be72fdf", handler)
418	want := FirewallRule{
419		ID:          "52161eb6af4241bb9d4b32394be72fdf",
420		Paused:      false,
421		Description: "challenge site",
422		Action:      "challenge",
423		Priority:    nil,
424		Filter: Filter{
425			ID:          "f2a64520581a4209aab12187a0081364",
426			Expression:  "not http.request.uri.path matches \"^/api/.*$\"",
427			Paused:      false,
428			Description: "not /api",
429		},
430	}
431
432	actual, err := client.UpdateFirewallRule(context.Background(), "d56084adb405e0b7e32c52321bf07be6", want)
433
434	if assert.NoError(t, err) {
435		assert.Equal(t, want, actual)
436	}
437}
438
439func TestUpdateMultipleFirewallRules(t *testing.T) {
440	setup()
441	defer teardown()
442
443	handler := func(w http.ResponseWriter, r *http.Request) {
444		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
445		w.Header().Set("content-type", "application/json")
446		fmt.Fprintf(w, `{
447			"result":[
448				{
449					"id":"f2d427378e7542acb295380d352e2ebd",
450					"paused":false,
451					"description":"do not challenge login from office",
452					"action":"allow",
453					"priority":null,
454					"filter":{
455						"id":"b7ff25282d394be7b945e23c7106ce8a",
456						"expression":"ip.src in {127.0.0.0/24}",
457						"paused":false,
458						"description":"Login from office"
459					}
460				},
461				{
462					"id":"cbf4b7a5a2a24e59a03044d6d44ceb09",
463					"paused":false,
464					"description":"challenge login",
465					"action":"challenge",
466					"priority":null,
467					"filter":{
468						"id":"c218c536b2bd406f958f278cf0fa8c0f",
469						"expression":"(http.request.uri.path ~ \"^.*/wp-login.php$\")",
470						"paused":false,
471						"description":"Login"
472					}
473				}
474			],
475			"success":true,
476			"errors":null,
477			"messages":null
478		}
479		`)
480	}
481
482	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules", handler)
483	want := []FirewallRule{
484		{
485			ID:          "f2d427378e7542acb295380d352e2ebd",
486			Paused:      false,
487			Description: "do not challenge login from office",
488			Action:      "allow",
489			Priority:    nil,
490			Filter: Filter{
491				ID:          "b7ff25282d394be7b945e23c7106ce8a",
492				Expression:  "ip.src in {127.0.0.0/24}",
493				Paused:      false,
494				Description: "Login from office",
495			},
496		},
497		{
498			ID:          "cbf4b7a5a2a24e59a03044d6d44ceb09",
499			Paused:      false,
500			Description: "challenge login",
501			Action:      "challenge",
502			Priority:    nil,
503			Filter: Filter{
504				ID:          "c218c536b2bd406f958f278cf0fa8c0f",
505				Expression:  "(http.request.uri.path ~ \"^.*/wp-login.php$\")",
506				Paused:      false,
507				Description: "Login",
508			},
509		},
510	}
511
512	actual, err := client.UpdateFirewallRules(context.Background(), "d56084adb405e0b7e32c52321bf07be6", want)
513
514	if assert.NoError(t, err) {
515		assert.Equal(t, want, actual)
516	}
517}
518
519func TestDeleteSingleFirewallRule(t *testing.T) {
520	setup()
521	defer teardown()
522
523	handler := func(w http.ResponseWriter, r *http.Request) {
524		assert.Equal(t, http.MethodDelete, r.Method, "Expected method 'DELETE', got %s", r.Method)
525		w.Header().Set("content-type", "application/json")
526		fmt.Fprintf(w, `{
527			"result": [],
528			"success": true,
529			"errors": null,
530			"messages": null
531		}
532		`)
533	}
534
535	mux.HandleFunc("/zones/d56084adb405e0b7e32c52321bf07be6/firewall/rules/f2d427378e7542acb295380d352e2ebd", handler)
536
537	err := client.DeleteFirewallRule(context.Background(), "d56084adb405e0b7e32c52321bf07be6", "f2d427378e7542acb295380d352e2ebd")
538	assert.NoError(t, err)
539}
540
541func TestDeleteFirewallRuleWithMissingID(t *testing.T) {
542	setup()
543	defer teardown()
544
545	err := client.DeleteFirewallRule(context.Background(), "d56084adb405e0b7e32c52321bf07be6", "")
546	assert.EqualError(t, err, "firewall rule ID cannot be empty")
547}
548