Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Get/Patch API Shield API Discovery Operations #1413

Merged
merged 3 commits into from
Oct 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/1413.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
api_shield_discovery: Add support for Get/Patch API Shield API Discovery Operations
```
190 changes: 190 additions & 0 deletions api_shield_api_discovery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package cloudflare

import (
"context"
"fmt"
"net/http"
"time"

"github.com/goccy/go-json"
)

// APIShieldDiscoveryOrigin is an enumeration on what discovery engine an operation was discovered by.
type APIShieldDiscoveryOrigin string

const (

// APIShieldDiscoveryOriginML discovered operations that were sourced using ML API Discovery.
APIShieldDiscoveryOriginML APIShieldDiscoveryOrigin = "ML"
// APIShieldDiscoveryOriginSessionIdentifier discovered operations that were sourced using Session Identifier
// API Discovery.
APIShieldDiscoveryOriginSessionIdentifier APIShieldDiscoveryOrigin = "SessionIdentifier"
)

// APIShieldDiscoveryState is an enumeration on states a discovery operation can be in.
type APIShieldDiscoveryState string

const (
// APIShieldDiscoveryStateReview discovered operations that are not saved into API Shield Endpoint Management.
APIShieldDiscoveryStateReview APIShieldDiscoveryState = "review"
// APIShieldDiscoveryStateSaved discovered operations that are already saved into API Shield Endpoint Management.
APIShieldDiscoveryStateSaved APIShieldDiscoveryState = "saved"
// APIShieldDiscoveryStateIgnored discovered operations that have been marked as ignored.
APIShieldDiscoveryStateIgnored APIShieldDiscoveryState = "ignored"
)

// APIShieldDiscoveryOperation is an operation that was discovered by API Discovery.
type APIShieldDiscoveryOperation struct {
// ID represents the ID of the operation, formatted as UUID
ID string `json:"id"`
djhworld marked this conversation as resolved.
Show resolved Hide resolved
// Origin represents the API discovery engine(s) that discovered this operation
Origin []APIShieldDiscoveryOrigin `json:"origin"`
// State represents the state of operation in API Discovery
State APIShieldDiscoveryState `json:"state"`
// LastUpdated timestamp of when this operation was last updated
LastUpdated *time.Time `json:"last_updated"`
// Features are additional data about the operation
Features map[string]any `json:"features,omitempty"`

Method string `json:"method"`
Host string `json:"host"`
Endpoint string `json:"endpoint"`
}

// ListAPIShieldDiscoveryOperationsParams represents the parameters to pass when retrieving discovered operations.
//
// API documentation: https://developers.cloudflare.com/api/operations/api-shield-api-discovery-retrieve-discovered-operations-on-a-zone
type ListAPIShieldDiscoveryOperationsParams struct {
// Direction to order results.
Direction string `url:"direction,omitempty"`
// OrderBy when requesting a feature, the feature keys are available for ordering as well, e.g., thresholds.suggested_threshold.
OrderBy string `url:"order,omitempty"`
// Hosts filters results to only include the specified hosts.
Hosts []string `url:"host,omitempty"`
// Methods filters results to only include the specified methods.
Methods []string `url:"method,omitempty"`
// Endpoint filters results to only include endpoints containing this pattern.
Endpoint string `url:"endpoint,omitempty"`
// Diff when true, only return API Discovery results that are not saved into API Shield Endpoint Management
Diff bool `url:"diff,omitempty"`
// Origin filters results to only include discovery results sourced from a particular discovery engine
// See APIShieldDiscoveryOrigin for valid values.
Origin APIShieldDiscoveryOrigin `url:"origin,omitempty"`
// State filters results to only include discovery results in a particular state
// See APIShieldDiscoveryState for valid values.
State APIShieldDiscoveryState `url:"state,omitempty"`

// Pagination options to apply to the request.
PaginationOptions
}

// UpdateAPIShieldDiscoveryOperationParams represents the parameters to pass to patch a discovery operation
//
// API documentation: https://developers.cloudflare.com/api/operations/api-shield-api-patch-discovered-operation
type UpdateAPIShieldDiscoveryOperationParams struct {
// OperationID is the ID, formatted as UUID, of the operation to be updated
OperationID string `json:"-" url:"-"`
State APIShieldDiscoveryState `json:"state" url:"-"`
}

// UpdateAPIShieldDiscoveryOperationsParams maps discovery operation IDs to PatchAPIShieldDiscoveryOperation structs
//
// Example:
//
// UpdateAPIShieldDiscoveryOperations{
// "99522293-a505-45e5-bbad-bbc339f5dc40": PatchAPIShieldDiscoveryOperation{ State: "review" },
// }
//
// API documentation: https://developers.cloudflare.com/api/operations/api-shield-api-patch-discovered-operations
type UpdateAPIShieldDiscoveryOperationsParams map[string]UpdateAPIShieldDiscoveryOperation

// UpdateAPIShieldDiscoveryOperation represents the state to set on a discovery operation.
type UpdateAPIShieldDiscoveryOperation struct {
// State is the state to set on the operation
State APIShieldDiscoveryState `json:"state" url:"-"`
}

// APIShieldListDiscoveryOperationsResponse represents the response from the api_gateway/discovery/operations endpoint.
type APIShieldListDiscoveryOperationsResponse struct {
Result []APIShieldDiscoveryOperation `json:"result"`
ResultInfo `json:"result_info"`
Response
}

// APIShieldPatchDiscoveryOperationResponse represents the response from the PATCH api_gateway/discovery/operations/{id} endpoint.
type APIShieldPatchDiscoveryOperationResponse struct {
Result UpdateAPIShieldDiscoveryOperation `json:"result"`
Response
}

// APIShieldPatchDiscoveryOperationsResponse represents the response from the PATCH api_gateway/discovery/operations endpoint.
type APIShieldPatchDiscoveryOperationsResponse struct {
Result UpdateAPIShieldDiscoveryOperationsParams `json:"result"`
Response
}

// ListAPIShieldDiscoveryOperations retrieve the most up to date view of discovered operations.
//
// API documentation: https://developers.cloudflare.com/api/operations/api-shield-api-discovery-retrieve-discovered-operations-on-a-zone
func (api *API) ListAPIShieldDiscoveryOperations(ctx context.Context, rc *ResourceContainer, params ListAPIShieldDiscoveryOperationsParams) ([]APIShieldDiscoveryOperation, ResultInfo, error) {
uri := buildURI(fmt.Sprintf("/zones/%s/api_gateway/discovery/operations", rc.Identifier), params)

res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, ResultInfo{}, err
}

var asResponse APIShieldListDiscoveryOperationsResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, ResultInfo{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return asResponse.Result, asResponse.ResultInfo, nil
}

// UpdateAPIShieldDiscoveryOperation updates certain fields on a discovered operation.
//
// API Documentation: https://developers.cloudflare.com/api/operations/api-shield-api-patch-discovered-operation
func (api *API) UpdateAPIShieldDiscoveryOperation(ctx context.Context, rc *ResourceContainer, params UpdateAPIShieldDiscoveryOperationParams) (*UpdateAPIShieldDiscoveryOperation, error) {
if params.OperationID == "" {
return nil, fmt.Errorf("operation ID must be provided")
}

uri := fmt.Sprintf("/zones/%s/api_gateway/discovery/operations/%s", rc.Identifier, params.OperationID)

res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check: Does the .makeRequestContext function add the respective request body and corresponding headers?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it know that it should only add the state field and not the operation ID to the request body?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or should we rather pass the UpdateAPIShieldDiscoveryOperation type here?

Copy link
Contributor Author

@djhworld djhworld Oct 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, the params (of type UpdateAPIShieldDiscoveryOperationParams) is what gets put in the body.

The params struct is serialized to JSON here

cloudflare-go/cloudflare.go

Lines 222 to 227 in 217beeb

var jsonBody []byte
jsonBody, err = json.Marshal(params)
if err != nil {
return nil, fmt.Errorf("error marshalling params to JSON: %w", err)
}
reqBody = bytes.NewReader(jsonBody)

and the content-type header is set to application/json if none is set here

cloudflare-go/cloudflare.go

Lines 382 to 384 in 217beeb

if req.Header.Get("Content-Type") == "" {
req.Header.Set("Content-Type", "application/json")
}

The operation ID does not get serialized as we set the json tag to - https://github.com/cloudflare/cloudflare-go/pull/1413/files#diff-a8b0a1c475237e4f8ba6786369d22fa9008203858112922a8a8c1426ce423e6dR60

if err != nil {
return nil, err
}

// Result should be the updated schema that was patched
var asResponse APIShieldPatchDiscoveryOperationResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return &asResponse.Result, nil
}

// UpdateAPIShieldDiscoveryOperations bulk updates certain fields on multiple discovered operations
//
// API documentation: https://developers.cloudflare.com/api/operations/api-shield-api-patch-discovered-operations
func (api *API) UpdateAPIShieldDiscoveryOperations(ctx context.Context, rc *ResourceContainer, params UpdateAPIShieldDiscoveryOperationsParams) (*UpdateAPIShieldDiscoveryOperationsParams, error) {
uri := fmt.Sprintf("/zones/%s/api_gateway/discovery/operations", rc.Identifier)

res, err := api.makeRequestContext(ctx, http.MethodPatch, uri, params)
if err != nil {
return nil, err
}

// Result should be the updated schema that was patched
var asResponse APIShieldPatchDiscoveryOperationsResponse
err = json.Unmarshal(res, &asResponse)
if err != nil {
return nil, fmt.Errorf("%s: %w", errUnmarshalError, err)
}

return &asResponse.Result, nil
}