Skip to content

Commit

Permalink
Merge pull request #47 from hyperledger-labs/develop
Browse files Browse the repository at this point in the history
CC-Tools 1.0
  • Loading branch information
samuelvenzi authored Jun 28, 2024
2 parents 0bb45a7 + a32bd38 commit f6e887b
Show file tree
Hide file tree
Showing 42 changed files with 736 additions and 235 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v2
with:
go-version: 1.16
go-version: 1.21

- name: Build
run: go build -v ./...
Expand Down
1 change: 1 addition & 0 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* @hyperledger-labs/cc-tools-committers
18 changes: 18 additions & 0 deletions MANTAINERS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Maintainers

### **Active Maintainers**

| Nome | Github |
|:-------|:--------|
| André Macedo | [andremacedopv](https://github.com/andremacedopv) |
| Samuel Venzi | [samuelvenzi](https://github.com/samuelvenzi) |
| Lucas Campelo | [lucas-campelo](https://github.com/lucas-campelo) |
| Marcos Sarres | [goledger](https://github.com/goledger) |


### **Retired Maintainers**
| Nome | Github |
|:-------|:--------|
| Bruno Andreghetti | [bandreghetti](https://github.com/bandreghetti) |
| João Pedro | [JoaoPedroAssis](https://github.com/JoaoPedroAssis) |
| Arthur Paiva | [ArthurPaivaT](https://github.com/ArthurPaivaT) |
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
[![Go Report Card](https://goreportcard.com/badge/github.com/hyperledger-labs/cc-tools)](https://goreportcard.com/report/github.com/hyperledger-labs/cc-tools)
[![GoDoc](https://godoc.org/github.com/hyperledger-labs/cc-tools?status.svg)](https://godoc.org/github.com/hyperledger-labs/cc-tools)

This project is a GoLedger open-source project aimed at providing tools for Hyperledger Fabric chaincode development in Golang. This might have breaking changes before we arrive at release v1.0.0.
This project is a GoLedger open-source project aimed at providing tools for Hyperledger Fabric chaincode development in Golang.

## Getting Started

Make sure you visit the repository [hyperledger-labs/cc-tools-demo](https://github.com/hyperledger-labs/cc-tools-demo), which is a template of a functional chaincode that uses cc-tools and provides ready-to-use scripts to deploy development networks. This is our preferred way of working, but you can feel free to import the package and assemble the chaincode as you choose.

CC Tools has been tested with Hyperledger Fabric 1.x and 2.x realeases.
CC Tools has been tested with Hyperledger Fabric v2.2, v2.4 and v2.5 releases.

## Features
- Standard asset data mapping (and their properties)
Expand Down
89 changes: 89 additions & 0 deletions accesscontrol/allowCaller.go
Original file line number Diff line number Diff line change
@@ -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
}
7 changes: 7 additions & 0 deletions accesscontrol/caller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package accesscontrol

type Caller struct {
MSP string `json:"msp"`
OU string `json:"ou"`
Attributes map[string]string `json:"attributes"`
}
10 changes: 10 additions & 0 deletions assets/asset.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ func (a Asset) IsPrivate() bool {
return assetTypeDef.IsPrivate()
}

func (a Asset) CollectionName() string {
// Fetch asset properties
assetTypeDef := a.Type()
if assetTypeDef == nil {
return ""
}

return assetTypeDef.CollectionName()
}

// TypeTag returns the @assetType attribute.
func (a Asset) TypeTag() string {
assetType, _ := a["@assetType"].(string)
Expand Down
1 change: 1 addition & 0 deletions assets/assetProp.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type AssetProp struct {
// Primary types: "string", "number", "integer", "boolean", "datetime"
// Special types:
// -><assetType>: the specific asset type key (reference) as defined by <assetType> in the assets packages
// ->@asset: an arbitrary asset type key (reference)
// []<type>: an array of elements specified by <type> as any of the above valid types
DataType string `json:"dataType"`

Expand Down
16 changes: 16 additions & 0 deletions assets/assetType.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ type AssetType struct {

// Dynamic is a flag that indicates if the asset type is dynamic.
Dynamic bool `json:"dynamic,omitempty"`

// Private collection name it belongs to. When empty and len(readers) > 0,
// Tag is considered instead
Collection string `json:"collection,omitempty"`
}

// Keys returns a list of asset properties which are defined as primary keys. (IsKey == true)
Expand All @@ -48,6 +52,9 @@ func (t AssetType) SubAssets() (subAssets []AssetProp) {
dataType := prop.DataType
dataType = strings.TrimPrefix(dataType, "[]")
dataType = strings.TrimPrefix(dataType, "->")
if dataType == "@asset" {
subAssets = append(subAssets, prop)
}
subAssetType := FetchAssetType(dataType)
if subAssetType != nil {
subAssets = append(subAssets, prop)
Expand Down Expand Up @@ -81,6 +88,15 @@ func (t AssetType) IsPrivate() bool {
return len(t.Readers) > 0
}

// CollectionName returns the private collection name. Default is tag.
func (t AssetType) CollectionName() string {
if t.Collection == "" {
return t.Tag
}

return t.Collection
}

// ToMap returns a map representation of the asset type.
func (t AssetType) ToMap() map[string]interface{} {
return map[string]interface{}{
Expand Down
51 changes: 51 additions & 0 deletions assets/dataType.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math"
"net/http"
"strconv"
"strings"
"time"

"github.com/hyperledger-labs/cc-tools/errors"
Expand All @@ -29,6 +30,11 @@ type DataType struct {

// CustomDataTypes allows cc developer to inject custom primitive data types
func CustomDataTypes(m map[string]DataType) error {
// Avoid initialization cycle
if FetchAssetType("->@asset") == nil {
dataTypeMap["->@asset"] = &assetDatatype
}

for k, v := range m {
if v.Parse == nil {
return errors.NewCCError(fmt.Sprintf("invalid custom data type '%s': nil Parse function", k), 500)
Expand Down Expand Up @@ -192,3 +198,48 @@ var dataTypeMap = map[string]*DataType{
},
},
}

var assetDatatype = DataType{
AcceptedFormats: []string{"->@asset"},
Parse: func(data interface{}) (string, interface{}, errors.ICCError) {
dataVal, ok := data.(map[string]interface{})
if !ok {
switch v := data.(type) {
case []byte:
err := json.Unmarshal(v, &dataVal)
if err != nil {
return "", nil, errors.WrapErrorWithStatus(err, "failed to unmarshal []byte into map[string]interface{}", http.StatusBadRequest)
}
case string:
err := json.Unmarshal([]byte(v), &dataVal)
if err != nil {
return "", nil, errors.WrapErrorWithStatus(err, "failed to unmarshal string into map[string]interface{}", http.StatusBadRequest)
}
default:
return "", nil, errors.NewCCError(fmt.Sprintf("asset property must be either a byte array or a string, but received type is: %T", data), http.StatusBadRequest)
}
}

key, er := GenerateKey(dataVal)
if er != nil {
return "", nil, errors.WrapError(er, "failed to generate key")
}
dataVal["@key"] = key

assetType, ok := dataVal["@assetType"].(string)
if ok {
if !strings.Contains(key, assetType) {
return "", nil, errors.NewCCError(fmt.Sprintf("asset type '%s' doesnt match key '%s'", assetType, key), http.StatusBadRequest)
}
} else {
dataVal["@assetType"] = key[:strings.IndexByte(key, ':')]
}

retVal, err := json.Marshal(dataVal)
if err != nil {
return "", nil, errors.WrapErrorWithStatus(err, "failed to marshal return value", http.StatusInternalServerError)
}

return string(retVal), dataVal, nil
},
}
2 changes: 1 addition & 1 deletion assets/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func (a *Asset) delete(stub *sw.StubWrapper) ([]byte, errors.ICCError) {
return nil, errors.WrapError(err, "failed to marshal asset")
}
} else {
err = stub.DelPrivateData(a.TypeTag(), a.Key())
err = stub.DelPrivateData(a.CollectionName(), a.Key())
if err != nil {
return nil, errors.WrapError(err, "failed to delete state from private collection")
}
Expand Down
3 changes: 3 additions & 0 deletions assets/dynamicAssetTypeFuncs.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,9 @@ func CheckDataType(dataType string, newTypesList []interface{}) errors.ICCError

if strings.HasPrefix(trimDataType, "->") {
trimDataType = strings.TrimPrefix(trimDataType, "->")
if trimDataType == "@asset" {
return nil
}

assetType := FetchAssetType(trimDataType)
if assetType == nil {
Expand Down
20 changes: 10 additions & 10 deletions assets/existsInLedger.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import (
sw "github.com/hyperledger-labs/cc-tools/stubwrapper"
)

func existsInLedger(stub *sw.StubWrapper, isPrivate bool, typeTag, key string) (bool, errors.ICCError) {
func existsInLedger(stub *sw.StubWrapper, isPrivate bool, collection, key string) (bool, errors.ICCError) {
var assetBytes []byte
var err error
if isPrivate {
_, isMock := stub.Stub.(*mock.MockStub)
if isMock {
assetBytes, err = stub.GetPrivateData(typeTag, key)
assetBytes, err = stub.GetPrivateData(collection, key)
} else {
assetBytes, err = stub.GetPrivateDataHash(typeTag, key)
assetBytes, err = stub.GetPrivateDataHash(collection, key)
}
} else {
assetBytes, err = stub.GetState(key)
Expand All @@ -34,28 +34,28 @@ func (a *Asset) ExistsInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
if a.Key() == "" {
return false, errors.NewCCError("asset key is empty", 500)
}
return existsInLedger(stub, a.IsPrivate(), a.TypeTag(), a.Key())
return existsInLedger(stub, a.IsPrivate(), a.CollectionName(), a.Key())
}

// ExistsInLedger checks if asset referenced by a key object currently has a state.
func (k *Key) ExistsInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
if k.Key() == "" {
return false, errors.NewCCError("key is empty", 500)
}
return existsInLedger(stub, k.IsPrivate(), k.TypeTag(), k.Key())
return existsInLedger(stub, k.IsPrivate(), k.CollectionName(), k.Key())
}

// ----------------------------------------

func committedInLedger(stub *sw.StubWrapper, isPrivate bool, typeTag, key string) (bool, errors.ICCError) {
func committedInLedger(stub *sw.StubWrapper, isPrivate bool, collection, key string) (bool, errors.ICCError) {
var assetBytes []byte
var err error
if isPrivate {
_, isMock := stub.Stub.(*mock.MockStub)
if isMock {
assetBytes, err = stub.Stub.GetPrivateData(typeTag, key)
assetBytes, err = stub.Stub.GetPrivateData(collection, key)
} else {
assetBytes, err = stub.Stub.GetPrivateDataHash(typeTag, key)
assetBytes, err = stub.Stub.GetPrivateDataHash(collection, key)
}
} else {
assetBytes, err = stub.Stub.GetState(key)
Expand All @@ -75,13 +75,13 @@ func (a *Asset) CommittedInLedger(stub *sw.StubWrapper) (bool, errors.ICCError)
if a.Key() == "" {
return false, errors.NewCCError("asset key is empty", 500)
}
return committedInLedger(stub, a.IsPrivate(), a.TypeTag(), a.Key())
return committedInLedger(stub, a.IsPrivate(), a.CollectionName(), a.Key())
}

// CommittedInLedger checks if asset referenced by a key object currently has a state committed in ledger.
func (k *Key) CommittedInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
if k.Key() == "" {
return false, errors.NewCCError("key is empty", 500)
}
return committedInLedger(stub, k.IsPrivate(), k.TypeTag(), k.Key())
return committedInLedger(stub, k.IsPrivate(), k.CollectionName(), k.Key())
}
13 changes: 8 additions & 5 deletions assets/generateKey.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ func GenerateKey(asset map[string]interface{}) (string, errors.ICCError) {
keySeed += seed
} else {
// If key is a subAsset, generate subAsset's key to append to seed
assetTypeDef := FetchAssetType(dataTypeName)
if assetTypeDef == nil {
return "", errors.NewCCError(fmt.Sprintf("internal error: invalid sub asset type %s", prop.DataType), 500)
}

var propMap map[string]interface{}
switch t := propInterface.(type) {
case map[string]interface{}:
Expand All @@ -108,6 +103,14 @@ func GenerateKey(asset map[string]interface{}) (string, errors.ICCError) {
return "", errors.NewCCError(errMsg, 400)
}

if dataTypeName == "@asset" {
dataTypeName = propMap["@assetType"].(string)
}
assetTypeDef := FetchAssetType(dataTypeName)
if assetTypeDef == nil {
return "", errors.NewCCError(fmt.Sprintf("internal error: invalid sub asset type %s", prop.DataType), 500)
}

propMap["@assetType"] = dataTypeName
subAssetKey, err := GenerateKey(propMap)
if err != nil {
Expand Down
Loading

0 comments on commit f6e887b

Please sign in to comment.