Repository: cloudflare/cloudflare-go (go)

Issue:
Add support for Cloudflare Bot Management
Bot management is a service provided by CloudFlare that detects bots and exposes new variables you can use in custom firewall rules. For more information, see here: https://www.cloudflare.com/products/bot-management/

It is enabled/disabled in the Settings menu of a zone, but it is not exposed via the settings API so it can not be set using this library at the moment. It is exposed via the following URL: `https://api.cloudflare.com/client/v4/zones/$ZONE_ID/bot_management`

This endpoint allows setting of the `enabled` flag, which enables bot management, and the `fight_mode` flag which will automatically challenge probable bots.

example GET response:
```
{
  "result": {
    "fight_mode": false,
    "enabled": true
  },
  "success": true,
  "errors": [],
  "messages": []
}
```

and via PUT you can set values:
```
{
  "enabled": true,
  "fight_mode": true
}
```
Would be great if this feature was supported so it then can also be used in the Cloudflare Terraform provider.

Pull request description:
Add support for Bot Management
<!-- 
Provide a general summary of your changes in the title above. You should
remove this overview, any sections and any section descriptions you
don't need below before submitting. There isn't a strict requirement to
use this template if you can structure your description and still cover
these points. 
-->

## Description

This change updates the SDK to properly support updating and retrieving the bot management configuration for a zone.

Closes #467

<!--
Describe your changes in detail through motivation and context. Why is
this change required? What problem does it solve? If it fixes an open
issue, link to the issue using GitHub's closing issues keywords[1].
-->
## Has your change been tested?

Yes, I have included unit tests in this PR. I have also manually tested the changes to verify that my changes work as expected.
<!--
Explain how the change has been tested and what you ran to confirm your
change affects other parts of the code. Automated tests are generally
expected and changes without tests should explain why they aren't
required.
-->

## Screenshots (if appropriate):

## Types of changes

What sort of change does your code introduce/modify?

- [ ] Bug fix (non-breaking change which fixes an issue)
- [x] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)

## Checklist:

