github.com/hellobchain/third_party@v0.0.0-20230331131523-deb0478a2e52/hyperledger/fabric-config/configtx/internal/policydsl/policyparser.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package policydsl
     8  
     9  import (
    10  	"fmt"
    11  	"reflect"
    12  	"regexp"
    13  	"strconv"
    14  	"strings"
    15  
    16  	"github.com/Knetic/govaluate"
    17  	"github.com/golang/protobuf/proto"
    18  	cb "github.com/hyperledger/fabric-protos-go/common"
    19  	mb "github.com/hyperledger/fabric-protos-go/msp"
    20  )
    21  
    22  // Gate values
    23  const (
    24  	GateAnd   = "And"
    25  	GateOr    = "Or"
    26  	GateOutOf = "OutOf"
    27  )
    28  
    29  // Role values for principals
    30  const (
    31  	RoleAdmin   = "admin"
    32  	RoleMember  = "member"
    33  	RoleClient  = "client"
    34  	RolePeer    = "peer"
    35  	RoleOrderer = "orderer"
    36  )
    37  
    38  var (
    39  	regex = regexp.MustCompile(
    40  		fmt.Sprintf("^([[:alnum:].-]+)([.])(%s|%s|%s|%s|%s)$",
    41  			RoleAdmin, RoleMember, RoleClient, RolePeer, RoleOrderer),
    42  	)
    43  	regexErr = regexp.MustCompile("^No parameter '([^']+)' found[.]$")
    44  )
    45  
    46  // SignedBy creates a SignaturePolicy requiring a given signer's signature
    47  func SignedBy(index int32) *cb.SignaturePolicy {
    48  	return &cb.SignaturePolicy{
    49  		Type: &cb.SignaturePolicy_SignedBy{
    50  			SignedBy: index,
    51  		},
    52  	}
    53  }
    54  
    55  // And is a convenience method which utilizes NOutOf to produce And equivalent behavior
    56  func And(lhs, rhs *cb.SignaturePolicy) *cb.SignaturePolicy {
    57  	return NOutOf(2, []*cb.SignaturePolicy{lhs, rhs})
    58  }
    59  
    60  // Or is a convenience method which utilizes NOutOf to produce Or equivalent behavior
    61  func Or(lhs, rhs *cb.SignaturePolicy) *cb.SignaturePolicy {
    62  	return NOutOf(1, []*cb.SignaturePolicy{lhs, rhs})
    63  }
    64  
    65  // NOutOf creates a policy which requires N out of the slice of policies to evaluate to true
    66  func NOutOf(n int32, policies []*cb.SignaturePolicy) *cb.SignaturePolicy {
    67  	return &cb.SignaturePolicy{
    68  		Type: &cb.SignaturePolicy_NOutOf_{
    69  			NOutOf: &cb.SignaturePolicy_NOutOf{
    70  				N:     n,
    71  				Rules: policies,
    72  			},
    73  		},
    74  	}
    75  }
    76  
    77  // a stub function - it returns the same string as it's passed.
    78  // This will be evaluated by second/third passes to convert to a proto policy
    79  func outof(args ...interface{}) (interface{}, error) {
    80  	toret := "outof("
    81  
    82  	if len(args) < 2 {
    83  		return nil, fmt.Errorf("expected at least two arguments to NOutOf. Given %d", len(args))
    84  	}
    85  
    86  	arg0 := args[0]
    87  	// govaluate treats all numbers as float64 only. But and/or may pass int/string. Allowing int/string for flexibility of caller
    88  	if n, ok := arg0.(float64); ok {
    89  		toret += strconv.Itoa(int(n))
    90  	} else if n, ok := arg0.(int); ok {
    91  		toret += strconv.Itoa(n)
    92  	} else if n, ok := arg0.(string); ok {
    93  		toret += n
    94  	} else {
    95  		return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg0))
    96  	}
    97  
    98  	for _, arg := range args[1:] {
    99  		toret += ", "
   100  
   101  		switch t := arg.(type) {
   102  		case string:
   103  			if regex.MatchString(t) {
   104  				toret += "'" + t + "'"
   105  			} else {
   106  				toret += t
   107  			}
   108  		default:
   109  			return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg))
   110  		}
   111  	}
   112  
   113  	return toret + ")", nil
   114  }
   115  
   116  func and(args ...interface{}) (interface{}, error) {
   117  	args = append([]interface{}{len(args)}, args...)
   118  	return outof(args...)
   119  }
   120  
   121  func or(args ...interface{}) (interface{}, error) {
   122  	args = append([]interface{}{1}, args...)
   123  	return outof(args...)
   124  }
   125  
   126  func firstPass(args ...interface{}) (interface{}, error) {
   127  	toret := "outof(ID"
   128  	for _, arg := range args {
   129  		toret += ", "
   130  
   131  		switch t := arg.(type) {
   132  		case string:
   133  			if regex.MatchString(t) {
   134  				toret += "'" + t + "'"
   135  			} else {
   136  				toret += t
   137  			}
   138  		case float32:
   139  		case float64:
   140  			toret += strconv.Itoa(int(t))
   141  		default:
   142  			return nil, fmt.Errorf("unexpected type %s", reflect.TypeOf(arg))
   143  		}
   144  	}
   145  
   146  	return toret + ")", nil
   147  }
   148  
   149  func secondPass(args ...interface{}) (interface{}, error) {
   150  	/* general sanity check, we expect at least 3 args */
   151  	if len(args) < 3 {
   152  		return nil, fmt.Errorf("at least 3 arguments expected, got %d", len(args))
   153  	}
   154  
   155  	/* get the first argument, we expect it to be the context */
   156  	var ctx *context
   157  	switch v := args[0].(type) {
   158  	case *context:
   159  		ctx = v
   160  	default:
   161  		return nil, fmt.Errorf("unrecognized type, expected the context, got %s", reflect.TypeOf(args[0]))
   162  	}
   163  
   164  	/* get the second argument, we expect an integer telling us
   165  	   how many of the remaining we expect to have*/
   166  	var t int
   167  	switch arg := args[1].(type) {
   168  	case float64:
   169  		t = int(arg)
   170  	default:
   171  		return nil, fmt.Errorf("unrecognized type, expected a number, got %s", reflect.TypeOf(args[1]))
   172  	}
   173  
   174  	/* get the n in the t out of n */
   175  	n := len(args) - 2
   176  
   177  	/* sanity check - t should be positive, permit equal to n+1, but disallow over n+1 */
   178  	if t < 0 || t > n+1 {
   179  		return nil, fmt.Errorf("invalid t-out-of-n predicate, t %d, n %d", t, n)
   180  	}
   181  
   182  	policies := make([]*cb.SignaturePolicy, 0)
   183  
   184  	/* handle the rest of the arguments */
   185  	for _, principal := range args[2:] {
   186  		switch t := principal.(type) {
   187  		/* if it's a string, we expect it to be formed as
   188  		   <MSP_ID> . <ROLE>, where MSP_ID is the MSP identifier
   189  		   and ROLE is either a member, an admin, a client, a peer or an orderer*/
   190  		case string:
   191  			/* split the string */
   192  			subm := regex.FindAllStringSubmatch(t, -1)
   193  			if subm == nil || len(subm) != 1 || len(subm[0]) != 4 {
   194  				return nil, fmt.Errorf("error parsing principal %s", t)
   195  			}
   196  
   197  			/* get the right role */
   198  			var r mb.MSPRole_MSPRoleType
   199  
   200  			switch subm[0][3] {
   201  			case RoleMember:
   202  				r = mb.MSPRole_MEMBER
   203  			case RoleAdmin:
   204  				r = mb.MSPRole_ADMIN
   205  			case RoleClient:
   206  				r = mb.MSPRole_CLIENT
   207  			case RolePeer:
   208  				r = mb.MSPRole_PEER
   209  			case RoleOrderer:
   210  				r = mb.MSPRole_ORDERER
   211  			default:
   212  				return nil, fmt.Errorf("error parsing role %s", t)
   213  			}
   214  
   215  			/* build the principal we've been told */
   216  			mspRole, err := proto.Marshal(&mb.MSPRole{MspIdentifier: subm[0][1], Role: r})
   217  			if err != nil {
   218  				return nil, fmt.Errorf("error marshalling msp role: %s", err)
   219  			}
   220  
   221  			p := &mb.MSPPrincipal{
   222  				PrincipalClassification: mb.MSPPrincipal_ROLE,
   223  				Principal:               mspRole,
   224  			}
   225  			ctx.principals = append(ctx.principals, p)
   226  
   227  			/* create a SignaturePolicy that requires a signature from
   228  			   the principal we've just built*/
   229  			dapolicy := SignedBy(int32(ctx.IDNum))
   230  			policies = append(policies, dapolicy)
   231  
   232  			/* increment the identity counter. Note that this is
   233  			   suboptimal as we are not reusing identities. We
   234  			   can deduplicate them easily and make this puppy
   235  			   smaller. For now it's fine though */
   236  			// TODO: deduplicate principals
   237  			ctx.IDNum++
   238  
   239  		/* if we've already got a policy we're good, just append it */
   240  		case *cb.SignaturePolicy:
   241  			policies = append(policies, t)
   242  
   243  		default:
   244  			return nil, fmt.Errorf("unrecognized type, expected a principal or a policy, got %s", reflect.TypeOf(principal))
   245  		}
   246  	}
   247  
   248  	return NOutOf(int32(t), policies), nil
   249  }
   250  
   251  type context struct {
   252  	IDNum      int
   253  	principals []*mb.MSPPrincipal
   254  }
   255  
   256  func newContext() *context {
   257  	return &context{IDNum: 0, principals: make([]*mb.MSPPrincipal, 0)}
   258  }
   259  
   260  // FromString takes a string representation of the policy,
   261  // parses it and returns a SignaturePolicyEnvelope that
   262  // implements that policy. The supported language is as follows:
   263  //
   264  // GATE(P[, P])
   265  //
   266  // where:
   267  //	- GATE is either "and" or "or"
   268  //	- P is either a principal or another nested call to GATE
   269  //
   270  // A principal is defined as:
   271  //
   272  // ORG.ROLE
   273  //
   274  // where:
   275  //	- ORG is a string (representing the MSP identifier)
   276  //	- ROLE takes the value of any of the RoleXXX constants representing
   277  //    the required role
   278  func FromString(policy string) (*cb.SignaturePolicyEnvelope, error) {
   279  	// first we translate the and/or business into outof gates
   280  	intermediate, err := govaluate.NewEvaluableExpressionWithFunctions(
   281  		policy, map[string]govaluate.ExpressionFunction{
   282  			GateAnd:                    and,
   283  			strings.ToLower(GateAnd):   and,
   284  			strings.ToUpper(GateAnd):   and,
   285  			GateOr:                     or,
   286  			strings.ToLower(GateOr):    or,
   287  			strings.ToUpper(GateOr):    or,
   288  			GateOutOf:                  outof,
   289  			strings.ToLower(GateOutOf): outof,
   290  			strings.ToUpper(GateOutOf): outof,
   291  		},
   292  	)
   293  	if err != nil {
   294  		return nil, err
   295  	}
   296  
   297  	intermediateRes, err := intermediate.Evaluate(map[string]interface{}{})
   298  	if err != nil {
   299  		// attempt to produce a meaningful error
   300  		if regexErr.MatchString(err.Error()) {
   301  			sm := regexErr.FindStringSubmatch(err.Error())
   302  			if len(sm) == 2 {
   303  				return nil, fmt.Errorf("unrecognized token '%s' in policy string", sm[1])
   304  			}
   305  		}
   306  
   307  		return nil, err
   308  	}
   309  
   310  	resStr, ok := intermediateRes.(string)
   311  	if !ok {
   312  		return nil, fmt.Errorf("invalid policy string '%s'", policy)
   313  	}
   314  
   315  	// we still need two passes. The first pass just adds an extra
   316  	// argument ID to each of the outof calls. This is
   317  	// required because govaluate has no means of giving context
   318  	// to user-implemented functions other than via arguments.
   319  	// We need this argument because we need a global place where
   320  	// we put the identities that the policy requires
   321  	exp, err := govaluate.NewEvaluableExpressionWithFunctions(
   322  		resStr,
   323  		map[string]govaluate.ExpressionFunction{"outof": firstPass},
   324  	)
   325  	if err != nil {
   326  		return nil, err
   327  	}
   328  
   329  	res, err := exp.Evaluate(map[string]interface{}{})
   330  	if err != nil {
   331  		// attempt to produce a meaningful error
   332  		if regexErr.MatchString(err.Error()) {
   333  			sm := regexErr.FindStringSubmatch(err.Error())
   334  			if len(sm) == 2 {
   335  				return nil, fmt.Errorf("unrecognized token '%s' in policy string", sm[1])
   336  			}
   337  		}
   338  
   339  		return nil, err
   340  	}
   341  
   342  	resStr, ok = res.(string)
   343  	if !ok {
   344  		return nil, fmt.Errorf("invalid policy string '%s'", policy)
   345  	}
   346  
   347  	ctx := newContext()
   348  	parameters := make(map[string]interface{}, 1)
   349  	parameters["ID"] = ctx
   350  
   351  	exp, err = govaluate.NewEvaluableExpressionWithFunctions(
   352  		resStr,
   353  		map[string]govaluate.ExpressionFunction{"outof": secondPass},
   354  	)
   355  	if err != nil {
   356  		return nil, err
   357  	}
   358  
   359  	res, err = exp.Evaluate(parameters)
   360  	if err != nil {
   361  		// attempt to produce a meaningful error
   362  		if regexErr.MatchString(err.Error()) {
   363  			sm := regexErr.FindStringSubmatch(err.Error())
   364  			if len(sm) == 2 {
   365  				return nil, fmt.Errorf("unrecognized token '%s' in policy string", sm[1])
   366  			}
   367  		}
   368  
   369  		return nil, err
   370  	}
   371  
   372  	rule, ok := res.(*cb.SignaturePolicy)
   373  	if !ok {
   374  		return nil, fmt.Errorf("invalid policy string '%s'", policy)
   375  	}
   376  
   377  	p := &cb.SignaturePolicyEnvelope{
   378  		Identities: ctx.principals,
   379  		Version:    0,
   380  		Rule:       rule,
   381  	}
   382  
   383  	return p, nil
   384  }