forked from TrueCloudLab/policy-engine
[#1] chain: Add json marshal/unmarshal
Signed-off-by: Dmitrii Stepanov <d.stepanov@yadro.com>
This commit is contained in:
parent
58386edf58
commit
88c2a476b0
10 changed files with 398 additions and 51 deletions
14
Makefile
14
Makefile
|
@ -5,6 +5,8 @@ TMP_DIR := .cache
|
|||
OUTPUT_LINT_DIR ?= $(shell pwd)/bin
|
||||
LINT_VERSION ?= 1.55.1
|
||||
LINT_DIR = $(OUTPUT_LINT_DIR)/golangci-lint-$(LINT_VERSION)-v$(TRUECLOUDLAB_LINT_VERSION)
|
||||
EASYJSON_VERSION ?= $(shell go list -f '{{.Version}}' -m github.com/mailru/easyjson)
|
||||
EASYJSON_DIR ?= $(shell pwd)/bin/easyjson-$(EASYJSON_VERSION)
|
||||
|
||||
# Run all code formatters
|
||||
fmts: fmt imports
|
||||
|
@ -60,3 +62,15 @@ staticcheck-install:
|
|||
# Run staticcheck
|
||||
staticcheck-run:
|
||||
@staticcheck ./...
|
||||
|
||||
easyjson-install:
|
||||
@rm -rf $(EASYJSON_DIR)
|
||||
@mkdir -p $(EASYJSON_DIR)
|
||||
@GOBIN=$(EASYJSON_DIR) go install github.com/mailru/easyjson/...@$(EASYJSON_VERSION)
|
||||
|
||||
generate:
|
||||
@if [ ! -d "$(EASYJSON_DIR)" ]; then \
|
||||
make easyjson-install; \
|
||||
fi
|
||||
find ./ -name "_easyjson.go" -exec rm -rf {} \;
|
||||
$(EASYJSON_DIR)/easyjson ./pkg/chain/chain.go
|
2
go.mod
2
go.mod
|
@ -5,6 +5,7 @@ go 1.20
|
|||
require (
|
||||
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958
|
||||
github.com/google/uuid v1.3.0
|
||||
github.com/mailru/easyjson v0.7.7
|
||||
github.com/nspcc-dev/neo-go v0.103.0
|
||||
github.com/stretchr/testify v1.8.4
|
||||
golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63
|
||||
|
@ -14,6 +15,7 @@ require (
|
|||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
|
||||
github.com/hashicorp/golang-lru v0.6.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/mr-tron/base58 v1.2.0 // indirect
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 // indirect
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20231020160724-c3955f87d1b5 // indirect
|
||||
|
|
4
go.sum
4
go.sum
|
@ -11,6 +11,10 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
|
|||
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
|
||||
github.com/hashicorp/golang-lru v0.6.0 h1:uL2shRDx7RTrOrTCUZEGP/wJUFiUI8QT6E7z5o8jga4=
|
||||
github.com/hashicorp/golang-lru v0.6.0/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
|
||||
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
|
||||
github.com/nspcc-dev/go-ordered-json v0.0.0-20220111165707-25110be27d22 h1:n4ZaFCKt1pQJd7PXoMJabZWK9ejjbLOVrkl/lOUmshg=
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package chain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -23,6 +22,7 @@ const (
|
|||
MatchTypeFirstMatch MatchType = 1
|
||||
)
|
||||
|
||||
//easyjson:json
|
||||
type Chain struct {
|
||||
ID ID
|
||||
|
||||
|
@ -31,19 +31,6 @@ type Chain struct {
|
|||
MatchType MatchType
|
||||
}
|
||||
|
||||
func (id ID) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([]byte(id))
|
||||
}
|
||||
|
||||
func (id *ID) UnmarshalJSON(data []byte) error {
|
||||
var idRaw []byte
|
||||
if err := json.Unmarshal(data, &idRaw); err != nil {
|
||||
return err
|
||||
}
|
||||
*id = ID(idRaw)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Chain) Bytes() []byte {
|
||||
data, err := c.MarshalBinary()
|
||||
if err != nil {
|
||||
|
@ -123,45 +110,36 @@ const (
|
|||
CondSliceContains
|
||||
)
|
||||
|
||||
var condToStr = []struct {
|
||||
ct ConditionType
|
||||
str string
|
||||
}{
|
||||
{CondStringEquals, "StringEquals"},
|
||||
{CondStringNotEquals, "StringNotEquals"},
|
||||
{CondStringEqualsIgnoreCase, "StringEqualsIgnoreCase"},
|
||||
{CondStringNotEqualsIgnoreCase, "StringNotEqualsIgnoreCase"},
|
||||
{CondStringLike, "StringLike"},
|
||||
{CondStringNotLike, "StringNotLike"},
|
||||
{CondStringLessThan, "StringLessThan"},
|
||||
{CondStringLessThanEquals, "StringLessThanEquals"},
|
||||
{CondStringGreaterThan, "StringGreaterThan"},
|
||||
{CondStringGreaterThanEquals, "StringGreaterThanEquals"},
|
||||
{CondNumericEquals, "NumericEquals"},
|
||||
{CondNumericNotEquals, "NumericNotEquals"},
|
||||
{CondNumericLessThan, "NumericLessThan"},
|
||||
{CondNumericLessThanEquals, "NumericLessThanEquals"},
|
||||
{CondNumericGreaterThan, "NumericGreaterThan"},
|
||||
{CondNumericGreaterThanEquals, "NumericGreaterThanEquals"},
|
||||
{CondSliceContains, "SliceContains"},
|
||||
}
|
||||
|
||||
func (c ConditionType) String() string {
|
||||
switch c {
|
||||
case CondStringEquals:
|
||||
return "StringEquals"
|
||||
case CondStringNotEquals:
|
||||
return "StringNotEquals"
|
||||
case CondStringEqualsIgnoreCase:
|
||||
return "StringEqualsIgnoreCase"
|
||||
case CondStringNotEqualsIgnoreCase:
|
||||
return "StringNotEqualsIgnoreCase"
|
||||
case CondStringLike:
|
||||
return "StringLike"
|
||||
case CondStringNotLike:
|
||||
return "StringNotLike"
|
||||
case CondStringLessThan:
|
||||
return "StringLessThan"
|
||||
case CondStringLessThanEquals:
|
||||
return "StringLessThanEquals"
|
||||
case CondStringGreaterThan:
|
||||
return "StringGreaterThan"
|
||||
case CondStringGreaterThanEquals:
|
||||
return "StringGreaterThanEquals"
|
||||
case CondNumericEquals:
|
||||
return "NumericEquals"
|
||||
case CondNumericNotEquals:
|
||||
return "NumericNotEquals"
|
||||
case CondNumericLessThan:
|
||||
return "NumericLessThan"
|
||||
case CondNumericLessThanEquals:
|
||||
return "NumericLessThanEquals"
|
||||
case CondNumericGreaterThan:
|
||||
return "NumericGreaterThan"
|
||||
case CondNumericGreaterThanEquals:
|
||||
return "NumericGreaterThanEquals"
|
||||
case CondSliceContains:
|
||||
return "SliceContains"
|
||||
default:
|
||||
return "unknown condition type"
|
||||
for _, v := range condToStr {
|
||||
if v.ct == c {
|
||||
return v.str
|
||||
}
|
||||
}
|
||||
return "unknown condition type"
|
||||
}
|
||||
|
||||
const condSliceContainsDelimiter = "\x00"
|
||||
|
|
BIN
pkg/chain/chain_easyjson.go
Normal file
BIN
pkg/chain/chain_easyjson.go
Normal file
Binary file not shown.
153
pkg/chain/marshal_json.go
Normal file
153
pkg/chain/marshal_json.go
Normal file
|
@ -0,0 +1,153 @@
|
|||
package chain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
)
|
||||
|
||||
// Run `make generate`` if types added or changed
|
||||
|
||||
var matchTypeToJSONValue = []struct {
|
||||
mt MatchType
|
||||
str string
|
||||
}{
|
||||
{MatchTypeDenyPriority, "DenyPriority"},
|
||||
{MatchTypeFirstMatch, "FirstMatch"},
|
||||
}
|
||||
|
||||
var statusToJSONValue = []struct {
|
||||
s Status
|
||||
str string
|
||||
}{
|
||||
{Allow, "Allow"},
|
||||
{NoRuleFound, "NoRuleFound"},
|
||||
{AccessDenied, "AccessDenied"},
|
||||
{QuotaLimitReached, "QuotaLimitReached"},
|
||||
}
|
||||
|
||||
var objectTypeToJSONValue = []struct {
|
||||
t ObjectType
|
||||
str string
|
||||
}{
|
||||
{ObjectRequest, "Request"},
|
||||
{ObjectResource, "Resource"},
|
||||
}
|
||||
|
||||
func (mt MatchType) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
for _, p := range matchTypeToJSONValue {
|
||||
if p.mt == mt {
|
||||
w.String(p.str)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.String(strconv.FormatUint(uint64(mt), 10))
|
||||
}
|
||||
|
||||
func (mt *MatchType) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
str := l.String()
|
||||
for _, p := range matchTypeToJSONValue {
|
||||
if p.str == str {
|
||||
*mt = p.mt
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(str, 10, 8)
|
||||
if err != nil {
|
||||
l.AddError(fmt.Errorf("failed to parse match type: %w", err))
|
||||
return
|
||||
}
|
||||
*mt = MatchType(v)
|
||||
}
|
||||
|
||||
func (st Status) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
for _, p := range statusToJSONValue {
|
||||
if p.s == st {
|
||||
w.String(p.str)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.String(strconv.FormatUint(uint64(st), 10))
|
||||
}
|
||||
|
||||
func (st *Status) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
str := l.String()
|
||||
for _, p := range statusToJSONValue {
|
||||
if p.str == str {
|
||||
*st = p.s
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(str, 10, 8)
|
||||
if err != nil {
|
||||
l.AddError(fmt.Errorf("failed to parse status: %w", err))
|
||||
return
|
||||
}
|
||||
*st = Status(v)
|
||||
}
|
||||
|
||||
func (ot ObjectType) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
for _, p := range objectTypeToJSONValue {
|
||||
if p.t == ot {
|
||||
w.String(p.str)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.String(strconv.FormatUint(uint64(ot), 10))
|
||||
}
|
||||
|
||||
func (ot *ObjectType) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
str := l.String()
|
||||
for _, p := range objectTypeToJSONValue {
|
||||
if p.str == str {
|
||||
*ot = p.t
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(str, 10, 8)
|
||||
if err != nil {
|
||||
l.AddError(fmt.Errorf("failed to parse object type: %w", err))
|
||||
return
|
||||
}
|
||||
*ot = ObjectType(v)
|
||||
}
|
||||
|
||||
func (ct ConditionType) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
for _, p := range condToStr {
|
||||
if p.ct == ct {
|
||||
w.String(p.str)
|
||||
return
|
||||
}
|
||||
}
|
||||
w.String(strconv.FormatUint(uint64(ct), 10))
|
||||
}
|
||||
|
||||
func (ct *ConditionType) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
str := l.String()
|
||||
for _, p := range condToStr {
|
||||
if p.str == str {
|
||||
*ct = p.ct
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
v, err := strconv.ParseUint(str, 10, 8)
|
||||
if err != nil {
|
||||
l.AddError(fmt.Errorf("failed to parse condition type: %w", err))
|
||||
return
|
||||
}
|
||||
*ct = ConditionType(v)
|
||||
}
|
||||
|
||||
func (id ID) MarshalEasyJSON(w *jwriter.Writer) {
|
||||
w.Base64Bytes([]byte(id))
|
||||
}
|
||||
|
||||
func (id *ID) UnmarshalEasyJSON(l *jlexer.Lexer) {
|
||||
*id = ID(l.Bytes())
|
||||
}
|
121
pkg/chain/marshal_json_test.go
Normal file
121
pkg/chain/marshal_json_test.go
Normal file
|
@ -0,0 +1,121 @@
|
|||
package chain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestID(t *testing.T) {
|
||||
key, err := keys.NewPrivateKeyFromWIF("L5eVx6HcHaFpQpvjQ3fy29uKDZ8rQ34bfMVx4XfZMm52EqafpNMg") // s3-gw key
|
||||
require.NoError(t, err)
|
||||
|
||||
chain1 := &Chain{ID: ID(key.PublicKey().GetScriptHash().BytesBE())}
|
||||
data := chain1.Bytes()
|
||||
|
||||
var chain2 Chain
|
||||
require.NoError(t, chain2.DecodeBytes(data))
|
||||
|
||||
require.Equal(t, chain1.ID, chain2.ID)
|
||||
|
||||
data, err = chain1.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, chain2.UnmarshalJSON(data))
|
||||
|
||||
require.Equal(t, chain1.ID, chain2.ID)
|
||||
}
|
||||
|
||||
func TestMatchTypeJson(t *testing.T) {
|
||||
for _, mt := range []MatchType{MatchTypeDenyPriority, MatchTypeFirstMatch, MatchType(100)} {
|
||||
var chain Chain
|
||||
chain.MatchType = mt
|
||||
|
||||
data, err := chain.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
if mt == MatchTypeDenyPriority {
|
||||
require.Equal(t, []byte("{\"ID\":\"\",\"Rules\":null,\"MatchType\":\"DenyPriority\"}"), data)
|
||||
} else if mt == MatchTypeFirstMatch {
|
||||
require.Equal(t, []byte("{\"ID\":\"\",\"Rules\":null,\"MatchType\":\"FirstMatch\"}"), data)
|
||||
} else {
|
||||
require.Equal(t, []byte(fmt.Sprintf("{\"ID\":\"\",\"Rules\":null,\"MatchType\":\"%d\"}", mt)), data)
|
||||
}
|
||||
|
||||
var parsed Chain
|
||||
require.NoError(t, parsed.UnmarshalJSON(data))
|
||||
require.Equal(t, chain, parsed)
|
||||
|
||||
require.Error(t, parsed.UnmarshalJSON([]byte("{\"ID\":\"\",\"Rules\":null,\"MatchType\":\"NotValid\"}")))
|
||||
}
|
||||
}
|
||||
|
||||
func TestJsonEnums(t *testing.T) {
|
||||
chain := Chain{
|
||||
ID: "2cca5ae7-cee8-428d-b45f-567fb1d03f01", // will be encoded to base64
|
||||
MatchType: MatchTypeFirstMatch,
|
||||
Rules: []Rule{
|
||||
{
|
||||
Status: AccessDenied,
|
||||
Actions: Actions{
|
||||
Names: []string{native.MethodDeleteObject, native.MethodGetContainer},
|
||||
},
|
||||
Resources: Resources{
|
||||
Names: []string{native.ResourceFormatAllObjects},
|
||||
},
|
||||
Condition: []Condition{
|
||||
{
|
||||
Op: CondStringEquals,
|
||||
Object: ObjectRequest,
|
||||
Key: native.PropertyKeyActorRole,
|
||||
Value: native.PropertyValueContainerRoleOthers,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Status: QuotaLimitReached,
|
||||
Actions: Actions{
|
||||
Inverted: true,
|
||||
Names: []string{native.MethodPutObject},
|
||||
},
|
||||
Resources: Resources{
|
||||
Names: []string{fmt.Sprintf(native.ResourceFormatRootContainerObjects, "9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J")},
|
||||
},
|
||||
Any: true,
|
||||
Condition: []Condition{
|
||||
{
|
||||
Op: CondStringNotLike,
|
||||
Object: ObjectResource,
|
||||
Key: native.PropertyKeyObjectType,
|
||||
Value: "regular",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Status: Status(100),
|
||||
Condition: []Condition{
|
||||
{
|
||||
Op: ConditionType(255),
|
||||
Object: ObjectType(128),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
data, err := chain.MarshalJSON()
|
||||
require.NoError(t, err)
|
||||
|
||||
var parsed Chain
|
||||
require.NoError(t, parsed.UnmarshalJSON(data))
|
||||
require.Equal(t, chain, parsed)
|
||||
|
||||
expected, err := os.ReadFile("./testdata/test_status_json.json")
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NoError(t, parsed.UnmarshalJSON(expected))
|
||||
require.Equal(t, chain, parsed)
|
||||
}
|
75
pkg/chain/testdata/test_status_json.json
vendored
Normal file
75
pkg/chain/testdata/test_status_json.json
vendored
Normal file
|
@ -0,0 +1,75 @@
|
|||
{
|
||||
"ID": "MmNjYTVhZTctY2VlOC00MjhkLWI0NWYtNTY3ZmIxZDAzZjAx",
|
||||
"Rules": [
|
||||
{
|
||||
"Status": "AccessDenied",
|
||||
"Actions": {
|
||||
"Inverted": false,
|
||||
"Names": [
|
||||
"DeleteObject",
|
||||
"GetContainer"
|
||||
]
|
||||
},
|
||||
"Resources": {
|
||||
"Inverted": false,
|
||||
"Names": [
|
||||
"native:object/*"
|
||||
]
|
||||
},
|
||||
"Any": false,
|
||||
"Condition": [
|
||||
{
|
||||
"Op": "StringEquals",
|
||||
"Object": "Request",
|
||||
"Key": "$Actor:role",
|
||||
"Value": "others"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Status": "QuotaLimitReached",
|
||||
"Actions": {
|
||||
"Inverted": true,
|
||||
"Names": [
|
||||
"PutObject"
|
||||
]
|
||||
},
|
||||
"Resources": {
|
||||
"Inverted": false,
|
||||
"Names": [
|
||||
"native:object//9LPLUFZpEmfidG4n44vi2cjXKXSqWT492tCvLJiJ8W1J/*"
|
||||
]
|
||||
},
|
||||
"Any": true,
|
||||
"Condition": [
|
||||
{
|
||||
"Op": "StringNotLike",
|
||||
"Object": "Resource",
|
||||
"Key": "$Object:objectType",
|
||||
"Value": "regular"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Status": "100",
|
||||
"Actions": {
|
||||
"Inverted": false,
|
||||
"Names": null
|
||||
},
|
||||
"Resources": {
|
||||
"Inverted": false,
|
||||
"Names": null
|
||||
},
|
||||
"Any": false,
|
||||
"Condition": [
|
||||
{
|
||||
"Op": "255",
|
||||
"Object": "128",
|
||||
"Key": "",
|
||||
"Value": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"MatchType": "FirstMatch"
|
||||
}
|
Loading…
Reference in a new issue