github.com/yacovm/fabric@v2.0.0-alpha.0.20191128145320-c5d4087dc723+incompatible/common/cauthdsl/policyparser.go (about)

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