Skip to content

Commit

Permalink
Merge pull request #1887 from inanna-malick/inanna/DLP-1800
Browse files Browse the repository at this point in the history
DLP-1800: add support for zero trust user risk scoring
  • Loading branch information
jacobbednarz authored May 3, 2024
2 parents a9d982f + 5cbbda6 commit 1096abb
Show file tree
Hide file tree
Showing 3 changed files with 288 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .changelog/1887.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
dlp: add support for zt risk behavior configuration
```
126 changes: 126 additions & 0 deletions zt_risk_behaviors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package cloudflare

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

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

// Behavior represents a single zt risk behavior config.
type Behavior struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
RiskLevel RiskLevel `json:"risk_level"`
Enabled *bool `json:"enabled"`
}

// Wrapper used to have full-fidelity repro of json structure.
type Behaviors struct {
Behaviors map[string]Behavior `json:"behaviors"`
}

// BehaviorResponse represents the response from the zt risk scoring endpoint
// and contains risk behaviors for an account.
type BehaviorResponse struct {
Success bool `json:"success"`
Result Behaviors `json:"result"`
Errors []string `json:"errors"`
Messages []string `json:"messages"`
}

// Behaviors returns all zero trust risk scoring behaviors for the provided account
//
// API reference: https://developers.cloudflare.com/api/operations/dlp-zt-risk-score-get-behaviors
func (api *API) Behaviors(ctx context.Context, accountID string) (Behaviors, error) {
uri := fmt.Sprintf("/accounts/%s/zt_risk_scoring/behaviors", accountID)

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

var r BehaviorResponse
err = json.Unmarshal(res, &r)
if err != nil {
return Behaviors{}, fmt.Errorf("%s: %w", errUnmarshalError, err)
}
return r.Result, nil
}

// UpdateBehaviors returns all zero trust risk scoring behaviors for the provided account
// NOTE: description/name updates are no-ops, risk_level [low medium high] and enabled [true/false] results in modifications
//
// API reference: https://developers.cloudflare.com/api/operations/dlp-zt-risk-score-put-behaviors
func (api *API) UpdateBehaviors(ctx context.Context, accountID string, behaviors Behaviors) (Behaviors, error) {
uri := fmt.Sprintf("/accounts/%s/zt_risk_scoring/behaviors", accountID)

res, err := api.makeRequestContext(ctx, http.MethodPut, uri, behaviors)
if err != nil {
return Behaviors{}, err
}

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

return r.Result, nil
}

type RiskLevel int

const (
_ RiskLevel = iota
Low
Medium
High
)

func (p RiskLevel) MarshalJSON() ([]byte, error) {
return json.Marshal(p.String())
}

func (p RiskLevel) String() string {
return [...]string{"low", "medium", "high"}[p-1]
}

func (p *RiskLevel) UnmarshalJSON(data []byte) error {
var (
s string
err error
)
err = json.Unmarshal(data, &s)
if err != nil {
return err
}
v, err := RiskLevelFromString(s)
if err != nil {
return err
}
*p = *v
return nil
}

func RiskLevelFromString(s string) (*RiskLevel, error) {
s = strings.ToLower(s)
var v RiskLevel
switch s {
case "low":
v = Low
case "medium":
v = Medium
case "high":
v = High
default:
return nil, fmt.Errorf("unknown variant for risk level: %s", s)
}
return &v, nil
}

func (p RiskLevel) IntoRef() *RiskLevel {
return &p
}
159 changes: 159 additions & 0 deletions zt_risk_behaviors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package cloudflare

import (
"context"
"fmt"
"io"
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

var (
expectedBehaviors = Behaviors{
Behaviors: map[string]Behavior{
"high_dlp": {
Name: "High Number of DLP Policies Triggered",
Description: "User has triggered an active DLP profile in a Gateway policy fifteen times or more within one minute.",
RiskLevel: Low,
Enabled: BoolPtr(true),
},
"imp_travel": {
Name: "Impossible Travel",
Description: "A user had a successful Access application log in from two locations that they could not have traveled to in that period of time.",
RiskLevel: High,
Enabled: BoolPtr(false),
},
},
}

updateBehaviors = Behaviors{
Behaviors: map[string]Behavior{
"high_dlp": {
RiskLevel: Low,
Enabled: BoolPtr(true),
},
"imp_travel": {
RiskLevel: High,
Enabled: BoolPtr(false),
},
},
}
)

func TestBehaviors(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)
w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"result": {
"behaviors": {
"high_dlp": {
"name": "High Number of DLP Policies Triggered",
"description": "User has triggered an active DLP profile in a Gateway policy fifteen times or more within one minute.",
"risk_level": "low",
"enabled":true
},
"imp_travel": {
"name": "Impossible Travel",
"description": "A user had a successful Access application log in from two locations that they could not have traveled to in that period of time.",
"risk_level": "high",
"enabled": false
}
}
},
"success": true,
"errors": [],
"messages": []
}`)
}

mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/zt_risk_scoring/behaviors", handler)
want := expectedBehaviors

actual, err := client.Behaviors(context.Background(), "01a7362d577a6c3019a474fd6f485823")

if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestUpdateBehaviors(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)
b, err := io.ReadAll(r.Body)
defer r.Body.Close()

if assert.NoError(t, err) {
assert.JSONEq(t, `{
"behaviors": {
"high_dlp": {
"risk_level": "low",
"enabled":true
},
"imp_travel": {
"risk_level": "high",
"enabled": false
}
}
}`, string(b), "JSON payload not equal")
}

w.Header().Set("content-type", "application/json")
fmt.Fprintf(w, `{
"result": {
"behaviors": {
"high_dlp": {
"name": "High Number of DLP Policies Triggered",
"description": "User has triggered an active DLP profile in a Gateway policy fifteen times or more within one minute.",
"risk_level": "low",
"enabled":true
},
"imp_travel": {
"name": "Impossible Travel",
"description": "A user had a successful Access application log in from two locations that they could not have traveled to in that period of time.",
"risk_level": "high",
"enabled": false
}
}
},
"success": true,
"errors": [],
"messages": []
}`)
}

mux.HandleFunc("/accounts/01a7362d577a6c3019a474fd6f485823/zt_risk_scoring/behaviors", handler)

want := expectedBehaviors
actual, err := client.UpdateBehaviors(context.Background(), "01a7362d577a6c3019a474fd6f485823", updateBehaviors)

if assert.NoError(t, err) {
assert.Equal(t, want, actual)
}
}

func TestRiskLevelFromString(t *testing.T) {
got, _ := RiskLevelFromString("high")
want := High

if *got != want {
t.Errorf("got %#v, wanted %#v", *got, want)
}
}

func TestStringFromRiskLevel(t *testing.T) {
got := fmt.Sprint(High)
want := "high"

if got != want {
t.Errorf("got %#v, wanted %#v", got, want)
}
}

0 comments on commit 1096abb

Please sign in to comment.