Skip to content

Commit

Permalink
SiteShield API added
Browse files Browse the repository at this point in the history
  • Loading branch information
Andrei Petriv committed Jul 26, 2021
1 parent 093f835 commit d0ee313
Show file tree
Hide file tree
Showing 8 changed files with 746 additions and 0 deletions.
75 changes: 75 additions & 0 deletions pkg/siteshield/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package siteshield

import (
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
)

var (
// ErrBadRequest is returned when a required parameter is missing
ErrBadRequest = errors.New("missing argument")
)

type (
// Error is a appsec error interface
Error struct {
Type string `json:"type"`
Title string `json:"title"`
Detail string `json:"detail"`
Instance string `json:"instance,omitempty"`
BehaviorName string `json:"behaviorName,omitempty"`
ErrorLocation string `json:"errorLocation,omitempty"`
StatusCode int `json:"-"`
}
)

// Error parses an error from the response
func (s *siteshieldmap) Error(r *http.Response) error {
var e Error

var body []byte

body, err := ioutil.ReadAll(r.Body)
if err != nil {
s.Log(r.Request.Context()).Errorf("reading error response body: %s", err)
e.StatusCode = r.StatusCode
e.Title = fmt.Sprintf("Failed to read error body")
e.Detail = err.Error()
return &e
}

if err := json.Unmarshal(body, &e); err != nil {
s.Log(r.Request.Context()).Errorf("could not unmarshal API error: %s", err)
e.Title = fmt.Sprintf("Failed to unmarshal error body")
e.Detail = err.Error()
}

e.StatusCode = r.StatusCode

return &e
}

func (e *Error) Error() string {
return fmt.Sprintf("Title: %s; Type: %s; Detail: %s", e.Title, e.Type, e.Detail)
}

// Is handles error comparisons
func (e *Error) Is(target error) bool {
var t *Error
if !errors.As(target, &t) {
return false
}

if e == t {
return true
}

if e.StatusCode != t.StatusCode {
return false
}

return e.Error() == t.Error()
}
68 changes: 68 additions & 0 deletions pkg/siteshield/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package siteshield

import (
"context"
"io/ioutil"
"net/http"
"strings"
"testing"

"github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/session"
"github.com/stretchr/testify/require"
"github.com/tj/assert"
)

func TestNewError(t *testing.T) {
sess, err := session.New()
require.NoError(t, err)

req, err := http.NewRequestWithContext(
context.TODO(),
http.MethodHead,
"/",
nil)
require.NoError(t, err)

tests := map[string]struct {
response *http.Response
expected *Error
}{
"valid response, status code 500": {
response: &http.Response{
Status: "Internal Server Error",
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(strings.NewReader(
`{"type":"a","title":"b","detail":"c"}`),
),
Request: req,
},
expected: &Error{
Type: "a",
Title: "b",
Detail: "c",
StatusCode: http.StatusInternalServerError,
},
},
"invalid response body, assign status code": {
response: &http.Response{
Status: "Internal Server Error",
StatusCode: http.StatusInternalServerError,
Body: ioutil.NopCloser(strings.NewReader(
`test`),
),
Request: req,
},
expected: &Error{
Title: "Failed to unmarshal error body",
Detail: "invalid character 'e' in literal true (expecting 'r')",
StatusCode: http.StatusInternalServerError,
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
res := Client(sess).(*siteshieldmap).Error(test.response)
assert.Equal(t, test.expected, res)
})
}
}
43 changes: 43 additions & 0 deletions pkg/siteshield/siteshield.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Package siteshield provides access to the Akamai Site Shield APIs
package siteshield

import (
"errors"

"github.com/akamai/AkamaiOPEN-edgegrid-golang/v2/pkg/session"
)

var (
// ErrStructValidation is returned returned when given struct validation failed
ErrStructValidation = errors.New("struct validation")
)

type (
// SSMAPS is the siteshieldmap api interface
SSMAPS interface {
SiteShieldMap
}

siteshieldmap struct {
session.Session
usePrefixes bool
}

// Option defines a siteshieldmap option
Option func(*siteshieldmap)

// ClientFunc is a siteshieldmap client new method, this can used for mocking
ClientFunc func(sess session.Session, opts ...Option) SSMAPS
)

// Client returns a new siteshieldmap Client instance with the specified controller
func Client(sess session.Session, opts ...Option) SSMAPS {
s := &siteshieldmap{
Session: sess,
}

for _, opt := range opts {
opt(s)
}
return s
}
161 changes: 161 additions & 0 deletions pkg/siteshield/siteshield_map.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package siteshield

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

validation "github.com/go-ozzo/ozzo-validation"
)

// SiteShieldMap represents a collection of Site Shield
//
// See: SiteShieldMap.GetSiteShieldMaps()
// API Docs: // site_shield v1
//
// https://developer.akamai.com/api/cloud_security/site_shield/v1.html

