package ape import ( "context" "fmt" "net" "testing" objectV2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" aperequest "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/ape/request" checksumtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/checksum/test" objectSDK "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" commonschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/common" nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "github.com/stretchr/testify/require" "google.golang.org/grpc/peer" ) const ( testOwnerID = "FPPtmAi9TCX329" incomingIP = "192.92.33.1" ) func ctxWithPeerInfo() context.Context { return peer.NewContext(context.Background(), &peer.Peer{ Addr: &net.TCPAddr{ IP: net.ParseIP(incomingIP), Port: 41111, }, }) } func TestObjectProperties(t *testing.T) { for _, test := range []struct { name string container string object *string header *headerObjectSDKParams }{ { name: "fully filled header", container: containerID, object: stringPtr(objectID), header: &headerObjectSDKParams{ majorVersion: 1, minorVersion: 1, owner: usertest.ID(), epoch: 3, payloadSize: 1000, typ: objectSDK.TypeRegular, payloadChecksum: checksumtest.Checksum(), payloadHomomorphicHash: checksumtest.Checksum(), attributes: []struct { key string val string }{ { key: "attr1", val: "val1", }, { key: "attr2", val: "val2", }, }, }, }, { name: "partially filled header", container: containerID, header: &headerObjectSDKParams{ majorVersion: 1, minorVersion: 1, owner: usertest.ID(), epoch: 3, attributes: []struct { key string val string }{ { key: "attr1", val: "val1", }, }, }, }, { name: "only address paramaters set in header", container: containerID, object: stringPtr(objectID), }, { name: "only container set in header", container: containerID, }, } { t.Run(test.name, func(t *testing.T) { cnr := newContainerIDSDK(t, test.container) obj := newObjectIDSDK(t, test.object) header := newHeaderObjectSDK(cnr, obj, test.header) var testCnrOwner user.ID require.NoError(t, testCnrOwner.DecodeString(testOwnerID)) props := objectProperties(cnr, obj, testCnrOwner, header.ToV2().GetHeader()) require.Equal(t, test.container, props[nativeschema.PropertyKeyObjectContainerID]) require.Equal(t, testOwnerID, props[nativeschema.PropertyKeyContainerOwnerID]) if obj != nil { require.Equal(t, *test.object, props[nativeschema.PropertyKeyObjectID]) } if test.header != nil { require.Equal(t, fmt.Sprintf("v%d.%d", test.header.majorVersion, test.header.minorVersion), props[nativeschema.PropertyKeyObjectVersion], ) require.Equal(t, test.header.owner.EncodeToString(), props[nativeschema.PropertyKeyObjectOwnerID]) require.Equal(t, fmt.Sprintf("%d", test.header.epoch), props[nativeschema.PropertyKeyObjectCreationEpoch]) require.Equal(t, fmt.Sprintf("%d", test.header.payloadSize), props[nativeschema.PropertyKeyObjectPayloadLength]) require.Equal(t, test.header.typ.String(), props[nativeschema.PropertyKeyObjectType]) require.Equal(t, test.header.payloadChecksum.String(), props[nativeschema.PropertyKeyObjectPayloadHash]) require.Equal(t, test.header.payloadHomomorphicHash.String(), props[nativeschema.PropertyKeyObjectHomomorphicHash]) for _, attr := range test.header.attributes { require.Equal(t, attr.val, props[attr.key]) } } }) } } func TestNewAPERequest(t *testing.T) { tests := []struct { name string methods []string namespace string container string object *string header testHeader expectErr error }{ { name: "oid required requests", methods: methodsRequiredOID, namespace: namespace, container: containerID, object: stringPtr(objectID), header: testHeader{ headerObjSDK: &headerObjectSDKParams{ majorVersion: 1, minorVersion: 1, owner: usertest.ID(), epoch: 3, payloadSize: 1000, typ: objectSDK.TypeRegular, payloadChecksum: checksumtest.Checksum(), payloadHomomorphicHash: checksumtest.Checksum(), }, fromHeaderProvider: true, }, }, { name: "oid required requests but header cannot be found locally", methods: methodsRequiredOID, namespace: namespace, container: containerID, object: stringPtr(objectID), header: testHeader{}, }, { name: "oid required requests missed oid", methods: methodsRequiredOID, namespace: namespace, container: containerID, object: nil, header: testHeader{}, expectErr: errMissingOID, }, { name: "response for oid required requests", methods: methodsRequiredOID, namespace: namespace, container: containerID, object: stringPtr(objectID), header: testHeader{ headerObjSDK: &headerObjectSDKParams{ majorVersion: 1, minorVersion: 1, owner: usertest.ID(), epoch: 3, payloadSize: 1000, typ: objectSDK.TypeRegular, payloadChecksum: checksumtest.Checksum(), payloadHomomorphicHash: checksumtest.Checksum(), }, fromRequestResponseHeader: true, }, }, { name: "oid not required methods request", methods: methodsOptionalOID, namespace: namespace, container: containerID, object: nil, header: testHeader{ headerObjSDK: &headerObjectSDKParams{ majorVersion: 6, minorVersion: 66, owner: usertest.ID(), epoch: 3, typ: objectSDK.TypeLock, }, fromRequestResponseHeader: true, }, }, { name: "oid not required methods request but no header", methods: methodsOptionalOID, namespace: namespace, container: containerID, object: nil, header: testHeader{}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { for _, method := range test.methods { t.Run(method, func(t *testing.T) { cnr := newContainerIDSDK(t, test.container) obj := newObjectIDSDK(t, test.object) var testCnrOwner user.ID require.NoError(t, testCnrOwner.DecodeString(testOwnerID)) prm := Prm{ Namespace: test.namespace, Method: method, Container: cnr, Object: obj, Role: role, SenderKey: senderKey, ContainerOwner: testCnrOwner, } headerSource := newHeaderProviderMock() ffidProvider := newFrostfsIDProviderMock(t) var headerObjSDK *objectSDK.Object if test.header.headerObjSDK != nil { headerObjSDK = newHeaderObjectSDK(cnr, obj, test.header.headerObjSDK) if test.header.fromHeaderProvider { require.NotNil(t, obj, "oid is required if a header is expected to be found in header provider") headerSource.addHeader(cnr, *obj, headerObjSDK) } else if test.header.fromRequestResponseHeader { prm.Header = headerObjSDK.ToV2().GetHeader() } } c := checkerImpl{ headerProvider: headerSource, frostFSIDClient: ffidProvider, } r, err := c.newAPERequest(ctxWithPeerInfo(), prm) if test.expectErr != nil { require.Error(t, err) require.ErrorIs(t, err, test.expectErr) return } expectedRequest := aperequest.NewRequest( method, aperequest.NewResource( resourceName(cnr, obj, prm.Namespace), objectProperties(cnr, obj, testCnrOwner, func() *objectV2.Header { if headerObjSDK != nil { return headerObjSDK.ToV2().GetHeader() } return prm.Header }())), map[string]string{ nativeschema.PropertyKeyActorPublicKey: prm.SenderKey, nativeschema.PropertyKeyActorRole: prm.Role, fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr1"): "value1", fmt.Sprintf(commonschema.PropertyKeyFormatFrostFSIDUserClaim, "tag-attr2"): "value2", commonschema.PropertyKeyFrostFSIDGroupID: "1", commonschema.PropertyKeyFrostFSSourceIP: incomingIP, }, ) require.Equal(t, expectedRequest, r) }) } }) } } func TestResourceName(t *testing.T) { for _, test := range []struct { name string namespace string container string object *string expected string }{ { name: "non-root namespace, CID", namespace: namespace, container: containerID, expected: fmt.Sprintf("native:object/%s/%s/*", namespace, containerID), }, { name: "non-root namespace, CID, OID", namespace: namespace, container: containerID, object: stringPtr(objectID), expected: fmt.Sprintf("native:object/%s/%s/%s", namespace, containerID, objectID), }, { name: "empty namespace, CID", namespace: "", container: containerID, expected: fmt.Sprintf("native:object//%s/*", containerID), }, { name: "empty namespace, CID, OID", namespace: "", container: containerID, object: stringPtr(objectID), expected: fmt.Sprintf("native:object//%s/%s", containerID, objectID), }, { name: "root namespace, CID", namespace: "root", container: containerID, expected: fmt.Sprintf("native:object//%s/*", containerID), }, { name: "root namespace, CID, OID", namespace: "root", container: containerID, object: stringPtr(objectID), expected: fmt.Sprintf("native:object//%s/%s", containerID, objectID), }, } { t.Run(test.name, func(t *testing.T) { cnr := newContainerIDSDK(t, test.container) obj := newObjectIDSDK(t, test.object) require.Equal(t, test.expected, resourceName(cnr, obj, test.namespace)) }) } }