1package cloudflare
2
3import (
4	"context"
5	"encoding/json"
6	"fmt"
7	"net/http"
8	"net/url"
9	"strconv"
10	"time"
11
12	"github.com/pkg/errors"
13)
14
15// FirewallRule is the struct of the firewall rule.
16type FirewallRule struct {
17	ID          string      `json:"id,omitempty"`
18	Paused      bool        `json:"paused"`
19	Description string      `json:"description"`
20	Action      string      `json:"action"`
21	Priority    interface{} `json:"priority"`
22	Filter      Filter      `json:"filter"`
23	Products    []string    `json:"products,omitempty"`
24	CreatedOn   time.Time   `json:"created_on,omitempty"`
25	ModifiedOn  time.Time   `json:"modified_on,omitempty"`
26}
27
28// FirewallRulesDetailResponse is the API response for the firewall
29// rules.
30type FirewallRulesDetailResponse struct {
31	Result     []FirewallRule `json:"result"`
32	ResultInfo `json:"result_info"`
33	Response
34}
35
36// FirewallRuleResponse is the API response that is returned
37// for requesting a single firewall rule on a zone.
38type FirewallRuleResponse struct {
39	Result     FirewallRule `json:"result"`
40	ResultInfo `json:"result_info"`
41	Response
42}
43
44// FirewallRules returns all firewall rules.
45//
46// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-all-rules
47func (api *API) FirewallRules(ctx context.Context, zoneID string, pageOpts PaginationOptions) ([]FirewallRule, error) {
48	uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID)
49	v := url.Values{}
50
51	if pageOpts.PerPage > 0 {
52		v.Set("per_page", strconv.Itoa(pageOpts.PerPage))
53	}
54
55	if pageOpts.Page > 0 {
56		v.Set("page", strconv.Itoa(pageOpts.Page))
57	}
58
59	if len(v) > 0 {
60		uri = fmt.Sprintf("%s?%s", uri, v.Encode())
61	}
62
63	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
64	if err != nil {
65		return []FirewallRule{}, err
66	}
67
68	var firewallDetailResponse FirewallRulesDetailResponse
69	err = json.Unmarshal(res, &firewallDetailResponse)
70	if err != nil {
71		return []FirewallRule{}, errors.Wrap(err, errUnmarshalError)
72	}
73
74	return firewallDetailResponse.Result, nil
75}
76
77// FirewallRule returns a single firewall rule based on the ID.
78//
79// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/get/#get-by-rule-id
80func (api *API) FirewallRule(ctx context.Context, zoneID, firewallRuleID string) (FirewallRule, error) {
81	uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRuleID)
82
83	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
84	if err != nil {
85		return FirewallRule{}, err
86	}
87
88	var firewallRuleResponse FirewallRuleResponse
89	err = json.Unmarshal(res, &firewallRuleResponse)
90	if err != nil {
91		return FirewallRule{}, errors.Wrap(err, errUnmarshalError)
92	}
93
94	return firewallRuleResponse.Result, nil
95}
96
97// CreateFirewallRules creates new firewall rules.
98//
99// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/post/
100func (api *API) CreateFirewallRules(ctx context.Context, zoneID string, firewallRules []FirewallRule) ([]FirewallRule, error) {
101	uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID)
102
103	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, firewallRules)
104	if err != nil {
105		return []FirewallRule{}, err
106	}
107
108	var firewallRulesDetailResponse FirewallRulesDetailResponse
109	err = json.Unmarshal(res, &firewallRulesDetailResponse)
110	if err != nil {
111		return []FirewallRule{}, errors.Wrap(err, errUnmarshalError)
112	}
113
114	return firewallRulesDetailResponse.Result, nil
115}
116
117// UpdateFirewallRule updates a single firewall rule.
118//
119// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-a-single-rule
120func (api *API) UpdateFirewallRule(ctx context.Context, zoneID string, firewallRule FirewallRule) (FirewallRule, error) {
121	if firewallRule.ID == "" {
122		return FirewallRule{}, errors.Errorf("firewall rule ID cannot be empty")
123	}
124
125	uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRule.ID)
126
127	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, firewallRule)
128	if err != nil {
129		return FirewallRule{}, err
130	}
131
132	var firewallRuleResponse FirewallRuleResponse
133	err = json.Unmarshal(res, &firewallRuleResponse)
134	if err != nil {
135		return FirewallRule{}, errors.Wrap(err, errUnmarshalError)
136	}
137
138	return firewallRuleResponse.Result, nil
139}
140
141// UpdateFirewallRules updates a single firewall rule.
142//
143// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/put/#update-multiple-rules
144func (api *API) UpdateFirewallRules(ctx context.Context, zoneID string, firewallRules []FirewallRule) ([]FirewallRule, error) {
145	for _, firewallRule := range firewallRules {
146		if firewallRule.ID == "" {
147			return []FirewallRule{}, errors.Errorf("firewall ID cannot be empty")
148		}
149	}
150
151	uri := fmt.Sprintf("/zones/%s/firewall/rules", zoneID)
152
153	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, firewallRules)
154	if err != nil {
155		return []FirewallRule{}, err
156	}
157
158	var firewallRulesDetailResponse FirewallRulesDetailResponse
159	err = json.Unmarshal(res, &firewallRulesDetailResponse)
160	if err != nil {
161		return []FirewallRule{}, errors.Wrap(err, errUnmarshalError)
162	}
163
164	return firewallRulesDetailResponse.Result, nil
165}
166
167// DeleteFirewallRule deletes a single firewall rule.
168//
169// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/delete/#delete-a-single-rule
170func (api *API) DeleteFirewallRule(ctx context.Context, zoneID, firewallRuleID string) error {
171	if firewallRuleID == "" {
172		return errors.Errorf("firewall rule ID cannot be empty")
173	}
174
175	uri := fmt.Sprintf("/zones/%s/firewall/rules/%s", zoneID, firewallRuleID)
176
177	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
178	if err != nil {
179		return err
180	}
181
182	return nil
183}
184
185// DeleteFirewallRules deletes multiple firewall rules at once.
186//
187// API reference: https://developers.cloudflare.com/firewall/api/cf-firewall-rules/delete/#delete-multiple-rules
188func (api *API) DeleteFirewallRules(ctx context.Context, zoneID string, firewallRuleIDs []string) error {
189	v := url.Values{}
190
191	for _, ruleID := range firewallRuleIDs {
192		v.Add("id", ruleID)
193	}
194
195	uri := fmt.Sprintf("/zones/%s/firewall/rules?%s", zoneID, v.Encode())
196
197	_, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
198	if err != nil {
199		return err
200	}
201
202	return nil
203}
204