diff --git a/accesscontrol/allowCaller.go b/accesscontrol/allowCaller.go new file mode 100644 index 0000000..5be780c --- /dev/null +++ b/accesscontrol/allowCaller.go @@ -0,0 +1,89 @@ +package accesscontrol + +import ( + "regexp" + + "github.com/hyperledger-labs/cc-tools/errors" + "github.com/hyperledger/fabric-chaincode-go/pkg/cid" + "github.com/hyperledger/fabric-chaincode-go/shim" +) + +func AllowCaller(stub shim.ChaincodeStubInterface, allowedCallers []Caller) (bool, error) { + if allowedCallers == nil { + return true, nil + } + + callerMSP, err := cid.GetMSPID(stub) + if err != nil { + return false, errors.WrapError(err, "could not get MSP id") + } + + var grantedPermission bool + for i := 0; i < len(allowedCallers) && !grantedPermission; i++ { + allowed := allowedCallers[i] + isAllowedMSP, err := checkMSP(callerMSP, allowed.MSP) + if err != nil { + return false, errors.WrapError(err, "failed to check MSP") + } + + isAllowedOU, err := checkOU(stub, allowed.OU) + if err != nil { + return false, errors.WrapError(err, "failed to check OU") + } + + isAllowedAttrs, err := checkAttributes(stub, allowed.Attributes) + if err != nil { + return false, errors.WrapError(err, "failed to check attributes") + } + + grantedPermission = isAllowedMSP && isAllowedOU && isAllowedAttrs + } + + return grantedPermission, nil +} + +func checkMSP(callerMsp, allowedMSP string) (bool, error) { + if len(allowedMSP) <= 1 { + return true, nil + } + + // if caller is regexp + if allowedMSP[0] == '$' { + match, err := regexp.MatchString(allowedMSP[1:], callerMsp) + if err != nil { + return false, errors.NewCCError("failed to check if caller matches regexp", 500) + } + + return match, nil + } + + // if caller is not regexss + return callerMsp == allowedMSP, nil +} + +func checkOU(stub shim.ChaincodeStubInterface, allowedOU string) (bool, error) { + if allowedOU == "" { + return true, nil + } + + return cid.HasOUValue(stub, allowedOU) +} + +func checkAttributes(stub shim.ChaincodeStubInterface, allowedAttrs map[string]string) (bool, error) { + if allowedAttrs == nil { + return true, nil + } + + for key, value := range allowedAttrs { + callerValue, _, err := cid.GetAttributeValue(stub, key) + if err != nil { + return false, err + } + + if callerValue != value { + return false, nil + } + } + + return true, nil +} diff --git a/accesscontrol/caller.go b/accesscontrol/caller.go new file mode 100644 index 0000000..746f6c6 --- /dev/null +++ b/accesscontrol/caller.go @@ -0,0 +1,7 @@ +package accesscontrol + +type Caller struct { + MSP string `json:"msp"` + OU string `json:"ou"` + Attributes map[string]string `json:"attributes"` +} diff --git a/mock/mockstub.go b/mock/mockstub.go index 718fdbd..2a498c7 100644 --- a/mock/mockstub.go +++ b/mock/mockstub.go @@ -17,6 +17,7 @@ import ( "github.com/golang/protobuf/ptypes/timestamp" "github.com/hyperledger/fabric-chaincode-go/shim" "github.com/hyperledger/fabric-protos-go/ledger/queryresult" + "github.com/hyperledger/fabric-protos-go/msp" pb "github.com/hyperledger/fabric-protos-go/peer" ) @@ -291,10 +292,10 @@ func (stub *MockStub) GetStateByRange(startKey, endKey string) (shim.StateQueryI return NewMockStateRangeQueryIterator(stub, startKey, endKey), nil } -//To ensure that simple keys do not go into composite key namespace, -//we validate simplekey to check whether the key starts with 0x00 (which -//is the namespace for compositeKey). This helps in avoding simple/composite -//key collisions. +// To ensure that simple keys do not go into composite key namespace, +// we validate simplekey to check whether the key starts with 0x00 (which +// is the namespace for compositeKey). This helps in avoding simple/composite +// key collisions. func validateSimpleKeys(simpleKeys ...string) error { for _, key := range simpleKeys { if len(key) > 0 && key[0] == compositeKeyNamespace[0] { @@ -508,10 +509,25 @@ func NewMockStub(name string, cc shim.Chaincode) *MockStub { s.Keys = list.New() s.ChaincodeEventsChannel = make(chan *pb.ChaincodeEvent, 100) //define large capacity for non-blocking setEvent calls. s.Decorations = make(map[string][]byte) - + s.Creator, _ = newCreator(name, []byte{}) return s } +// NewMockStubWithCert Constructor to initialize mock stub with a certificate. Useful for transactions with robust permissioning. +func NewMockStubWithCert(name string, cc shim.Chaincode, cert []byte) (*MockStub, error) { + s := NewMockStub(name, cc) + var err error + s.Creator, err = newCreator(name, cert) + + return s, err +} + +func newCreator(orgMSP string, cert []byte) ([]byte, error) { + sid := &msp.SerializedIdentity{Mspid: orgMSP, + IdBytes: cert} + return proto.Marshal(sid) +} + /***************************** Range Query Iterator *****************************/ diff --git a/transactions/getTx.go b/transactions/getTx.go index 4ad5f1c..baf98f9 100644 --- a/transactions/getTx.go +++ b/transactions/getTx.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" + "github.com/hyperledger-labs/cc-tools/accesscontrol" "github.com/hyperledger-labs/cc-tools/errors" sw "github.com/hyperledger-labs/cc-tools/stubwrapper" ) @@ -50,10 +51,10 @@ var getTx = Transaction{ // If user requested asset list type txListElem struct { - Tag string `json:"tag"` - Label string `json:"label"` - Description string `json:"description"` - Callers []string `json:"callers,omitempty"` + Tag string `json:"tag"` + Label string `json:"label"` + Description string `json:"description"` + Callers []accesscontrol.Caller `json:"callers,omitempty"` } var txRetList []txListElem for _, tx := range txList { diff --git a/transactions/run.go b/transactions/run.go index 1573f81..d3d3db9 100644 --- a/transactions/run.go +++ b/transactions/run.go @@ -2,8 +2,8 @@ package transactions import ( "fmt" - "regexp" + "github.com/hyperledger-labs/cc-tools/accesscontrol" "github.com/hyperledger-labs/cc-tools/assets" "github.com/hyperledger-labs/cc-tools/errors" sw "github.com/hyperledger-labs/cc-tools/stubwrapper" @@ -40,38 +40,13 @@ func Run(stub shim.ChaincodeStubInterface) ([]byte, errors.ICCError) { } // Verify callers permissions - if tx.Callers != nil { - // Get tx caller MSP ID - txCaller, err := sw.GetMSPID() - if err != nil { - return nil, errors.WrapErrorWithStatus(err, "error getting tx caller", 500) - } + callPermission, err := accesscontrol.AllowCaller(stub, tx.Callers) + if err != nil { + return nil, errors.WrapError(err, "failed to check permissions") + } - // Check if caller is allowed - callPermission := false - for _, c := range tx.Callers { - if len(c) <= 1 { - continue - } - if c[0] == '$' { // if caller is regexp - match, err := regexp.MatchString(c[1:], txCaller) - if err != nil { - return nil, errors.NewCCError("failed to check if caller matches regexp", 500) - } - if match { - callPermission = true - break - } - } else { // if caller is not regexp - if c == txCaller { - callPermission = true - break - } - } - } - if !callPermission { - return nil, errors.NewCCError(fmt.Sprintf("%s cannot call this transaction", txCaller), 403) - } + if !callPermission { + return nil, errors.NewCCError("current caller not allowed", 403) } return tx.Routine(sw, reqMap) diff --git a/transactions/startupCheck.go b/transactions/startupCheck.go index 1fea9ac..0fb5d65 100644 --- a/transactions/startupCheck.go +++ b/transactions/startupCheck.go @@ -15,13 +15,13 @@ func StartupCheck() errors.ICCError { for _, tx := range txList { txName := tx.Tag for _, c := range tx.Callers { - if len(c) <= 1 { + if len(c.MSP) <= 1 { continue } - if c[0] == '$' { - _, err := regexp.Compile(c[1:]) + if c.MSP[0] == '$' { + _, err := regexp.Compile(c.MSP[1:]) if err != nil { - return errors.WrapErrorWithStatus(err, fmt.Sprintf("invalid caller regular expression %s for tx %s", c, txName), 500) + return errors.WrapErrorWithStatus(err, fmt.Sprintf("invalid caller msp regular expression %s for tx %s", c, txName), 500) } } } diff --git a/transactions/transaction.go b/transactions/transaction.go index 1455524..c36fea0 100644 --- a/transactions/transaction.go +++ b/transactions/transaction.go @@ -1,6 +1,7 @@ package transactions import ( + "github.com/hyperledger-labs/cc-tools/accesscontrol" "github.com/hyperledger-labs/cc-tools/errors" sw "github.com/hyperledger-labs/cc-tools/stubwrapper" ) @@ -11,7 +12,7 @@ type Transaction struct { // Regexp is supported by putting '$' before the MSP regexp e.g. []string{`$org\dMSP`}. // Please note this restriction DOES NOT protect ledger data from being // read by unauthorized organizations, this should be done with Private Data. - Callers []string `json:"callers,omitempty"` + Callers []accesscontrol.Caller `json:"callers,omitempty"` // Tag is how the tx will be called. Tag string `json:"tag"` diff --git a/transactions/txList.go b/transactions/txList.go index f8b1271..0786474 100644 --- a/transactions/txList.go +++ b/transactions/txList.go @@ -1,6 +1,9 @@ package transactions -import "github.com/hyperledger-labs/cc-tools/assets" +import ( + "github.com/hyperledger-labs/cc-tools/accesscontrol" + "github.com/hyperledger-labs/cc-tools/assets" +) var txList = []Transaction{} @@ -45,7 +48,14 @@ func FetchTx(txName string) *Transaction { func InitTxList(l []Transaction) { txList = append(l, basicTxs...) if assets.GetEnabledDynamicAssetType() { - callers := assets.GetAssetAdminsDynamicAssetType() + callersMSP := assets.GetAssetAdminsDynamicAssetType() + var callers []accesscontrol.Caller + for _, msp := range callersMSP { + callers = append(callers, accesscontrol.Caller{ + MSP: msp, + }) + } + for i := range dynamicAssetTypesTxs { if dynamicAssetTypesTxs[i].Tag != "loadAssetTypeList" { dynamicAssetTypesTxs[i].Callers = callers