1package cloudflare 2 3import ( 4 "encoding/json" 5 "fmt" 6 "net/url" 7 "strconv" 8 "strings" 9 10 "github.com/pkg/errors" 11) 12 13// Filter holds the structure of the filter type. 14type Filter struct { 15 ID string `json:"id,omitempty"` 16 Expression string `json:"expression"` 17 Paused bool `json:"paused"` 18 Description string `json:"description"` 19 20 // Property is mentioned in documentation however isn't populated in 21 // any of the API requests. For now, let's just omit it unless it's 22 // provided. 23 Ref string `json:"ref,omitempty"` 24} 25 26// FiltersDetailResponse is the API response that is returned 27// for requesting all filters on a zone. 28type FiltersDetailResponse struct { 29 Result []Filter `json:"result"` 30 ResultInfo `json:"result_info"` 31 Response 32} 33 34// FilterDetailResponse is the API response that is returned 35// for requesting a single filter on a zone. 36type FilterDetailResponse struct { 37 Result Filter `json:"result"` 38 ResultInfo `json:"result_info"` 39 Response 40} 41 42// FilterValidateExpression represents the JSON payload for checking 43// an expression. 44type FilterValidateExpression struct { 45 Expression string `json:"expression"` 46} 47 48// FilterValidateExpressionResponse represents the API response for 49// checking the expression. It conforms to the JSON API approach however 50// we don't need all of the fields exposed. 51type FilterValidateExpressionResponse struct { 52 Success bool `json:"success"` 53 Errors []FilterValidationExpressionMessage `json:"errors"` 54} 55 56// FilterValidationExpressionMessage represents the API error message. 57type FilterValidationExpressionMessage struct { 58 Message string `json:"message"` 59} 60 61// Filter returns a single filter in a zone based on the filter ID. 62// 63// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-by-filter-id 64func (api *API) Filter(zoneID, filterID string) (Filter, error) { 65 uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filterID) 66 67 res, err := api.makeRequest("GET", uri, nil) 68 if err != nil { 69 return Filter{}, errors.Wrap(err, errMakeRequestError) 70 } 71 72 var filterResponse FilterDetailResponse 73 err = json.Unmarshal(res, &filterResponse) 74 if err != nil { 75 return Filter{}, errors.Wrap(err, errUnmarshalError) 76 } 77 78 return filterResponse.Result, nil 79} 80 81// Filters returns all filters for a zone. 82// 83// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/get/#get-all-filters 84func (api *API) Filters(zoneID string, pageOpts PaginationOptions) ([]Filter, error) { 85 uri := "/zones/" + zoneID + "/filters" 86 v := url.Values{} 87 88 if pageOpts.PerPage > 0 { 89 v.Set("per_page", strconv.Itoa(pageOpts.PerPage)) 90 } 91 92 if pageOpts.Page > 0 { 93 v.Set("page", strconv.Itoa(pageOpts.Page)) 94 } 95 96 if len(v) > 0 { 97 uri = uri + "?" + v.Encode() 98 } 99 100 res, err := api.makeRequest("GET", uri, nil) 101 if err != nil { 102 return []Filter{}, errors.Wrap(err, errMakeRequestError) 103 } 104 105 var filtersResponse FiltersDetailResponse 106 err = json.Unmarshal(res, &filtersResponse) 107 if err != nil { 108 return []Filter{}, errors.Wrap(err, errUnmarshalError) 109 } 110 111 return filtersResponse.Result, nil 112} 113 114// CreateFilters creates new filters. 115// 116// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/post/ 117func (api *API) CreateFilters(zoneID string, filters []Filter) ([]Filter, error) { 118 uri := "/zones/" + zoneID + "/filters" 119 120 res, err := api.makeRequest("POST", uri, filters) 121 if err != nil { 122 return []Filter{}, errors.Wrap(err, errMakeRequestError) 123 } 124 125 var filtersResponse FiltersDetailResponse 126 err = json.Unmarshal(res, &filtersResponse) 127 if err != nil { 128 return []Filter{}, errors.Wrap(err, errUnmarshalError) 129 } 130 131 return filtersResponse.Result, nil 132} 133 134// UpdateFilter updates a single filter. 135// 136// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-a-single-filter 137func (api *API) UpdateFilter(zoneID string, filter Filter) (Filter, error) { 138 if filter.ID == "" { 139 return Filter{}, errors.Errorf("filter ID cannot be empty") 140 } 141 142 uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filter.ID) 143 144 res, err := api.makeRequest("PUT", uri, filter) 145 if err != nil { 146 return Filter{}, errors.Wrap(err, errMakeRequestError) 147 } 148 149 var filterResponse FilterDetailResponse 150 err = json.Unmarshal(res, &filterResponse) 151 if err != nil { 152 return Filter{}, errors.Wrap(err, errUnmarshalError) 153 } 154 155 return filterResponse.Result, nil 156} 157 158// UpdateFilters updates many filters at once. 159// 160// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/put/#update-multiple-filters 161func (api *API) UpdateFilters(zoneID string, filters []Filter) ([]Filter, error) { 162 for _, filter := range filters { 163 if filter.ID == "" { 164 return []Filter{}, errors.Errorf("filter ID cannot be empty") 165 } 166 } 167 168 uri := "/zones/" + zoneID + "/filters" 169 170 res, err := api.makeRequest("PUT", uri, filters) 171 if err != nil { 172 return []Filter{}, errors.Wrap(err, errMakeRequestError) 173 } 174 175 var filtersResponse FiltersDetailResponse 176 err = json.Unmarshal(res, &filtersResponse) 177 if err != nil { 178 return []Filter{}, errors.Wrap(err, errUnmarshalError) 179 } 180 181 return filtersResponse.Result, nil 182} 183 184// DeleteFilter deletes a single filter. 185// 186// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/delete/#delete-a-single-filter 187func (api *API) DeleteFilter(zoneID, filterID string) error { 188 if filterID == "" { 189 return errors.Errorf("filter ID cannot be empty") 190 } 191 192 uri := fmt.Sprintf("/zones/%s/filters/%s", zoneID, filterID) 193 194 _, err := api.makeRequest("DELETE", uri, nil) 195 if err != nil { 196 return errors.Wrap(err, errMakeRequestError) 197 } 198 199 return nil 200} 201 202// DeleteFilters deletes multiple filters. 203// 204// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/delete/#delete-multiple-filters 205func (api *API) DeleteFilters(zoneID string, filterIDs []string) error { 206 ids := strings.Join(filterIDs, ",") 207 uri := fmt.Sprintf("/zones/%s/filters?id=%s", zoneID, ids) 208 209 _, err := api.makeRequest("DELETE", uri, nil) 210 if err != nil { 211 return errors.Wrap(err, errMakeRequestError) 212 } 213 214 return nil 215} 216 217// ValidateFilterExpression checks correctness of a filter expression. 218// 219// API reference: https://developers.cloudflare.com/firewall/api/cf-filters/validation/ 220func (api *API) ValidateFilterExpression(expression string) error { 221 uri := fmt.Sprintf("/filters/validate-expr") 222 expressionPayload := FilterValidateExpression{Expression: expression} 223 224 _, err := api.makeRequest("POST", uri, expressionPayload) 225 if err != nil { 226 var filterValidationResponse FilterValidateExpressionResponse 227 228 jsonErr := json.Unmarshal([]byte(err.Error()), &filterValidationResponse) 229 if jsonErr != nil { 230 return errors.Wrap(jsonErr, errUnmarshalError) 231 } 232 233 if filterValidationResponse.Success != true { 234 // Unsure why but the API returns `errors` as an array but it only 235 // ever shows the issue with one problem at a time ¯\_(ツ)_/¯ 236 return errors.Errorf(filterValidationResponse.Errors[0].Message) 237 } 238 } 239 240 return nil 241} 242