From 64e7356acc7326faafe8454d884d0c260642805d Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Fri, 17 Feb 2023 15:52:52 +0300 Subject: [PATCH] [TrueCloudLab#32] Add custom policy unmarshaler Signed-off-by: Denis Kirillov --- api/handler/acl.go | 84 +++++++++++++++++++++++++++++++++++++++++ api/handler/acl_test.go | 79 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+) diff --git a/api/handler/acl.go b/api/handler/acl.go index 60c49e2a..2c5ad128 100644 --- a/api/handler/acl.go +++ b/api/handler/acl.go @@ -158,6 +158,90 @@ func (s ServiceRecord) ToEACLRecord() *eacl.Record { return serviceRecord } +var ( + errInvalidStatement = stderrors.New("invalid statement") + errInvalidPrincipal = stderrors.New("invalid principal") +) + +func (s *statement) UnmarshalJSON(data []byte) error { + var statementMap map[string]interface{} + if err := json.Unmarshal(data, &statementMap); err != nil { + return err + } + + sidField, ok := statementMap["Sid"] + if ok { + if s.Sid, ok = sidField.(string); !ok { + return errInvalidStatement + } + } + + effectField, ok := statementMap["Effect"] + if ok { + if s.Effect, ok = effectField.(string); !ok { + return errInvalidStatement + } + } + + principalField, ok := statementMap["Principal"] + if ok { + principalMap, ok := principalField.(map[string]interface{}) + if !ok { + return errInvalidPrincipal + } + + awsField, ok := principalMap["AWS"] + if ok { + if s.Principal.AWS, ok = awsField.(string); !ok { + return fmt.Errorf("%w: 'AWS' field must be string", errInvalidPrincipal) + } + } + + canonicalUserField, ok := principalMap["CanonicalUser"] + if ok { + if s.Principal.CanonicalUser, ok = canonicalUserField.(string); !ok { + return errInvalidPrincipal + } + } + } + + actionField, ok := statementMap["Action"] + if ok { + switch actionField := actionField.(type) { + case []interface{}: + s.Action = make([]string, len(actionField)) + for i, action := range actionField { + if s.Action[i], ok = action.(string); !ok { + return errInvalidStatement + } + } + case string: + s.Action = []string{actionField} + default: + return errInvalidStatement + } + } + + resourceField, ok := statementMap["Resource"] + if ok { + switch resourceField := resourceField.(type) { + case []interface{}: + s.Resource = make([]string, len(resourceField)) + for i, action := range resourceField { + if s.Resource[i], ok = action.(string); !ok { + return errInvalidStatement + } + } + case string: + s.Resource = []string{resourceField} + default: + return errInvalidStatement + } + } + + return nil +} + func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { reqInfo := api.GetReqInfo(r.Context()) diff --git a/api/handler/acl_test.go b/api/handler/acl_test.go index d2cd2e7b..8c25c934 100644 --- a/api/handler/acl_test.go +++ b/api/handler/acl_test.go @@ -1352,6 +1352,85 @@ func TestBucketPolicy(t *testing.T) { } } +func TestBucketPolicyUnmarshal(t *testing.T) { + for _, tc := range []struct { + name string + policy string + }{ + { + name: "action/resource array", + policy: ` +{ + "Version": "2012-10-17", + "Statement": [{ + "Principal": { + "AWS": "arn:aws:iam::111122223333:role/JohnDoe" + }, + "Effect": "Allow", + "Action": [ + "s3:GetObject", + "s3:GetObjectVersion" + ], + "Resource": [ + "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*", + "arn:aws:s3:::DOC-EXAMPLE-BUCKET2/*" + ] + }] +} +`, + }, + { + name: "action/resource string", + policy: ` +{ + "Version": "2012-10-17", + "Statement": [{ + "Principal": { + "AWS": "arn:aws:iam::111122223333:role/JohnDoe" + }, + "Effect": "Deny", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*" + }] +} +`, + }, + } { + t.Run(tc.name, func(t *testing.T) { + bktPolicy := &bucketPolicy{} + err := json.Unmarshal([]byte(tc.policy), bktPolicy) + require.NoError(t, err) + }) + } +} + +func TestPutBucketPolicy(t *testing.T) { + bktPolicy := ` +{ + "Version": "2012-10-17", + "Statement": [{ + "Principal": { + "AWS": "*" + }, + "Effect": "Deny", + "Action": "s3:GetObject", + "Resource": "arn:aws:s3:::bucket-for-policy/*" + }] +} +` + hc := prepareHandlerContext(t) + bktName := "bucket-for-policy" + + box, _ := createAccessBox(t) + createBucket(t, hc, bktName, box) + + w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader([]byte(bktPolicy))) + ctx := context.WithValue(r.Context(), api.BoxData, box) + r = r.WithContext(ctx) + hc.Handler().PutBucketPolicyHandler(w, r) + assertStatus(hc.t, w, http.StatusOK) +} + func getBucketPolicy(hc *handlerContext, bktName string) *bucketPolicy { w, r := prepareTestRequest(hc, bktName, "", nil) hc.Handler().GetBucketPolicyHandler(w, r)