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