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 }