- [x] My code follows the code style of this project.
- [x] My change requires a change to the documentation. (done)[https://developers.cloudflare.com/api/operations/bot-management-for-a-zone-get-config]
- [x] I have updated the documentation accordingly. (done)[https://developers.cloudflare.com/api/operations/bot-management-for-a-zone-get-config]
- [x] I have added tests to cover my changes.
- [x] All new and existing tests passed.
- [x] This change is using publicly documented in [cloudflare/api-schemas](https://github.com/cloudflare/api-schemas) 
      and relies on stable APIs.

[1]: https://help.github.com/articles/closing-issues-using-keywords/


Patch:
diff --git a/.changelog/1363.txt b/.changelog/1363.txt
new file mode 100644
index 000000000..e538706f3
--- /dev/null
+++ b/.changelog/1363.txt
@@ -0,0 +1,3 @@
+```release-note:enhancement
+bot_management: add support for bot_management API
+```
\ No newline at end of file
diff --git a/bot_management.go b/bot_management.go
new file mode 100644
index 000000000..f44d39ffb
--- /dev/null
+++ b/bot_management.go
@@ -0,0 +1,90 @@
+package cloudflare
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+)
+
+// BotManagement represents the bots config for a zone.
+type BotManagement struct {
+	EnableJS                     *bool   `json:"enable_js,omitempty"`
+	FightMode                    *bool   `json:"fight_mode,omitempty"`
+	SBFMDefinitelyAutomated      *string `json:"sbfm_definitely_automated,omitempty"`
+	SBFMLikelyAutomated          *string `json:"sbfm_likely_automated,omitempty"`
+	SBFMVerifiedBots             *string `json:"sbfm_verified_bots,omitempty"`
+	SBFMStaticResourceProtection *bool   `json:"sbfm_static_resource_protection,omitempty"`
+	OptimizeWordpress            *bool   `json:"optimize_wordpress,omitempty"`
+	SuppressSessionScore         *bool   `json:"suppress_session_score,omitempty"`
+	AutoUpdateModel              *bool   `json:"auto_update_model,omitempty"`
+	UsingLatestModel             *bool   `json:"using_latest_model,omitempty"`
+}
+
+// BotManagementResponse represents the response from the bot_management endpoint.
+type BotManagementResponse struct {
+	Result BotManagement `json:"result"`
+	Response
+}
+
+type UpdateBotManagementParams struct {
+	EnableJS                     *bool   `json:"enable_js,omitempty"`
+	FightMode                    *bool   `json:"fight_mode,omitempty"`
+	SBFMDefinitelyAutomated      *string `json:"sbfm_definitely_automated,omitempty"`
+	SBFMLikelyAutomated          *string `json:"sbfm_likely_automated,omitempty"`
+	SBFMVerifiedBots             *string `json:"sbfm_verified_bots,omitempty"`
+	SBFMStaticResourceProtection *bool   `json:"sbfm_static_resource_protection,omitempty"`
+	OptimizeWordpress            *bool   `json:"optimize_wordpress,omitempty"`
+	SuppressSessionScore         *bool   `json:"suppress_session_score,omitempty"`
+	AutoUpdateModel              *bool   `json:"auto_update_model,omitempty"`
+}
+
+// GetBotManagement gets a zone API shield configuration.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/bot-management-for-a-zone-get-config
+func (api *API) GetBotManagement(ctx context.Context, rc *ResourceContainer) (BotManagement, error) {
+	uri := fmt.Sprintf("/zones/%s/bot_management", rc.Identifier)
+
+	res, err := api.makeRequestContextWithHeaders(ctx, http.MethodGet, uri, nil, botV2Header())
+	if err != nil {
+		return BotManagement{}, err
+	}
+	var bmResponse BotManagementResponse
+	err = json.Unmarshal(res, &bmResponse)
+	if err != nil {
+		return BotManagement{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return bmResponse.Result, nil
+}
+
+// UpdateBotManagement sets a zone API shield configuration.
+//
+// API documentation: https://developers.cloudflare.com/api/operations/bot-management-for-a-zone-update-config
+func (api *API) UpdateBotManagement(ctx context.Context, rc *ResourceContainer, params UpdateBotManagementParams) (BotManagement, error) {
+	uri := fmt.Sprintf("/zones/%s/bot_management", rc.Identifier)
+
+	res, err := api.makeRequestContextWithHeaders(ctx, http.MethodPut, uri, params, botV2Header())
+	if err != nil {
+		return BotManagement{}, err
+	}
+
+	var bmResponse BotManagementResponse
+	err = json.Unmarshal(res, &bmResponse)
+	if err != nil {
+		return BotManagement{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
+	}
+
+	return bmResponse.Result, nil
+}
+
+// We are currently undergoing the process of updating the bot management API.
+// The older 1.0.0 version of the is still the default version, so we will need
+// to explicitly set this special header on all requests. We will eventually
+// make 2.0.0 the default version, and later we will remove the 1.0.0 entirely.
+func botV2Header() http.Header {
+	header := make(http.Header)
+	header.Set("Cloudflare-Version", "2.0.0")
+
+	return header
+}


Test patch:
diff --git a/bot_management_test.go b/bot_management_test.go
new file mode 100644
index 000000000..e0837804e
--- /dev/null
+++ b/bot_management_test.go
@@ -0,0 +1,87 @@
+package cloudflare
+
+import (
+	"context"
+	"fmt"
+	"net/http"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestGetBotManagement(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodGet, r.Method, "Expected method 'GET', got %s", r.Method)
+		assert.Equal(t, "2.0.0", r.Header.Get("Cloudflare-Version"))
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+	"success" : true,
+	"errors": [],
+	"messages": [],
+	"result": {
+		"enable_js": false,
+		"fight_mode": true,
+		"using_latest_model": true
+	}
+}
+		`)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/bot_management", handler)
+
+	want := BotManagement{
+		EnableJS:         BoolPtr(false),
+		FightMode:        BoolPtr(true),
+		UsingLatestModel: BoolPtr(true),
+	}
+
+	actual, err := client.GetBotManagement(context.Background(), ZoneIdentifier(testZoneID))
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}
+
+func TestUpdateBotManagement(t *testing.T) {
+	setup()
+	defer teardown()
+
+	handler := func(w http.ResponseWriter, r *http.Request) {
+		assert.Equal(t, http.MethodPut, r.Method, "Expected method 'PUT', got %s", r.Method)
+		assert.Equal(t, "2.0.0", r.Header.Get("Cloudflare-Version"))
+		w.Header().Set("content-type", "application/json")
+		fmt.Fprintf(w, `{
+	"success" : true,
+	"errors": [],
+	"messages": [],
+	"result": {
+		"enable_js": false,
+		"fight_mode": true,
+		"using_latest_model": true
+	}
+}
+		`)
+	}
+
+	mux.HandleFunc("/zones/"+testZoneID+"/bot_management", handler)
+
+	bmData := UpdateBotManagementParams{
+		EnableJS:  BoolPtr(false),
+		FightMode: BoolPtr(true),
+	}
+
+	want := BotManagement{
+		EnableJS:         BoolPtr(false),
+		FightMode:        BoolPtr(true),
+		UsingLatestModel: BoolPtr(true),
+	}
+
+	actual, err := client.UpdateBotManagement(context.Background(), ZoneIdentifier(testZoneID), bmData)
+
+	if assert.NoError(t, err) {
+		assert.Equal(t, want, actual)
+	}
+}