-
Notifications
You must be signed in to change notification settings - Fork 8.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[FAB-5869] Implement a base collection object
This simple collection object implements a base collection type. It is set up using a StaticCollectionConfig, with the member access policy defined by a SignaturePolicyEnvelope. Change-Id: I96acb2dc5730b3bc4aa731c2fd34d6faf4f96b24 Signed-off-by: Matthias Neugschwandtner <eug@zurich.ibm.com>
- Loading branch information
Showing
11 changed files
with
371 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package privdata | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/golang/protobuf/proto" | ||
"github.com/hyperledger/fabric/common/cauthdsl" | ||
"github.com/hyperledger/fabric/common/policies" | ||
"github.com/hyperledger/fabric/msp" | ||
"github.com/hyperledger/fabric/protos/common" | ||
m "github.com/hyperledger/fabric/protos/msp" | ||
"github.com/pkg/errors" | ||
) | ||
|
||
// SimpleCollection implements a collection with static properties | ||
// and a public member set | ||
type SimpleCollection struct { | ||
name string | ||
accessPolicy policies.Policy | ||
memberOrgs []string | ||
requiredExternalPeerCount int | ||
requiredInternalPeerCount int | ||
} | ||
|
||
// CollectionID returns the collection's ID | ||
func (sc *SimpleCollection) CollectionID() string { | ||
return sc.name | ||
} | ||
|
||
// MemberOrgs returns the MSP IDs that are part of this collection | ||
func (sc *SimpleCollection) MemberOrgs() []string { | ||
return sc.memberOrgs | ||
} | ||
|
||
// RequiredExternalPeerCount returns the minimum number of external peers | ||
// required to send private data to | ||
func (sc *SimpleCollection) RequiredExternalPeerCount() int { | ||
return sc.requiredExternalPeerCount | ||
} | ||
|
||
// RequiredInternalPeerCount returns the minimum number of internal peers | ||
// required to send private data to | ||
func (sc *SimpleCollection) RequiredInternalPeerCount() int { | ||
return sc.requiredInternalPeerCount | ||
} | ||
|
||
// AccessFilter returns the member filter function that evaluates signed data | ||
// against the member access policy of this collection | ||
func (sc *SimpleCollection) AccessFilter() Filter { | ||
return func(sd common.SignedData) bool { | ||
if err := sc.accessPolicy.Evaluate([]*common.SignedData{&sd}); err != nil { | ||
return false | ||
} | ||
return true | ||
} | ||
} | ||
|
||
// Setup configures a simple collection object based on a given | ||
// StaticCollectionConfig proto that has all the necessary information | ||
func (sc *SimpleCollection) Setup(collectionConfig *common.StaticCollectionConfig, deserializer msp.IdentityDeserializer) error { | ||
if collectionConfig == nil { | ||
return errors.New("Nil config passed to collection setup") | ||
} | ||
sc.name = collectionConfig.GetName() | ||
|
||
// get the access signature policy envelope | ||
collectionPolicyConfig := collectionConfig.GetMemberOrgsPolicy() | ||
if collectionPolicyConfig == nil { | ||
return errors.New("Collection config policy is nil") | ||
} | ||
accessPolicyEnvelope := collectionPolicyConfig.GetSignaturePolicy() | ||
if accessPolicyEnvelope == nil { | ||
return errors.New("Collection config access policy is nil") | ||
} | ||
|
||
// create access policy from the envelope | ||
npp := cauthdsl.NewPolicyProvider(deserializer) | ||
polBytes, err := proto.Marshal(accessPolicyEnvelope) | ||
if err != nil { | ||
return err | ||
} | ||
sc.accessPolicy, _, err = npp.NewPolicy(polBytes) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// get member org MSP IDs from the envelope | ||
for _, principal := range accessPolicyEnvelope.Identities { | ||
switch principal.PrincipalClassification { | ||
case m.MSPPrincipal_ROLE: | ||
// Principal contains the msp role | ||
mspRole := &m.MSPRole{} | ||
err := proto.Unmarshal(principal.Principal, mspRole) | ||
if err != nil { | ||
return errors.Wrap(err, "Could not unmarshal MSPRole from principal") | ||
} | ||
sc.memberOrgs = append(sc.memberOrgs, mspRole.MspIdentifier) | ||
case m.MSPPrincipal_IDENTITY: | ||
principalId, err := deserializer.DeserializeIdentity(principal.Principal) | ||
if err != nil { | ||
return errors.Wrap(err, "Invalid identity principal, not a certificate") | ||
} | ||
sc.memberOrgs = append(sc.memberOrgs, principalId.GetMSPIdentifier()) | ||
case m.MSPPrincipal_ORGANIZATION_UNIT: | ||
OU := &m.OrganizationUnit{} | ||
err := proto.Unmarshal(principal.Principal, OU) | ||
if err != nil { | ||
return errors.Wrap(err, "Could not unmarshal OrganizationUnit from principal") | ||
} | ||
sc.memberOrgs = append(sc.memberOrgs, OU.MspIdentifier) | ||
default: | ||
return errors.New(fmt.Sprintf("Invalid principal type %d", int32(principal.PrincipalClassification))) | ||
} | ||
} | ||
|
||
// set required peer counts | ||
sc.requiredInternalPeerCount = int(collectionConfig.GetRequiredInternalPeerCount()) | ||
sc.requiredExternalPeerCount = int(collectionConfig.GetRequiredExternalPeerCount()) | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
/* | ||
Copyright IBM Corp. All Rights Reserved. | ||
SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package privdata | ||
|
||
import ( | ||
"bytes" | ||
"errors" | ||
"testing" | ||
"time" | ||
|
||
"github.com/hyperledger/fabric/common/cauthdsl" | ||
"github.com/hyperledger/fabric/msp" | ||
pb "github.com/hyperledger/fabric/protos/common" | ||
mb "github.com/hyperledger/fabric/protos/msp" | ||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func createCollectionPolicyConfig(accessPolicy *pb.SignaturePolicyEnvelope) *pb.CollectionPolicyConfig { | ||
cpcSp := &pb.CollectionPolicyConfig_SignaturePolicy{ | ||
SignaturePolicy: accessPolicy, | ||
} | ||
cpc := &pb.CollectionPolicyConfig{ | ||
Payload: cpcSp, | ||
} | ||
return cpc | ||
} | ||
|
||
type mockIdentity struct { | ||
idBytes []byte | ||
} | ||
|
||
func (id *mockIdentity) ExpiresAt() time.Time { | ||
return time.Time{} | ||
} | ||
|
||
func (id *mockIdentity) SatisfiesPrincipal(p *mb.MSPPrincipal) error { | ||
if bytes.Compare(id.idBytes, p.Principal) == 0 { | ||
return nil | ||
} | ||
return errors.New("Principals do not match") | ||
} | ||
|
||
func (id *mockIdentity) GetIdentifier() *msp.IdentityIdentifier { | ||
return &msp.IdentityIdentifier{Mspid: "Mock", Id: string(id.idBytes)} | ||
} | ||
|
||
func (id *mockIdentity) GetMSPIdentifier() string { | ||
return string(id.idBytes) | ||
} | ||
|
||
func (id *mockIdentity) Validate() error { | ||
return nil | ||
} | ||
|
||
func (id *mockIdentity) GetOrganizationalUnits() []*msp.OUIdentifier { | ||
return nil | ||
} | ||
|
||
func (id *mockIdentity) Verify(msg []byte, sig []byte) error { | ||
if bytes.Compare(sig, []byte("badsigned")) == 0 { | ||
return errors.New("Invalid signature") | ||
} | ||
return nil | ||
} | ||
|
||
func (id *mockIdentity) Serialize() ([]byte, error) { | ||
return id.idBytes, nil | ||
} | ||
|
||
type mockDeserializer struct { | ||
fail error | ||
} | ||
|
||
func (md *mockDeserializer) DeserializeIdentity(serializedIdentity []byte) (msp.Identity, error) { | ||
if md.fail != nil { | ||
return nil, md.fail | ||
} | ||
return &mockIdentity{idBytes: serializedIdentity}, nil | ||
} | ||
|
||
func TestSetupBadConfig(t *testing.T) { | ||
// set up simple collection with invalid data | ||
var sc SimpleCollection | ||
err := sc.Setup(&pb.StaticCollectionConfig{}, &mockDeserializer{}) | ||
assert.Error(t, err) | ||
} | ||
|
||
func TestSetupGoodConfigCollection(t *testing.T) { | ||
// create member access policy | ||
var signers = [][]byte{[]byte("signer0"), []byte("signer1")} | ||
policyEnvelope := cauthdsl.Envelope(cauthdsl.Or(cauthdsl.SignedBy(0), cauthdsl.SignedBy(1)), signers) | ||
accessPolicy := createCollectionPolicyConfig(policyEnvelope) | ||
|
||
// create static collection config | ||
collectionConfig := &pb.StaticCollectionConfig{ | ||
Name: "test collection", | ||
RequiredInternalPeerCount: 1, | ||
RequiredExternalPeerCount: 1, | ||
MemberOrgsPolicy: accessPolicy, | ||
} | ||
|
||
// set up simple collection with valid data | ||
var sc SimpleCollection | ||
err := sc.Setup(collectionConfig, &mockDeserializer{}) | ||
assert.NoError(t, err) | ||
|
||
// check name | ||
assert.True(t, sc.CollectionID() == "test collection") | ||
|
||
// check members | ||
members := sc.MemberOrgs() | ||
assert.True(t, members[0] == "signer0") | ||
assert.True(t, members[1] == "signer1") | ||
|
||
// check required peer count | ||
assert.True(t, sc.RequiredInternalPeerCount() == 1) | ||
assert.True(t, sc.RequiredExternalPeerCount() == 1) | ||
} | ||
|
||
func TestSimpleCollectionFilter(t *testing.T) { | ||
// create member access policy | ||
var signers = [][]byte{[]byte("signer0"), []byte("signer1")} | ||
policyEnvelope := cauthdsl.Envelope(cauthdsl.Or(cauthdsl.SignedBy(0), cauthdsl.SignedBy(1)), signers) | ||
accessPolicy := createCollectionPolicyConfig(policyEnvelope) | ||
|
||
// create static collection config | ||
collectionConfig := &pb.StaticCollectionConfig{ | ||
Name: "test collection", | ||
RequiredInternalPeerCount: 1, | ||
RequiredExternalPeerCount: 1, | ||
MemberOrgsPolicy: accessPolicy, | ||
} | ||
|
||
// set up simple collection | ||
var sc SimpleCollection | ||
err := sc.Setup(collectionConfig, &mockDeserializer{}) | ||
assert.NoError(t, err) | ||
|
||
// get the collection access filter | ||
var cap CollectionAccessPolicy | ||
cap = &sc | ||
accessFilter := cap.AccessFilter() | ||
|
||
// check filter: not a member of the collection | ||
notMember := pb.SignedData{ | ||
Identity: []byte{1, 2, 3}, | ||
Signature: []byte{}, | ||
Data: []byte{}, | ||
} | ||
assert.False(t, accessFilter(notMember)) | ||
|
||
// check filter: member of the collection | ||
member := pb.SignedData{ | ||
Identity: signers[0], | ||
Signature: []byte{}, | ||
Data: []byte{}, | ||
} | ||
assert.True(t, accessFilter(member)) | ||
} |
Oops, something went wrong.