Skip to content

Commit

Permalink
[FAB-5869] Implement a base collection object
Browse files Browse the repository at this point in the history
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
Matthias Neugschwandtner authored and yacovm committed Oct 6, 2017
1 parent 8cdcd5e commit 6dc9301
Show file tree
Hide file tree
Showing 11 changed files with 371 additions and 59 deletions.
22 changes: 11 additions & 11 deletions core/common/privdata/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,29 +16,29 @@ type Collection interface {
// as txid, nonce, creator -- for future use
// SetTxContext(parameters ...interface{})

// GetCollectionID returns this collection's ID
GetCollectionID() string
// CollectionID returns this collection's ID
CollectionID() string

// GetEndorsementPolicy returns the endorsement policy for validation -- for
// future use
// GetEndorsementPolicy() string

// GetMemberOrgs returns the collection's members as MSP IDs. This serves as
// MemberOrgs returns the collection's members as MSP IDs. This serves as
// a human-readable way of quickly identifying who is part of a collection.
GetMemberOrgs() []string
MemberOrgs() []string
}

// CollectionAccess encapsulates functions for the access policy of a collection
// CollectionAccessPolicy encapsulates functions for the access policy of a collection
type CollectionAccessPolicy interface {
// GetAccessFilter returns a member filter function for a collection
GetAccessFilter() Filter
// AccessFilter returns a member filter function for a collection
AccessFilter() Filter

// RequiredExternalPeerCount returns the minimum number of external peers
// required to hold private data
// required to send private data to
RequiredExternalPeerCount() int

// RequiredExternalPeerCount returns the minimum number of internal peers
// required to hold private data
// required to send private data to
RequiredInternalPeerCount() int
}

Expand All @@ -59,8 +59,8 @@ type CollectionStore interface {
// latest configuration that was committed into the ledger before this txID
// was committed.
// Else - it's the latest configuration for the collection.
GetCollection(common.CollectionCriteria) Collection
RetrieveCollection(common.CollectionCriteria) Collection

// GetCollectionAccessPolicy retrieves a collection's access policy
GetCollectionAccessPolicy(common.CollectionCriteria) CollectionAccessPolicy
RetrieveCollectionAccessPolicy(common.CollectionCriteria) CollectionAccessPolicy
}
14 changes: 5 additions & 9 deletions core/common/privdata/nopcollection.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,11 @@ import (
type NopCollection struct {
}

func (nc *NopCollection) GetCollectionID() string {
func (nc *NopCollection) CollectionID() string {
return ""
}

func (nc *NopCollection) GetEndorsementPolicy() string {
return ""
}

func (nc *NopCollection) GetMemberOrgs() []string {
func (nc *NopCollection) MemberOrgs() []string {
return nil
}

Expand All @@ -35,7 +31,7 @@ func (nc *NopCollection) RequiredInternalPeerCount() int {
return viper.GetInt("peer.gossip.pvtData.minInternalPeers")
}

func (nc *NopCollection) GetAccessFilter() Filter {
func (nc *NopCollection) AccessFilter() Filter {
// return true for all
return func(common.SignedData) bool {
return true
Expand All @@ -45,10 +41,10 @@ func (nc *NopCollection) GetAccessFilter() Filter {
type NopCollectionStore struct {
}

func (*NopCollectionStore) GetCollection(common.CollectionCriteria) Collection {
func (*NopCollectionStore) RetrieveCollection(common.CollectionCriteria) Collection {
return &NopCollection{}
}

func (*NopCollectionStore) GetCollectionAccessPolicy(common.CollectionCriteria) CollectionAccessPolicy {
func (*NopCollectionStore) RetrieveCollectionAccessPolicy(common.CollectionCriteria) CollectionAccessPolicy {
return &NopCollection{}
}
127 changes: 127 additions & 0 deletions core/common/privdata/simplecollection.go
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
}
163 changes: 163 additions & 0 deletions core/common/privdata/simplecollection_test.go
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))
}
Loading

0 comments on commit 6dc9301

Please sign in to comment.