Skip to content

Commit f6e887b

Browse files
authored
Merge pull request #47 from hyperledger-labs/develop
CC-Tools 1.0
2 parents 0bb45a7 + a32bd38 commit f6e887b

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+736
-235
lines changed

.github/workflows/go.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- name: Set up Go
1717
uses: actions/setup-go@v2
1818
with:
19-
go-version: 1.16
19+
go-version: 1.21
2020

2121
- name: Build
2222
run: go build -v ./...

CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
* @hyperledger-labs/cc-tools-committers

MANTAINERS.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Maintainers
2+
3+
### **Active Maintainers**
4+
5+
| Nome | Github |
6+
|:-------|:--------|
7+
| André Macedo | [andremacedopv](https://github.com/andremacedopv) |
8+
| Samuel Venzi | [samuelvenzi](https://github.com/samuelvenzi) |
9+
| Lucas Campelo | [lucas-campelo](https://github.com/lucas-campelo) |
10+
| Marcos Sarres | [goledger](https://github.com/goledger) |
11+
12+
13+
### **Retired Maintainers**
14+
| Nome | Github |
15+
|:-------|:--------|
16+
| Bruno Andreghetti | [bandreghetti](https://github.com/bandreghetti) |
17+
| João Pedro | [JoaoPedroAssis](https://github.com/JoaoPedroAssis) |
18+
| Arthur Paiva | [ArthurPaivaT](https://github.com/ArthurPaivaT) |

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,13 @@
33
[![Go Report Card](https://goreportcard.com/badge/github.com/hyperledger-labs/cc-tools)](https://goreportcard.com/report/github.com/hyperledger-labs/cc-tools)
44
[![GoDoc](https://godoc.org/github.com/hyperledger-labs/cc-tools?status.svg)](https://godoc.org/github.com/hyperledger-labs/cc-tools)
55

6-
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.
6+
This project is a GoLedger open-source project aimed at providing tools for Hyperledger Fabric chaincode development in Golang.
77

88
## Getting Started
99

1010
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.
1111

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

1414
## Features
1515
- Standard asset data mapping (and their properties)

accesscontrol/allowCaller.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package accesscontrol
2+
3+
import (
4+
"regexp"
5+
6+
"github.com/hyperledger-labs/cc-tools/errors"
7+
"github.com/hyperledger/fabric-chaincode-go/pkg/cid"
8+
"github.com/hyperledger/fabric-chaincode-go/shim"
9+
)
10+
11+
func AllowCaller(stub shim.ChaincodeStubInterface, allowedCallers []Caller) (bool, error) {
12+
if allowedCallers == nil {
13+
return true, nil
14+
}
15+
16+
callerMSP, err := cid.GetMSPID(stub)
17+
if err != nil {
18+
return false, errors.WrapError(err, "could not get MSP id")
19+
}
20+
21+
var grantedPermission bool
22+
for i := 0; i < len(allowedCallers) && !grantedPermission; i++ {
23+
allowed := allowedCallers[i]
24+
isAllowedMSP, err := checkMSP(callerMSP, allowed.MSP)
25+
if err != nil {
26+
return false, errors.WrapError(err, "failed to check MSP")
27+
}
28+
29+
isAllowedOU, err := checkOU(stub, allowed.OU)
30+
if err != nil {
31+
return false, errors.WrapError(err, "failed to check OU")
32+
}
33+
34+
isAllowedAttrs, err := checkAttributes(stub, allowed.Attributes)
35+
if err != nil {
36+
return false, errors.WrapError(err, "failed to check attributes")
37+
}
38+
39+
grantedPermission = isAllowedMSP && isAllowedOU && isAllowedAttrs
40+
}
41+
42+
return grantedPermission, nil
43+
}
44+
45+
func checkMSP(callerMsp, allowedMSP string) (bool, error) {
46+
if len(allowedMSP) <= 1 {
47+
return true, nil
48+
}
49+
50+
// if caller is regexp
51+
if allowedMSP[0] == '$' {
52+
match, err := regexp.MatchString(allowedMSP[1:], callerMsp)
53+
if err != nil {
54+
return false, errors.NewCCError("failed to check if caller matches regexp", 500)
55+
}
56+
57+
return match, nil
58+
}
59+
60+
// if caller is not regexss
61+
return callerMsp == allowedMSP, nil
62+
}
63+
64+
func checkOU(stub shim.ChaincodeStubInterface, allowedOU string) (bool, error) {
65+
if allowedOU == "" {
66+
return true, nil
67+
}
68+
69+
return cid.HasOUValue(stub, allowedOU)
70+
}
71+
72+
func checkAttributes(stub shim.ChaincodeStubInterface, allowedAttrs map[string]string) (bool, error) {
73+
if allowedAttrs == nil {
74+
return true, nil
75+
}
76+
77+
for key, value := range allowedAttrs {
78+
callerValue, _, err := cid.GetAttributeValue(stub, key)
79+
if err != nil {
80+
return false, err
81+
}
82+
83+
if callerValue != value {
84+
return false, nil
85+
}
86+
}
87+
88+
return true, nil
89+
}

accesscontrol/caller.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package accesscontrol
2+
3+
type Caller struct {
4+
MSP string `json:"msp"`
5+
OU string `json:"ou"`
6+
Attributes map[string]string `json:"attributes"`
7+
}

assets/asset.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@ func (a Asset) IsPrivate() bool {
106106
return assetTypeDef.IsPrivate()
107107
}
108108

109+
func (a Asset) CollectionName() string {
110+
// Fetch asset properties
111+
assetTypeDef := a.Type()
112+
if assetTypeDef == nil {
113+
return ""
114+
}
115+
116+
return assetTypeDef.CollectionName()
117+
}
118+
109119
// TypeTag returns the @assetType attribute.
110120
func (a Asset) TypeTag() string {
111121
assetType, _ := a["@assetType"].(string)

assets/assetProp.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ type AssetProp struct {
2929
// Primary types: "string", "number", "integer", "boolean", "datetime"
3030
// Special types:
3131
// -><assetType>: the specific asset type key (reference) as defined by <assetType> in the assets packages
32+
// ->@asset: an arbitrary asset type key (reference)
3233
// []<type>: an array of elements specified by <type> as any of the above valid types
3334
DataType string `json:"dataType"`
3435

assets/assetType.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ type AssetType struct {
3030

3131
// Dynamic is a flag that indicates if the asset type is dynamic.
3232
Dynamic bool `json:"dynamic,omitempty"`
33+
34+
// Private collection name it belongs to. When empty and len(readers) > 0,
35+
// Tag is considered instead
36+
Collection string `json:"collection,omitempty"`
3337
}
3438

3539
// Keys returns a list of asset properties which are defined as primary keys. (IsKey == true)
@@ -48,6 +52,9 @@ func (t AssetType) SubAssets() (subAssets []AssetProp) {
4852
dataType := prop.DataType
4953
dataType = strings.TrimPrefix(dataType, "[]")
5054
dataType = strings.TrimPrefix(dataType, "->")
55+
if dataType == "@asset" {
56+
subAssets = append(subAssets, prop)
57+
}
5158
subAssetType := FetchAssetType(dataType)
5259
if subAssetType != nil {
5360
subAssets = append(subAssets, prop)
@@ -81,6 +88,15 @@ func (t AssetType) IsPrivate() bool {
8188
return len(t.Readers) > 0
8289
}
8390

91+
// CollectionName returns the private collection name. Default is tag.
92+
func (t AssetType) CollectionName() string {
93+
if t.Collection == "" {
94+
return t.Tag
95+
}
96+
97+
return t.Collection
98+
}
99+
84100
// ToMap returns a map representation of the asset type.
85101
func (t AssetType) ToMap() map[string]interface{} {
86102
return map[string]interface{}{

assets/dataType.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"math"
77
"net/http"
88
"strconv"
9+
"strings"
910
"time"
1011

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

3031
// CustomDataTypes allows cc developer to inject custom primitive data types
3132
func CustomDataTypes(m map[string]DataType) error {
33+
// Avoid initialization cycle
34+
if FetchAssetType("->@asset") == nil {
35+
dataTypeMap["->@asset"] = &assetDatatype
36+
}
37+
3238
for k, v := range m {
3339
if v.Parse == nil {
3440
return errors.NewCCError(fmt.Sprintf("invalid custom data type '%s': nil Parse function", k), 500)
@@ -192,3 +198,48 @@ var dataTypeMap = map[string]*DataType{
192198
},
193199
},
194200
}
201+
202+
var assetDatatype = DataType{
203+
AcceptedFormats: []string{"->@asset"},
204+
Parse: func(data interface{}) (string, interface{}, errors.ICCError) {
205+
dataVal, ok := data.(map[string]interface{})
206+
if !ok {
207+
switch v := data.(type) {
208+
case []byte:
209+
err := json.Unmarshal(v, &dataVal)
210+
if err != nil {
211+
return "", nil, errors.WrapErrorWithStatus(err, "failed to unmarshal []byte into map[string]interface{}", http.StatusBadRequest)
212+
}
213+
case string:
214+
err := json.Unmarshal([]byte(v), &dataVal)
215+
if err != nil {
216+
return "", nil, errors.WrapErrorWithStatus(err, "failed to unmarshal string into map[string]interface{}", http.StatusBadRequest)
217+
}
218+
default:
219+
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)
220+
}
221+
}
222+
223+
key, er := GenerateKey(dataVal)
224+
if er != nil {
225+
return "", nil, errors.WrapError(er, "failed to generate key")
226+
}
227+
dataVal["@key"] = key
228+
229+
assetType, ok := dataVal["@assetType"].(string)
230+
if ok {
231+
if !strings.Contains(key, assetType) {
232+
return "", nil, errors.NewCCError(fmt.Sprintf("asset type '%s' doesnt match key '%s'", assetType, key), http.StatusBadRequest)
233+
}
234+
} else {
235+
dataVal["@assetType"] = key[:strings.IndexByte(key, ':')]
236+
}
237+
238+
retVal, err := json.Marshal(dataVal)
239+
if err != nil {
240+
return "", nil, errors.WrapErrorWithStatus(err, "failed to marshal return value", http.StatusInternalServerError)
241+
}
242+
243+
return string(retVal), dataVal, nil
244+
},
245+
}

assets/delete.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func (a *Asset) delete(stub *sw.StubWrapper) ([]byte, errors.ICCError) {
3434
return nil, errors.WrapError(err, "failed to marshal asset")
3535
}
3636
} else {
37-
err = stub.DelPrivateData(a.TypeTag(), a.Key())
37+
err = stub.DelPrivateData(a.CollectionName(), a.Key())
3838
if err != nil {
3939
return nil, errors.WrapError(err, "failed to delete state from private collection")
4040
}

assets/dynamicAssetTypeFuncs.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ func CheckDataType(dataType string, newTypesList []interface{}) errors.ICCError
163163

164164
if strings.HasPrefix(trimDataType, "->") {
165165
trimDataType = strings.TrimPrefix(trimDataType, "->")
166+
if trimDataType == "@asset" {
167+
return nil
168+
}
166169

167170
assetType := FetchAssetType(trimDataType)
168171
if assetType == nil {

assets/existsInLedger.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ import (
66
sw "github.com/hyperledger-labs/cc-tools/stubwrapper"
77
)
88

9-
func existsInLedger(stub *sw.StubWrapper, isPrivate bool, typeTag, key string) (bool, errors.ICCError) {
9+
func existsInLedger(stub *sw.StubWrapper, isPrivate bool, collection, key string) (bool, errors.ICCError) {
1010
var assetBytes []byte
1111
var err error
1212
if isPrivate {
1313
_, isMock := stub.Stub.(*mock.MockStub)
1414
if isMock {
15-
assetBytes, err = stub.GetPrivateData(typeTag, key)
15+
assetBytes, err = stub.GetPrivateData(collection, key)
1616
} else {
17-
assetBytes, err = stub.GetPrivateDataHash(typeTag, key)
17+
assetBytes, err = stub.GetPrivateDataHash(collection, key)
1818
}
1919
} else {
2020
assetBytes, err = stub.GetState(key)
@@ -34,28 +34,28 @@ func (a *Asset) ExistsInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
3434
if a.Key() == "" {
3535
return false, errors.NewCCError("asset key is empty", 500)
3636
}
37-
return existsInLedger(stub, a.IsPrivate(), a.TypeTag(), a.Key())
37+
return existsInLedger(stub, a.IsPrivate(), a.CollectionName(), a.Key())
3838
}
3939

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

4848
// ----------------------------------------
4949

50-
func committedInLedger(stub *sw.StubWrapper, isPrivate bool, typeTag, key string) (bool, errors.ICCError) {
50+
func committedInLedger(stub *sw.StubWrapper, isPrivate bool, collection, key string) (bool, errors.ICCError) {
5151
var assetBytes []byte
5252
var err error
5353
if isPrivate {
5454
_, isMock := stub.Stub.(*mock.MockStub)
5555
if isMock {
56-
assetBytes, err = stub.Stub.GetPrivateData(typeTag, key)
56+
assetBytes, err = stub.Stub.GetPrivateData(collection, key)
5757
} else {
58-
assetBytes, err = stub.Stub.GetPrivateDataHash(typeTag, key)
58+
assetBytes, err = stub.Stub.GetPrivateDataHash(collection, key)
5959
}
6060
} else {
6161
assetBytes, err = stub.Stub.GetState(key)
@@ -75,13 +75,13 @@ func (a *Asset) CommittedInLedger(stub *sw.StubWrapper) (bool, errors.ICCError)
7575
if a.Key() == "" {
7676
return false, errors.NewCCError("asset key is empty", 500)
7777
}
78-
return committedInLedger(stub, a.IsPrivate(), a.TypeTag(), a.Key())
78+
return committedInLedger(stub, a.IsPrivate(), a.CollectionName(), a.Key())
7979
}
8080

8181
// CommittedInLedger checks if asset referenced by a key object currently has a state committed in ledger.
8282
func (k *Key) CommittedInLedger(stub *sw.StubWrapper) (bool, errors.ICCError) {
8383
if k.Key() == "" {
8484
return false, errors.NewCCError("key is empty", 500)
8585
}
86-
return committedInLedger(stub, k.IsPrivate(), k.TypeTag(), k.Key())
86+
return committedInLedger(stub, k.IsPrivate(), k.CollectionName(), k.Key())
8787
}

assets/generateKey.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,6 @@ func GenerateKey(asset map[string]interface{}) (string, errors.ICCError) {
9090
keySeed += seed
9191
} else {
9292
// If key is a subAsset, generate subAsset's key to append to seed
93-
assetTypeDef := FetchAssetType(dataTypeName)
94-
if assetTypeDef == nil {
95-
return "", errors.NewCCError(fmt.Sprintf("internal error: invalid sub asset type %s", prop.DataType), 500)
96-
}
97-
9893
var propMap map[string]interface{}
9994
switch t := propInterface.(type) {
10095
case map[string]interface{}:
@@ -108,6 +103,14 @@ func GenerateKey(asset map[string]interface{}) (string, errors.ICCError) {
108103
return "", errors.NewCCError(errMsg, 400)
109104
}
110105

106+
if dataTypeName == "@asset" {
107+
dataTypeName = propMap["@assetType"].(string)
108+
}
109+
assetTypeDef := FetchAssetType(dataTypeName)
110+
if assetTypeDef == nil {
111+
return "", errors.NewCCError(fmt.Sprintf("internal error: invalid sub asset type %s", prop.DataType), 500)
112+
}
113+
111114
propMap["@assetType"] = dataTypeName
112115
subAssetKey, err := GenerateKey(propMap)
113116
if err != nil {

0 commit comments

Comments
 (0)