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 }