github.com/leonlxy/hyperledger@v1.0.0-alpha.0.20170427033203-34922035d248/common/cauthdsl/policyparser.go (about)

     1  /*
     2  Copyright IBM Corp. 2017 All Rights Reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8  		 http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cauthdsl
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"regexp"
    23  	"strconv"
    24  
    25  	"github.com/Knetic/govaluate"
    26  	"github.com/hyperledger/fabric/protos/common"
    27  	"github.com/hyperledger/fabric/protos/msp"
    28  	"github.com/hyperledger/fabric/protos/utils"
    29  )
    30  
    31  var regex *regexp.Regexp = regexp.MustCompile("^([[:alnum:]]+)([.])(member|admin)$")
    32  
    33  func and(args ...interface{}) (interface{}, error) {
    34  	toret := "outof(" + strconv.Itoa(len(args))
    35  	for _, arg := range args {
    36  		toret += ", "
    37  		switch t := arg.(type) {
    38  		case string:
    39  			if regex.MatchString(t) {
    40  				toret += "'" + t + "'"
    41  			} else {
    42  				toret += t
    43  			}
    44  		default:
    45  			return nil, fmt.Errorf("Unexpected type %s", reflect.TypeOf(arg))
    46  		}
    47  	}
    48  
    49  	return toret + ")", nil
    50  }
    51  
    52  func or(args ...interface{}) (interface{}, error) {
    53  	toret := "outof(" + strconv.Itoa(1)
    54  	for _, arg := range args {
    55  		toret += ", "
    56  		switch t := arg.(type) {
    57  		case string:
    58  			if regex.MatchString(t) {
    59  				toret += "'" + t + "'"
    60  			} else {
    61  				toret += t
    62  			}
    63  		default:
    64  			return nil, fmt.Errorf("Unexpected type %s", reflect.TypeOf(arg))
    65  		}
    66  	}
    67  
    68  	return toret + ")", nil
    69  }
    70  
    71  func firstPass(args ...interface{}) (interface{}, error) {
    72  	toret := "outof(ID"
    73  	for _, arg := range args {
    74  		toret += ", "
    75  		switch t := arg.(type) {
    76  		case string:
    77  			if regex.MatchString(t) {
    78  				toret += "'" + t + "'"
    79  			} else {
    80  				toret += t
    81  			}
    82  		case float32:
    83  		case float64:
    84  			toret += strconv.Itoa(int(t))
    85  		default:
    86  			return nil, fmt.Errorf("Unexpected type %s", reflect.TypeOf(arg))
    87  		}
    88  	}
    89  
    90  	return toret + ")", nil
    91  }
    92  
    93  func secondPass(args ...interface{}) (interface{}, error) {
    94  	/* general sanity check, we expect at least 3 args */
    95  	if len(args) < 3 {
    96  		return nil, fmt.Errorf("At least 3 arguments expected, got %d", len(args))
    97  	}
    98  
    99  	/* get the first argument, we expect it to be the context */
   100  	var ctx *context
   101  	switch v := args[0].(type) {
   102  	case *context:
   103  		ctx = v
   104  	default:
   105  		return nil, fmt.Errorf("Unrecognized type, expected the context, got %s", reflect.TypeOf(args[0]))
   106  	}
   107  
   108  	/* get the second argument, we expect an integer telling us
   109  	   how many of the remaining we expect to have*/
   110  	var t int
   111  	switch arg := args[1].(type) {
   112  	case float64:
   113  		t = int(arg)
   114  	default:
   115  		return nil, fmt.Errorf("Unrecognized type, expected a number, got %s", reflect.TypeOf(args[1]))
   116  	}
   117  
   118  	/* get the n in the t out of n */
   119  	var n int = len(args) - 1
   120  
   121  	/* sanity check - t better be <= n */
   122  	if t > n {
   123  		return nil, fmt.Errorf("Invalid t-out-of-n predicate, t %d, n %d", t, n)
   124  	}
   125  
   126  	policies := make([]*common.SignaturePolicy, 0)
   127  
   128  	/* handle the rest of the arguments */
   129  	for _, principal := range args[2:] {
   130  		switch t := principal.(type) {
   131  		/* if it's a string, we expect it to be formed as
   132  		   <MSP_ID> . <ROLE>, where MSP_ID is the MSP identifier
   133  		   and ROLE is either a member of an admin*/
   134  		case string:
   135  			/* split the string */
   136  			subm := regex.FindAllStringSubmatch(t, -1)
   137  			if subm == nil || len(subm) != 1 || len(subm[0]) != 4 {
   138  				return nil, fmt.Errorf("Error parsing principal %s", t)
   139  			}
   140  
   141  			/* get the right role */
   142  			var r msp.MSPRole_MSPRoleType
   143  			if subm[0][3] == "member" {
   144  				r = msp.MSPRole_MEMBER
   145  			} else {
   146  				r = msp.MSPRole_ADMIN
   147  			}
   148  
   149  			/* build the principal we've been told */
   150  			p := &msp.MSPPrincipal{
   151  				PrincipalClassification: msp.MSPPrincipal_ROLE,
   152  				Principal:               utils.MarshalOrPanic(&msp.MSPRole{MspIdentifier: subm[0][1], Role: r})}
   153  			ctx.principals = append(ctx.principals, p)
   154  
   155  			/* create a SignaturePolicy that requires a signature from
   156  			   the principal we've just built*/
   157  			dapolicy := SignedBy(int32(ctx.IDNum))
   158  			policies = append(policies, dapolicy)
   159  
   160  			/* increment the identity counter. Note that this is
   161  			   suboptimal as we are not reusing identities. We
   162  			   can deduplicate them easily and make this puppy
   163  			   smaller. For now it's fine though */
   164  			// TODO: deduplicate principals
   165  			ctx.IDNum++
   166  
   167  		/* if we've already got a policy we're good, just append it */
   168  		case *common.SignaturePolicy:
   169  			policies = append(policies, t)
   170  
   171  		default:
   172  			return nil, fmt.Errorf("Unrecognized type, expected a principal or a policy, got %s", reflect.TypeOf(principal))
   173  		}
   174  	}
   175  
   176  	return NOutOf(int32(t), policies), nil
   177  }
   178  
   179  type context struct {
   180  	IDNum      int
   181  	principals []*msp.MSPPrincipal
   182  }
   183  
   184  func newContext() *context {
   185  	return &context{IDNum: 0, principals: make([]*msp.MSPPrincipal, 0)}
   186  }
   187  
   188  // FromString takes a string representation of the policy,
   189  // parses it and returns a SignaturePolicyEnvelope that
   190  // implements that policy. The supported language is as follows
   191  //
   192  // GATE(P[, P])
   193  //
   194  // where
   195  //	- GATE is either "and" or "or"
   196  //	- P is either a principal or another nested call to GATE
   197  //
   198  // a principal is defined as
   199  //
   200  // ORG.ROLE
   201  //
   202  // where
   203  //	- ORG is a string (representing the MSP identifier)
   204  //	- ROLE is either the string "member" or the string "admin" representing the required role
   205  func FromString(policy string) (*common.SignaturePolicyEnvelope, error) {
   206  	// first we translate the and/or business into outof gates
   207  	intermediate, err := govaluate.NewEvaluableExpressionWithFunctions(policy, map[string]govaluate.ExpressionFunction{"AND": and, "and": and, "OR": or, "or": or})
   208  	if err != nil {
   209  		return nil, err
   210  	}
   211  
   212  	intermediateRes, err := intermediate.Evaluate(nil)
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	// we still need two passes. The first pass just adds an extra
   218  	// argument ID to each of the outof calls. This is
   219  	// required because govaluate has no means of giving context
   220  	// to user-implemented functions other than via arguments.
   221  	// We need this argument because we need a global place where
   222  	// we put the identities that the policy requires
   223  	exp, err := govaluate.NewEvaluableExpressionWithFunctions(intermediateRes.(string), map[string]govaluate.ExpressionFunction{"outof": firstPass})
   224  	if err != nil {
   225  		return nil, err
   226  	}
   227  
   228  	res, err := exp.Evaluate(nil)
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	ctx := newContext()
   234  	parameters := make(map[string]interface{}, 1)
   235  	parameters["ID"] = ctx
   236  
   237  	exp, err = govaluate.NewEvaluableExpressionWithFunctions(res.(string), map[string]govaluate.ExpressionFunction{"outof": secondPass})
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  
   242  	res, err = exp.Evaluate(parameters)
   243  	if err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	p := &common.SignaturePolicyEnvelope{
   248  		Identities: ctx.principals,
   249  		Version:    0,
   250  		Policy:     res.(*common.SignaturePolicy),
   251  	}
   252  
   253  	return p, nil
   254  }