diff --git a/README.md b/README.md index d3e4ff58..0edf4aeb 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ transfer an asset in a more realistic transfer scenario. | [State-Based Endorsement](asset-transfer-sbe) | This sample demonstrates how to override the chaincode-level endorsement policy to set endorsement policies at the key-level (data/asset level). | [Using State-based endorsement](https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-sbe) | Java, TypeScript | JavaScript | | [Secured agreement](asset-transfer-secured-agreement) | Smart contract that uses implicit private data collections, state-based endorsement, and organization-based ownership and access control to keep data private and securely transfer an asset with the consent of both the current owner and buyer. | [Secured asset transfer](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) | Go | JavaScript | | [Events](asset-transfer-events) | The events sample demonstrates how smart contracts can emit events that are read by the applications interacting with the network. | [README](asset-transfer-events/README.md) | JavaScript, Java | JavaScript | +| [Attribute-based access control](asset-transfer-abac) | Demonstrates the use of attribute and identity based access control using a simple asset transfer scenario | [README](asset-transfer-abac/README.md) | Go | None | + ## Additional samples diff --git a/asset-transfer-abac/README.md b/asset-transfer-abac/README.md new file mode 100644 index 00000000..46350d8c --- /dev/null +++ b/asset-transfer-abac/README.md @@ -0,0 +1,173 @@ +# Attribute based access control + +The `asset-transfer-abac` sample demonstrates the use of Attribute-based access control within the context of a simple asset transfer scenario. The sample also uses authorization based on individual client identities to allow the users that interact with the network to own assets on the blockchain ledger. + +Attribute-Based Access Control (ABAC) refers to the ability to restrict access to certain functionality within a smart contract based on the attributes within a users certificate. For example, you may want certain functions within a smart contract to be used only by application administrators. ABAC allows organizations to provide elevated access to certain users without requiring those users be administrators of the network. For more information, see [Attribute-based access control](https://hyperledger-fabric-ca.readthedocs.io/en/latest/users-guide.html#attribute-based-access-control) in the Fabric CA users guide. + +The `asset-transfer-abac` smart contract allows you to create assets that can be updated or transferred by the asset owner. However, the ability to create or remove assets from the ledger is restricted to identities with the `abac.creator=true` attribute. The identity that creates the asset is assigned as the asset owner. Only the owner can transfer the asset to a new owner or update the asset properties. In the course of the tutorial, we will use the Fabric CA client to create identities with the attribute required to create a new asset. We will then transfer the asset to another identity and demonstrate how the `GetID()` function can be used to enforce asset ownership. We will then use the owner identity to delete the asset. Both Attribute based access control and the `GetID()` function are provided by the [Client Identity Chaincode Library](https://github.com/hyperledger/fabric-chaincode-go/blob/master/pkg/cid/README.md). + +## Start the network and deploy the smart contract + +We can use the Fabric test network to deploy and interact with the `asset-transfer-abac` smart contract. Run the following command to change into the test network directory and bring down any running nodes: +``` +cd fabric-samples/test-network +./network.sh down +``` + +Run the following command to deploy the test network using Certificate Authorities: +``` +./network.sh up createChannel -ca +``` + +You can then use the test network script to deploy the `asset-transfer-abac` smart contract to a channel on the network: +``` +./network.sh deployCC -ccn abac -ccp ../asset-transfer-abac/chaincode-go/ -ccl go +``` + +## Register identities with attributes + +We can use the one of the test network Certificate Authorities to register and enroll identities with the attribute of `abac.creator=true`. First, we need to set the following environment variables in order to use the Fabric CA client. +``` +export PATH=${PWD}/../bin:${PWD}:$PATH +export FABRIC_CFG_PATH=$PWD/../config/ +``` + +We will create the identities using the Org1 CA. Set the Fabric CA client home to the MSP of the Org1 CA admin: +``` +export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/ +``` + +There are two ways to generate certificates with attributes added. We will use both methods and create two identities in the process. The first method is to specify that the attribute be added to the certificate by default when the identity is registered. The following command will register an identity named creator1 with the attribute of `abac.creator=true`. + +``` +fabric-ca-client register --id.name creator1 --id.secret creator1pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:ecert' --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem +``` + +The `ecert` suffix adds the attribute to the certificate automatically when the identity is enrolled. As a result, the following enroll command will contain the attribute that was provided in the registration command. + +``` +fabric-ca-client enroll -u https://creator1:creator1pw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem +``` + +Now that we have enrolled the identity, run the command below to copy the Node OU configuration file into the creator1 MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp/config.yaml +``` + +The second method is to request that the attribute be added upon enrollment. The following command will register an identity named creator2 with the same `abac.creator` attribute. +``` +fabric-ca-client register --id.name creator2 --id.secret creator2pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:' --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem +``` + +The following enroll command will add the attribute to the certificate: + +``` +fabric-ca-client enroll -u https://creator2:creator2pw@localhost:7054 --caname ca-org1 --enrollment.attrs "abac.creator" -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem +``` + +Run the command below to copy the Node OU configuration file into the creator2 MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp/config.yaml +``` + +## Create an asset + +You can use either identity with the `abac.creator=true` attribute to create an asset using the `asset-transfer-abac` smart contract. We will set the following environment variables to use the first identity that was generated, creator1: + +``` +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_LOCALMSPID="Org1MSP" +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp +export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt +export CORE_PEER_ADDRESS=localhost:7051 +export TARGET_TLS_OPTIONS="-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" +``` + +Run the following command to create Asset1: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset1","blue","20","100"]}' +``` + +You can use the command below to query the asset on the ledger: +``` +peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}' +``` +The result will list the creator1 identity as the asset owner. The `GetID()` API reads the name and issuer from the certificate of the identity that submitted the transaction and assigns that identity as the asset owner: +``` +{"ID":"Asset1","color":"blue","size":20,"owner":"x509::CN=creator1,OU=client+OU=org1,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100} +``` + +## Transfer the asset + +As the owner of Asset1, the creator1 identity has the ability to transfer the asset to another owner. In order to transfer the asset, the owner needs to provide the name and issuer of the new owner to the `TransferAsset` function. The `asset-transfer-abac` smart contract has a `GetSubmittingClientIdentity` function that allows users to retrieve their certificate information and provide it to the asset owner out of band (we omit this step). Issue the command below to transfer Asset1 to the user1 identity from Org1 that was created when the test network was deployed: +``` +export RECIPIENT="x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n abac -c '{"function":"TransferAsset","Args":["Asset1","'"$RECIPIENT"'"]}' +``` +Query the ledger to verify that the asset has a new owner: +``` +peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}' +``` +We can see that Asset1 with is now owned by User1: +``` +{"ID":"Asset1","color":"blue","size":20,"owner":"x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100} +``` + +## Update the asset + +Now that the asset has been transferred, the new owner can update the asset properties. The smart contract uses the `GetID()` API to ensure that the update is being submitted by the asset owner. To demonstrate the difference between identity and attribute based access control, lets try to update the asset using the creator1 identity first: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}' +``` + +Even though creator1 can create new assets, the smart contract detects that the transaction was not submitted by the identity that owns the asset, user1. The command returns the following error: +``` +Error: endorsement failure during invoke. response: status:500 message:"submitting client not authorized to update asset, does not own asset" +``` + +Run the following command to operate as the asset owner by setting the MSP path to User1: +``` +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp +``` + +We can now update the asset. Run the following command to change the asset color from blue to green. All other aspects of the asset will remain unchanged. +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}' +``` +Run the query command again to verify that the asset has changed color: +``` +peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}' +``` +The result will display that Asset1 is now green: +``` +{"ID":"Asset1","color":"green","size":20,"owner":"x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US","appraisedValue":100} +``` + +## Delete the asset + +The owner also has the ability to delete the asset. Run the following command to remove Asset1 from the ledger: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n abac -c '{"function":"DeleteAsset","Args":["Asset1"]}' +``` + +If you query the ledger once more, you will see that Asset1 no longer exists: +``` +peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}' +``` + +While we are operating as User1, we can demonstrate attribute based access control by trying to create an asset using an identity without the `abac.creator=true` attribute. Run the following command to try to create Asset1 as User1: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset2","red","20","100"]}' +``` + +The smart contract will return the following error: +``` +Error: endorsement failure during invoke. response: status:500 message:"submitting client not authorized to create asset, does not have abac.creator role" +``` + +## Clean up + +When you are finished, you can run the following command to bring down the test network: +``` +./network.sh down +``` diff --git a/asset-transfer-abac/chaincode-go/go.mod b/asset-transfer-abac/chaincode-go/go.mod new file mode 100644 index 00000000..4144e4cb --- /dev/null +++ b/asset-transfer-abac/chaincode-go/go.mod @@ -0,0 +1,5 @@ +module github.com/hyperledger/fabric-samples/asset-transfer-abac/chaincode-go + +go 1.15 + +require github.com/hyperledger/fabric-contract-api-go v1.1.1 diff --git a/asset-transfer-abac/chaincode-go/go.sum b/asset-transfer-abac/chaincode-go/go.sum new file mode 100644 index 00000000..116a4485 --- /dev/null +++ b/asset-transfer-abac/chaincode-go/go.sum @@ -0,0 +1,138 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= +github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= +github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= +github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= +github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= +github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= +github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc= +github.com/hyperledger/fabric-contract-api-go v1.1.1 h1:gDhOC18gjgElNZ85kFWsbCQq95hyUP/21n++m0Sv6B0= +github.com/hyperledger/fabric-contract-api-go v1.1.1/go.mod h1:+39cWxbh5py3NtXpRA63rAH7NzXyED+QJx1EZr0tJPo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e h1:9PS5iezHk/j7XriSlNuSQILyCOfcZ9wZ3/PiucmSE8E= +github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= +golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= +google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/asset-transfer-abac/chaincode-go/smart-contract/abac.go b/asset-transfer-abac/chaincode-go/smart-contract/abac.go new file mode 100644 index 00000000..746b09ca --- /dev/null +++ b/asset-transfer-abac/chaincode-go/smart-contract/abac.go @@ -0,0 +1,214 @@ +package abac + +import ( + "encoding/base64" + "encoding/json" + "fmt" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +// SmartContract provides functions for managing an Asset +type SmartContract struct { + contractapi.Contract +} + +// Asset describes basic details of what makes up a simple asset +type Asset struct { + ID string `json:"ID"` + Color string `json:"color"` + Size int `json:"size"` + Owner string `json:"owner"` + AppraisedValue int `json:"appraisedValue"` +} + +// CreateAsset issues a new asset to the world state with given details. +func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface, id string, color string, size int, appraisedValue int) error { + + // Demonstrate the use of Attribute-Based Access Control (ABAC) by checking + // to see if the caller has the "abac.creator" attribute with a value of true; + // if not, return an error. + // + err := ctx.GetClientIdentity().AssertAttributeValue("abac.creator", "true") + if err != nil { + return fmt.Errorf("submitting client not authorized to create asset, does not have abac.creator role") + } + + exists, err := s.AssetExists(ctx, id) + if err != nil { + return err + } + if exists { + return fmt.Errorf("the asset %s already exists", id) + } + + // Get ID of submitting client identity + clientID, err := s.GetSubmittingClientIdentity(ctx) + if err != nil { + return err + } + + asset := Asset{ + ID: id, + Color: color, + Size: size, + Owner: clientID, + AppraisedValue: appraisedValue, + } + assetJSON, err := json.Marshal(asset) + if err != nil { + return err + } + + return ctx.GetStub().PutState(id, assetJSON) +} + +// UpdateAsset updates an existing asset in the world state with provided parameters. +func (s *SmartContract) UpdateAsset(ctx contractapi.TransactionContextInterface, id string, newColor string, newSize int, newValue int) error { + + asset, err := s.ReadAsset(ctx, id) + if err != nil { + return err + } + + clientID, err := s.GetSubmittingClientIdentity(ctx) + if err != nil { + return err + } + + if clientID != asset.Owner { + return fmt.Errorf("submitting client not authorized to update asset, does not own asset") + } + + asset.Color = newColor + asset.Size = newSize + asset.AppraisedValue = newValue + + assetJSON, err := json.Marshal(asset) + if err != nil { + return err + } + + return ctx.GetStub().PutState(id, assetJSON) +} + +// DeleteAsset deletes a given asset from the world state. +func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface, id string) error { + + asset, err := s.ReadAsset(ctx, id) + if err != nil { + return err + } + + clientID, err := s.GetSubmittingClientIdentity(ctx) + if err != nil { + return err + } + + if clientID != asset.Owner { + return fmt.Errorf("submitting client not authorized to update asset, does not own asset") + } + + return ctx.GetStub().DelState(id) +} + +// TransferAsset updates the owner field of asset with given id in world state. +func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterface, id string, newOwner string) error { + + asset, err := s.ReadAsset(ctx, id) + if err != nil { + return err + } + + clientID, err := s.GetSubmittingClientIdentity(ctx) + if err != nil { + return err + } + + if clientID != asset.Owner { + return fmt.Errorf("submitting client not authorized to update asset, does not own asset") + } + + asset.Owner = newOwner + assetJSON, err := json.Marshal(asset) + if err != nil { + return err + } + + return ctx.GetStub().PutState(id, assetJSON) +} + +// ReadAsset returns the asset stored in the world state with given id. +func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, id string) (*Asset, error) { + + assetJSON, err := ctx.GetStub().GetState(id) + if err != nil { + return nil, fmt.Errorf("failed to read from world state: %v", err) + } + if assetJSON == nil { + return nil, fmt.Errorf("the asset %s does not exist", id) + } + + var asset Asset + err = json.Unmarshal(assetJSON, &asset) + if err != nil { + return nil, err + } + + return &asset, nil +} + +// GetAllAssets returns all assets found in world state +func (s *SmartContract) GetAllAssets(ctx contractapi.TransactionContextInterface) ([]*Asset, error) { + // range query with empty string for startKey and endKey does an + // open-ended query of all assets in the chaincode namespace. + resultsIterator, err := ctx.GetStub().GetStateByRange("", "") + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + var assets []*Asset + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + + var asset Asset + err = json.Unmarshal(queryResponse.Value, &asset) + if err != nil { + return nil, err + } + assets = append(assets, &asset) + } + + return assets, nil +} + +// AssetExists returns true when asset with given ID exists in world state +func (s *SmartContract) AssetExists(ctx contractapi.TransactionContextInterface, id string) (bool, error) { + + assetJSON, err := ctx.GetStub().GetState(id) + if err != nil { + return false, fmt.Errorf("failed to read from world state: %v", err) + } + + return assetJSON != nil, nil +} + +// GetSubmittingClientIdentity returns the name and issuer of the identity that +// invokes the smart contract. This function base64 decodes the identity string +// before returning the value to the client or smart contract. +func (s *SmartContract) GetSubmittingClientIdentity(ctx contractapi.TransactionContextInterface) (string, error) { + + b64ID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return "", fmt.Errorf("Failed to read clientID: %v", err) + } + decodeID, err := base64.StdEncoding.DecodeString(b64ID) + if err != nil { + return "", fmt.Errorf("failed to base64 decode clientID: %v", err) + } + return string(decodeID), nil +} diff --git a/asset-transfer-abac/chaincode-go/smartContract.go b/asset-transfer-abac/chaincode-go/smartContract.go new file mode 100644 index 00000000..c54c78b7 --- /dev/null +++ b/asset-transfer-abac/chaincode-go/smartContract.go @@ -0,0 +1,23 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +package main + +import ( + "log" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" + abac "github.com/hyperledger/fabric-samples/asset-transfer-abac/chaincode-go/smart-contract" +) + +func main() { + abacSmartContract, err := contractapi.NewChaincode(&abac.SmartContract{}) + if err != nil { + log.Panicf("Error creating abac chaincode: %v", err) + } + + if err := abacSmartContract.Start(); err != nil { + log.Panicf("Error starting abac chaincode: %v", err) + } +} diff --git a/chaincode/README.md b/chaincode/README.md index 889be012..510528c1 100644 --- a/chaincode/README.md +++ b/chaincode/README.md @@ -9,7 +9,6 @@ This folder contains example smart contracts. It is recommended that users start | [fabcar](fabcar) | Basic smart contract that allows you to add and change data on the ledger using the Fabric contract API. Also contains an example on how to run chaincode as an external service. | Go, Java, JavaScript, Typescript | | [marbles02](marbles02) | Sample that demonstrates how to deploy an index and use rich queries when you are using CouchDB as your state database. | Go | | [marbles02_private](marbles02_private) | Sample that demonstrates the use of private data collections. | Go | -| [abac](abac) | Smart contract that restricts access to the chaincode namespace using Attribute Based Access Control. | Go| | [sacc](sacc) | Simple asset chaincode that interacts with the ledger using the low-level APIs provided by the Fabric Chaincode Shim API. | Go | | [abstore](abstore) | Basic smart contract that allows you to transfer data (from A to B) using the Fabric contract API. | Go, Java, JavaScript | @@ -18,4 +17,4 @@ This folder contains example smart contracts. It is recommended that users start Hyperledger Project source code files are made available under the Apache License, Version 2.0 (Apache-2.0), located in the [LICENSE](LICENSE) file. Hyperledger Project documentation files are made available under the Creative -Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/. \ No newline at end of file +Commons Attribution 4.0 International License (CC-BY-4.0), available at http://creativecommons.org/licenses/by/4.0/. diff --git a/chaincode/abac/go/abac.go b/chaincode/abac/go/abac.go deleted file mode 100644 index 2d66cdfa..00000000 --- a/chaincode/abac/go/abac.go +++ /dev/null @@ -1,206 +0,0 @@ -/* -Copyright IBM Corp. 2016 All Rights Reserved. - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ - -package main - -import ( - "fmt" - "strconv" - - "github.com/hyperledger/fabric-chaincode-go/pkg/cid" - "github.com/hyperledger/fabric-chaincode-go/shim" - pb "github.com/hyperledger/fabric-protos-go/peer" -) - -// SimpleChaincode example simple Chaincode implementation -type SimpleChaincode struct { -} - -// Init initializes the chaincode -func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { - - fmt.Println("abac Init") - - // - // Demonstrate the use of Attribute-Based Access Control (ABAC) by checking - // to see if the caller has the "abac.init" attribute with a value of true; - // if not, return an error. - // - err := cid.AssertAttributeValue(stub, "abac.init", "true") - if err != nil { - return shim.Error(err.Error()) - } - - _, args := stub.GetFunctionAndParameters() - var A, B string // Entities - var Aval, Bval int // Asset holdings - - if len(args) != 4 { - return shim.Error("Incorrect number of arguments. Expecting 4") - } - - // Initialize the chaincode - A = args[0] - Aval, err = strconv.Atoi(args[1]) - if err != nil { - return shim.Error("Expecting integer value for asset holding") - } - B = args[2] - Bval, err = strconv.Atoi(args[3]) - if err != nil { - return shim.Error("Expecting integer value for asset holding") - } - fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) - - // Write the state to the ledger - err = stub.PutState(A, []byte(strconv.Itoa(Aval))) - if err != nil { - return shim.Error(err.Error()) - } - - err = stub.PutState(B, []byte(strconv.Itoa(Bval))) - if err != nil { - return shim.Error(err.Error()) - } - - return shim.Success(nil) -} - -func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { - fmt.Println("abac Invoke") - function, args := stub.GetFunctionAndParameters() - if function == "invoke" { - // Make payment of X units from A to B - return t.invoke(stub, args) - } else if function == "delete" { - // Deletes an entity from its state - return t.delete(stub, args) - } else if function == "query" { - // the old "Query" is now implemtned in invoke - return t.query(stub, args) - } - - return shim.Error("Invalid invoke function name. Expecting \"invoke\" \"delete\" \"query\"") -} - -// Transaction makes payment of X units from A to B -func (t *SimpleChaincode) invoke(stub shim.ChaincodeStubInterface, args []string) pb.Response { - var A, B string // Entities - var Aval, Bval int // Asset holdings - var X int // Transaction value - var err error - - if len(args) != 3 { - return shim.Error("Incorrect number of arguments. Expecting 3") - } - - A = args[0] - B = args[1] - - // Get the state from the ledger - // TODO: will be nice to have a GetAllState call to ledger - Avalbytes, err := stub.GetState(A) - if err != nil { - return shim.Error("Failed to get state") - } - if Avalbytes == nil { - return shim.Error("Entity not found") - } - Aval, _ = strconv.Atoi(string(Avalbytes)) - - Bvalbytes, err := stub.GetState(B) - if err != nil { - return shim.Error("Failed to get state") - } - if Bvalbytes == nil { - return shim.Error("Entity not found") - } - Bval, _ = strconv.Atoi(string(Bvalbytes)) - - // Perform the execution - X, err = strconv.Atoi(args[2]) - if err != nil { - return shim.Error("Invalid transaction amount, expecting a integer value") - } - Aval = Aval - X - Bval = Bval + X - fmt.Printf("Aval = %d, Bval = %d\n", Aval, Bval) - - // Write the state back to the ledger - err = stub.PutState(A, []byte(strconv.Itoa(Aval))) - if err != nil { - return shim.Error(err.Error()) - } - - err = stub.PutState(B, []byte(strconv.Itoa(Bval))) - if err != nil { - return shim.Error(err.Error()) - } - - return shim.Success(nil) -} - -// Deletes an entity from state -func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { - if len(args) != 1 { - return shim.Error("Incorrect number of arguments. Expecting 1") - } - - A := args[0] - - // Delete the key from the state in ledger - err := stub.DelState(A) - if err != nil { - return shim.Error("Failed to delete state") - } - - return shim.Success(nil) -} - -// query callback representing the query of a chaincode -func (t *SimpleChaincode) query(stub shim.ChaincodeStubInterface, args []string) pb.Response { - var A string // Entities - var err error - - if len(args) != 1 { - return shim.Error("Incorrect number of arguments. Expecting name of the person to query") - } - - A = args[0] - - // Get the state from the ledger - Avalbytes, err := stub.GetState(A) - if err != nil { - jsonResp := "{\"Error\":\"Failed to get state for " + A + "\"}" - return shim.Error(jsonResp) - } - - if Avalbytes == nil { - jsonResp := "{\"Error\":\"Nil amount for " + A + "\"}" - return shim.Error(jsonResp) - } - - jsonResp := "{\"Name\":\"" + A + "\",\"Amount\":\"" + string(Avalbytes) + "\"}" - fmt.Printf("Query Response:%s\n", jsonResp) - return shim.Success(Avalbytes) -} - -func main() { - err := shim.Start(new(SimpleChaincode)) - if err != nil { - fmt.Printf("Error starting Simple chaincode: %s", err) - } -} diff --git a/chaincode/abac/go/abac_test.go b/chaincode/abac/go/abac_test.go deleted file mode 100644 index 0ea98f6e..00000000 --- a/chaincode/abac/go/abac_test.go +++ /dev/null @@ -1,142 +0,0 @@ -/* -Copyright Hitachi America Ltd. All Rights Reserved. - -SPDX-License-Identifier: Apache-2.0 -*/ - -package main - -import ( - "fmt" - "testing" - - "github.com/gogo/protobuf/proto" - "github.com/hyperledger/fabric-chaincode-go/shim" - "github.com/hyperledger/fabric-chaincode-go/shimtest" - "github.com/hyperledger/fabric-protos-go/msp" -) - -// Cert with attribute. "abac.init":"true" -const certWithAttrs = `-----BEGIN CERTIFICATE----- -MIIC2TCCAn+gAwIBAgIUQ0IZAeWJyRqPFpcFshvpVbY1RzMwCgYIKoZIzj0EAwIw -ZjELMAkGA1UEBhMCVVMxFzAVBgNVBAgTDk5vcnRoIENhcm9saW5hMRQwEgYDVQQK -EwtIeXBlcmxlZGdlcjEPMA0GA1UECxMGY2xpZW50MRcwFQYDVQQDEw5yY2Etb3Jn -MS1hZG1pbjAeFw0xODExMTMxNzQ4MDBaFw0xOTExMTMxNzUzMDBaMG8xCzAJBgNV -BAYTAlVTMRcwFQYDVQQIEw5Ob3J0aCBDYXJvbGluYTEUMBIGA1UEChMLSHlwZXJs -ZWRnZXIxHDANBgNVBAsTBmNsaWVudDALBgNVBAsTBG9yZzExEzARBgNVBAMTCmFk -bWluLW9yZzEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAR196Xv7te+C5gkz7Ui -h8t2gl8QjjSs6iOLFTk18IEH5vLh+DovGT9q3ylvZpExtOap5zFkCva9GnChxP05 -4A0eo4IBADCB/TAOBgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAdBgNVHQ4E -FgQUXf9wjawRl/KosmHcVnYB4ay8IqswHwYDVR0jBBgwFoAUwqQ3h+jBjt2e2wC1 -f1amDdCHY7QwFwYDVR0RBBAwDoIMZjExN2MxODEyYzM3MIGDBggqAwQFBgcIAQR3 -eyJhdHRycyI6eyJhYmFjLmluaXQiOiJ0cnVlIiwiYWRtaW4iOiJ0cnVlIiwiaGYu -QWZmaWxpYXRpb24iOiJvcmcxIiwiaGYuRW5yb2xsbWVudElEIjoiYWRtaW4tb3Jn -MSIsImhmLlR5cGUiOiJjbGllbnQifX0wCgYIKoZIzj0EAwIDSAAwRQIhAN1v/XK0 -WmZf5u9X9FG5uGxwcJ9d5K/eFAC7KahSbs65AiB/GzS2u1cYznXzTDWoBm9oflxY -w8Ou1Sh9IjeXj/SDAA== ------END CERTIFICATE----- -` - -func checkInit(t *testing.T, stub *shimtest.MockStub, args [][]byte) { - res := stub.MockInit("1", args) - if res.Status != shim.OK { - fmt.Println("Init failed", string(res.Message)) - t.FailNow() - } -} - -func checkState(t *testing.T, stub *shimtest.MockStub, name string, value string) { - bytes := stub.State[name] - if bytes == nil { - fmt.Println("State", name, "failed to get value") - t.FailNow() - } - if string(bytes) != value { - fmt.Println("State value", name, "was not", value, "as expected") - t.FailNow() - } -} - -func checkQuery(t *testing.T, stub *shimtest.MockStub, name string, value string) { - res := stub.MockInvoke("1", [][]byte{[]byte("query"), []byte(name)}) - if res.Status != shim.OK { - fmt.Println("Query", name, "failed", string(res.Message)) - t.FailNow() - } - if res.Payload == nil { - fmt.Println("Query", name, "failed to get value") - t.FailNow() - } - if string(res.Payload) != value { - fmt.Println("Query value", name, "was not", value, "as expected") - t.FailNow() - } -} - -func checkInvoke(t *testing.T, stub *shimtest.MockStub, args [][]byte) { - res := stub.MockInvoke("1", args) - if res.Status != shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.FailNow() - } -} - -func setCreator(t *testing.T, stub *shimtest.MockStub, mspID string, idbytes []byte) { - sid := &msp.SerializedIdentity{Mspid: mspID, IdBytes: idbytes} - b, err := proto.Marshal(sid) - if err != nil { - t.FailNow() - } - stub.Creator = b -} - -func TestAbac_Init(t *testing.T) { - scc := new(SimpleChaincode) - stub := shimtest.NewMockStub("abac", scc) - - setCreator(t, stub, "org1MSP", []byte(certWithAttrs)) - - // Init A=123 B=234 - checkInit(t, stub, [][]byte{[]byte("init"), []byte("A"), []byte("123"), []byte("B"), []byte("234")}) - - checkState(t, stub, "A", "123") - checkState(t, stub, "B", "234") -} - -func TestAbac_Query(t *testing.T) { - scc := new(SimpleChaincode) - stub := shimtest.NewMockStub("abac", scc) - - setCreator(t, stub, "org1MSP", []byte(certWithAttrs)) - - // Init A=345 B=456 - checkInit(t, stub, [][]byte{[]byte("init"), []byte("A"), []byte("345"), []byte("B"), []byte("456")}) - - // Query A - checkQuery(t, stub, "A", "345") - - // Query B - checkQuery(t, stub, "B", "456") -} - -func TestAbac_Invoke(t *testing.T) { - scc := new(SimpleChaincode) - stub := shimtest.NewMockStub("abac", scc) - - setCreator(t, stub, "org1MSP", []byte(certWithAttrs)) - - // Init A=567 B=678 - checkInit(t, stub, [][]byte{[]byte("init"), []byte("A"), []byte("567"), []byte("B"), []byte("678")}) - - // Invoke A->B for 123 - checkInvoke(t, stub, [][]byte{[]byte("invoke"), []byte("A"), []byte("B"), []byte("123")}) - checkQuery(t, stub, "A", "444") - checkQuery(t, stub, "B", "801") - - // Invoke B->A for 234 - checkInvoke(t, stub, [][]byte{[]byte("invoke"), []byte("B"), []byte("A"), []byte("234")}) - checkQuery(t, stub, "A", "678") - checkQuery(t, stub, "B", "567") - checkQuery(t, stub, "A", "678") - checkQuery(t, stub, "B", "567") -} diff --git a/chaincode/abac/go/go.mod b/chaincode/abac/go/go.mod deleted file mode 100644 index c04ae304..00000000 --- a/chaincode/abac/go/go.mod +++ /dev/null @@ -1,13 +0,0 @@ -module github.com/hyperledger/fabric-samples/chaincode/abac/go - -go 1.12 - -require ( - github.com/gogo/protobuf v1.2.1 - github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 - github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 - golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 // indirect - golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a // indirect - golang.org/x/text v0.3.2 // indirect - google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 // indirect -) diff --git a/chaincode/abac/go/go.sum b/chaincode/abac/go/go.sum deleted file mode 100644 index da5b5719..00000000 --- a/chaincode/abac/go/go.sum +++ /dev/null @@ -1,82 +0,0 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 h1:VEm3tPRTCzq3J/1XpVERh1PbOSnshUVwx2G5s3cLiTw= -github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85/go.mod h1:HZK6PKLWrvdD/t0oSLiyaRaUM6fZ7qjJuOlb0zrn0mo= -github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 h1:WzttYAPO5xkQ87ZrxzEhvDZknfarSNu1PZt3NPMTE3Y= -github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7 h1:fHDIZ2oxGnUZRN6WgWFCbYBjH9uqVPRCUVUDhs0wnbA= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= -google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=