type (
// SiteShieldMap contains operations available on SiteShieldMap resource
// See: // site_shield v1
//
// https://developer.akamai.com/api/cloud_security/site_shield/v1.html#getamap
SiteShieldMap interface {
GetSiteShieldMaps(ctx context.Context) (*GetSiteShieldMapsResponse, error)
GetSiteShieldMap(ctx context.Context, params SiteShieldMapRequest) (*SiteShieldMapResponse, error)
AckSiteShieldMap(ctx context.Context, params SiteShieldMapRequest) (*SiteShieldMapResponse, error)
}

SiteShieldMapRequest struct {
UniqueID int
}

GetSiteShieldMapsResponse struct {
SiteShieldMaps []SiteShieldMapResponse `json:"siteShieldMaps"`
}

SiteShieldMapResponse struct {
Acknowledged bool `json:"acknowledged"`
Contacts []string `json:"contacts"`
CurrentCidrs []string `json:"currentCidrs"`
ProposedCidrs []string `json:"proposedCidrs"`
RuleName string `json:"ruleName"`
Type string `json:"type"`
Service string `json:"service"`
Shared bool `json:"shared"`
AcknowledgeRequiredBy int64 `json:"acknowledgeRequiredBy"`
PreviouslyAcknowledgedOn int64 `json:"previouslyAcknowledgedOn"`
ID int `json:"id,omitempty"`
LatestTicketID int `json:"latestTicketId,omitempty"`
MapAlias string `json:"mapAlias,omitempty"`
McmMapRuleID int `json:"mcmMapRuleId,omitempty"`
}
)

// Validate validates SiteShieldMapRequest
func (v SiteShieldMapRequest) Validate() error {
return validation.Errors{
"UniqueID": validation.Validate(v.UniqueID, validation.Required),
}.Filter()
}

// GetSiteShieldMaps will get a list of SiteShieldMap.
//
// API Docs: // site_shield v1
//
// https://developer.akamai.com/api/cloud_security/site_shield/v1.html#listmaps

func (s *siteshieldmap) GetSiteShieldMaps(ctx context.Context) (*GetSiteShieldMapsResponse, error) {
logger := s.Log(ctx)
logger.Debug("GetSiteShieldMaps")

var rval GetSiteShieldMapsResponse

uri := "/siteshield/v1/maps"

req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, fmt.Errorf("failed to create getSiteShieldMaps request: %s", err.Error())
}

resp, err := s.Exec(req, &rval)
if err != nil {
return nil, fmt.Errorf("getsiteshieldmaps request failed: %s", err.Error())
}

if resp.StatusCode != http.StatusOK {
return nil, s.Error(resp)
}

return &rval, nil

}

// GetSiteShieldMap will get a SiteShieldMap by unique ID.
//
// API Docs: // site_shield v1
//
// https://developer.akamai.com/api/cloud_security/site_shield/v1.html#getamap

func (s *siteshieldmap) GetSiteShieldMap(ctx context.Context, params SiteShieldMapRequest) (*SiteShieldMapResponse, error) {
if err := params.Validate(); err != nil {
return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
}

logger := s.Log(ctx)
logger.Debug("GetSiteShieldMap")

var rval SiteShieldMapResponse

uri := fmt.Sprintf("/siteshield/v1/maps/%d", params.UniqueID)

req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri, nil)
if err != nil {
return nil, fmt.Errorf("failed to create getSiteShieldMap request: %s", err.Error())
}

resp, err := s.Exec(req, &rval)
if err != nil {
return nil, fmt.Errorf("getSiteShieldMap request failed: %s", err.Error())
}

if resp.StatusCode != http.StatusOK {
return nil, s.Error(resp)
}

return &rval, nil
}

// AckSiteShieldMap will acknowledge changes to a SiteShieldMap.
//
// API Docs: // site_shield v1
//
// https://developer.akamai.com/api/cloud_security/site_shield/v1.html#acknowledgeamap

func (s *siteshieldmap) AckSiteShieldMap(ctx context.Context, params SiteShieldMapRequest) (*SiteShieldMapResponse, error) {
if err := params.Validate(); err != nil {
return nil, fmt.Errorf("%w: %s", ErrStructValidation, err.Error())
}

logger := s.Log(ctx)
logger.Debug("AckSiteShieldMap")

postURL := fmt.Sprintf("/siteshield/v1/maps/%d/acknowledge", params.UniqueID)

req, err := http.NewRequestWithContext(ctx, http.MethodPost, postURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create AckSiteShieldMap: %s", err.Error())
}

var rval SiteShieldMapResponse
resp, err := s.Exec(req, &rval, params)
if err != nil {
return nil, fmt.Errorf("AckSiteShieldMap request failed: %s", err.Error())
}

if resp.StatusCode != http.StatusOK {
return nil, s.Error(resp)
}

return &rval, nil
}
Loading

0 comments on commit d0ee313

Please sign in to comment.