package s3api import ( "bytes" "encoding/json" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" "github.com/seaweedfs/seaweedfs/weed/pb/iam_pb" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" "io" "net/http" "testing" ) var accountManager *IdentityAccessManagement func init() { accountManager = &IdentityAccessManagement{} _ = accountManager.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{ Accounts: []*iam_pb.Account{ { Id: "accountA", DisplayName: "accountAName", EmailAddress: "accountA@example.com", }, { Id: "accountB", DisplayName: "accountBName", EmailAddress: "accountB@example.com", }, }, }) } func TestGetAccountId(t *testing.T) { req := &http.Request{ Header: make(map[string][]string), } //case1 //accountId: "admin" req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAdminId) if GetAccountId(req) != s3_constants.AccountAdminId { t.Fatal("expect accountId: admin") } //case2 //accountId: "anoymous" req.Header.Set(s3_constants.AmzAccountId, s3_constants.AccountAnonymousId) if GetAccountId(req) != s3_constants.AccountAnonymousId { t.Fatal("expect accountId: anonymous") } //case3 //accountId is nil => "anonymous" req.Header.Del(s3_constants.AmzAccountId) if GetAccountId(req) != s3_constants.AccountAnonymousId { t.Fatal("expect accountId: anonymous") } } func TestExtractAcl(t *testing.T) { type Case struct { id int resultErrCode, expectErrCode s3err.ErrorCode resultGrants, expectGrants []*s3.Grant } testCases := make([]*Case, 0) accountAdminId := "admin" { //case1 (good case) //parse acp from request body req := &http.Request{ Header: make(map[string][]string), } req.Body = io.NopCloser(bytes.NewReader([]byte(` admin admin admin FULL_CONTROL http://acs.amazonaws.com/groups/global/AllUsers FULL_CONTROL `))) objectWriter := "accountA" grants, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, accountAdminId, objectWriter) testCases = append(testCases, &Case{ 1, errCode, s3err.ErrNone, grants, []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &accountAdminId, }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &s3_constants.PermissionFullControl, }, }, }) } { //case2 (good case) //parse acp from header (cannedAcl) req := &http.Request{ Header: make(map[string][]string), } req.Body = nil req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclPrivate) objectWriter := "accountA" grants, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, accountAdminId, objectWriter) testCases = append(testCases, &Case{ 2, errCode, s3err.ErrNone, grants, []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &objectWriter, }, Permission: &s3_constants.PermissionFullControl, }, }, }) } { //case3 (bad case) //parse acp from request body (content is invalid) req := &http.Request{ Header: make(map[string][]string), } req.Body = io.NopCloser(bytes.NewReader([]byte("zdfsaf"))) req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclPrivate) objectWriter := "accountA" _, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, accountAdminId, objectWriter) testCases = append(testCases, &Case{ id: 3, resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, }) } //case4 (bad case) //parse acp from header (cannedAcl is invalid) req := &http.Request{ Header: make(map[string][]string), } req.Body = nil req.Header.Set(s3_constants.AmzCannedAcl, "dfaksjfk") objectWriter := "accountA" _, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, "", objectWriter) testCases = append(testCases, &Case{ id: 4, resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, }) { //case5 (bad case) //parse acp from request body: owner is inconsistent req.Body = io.NopCloser(bytes.NewReader([]byte(` admin admin admin FULL_CONTROL http://acs.amazonaws.com/groups/global/AllUsers FULL_CONTROL `))) objectWriter = "accountA" _, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, objectWriter, objectWriter) testCases = append(testCases, &Case{ id: 5, resultErrCode: errCode, expectErrCode: s3err.ErrAccessDenied, }) } for _, tc := range testCases { if tc.resultErrCode != tc.expectErrCode { t.Fatalf("case[%d]: errorCode not expect", tc.id) } if !grantsEquals(tc.resultGrants, tc.expectGrants) { t.Fatalf("case[%d]: grants not expect", tc.id) } } } func TestParseAndValidateAclHeaders(t *testing.T) { type Case struct { id int resultOwner, expectOwner string resultErrCode, expectErrCode s3err.ErrorCode resultGrants, expectGrants []*s3.Grant } testCases := make([]*Case, 0) bucketOwner := "admin" { //case1 (good case) //parse custom acl req := &http.Request{ Header: make(map[string][]string), } objectWriter := "accountA" req.Header.Set(s3_constants.AmzAclFullControl, `uri="http://acs.amazonaws.com/groups/global/AllUsers", id="anonymous", emailAddress="admin@example.com"`) ownerId, grants, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) testCases = append(testCases, &Case{ 1, ownerId, objectWriter, errCode, s3err.ErrNone, grants, []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: aws.String(s3_constants.AccountAnonymousId), }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: aws.String(s3_constants.AccountAdminId), }, Permission: &s3_constants.PermissionFullControl, }, }, }) } { //case2 (good case) //parse canned acl (ownership=ObjectWriter) req := &http.Request{ Header: make(map[string][]string), } objectWriter := "accountA" req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) ownerId, grants, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) testCases = append(testCases, &Case{ 2, ownerId, objectWriter, errCode, s3err.ErrNone, grants, []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &objectWriter, }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &bucketOwner, }, Permission: &s3_constants.PermissionFullControl, }, }, }) } { //case3 (good case) //parse canned acl (ownership=OwnershipBucketOwnerPreferred) req := &http.Request{ Header: make(map[string][]string), } objectWriter := "accountA" req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) ownerId, grants, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwner, objectWriter, false) testCases = append(testCases, &Case{ 3, ownerId, bucketOwner, errCode, s3err.ErrNone, grants, []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &bucketOwner, }, Permission: &s3_constants.PermissionFullControl, }, }, }) } { //case4 (bad case) //parse custom acl (grantee id not exists) req := &http.Request{ Header: make(map[string][]string), } objectWriter := "accountA" req.Header.Set(s3_constants.AmzAclFullControl, `uri="http://acs.amazonaws.com/groups/global/AllUsers", id="notExistsAccount", emailAddress="admin@example.com"`) _, _, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) testCases = append(testCases, &Case{ id: 4, resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, }) } { //case5 (bad case) //parse custom acl (invalid format) req := &http.Request{ Header: make(map[string][]string), } objectWriter := "accountA" req.Header.Set(s3_constants.AmzAclFullControl, `uri="http:sfasf"`) _, _, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) testCases = append(testCases, &Case{ id: 5, resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, }) } { //case6 (bad case) //parse canned acl (invalid value) req := &http.Request{ Header: make(map[string][]string), } objectWriter := "accountA" req.Header.Set(s3_constants.AmzCannedAcl, `uri="http:sfasf"`) _, _, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) testCases = append(testCases, &Case{ id: 5, resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, }) } for _, tc := range testCases { if tc.expectErrCode != tc.resultErrCode { t.Errorf("case[%d]: errCode unexpect", tc.id) } if tc.resultOwner != tc.expectOwner { t.Errorf("case[%d]: ownerId unexpect", tc.id) } if !grantsEquals(tc.resultGrants, tc.expectGrants) { t.Fatalf("case[%d]: grants not expect", tc.id) } } } func grantsEquals(a, b []*s3.Grant) bool { if len(a) != len(b) { return false } for i, grant := range a { if !GrantEquals(grant, b[i]) { return false } } return true } func TestDetermineReqGrants(t *testing.T) { { //case1: request account is anonymous accountId := s3_constants.AccountAnonymousId reqPermission := s3_constants.PermissionRead resultGrants := DetermineReqGrants(accountId, reqPermission) expectGrants := []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &reqPermission, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &accountId, }, Permission: &reqPermission, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &accountId, }, Permission: &s3_constants.PermissionFullControl, }, } if !grantsEquals(resultGrants, expectGrants) { t.Fatalf("grants not expect") } } { //case2: request account is not anonymous (Iam authed) accountId := "accountX" reqPermission := s3_constants.PermissionRead resultGrants := DetermineReqGrants(accountId, reqPermission) expectGrants := []*s3.Grant{ { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &reqPermission, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &accountId, }, Permission: &reqPermission, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, ID: &accountId, }, Permission: &s3_constants.PermissionFullControl, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAuthenticatedUsers, }, Permission: &reqPermission, }, { Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAuthenticatedUsers, }, Permission: &s3_constants.PermissionFullControl, }, } if !grantsEquals(resultGrants, expectGrants) { t.Fatalf("grants not expect") } } } func TestAssembleEntryWithAcp(t *testing.T) { defaultOwner := "admin" //case1 //assemble with non-empty grants expectOwner := "accountS" expectGrants := []*s3.Grant{ { Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), URI: &s3_constants.GranteeGroupAllUsers, }, }, } entry := &filer_pb.Entry{} AssembleEntryWithAcp(entry, expectOwner, expectGrants) resultOwner := GetAcpOwner(entry.Extended, defaultOwner) if resultOwner != expectOwner { t.Fatalf("owner not expect") } resultGrants := GetAcpGrants(entry.Extended) if !grantsEquals(resultGrants, expectGrants) { t.Fatal("grants not expect") } //case2 //assemble with empty grants (override) AssembleEntryWithAcp(entry, "", nil) resultOwner = GetAcpOwner(entry.Extended, defaultOwner) if resultOwner != defaultOwner { t.Fatalf("owner not expect") } resultGrants = GetAcpGrants(entry.Extended) if len(resultGrants) != 0 { t.Fatal("grants not expect") } } func TestGrantEquals(t *testing.T) { testCases := map[bool]bool{ GrantEquals(nil, nil): true, GrantEquals(&s3.Grant{}, nil): false, GrantEquals(&s3.Grant{}, &s3.Grant{}): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, }, &s3.Grant{}): false, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, }): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{}, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{}, }): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{}, }): false, //type not present, compare other fields of grant is meaningless GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ ID: aws.String(s3_constants.AccountAdminId), //EmailAddress: &s3account.AccountAdmin.EmailAddress, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ ID: aws.String(s3_constants.AccountAdminId), }, }): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, }, }): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, }): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionWrite, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, }): false, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), }, }): true, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), URI: &s3_constants.GranteeGroupAllUsers, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), }, }): false, GrantEquals(&s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), URI: &s3_constants.GranteeGroupAllUsers, }, }, &s3.Grant{ Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, }): true, } for tc, expect := range testCases { if tc != expect { t.Fatal("TestGrantEquals not expect!") } } } func TestSetAcpOwnerHeader(t *testing.T) { ownerId := "accountZ" req := &http.Request{ Header: make(map[string][]string), } SetAcpOwnerHeader(req, ownerId) if req.Header.Get(s3_constants.ExtAmzOwnerKey) != ownerId { t.Fatalf("owner unexpect") } } func TestSetAcpGrantsHeader(t *testing.T) { req := &http.Request{ Header: make(map[string][]string), } grants := []*s3.Grant{ { Permission: &s3_constants.PermissionRead, Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, ID: aws.String(s3_constants.AccountAdminId), URI: &s3_constants.GranteeGroupAllUsers, }, }, } SetAcpGrantsHeader(req, grants) grantsJson, _ := json.Marshal(grants) if req.Header.Get(s3_constants.ExtAmzAclKey) != string(grantsJson) { t.Fatalf("owner unexpect") } }