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