github.com/hyperledger/fabric-ca@v2.0.0-alpha.0.20201120210307-7b4f34729db1+incompatible/lib/serveraffiliations.go (about) 1 /* 2 Copyright IBM Corp. All Rights Reserved. 3 4 SPDX-License-Identifier: Apache-2.0 5 */ 6 7 package lib 8 9 import ( 10 "fmt" 11 "strconv" 12 "strings" 13 14 "github.com/cloudflare/cfssl/log" 15 "github.com/hyperledger/fabric-ca/internal/pkg/api" 16 "github.com/hyperledger/fabric-ca/lib/attr" 17 "github.com/hyperledger/fabric-ca/lib/caerrors" 18 "github.com/hyperledger/fabric-ca/lib/server/db" 19 "github.com/hyperledger/fabric-ca/lib/server/db/util" 20 "github.com/hyperledger/fabric-ca/lib/server/user" 21 cadbuser "github.com/hyperledger/fabric-ca/lib/server/user" 22 "github.com/hyperledger/fabric-ca/lib/spi" 23 "github.com/pkg/errors" 24 ) 25 26 func newAffiliationsEndpoint(s *Server) *serverEndpoint { 27 return &serverEndpoint{ 28 Path: "affiliations/{affiliation}", 29 Methods: []string{"GET", "DELETE", "PUT"}, 30 Handler: affiliationsHandler, 31 Server: s, 32 successRC: 200, 33 } 34 } 35 36 func newAffiliationsStreamingEndpoint(s *Server) *serverEndpoint { 37 return &serverEndpoint{ 38 Path: "affiliations", 39 Methods: []string{"GET", "POST"}, 40 Handler: affiliationsStreamingHandler, 41 Server: s, 42 successRC: 200, 43 } 44 } 45 46 func affiliationsHandler(ctx *serverRequestContextImpl) (interface{}, error) { 47 var err error 48 // Authenticate 49 callerID, err := ctx.TokenAuthentication() 50 log.Debugf("Received affiliation update request from %s", callerID) 51 if err != nil { 52 return nil, err 53 } 54 caname, err := ctx.getCAName() 55 if err != nil { 56 return nil, err 57 } 58 caller, err := ctx.GetCaller() 59 if err != nil { 60 return nil, err 61 } 62 err = ctx.HasRole(attr.AffiliationMgr) 63 if err != nil { 64 return nil, err 65 } 66 // Process Request 67 resp, err := processAffiliationRequest(ctx, caname, caller) 68 if err != nil { 69 return nil, err 70 } 71 72 return resp, nil 73 } 74 75 func affiliationsStreamingHandler(ctx *serverRequestContextImpl) (interface{}, error) { 76 var err error 77 78 // Authenticate 79 callerID, err := ctx.TokenAuthentication() 80 log.Debugf("Received affiliation update request from %s", callerID) 81 if err != nil { 82 return nil, err 83 } 84 caname, err := ctx.getCAName() 85 if err != nil { 86 return nil, err 87 } 88 caller, err := ctx.GetCaller() 89 if err != nil { 90 return nil, err 91 } 92 err = ctx.HasRole(attr.AffiliationMgr) 93 if err != nil { 94 return nil, err 95 } 96 // Process Request 97 resp, err := processStreamingAffiliationRequest(ctx, caname, caller) 98 if err != nil { 99 return nil, err 100 } 101 102 return resp, nil 103 } 104 105 // processStreamingAffiliationRequest will process the configuration request 106 func processStreamingAffiliationRequest(ctx *serverRequestContextImpl, caname string, caller user.User) (interface{}, error) { 107 log.Debug("Processing affiliation configuration update request") 108 109 method := ctx.req.Method 110 switch method { 111 case "GET": 112 return processGetAllAffiliationsRequest(ctx, caller, caname) 113 case "POST": 114 return processAffiliationPostRequest(ctx, caname) 115 default: 116 return nil, errors.Errorf("Invalid request: %s", method) 117 } 118 } 119 120 // processRequest will process the configuration request 121 func processAffiliationRequest(ctx *serverRequestContextImpl, caname string, caller user.User) (interface{}, error) { 122 log.Debug("Processing affiliation configuration update request") 123 124 method := ctx.req.Method 125 switch method { 126 case "GET": 127 return processGetAffiliationRequest(ctx, caller, caname) 128 case "DELETE": 129 return processAffiliationDeleteRequest(ctx, caname) 130 case "PUT": 131 return processAffiliationPutRequest(ctx, caname) 132 default: 133 return nil, errors.Errorf("Invalid request: %s", method) 134 } 135 } 136 137 func processGetAllAffiliationsRequest(ctx *serverRequestContextImpl, caller user.User, caname string) (*api.AffiliationResponse, error) { 138 log.Debug("Processing GET all affiliations request") 139 140 resp, err := getAffiliations(ctx, caller, caname) 141 if err != nil { 142 return nil, err 143 } 144 145 return resp, nil 146 } 147 148 func processGetAffiliationRequest(ctx *serverRequestContextImpl, caller user.User, caname string) (*api.AffiliationResponse, error) { 149 log.Debug("Processing GET affiliation request") 150 151 affiliation, err := ctx.GetVar("affiliation") 152 if err != nil { 153 return nil, err 154 } 155 156 resp, err := getAffiliation(ctx, caller, affiliation, caname) 157 if err != nil { 158 return nil, err 159 } 160 161 return resp, nil 162 } 163 164 func getAffiliations(ctx *serverRequestContextImpl, caller user.User, caname string) (*api.AffiliationResponse, error) { 165 log.Debug("Requesting all affiliations that the caller is authorized view") 166 var err error 167 168 registry := ctx.ca.registry 169 callerAff := cadbuser.GetAffiliation(caller) 170 rows, err := registry.GetAllAffiliations(callerAff) 171 if err != nil { 172 return nil, errors.WithMessagef(err, "Failed to get all affiliations") 173 } 174 175 an := &affiliationNode{} 176 for rows.Next() { 177 var aff db.AffiliationRecord 178 err := rows.StructScan(&aff) 179 if err != nil { 180 return nil, caerrors.NewHTTPErr(500, caerrors.ErrGettingAffiliation, "Failed to get read row: %s", err) 181 } 182 183 an.insertByName(aff.Name) 184 } 185 root := an.GetRoot() 186 if root == nil { 187 return nil, caerrors.NewHTTPErr(404, caerrors.ErrGettingAffiliation, "No affiliations are configured on the CA") 188 } 189 190 resp := &api.AffiliationResponse{ 191 CAName: caname, 192 } 193 resp.Name = root.Name 194 resp.Affiliations = root.Affiliations 195 resp.Identities = root.Identities 196 197 return resp, nil 198 } 199 200 func getAffiliation(ctx *serverRequestContextImpl, caller user.User, requestedAffiliation, caname string) (*api.AffiliationResponse, error) { 201 log.Debugf("Requesting affiliation '%s'", requestedAffiliation) 202 203 registry := ctx.ca.registry 204 err := ctx.ContainsAffiliation(requestedAffiliation) 205 if err != nil { 206 return nil, err 207 } 208 209 result, err := registry.GetAffiliationTree(requestedAffiliation) 210 if err != nil { 211 return nil, errors.WithMessage(err, "Failed to get affiliation") 212 } 213 214 resp, err := getResponse(result, caname) 215 if err != nil { 216 return nil, err 217 } 218 219 return resp, nil 220 } 221 222 func processAffiliationDeleteRequest(ctx *serverRequestContextImpl, caname string) (*api.AffiliationResponse, error) { 223 log.Debug("Processing DELETE request") 224 225 if !ctx.ca.Config.Cfg.Affiliations.AllowRemove { 226 return nil, caerrors.NewAuthorizationErr(caerrors.ErrUpdateConfigRemoveAff, "Affiliation removal is disabled") 227 } 228 229 removeAffiliation, err := ctx.GetVar("affiliation") 230 if err != nil { 231 return nil, err 232 } 233 log.Debugf("Request to remove affiliation '%s'", removeAffiliation) 234 235 callerAff := cadbuser.GetAffiliation(ctx.caller) 236 if callerAff == removeAffiliation { 237 return nil, caerrors.NewAuthorizationErr(caerrors.ErrUpdateConfigRemoveAff, "Can't remove affiliation '%s' because the caller is associated with this affiliation", removeAffiliation) 238 } 239 240 err = ctx.ContainsAffiliation(removeAffiliation) 241 if err != nil { 242 return nil, err 243 } 244 245 force, err := ctx.GetBoolQueryParm("force") 246 if err != nil { 247 return nil, err 248 } 249 250 _, isRegistrar, err := ctx.isRegistrar() 251 if err != nil { 252 httpErr := getHTTPErr(err) 253 if httpErr.GetRemoteCode() != 20 { 254 return nil, err 255 } 256 } 257 258 identityRemoval := ctx.ca.Config.Cfg.Identities.AllowRemove 259 result, err := ctx.ca.registry.DeleteAffiliation(removeAffiliation, force, identityRemoval, isRegistrar) 260 if err != nil { 261 return nil, err 262 } 263 264 resp, err := getResponse(result, caname) 265 if err != nil { 266 return nil, err 267 } 268 269 return resp, nil 270 } 271 272 func processAffiliationPostRequest(ctx *serverRequestContextImpl, caname string) (*api.AffiliationResponse, error) { 273 log.Debug("Processing POST request") 274 275 ctx.endpoint.successRC = 201 276 277 var req api.AddAffiliationRequestNet 278 err := ctx.ReadBody(&req) 279 if err != nil { 280 return nil, err 281 } 282 283 addAffiliation := req.Name 284 log.Debugf("Request to add affiliation '%s'", addAffiliation) 285 286 registry := ctx.ca.registry 287 result, err := registry.GetAffiliation(addAffiliation) 288 if err != nil && !util.IsGetError(err) { 289 return nil, err 290 } 291 if result != nil { 292 return nil, caerrors.NewHTTPErr(409, caerrors.ErrUpdateConfigAddAff, "Affiliation already exists") 293 } 294 295 err = ctx.ContainsAffiliation(addAffiliation) 296 if err != nil { 297 return nil, err 298 } 299 300 force, err := ctx.GetBoolQueryParm("force") 301 if err != nil { 302 return nil, err 303 } 304 305 addAffiliationSlice := strings.Split(addAffiliation, ".") 306 var parentAffiliationPath string 307 308 affLevel := ctx.ca.server.levels.Affiliation 309 if force { 310 // With force option, add any parent affiliations that don't exist 311 var affiliationPath string 312 for _, addAff := range addAffiliationSlice { 313 affiliationPath = affiliationPath + addAff 314 err := registry.InsertAffiliation(affiliationPath, parentAffiliationPath, affLevel) 315 if err != nil { 316 return nil, caerrors.NewHTTPErr(500, caerrors.ErrUpdateConfigAddAff, "Failed to add affiliations '%s': %s", addAffiliation, err) 317 } 318 parentAffiliationPath = affiliationPath 319 affiliationPath = affiliationPath + "." 320 } 321 } else { 322 // If the affiliation being added has a parent affiliation, check to make sure that parent affiliation exists 323 if len(addAffiliationSlice) > 1 { 324 parentAffiliationPath = strings.Join(addAffiliationSlice[:len(addAffiliationSlice)-1], ".") // Get the path up until the last affiliation 325 _, err = registry.GetAffiliation(parentAffiliationPath) 326 if err != nil { 327 httpErr := getHTTPErr(err) 328 if httpErr.GetStatusCode() == 400 { 329 return nil, caerrors.NewHTTPErr(400, caerrors.ErrUpdateConfigAddAff, "Parent affiliation does not exist, 'force' option required on request to add affiliation") 330 } 331 return nil, err 332 } 333 err := registry.InsertAffiliation(addAffiliation, parentAffiliationPath, affLevel) 334 if err != nil { 335 return nil, caerrors.NewHTTPErr(500, caerrors.ErrUpdateConfigAddAff, "Failed to add affiliation '%s': %s", addAffiliation, err) 336 } 337 } else { 338 err := registry.InsertAffiliation(addAffiliation, "", affLevel) 339 if err != nil { 340 return nil, caerrors.NewHTTPErr(500, caerrors.ErrUpdateConfigAddAff, "Failed to add affiliation '%s': %s", addAffiliation, err) 341 } 342 } 343 344 } 345 346 resp := &api.AffiliationResponse{CAName: caname} 347 resp.Name = addAffiliation 348 349 return resp, nil 350 } 351 352 func processAffiliationPutRequest(ctx *serverRequestContextImpl, caname string) (*api.AffiliationResponse, error) { 353 log.Debug("Processing PUT request") 354 355 modifyAffiliation, err := ctx.GetVar("affiliation") 356 if err != nil { 357 return nil, err 358 } 359 360 var req api.ModifyAffiliationRequestNet 361 err = ctx.ReadBody(&req) 362 if err != nil { 363 return nil, err 364 } 365 newAffiliation := req.NewName 366 log.Debugf("Request to modify affiliation '%s' to '%s'", modifyAffiliation, newAffiliation) 367 368 err = ctx.ContainsAffiliation(modifyAffiliation) 369 if err != nil { 370 return nil, err 371 } 372 373 err = ctx.ContainsAffiliation(newAffiliation) 374 if err != nil { 375 return nil, err 376 } 377 378 force := false 379 forceStr := ctx.req.URL.Query().Get("force") 380 if forceStr != "" { 381 force, err = strconv.ParseBool(forceStr) 382 if err != nil { 383 return nil, caerrors.NewHTTPErr(400, caerrors.ErrUpdateConfigAddAff, "The 'force' query parameter value must be a boolean: %s", err) 384 } 385 386 } 387 388 _, isRegistrar, err := ctx.isRegistrar() 389 if err != nil { 390 httpErr := getHTTPErr(err) 391 if httpErr.GetLocalCode() != 20 { 392 return nil, err 393 } 394 } 395 396 registry := ctx.ca.registry 397 result, err := registry.ModifyAffiliation(modifyAffiliation, newAffiliation, force, isRegistrar) 398 if err != nil { 399 return nil, errors.WithMessage(err, fmt.Sprintf("Failed to modify affiliation from '%s' to '%s'", modifyAffiliation, newAffiliation)) 400 } 401 402 resp, err := getResponse(result, caname) 403 if err != nil { 404 return nil, err 405 } 406 407 return resp, nil 408 } 409 410 func getResponse(result *user.DbTxResult, caname string) (*api.AffiliationResponse, error) { 411 resp := &api.AffiliationResponse{CAName: caname} 412 // Get all root affiliation names from the result 413 rootNames := getRootAffiliationNames(result.Affiliations) 414 if len(rootNames) == 0 { 415 return resp, nil 416 } 417 if len(rootNames) != 1 { 418 return nil, errors.Errorf("multiple root affiliations found: %+v", rootNames) 419 } 420 affInfo := &api.AffiliationInfo{} 421 err := fillAffiliationInfo(affInfo, rootNames[0], result, result.Affiliations) 422 if err != nil { 423 return nil, err 424 } 425 resp.AffiliationInfo = *affInfo 426 return resp, nil 427 } 428 429 // Get all of the root affiliation names from this list of affiliations 430 func getRootAffiliationNames(affiliations []spi.Affiliation) []string { 431 roots := []string{} 432 for _, aff1 := range affiliations { 433 isRoot := true 434 for _, aff2 := range affiliations { 435 if isChildAffiliation(aff2.GetName(), aff1.GetName()) { 436 isRoot = false 437 break 438 } 439 } 440 if isRoot { 441 roots = append(roots, aff1.GetName()) 442 } 443 } 444 return roots 445 } 446 447 // Fill 'info' with affiliation info associated with affiliation 'name' hierarchically. 448 // Use 'affiliations' to find child affiliations for this affiliation, and 449 // 'identities' to find identities associated with this affiliation. 450 func fillAffiliationInfo(info *api.AffiliationInfo, name string, result *user.DbTxResult, affiliations []spi.Affiliation) error { 451 info.Name = name 452 // Add identities which have this affiliation 453 identities := []api.IdentityInfo{} 454 for _, identity := range result.Identities { 455 idAff := strings.Join(identity.GetAffiliationPath(), ".") 456 if idAff == name { 457 id, err := getIDInfo(identity) 458 if err != nil { 459 return err 460 } 461 identities = append(identities, *id) 462 } 463 } 464 if len(identities) > 0 { 465 info.Identities = identities 466 } 467 // Create child affiliations (if any) 468 children := []api.AffiliationInfo{} 469 var child spi.Affiliation 470 for { 471 child = nil 472 // Search for a child affiliations 473 for idx, aff := range affiliations { 474 affName := aff.GetName() 475 if isChildAffiliation(name, affName) { 476 child = aff 477 // Remove this child affiliation 478 affiliations = append(affiliations[:idx], affiliations[idx+1:]...) 479 break 480 } 481 } 482 if child == nil { 483 // No more children of this affiliation 'name' found 484 break 485 } 486 // Found a child of affiliation 'name' 487 childAff := api.AffiliationInfo{Name: child.GetName()} 488 err := fillAffiliationInfo(&childAff, child.GetName(), result, affiliations) 489 if err != nil { 490 return err 491 } 492 children = append(children, childAff) 493 } 494 if len(children) > 0 { 495 info.Affiliations = children 496 } 497 return nil 498 } 499 500 // Determine if the affiliation with name 'child' is a child of affiliation with name 'name' 501 func isChildAffiliation(name, child string) bool { 502 if !strings.HasPrefix(child, name+".") { 503 return false 504 } 505 nameParts := strings.Split(name, ".") 506 childParts := strings.Split(child, ".") 507 if len(childParts) != len(nameParts)+1 { 508 return false 509 } 510 return true 511 } 512 513 func getIDInfo(user user.User) (*api.IdentityInfo, error) { 514 allAttributes, err := user.GetAttributes(nil) 515 if err != nil { 516 return nil, err 517 } 518 return &api.IdentityInfo{ 519 ID: user.GetName(), 520 Type: user.GetType(), 521 Affiliation: cadbuser.GetAffiliation(user), 522 Attributes: allAttributes, 523 MaxEnrollments: user.GetMaxEnrollments(), 524 }, nil 525 } 526 527 type affiliationNode struct { 528 children map[string]*affiliationNode 529 } 530 531 func (an *affiliationNode) insertByName(name string) { 532 an.insertByPath(strings.Split(name, ".")) 533 } 534 535 func (an *affiliationNode) insertByPath(path []string) { 536 if len(path) == 0 { 537 return 538 } 539 if an.children == nil { 540 an.children = map[string]*affiliationNode{} 541 } 542 node := an.children[path[0]] 543 if node == nil { 544 node = &affiliationNode{} 545 an.children[path[0]] = node 546 } 547 node.insertByPath(path[1:]) 548 } 549 550 func (an *affiliationNode) GetRoot() *api.AffiliationInfo { 551 result := &api.AffiliationInfo{} 552 an.fill([]string{}, result) 553 switch len(result.Affiliations) { 554 case 0: 555 return nil 556 case 1: 557 return &result.Affiliations[0] 558 default: 559 return result 560 } 561 } 562 563 func (an *affiliationNode) fill(path []string, ai *api.AffiliationInfo) { 564 ai.Name = strings.Join(path, ".") 565 if len(an.children) > 0 { 566 ai.Affiliations = make([]api.AffiliationInfo, len(an.children)) 567 idx := 0 568 for key, child := range an.children { 569 child.fill(append(path, key), &ai.Affiliations[idx]) 570 idx++ 571 } 572 } 573 }