diff --git a/pkg/chaincode/signaturepolicy.go b/pkg/chaincode/signaturepolicy.go index 58a2592..e4a905b 100644 --- a/pkg/chaincode/signaturepolicy.go +++ b/pkg/chaincode/signaturepolicy.go @@ -6,6 +6,7 @@ SPDX-License-Identifier: Apache-2.0 package chaincode import ( + "bytes" "fmt" "reflect" "regexp" @@ -391,3 +392,68 @@ func signaturePolicyEnvelopeFromString(policy string) (*cb.SignaturePolicyEnvelo return p, nil } + +// SignaturePolicyEnvelopeToString parse a SignaturePolicyEnvelope to human readable expression +// the returned expression is GATE(P[, P]) +// +// where: +// - GATE is either "and" or "or" or "outof" +// - P is either a principal or another nested call to GATE +// +// A principal is defined as: +// +// # ORG.ROLE +// +// where: +// - ORG is a string (representing the MSP identifier) +// - ROLE takes the value of any of the RoleXXX constants representing +// the required role +func SignaturePolicyEnvelopeToString(policy *cb.SignaturePolicyEnvelope) (string, error) { + ids := []string{} + for _, id := range policy.GetIdentities() { + var mspRole mb.MSPRole + if err := proto.Unmarshal(id.GetPrincipal(), &mspRole); err != nil { + return "", err + } + + mspID := mspRole.GetMspIdentifier() + "." + strings.ToLower(mb.MSPRole_MSPRoleType_name[int32(mspRole.GetRole())]) + ids = append(ids, mspID) + } + + var buf bytes.Buffer + policyParse(policy.GetRule(), ids, &buf) + return buf.String(), nil +} + +// recursive parse +func policyParse(rule *cb.SignaturePolicy, ids []string, buf *bytes.Buffer) { + switch p := rule.GetType().(type) { + case *cb.SignaturePolicy_SignedBy: + buf.WriteString("'") + buf.WriteString(ids[p.SignedBy]) + buf.WriteString("'") + + case *cb.SignaturePolicy_NOutOf_: + n := p.NOutOf.GetN() + rules := p.NOutOf.GetRules() + + switch n { + case int32(len(rules)): + buf.WriteString("AND(") + case 1: + buf.WriteString("OR(") + default: + buf.WriteString("OutOf(") + buf.WriteString(strconv.Itoa(int(n))) + buf.WriteString(",") + } + + for i, r := range rules { + if i > 0 { + buf.WriteString(",") + } + policyParse(r, ids, buf) + } + buf.WriteString(")") + } +} diff --git a/pkg/chaincode/signaturepolicy_test.go b/pkg/chaincode/signaturepolicy_test.go new file mode 100644 index 0000000..0c8c606 --- /dev/null +++ b/pkg/chaincode/signaturepolicy_test.go @@ -0,0 +1,30 @@ +package chaincode + +import ( + "fmt" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = DescribeTable("signaturepolicyenvelope to string", + func(expression string) { + //gen a SignaturePolicyEnvelope from expression + applicationPolicy, err := NewApplicationPolicy(expression, "") + Expect(err).NotTo(HaveOccurred()) + policy := applicationPolicy.GetSignaturePolicy() + + //parse the SignaturePolicyEnvelope back to expression + dstExpression, err := SignaturePolicyEnvelopeToString(policy) + Expect(err).NotTo(HaveOccurred()) + + fmt.Println("src Expression:", expression) + fmt.Println("dst Expression:", dstExpression) + + Expect(dstExpression).To(Equal(expression)) + }, + Entry("When keyword has OR", `OR('Org3MSP.peer','Org1MSP.admin','Org2MSP.member')`), + Entry("When keyword has AND", `AND('Org3MSP.peer','Org1MSP.admin','Org2MSP.member')`), + Entry("When keyword has AND,OR", `AND('Org3MSP.peer',OR('Org1MSP.admin','Org2MSP.member'))`), + Entry("When keyword has OutOf", `OutOf(2,'Org1MSP.peer','Org2MSP.peer','Org3MSP.peer')`), +)