github.com/vmware/govmomi@v0.37.1/ssoadmin/simulator/simulator.go (about) 1 /* 2 Copyright (c) 2022-2023 VMware, Inc. 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 simulator 18 19 import ( 20 "net/url" 21 "strings" 22 23 "github.com/vmware/govmomi/simulator" 24 "github.com/vmware/govmomi/ssoadmin" 25 "github.com/vmware/govmomi/ssoadmin/methods" 26 "github.com/vmware/govmomi/ssoadmin/types" 27 "github.com/vmware/govmomi/vim25/soap" 28 vim "github.com/vmware/govmomi/vim25/types" 29 ) 30 31 func init() { 32 simulator.RegisterEndpoint(func(s *simulator.Service, r *simulator.Registry) { 33 if r.IsVPX() { 34 s.RegisterSDK(New(r, s.Listen), ssoadmin.SystemPath) 35 } 36 }) 37 } 38 39 var content = types.AdminServiceContent{ 40 SessionManager: vim.ManagedObjectReference{Type: "SsoSessionManager", Value: "ssoSessionManager"}, 41 ConfigurationManagementService: vim.ManagedObjectReference{Type: "SsoAdminConfigurationManagementService", Value: "configurationManagementService"}, 42 SmtpManagementService: vim.ManagedObjectReference{Type: "SsoAdminSmtpManagementService", Value: "smtpManagementService"}, 43 PrincipalDiscoveryService: vim.ManagedObjectReference{Type: "SsoAdminPrincipalDiscoveryService", Value: "principalDiscoveryService"}, 44 PrincipalManagementService: vim.ManagedObjectReference{Type: "SsoAdminPrincipalManagementService", Value: "principalManagementService"}, 45 RoleManagementService: vim.ManagedObjectReference{Type: "SsoAdminRoleManagementService", Value: "roleManagementService"}, 46 PasswordPolicyService: vim.ManagedObjectReference{Type: "SsoAdminPasswordPolicyService", Value: "passwordPolicyService"}, 47 LockoutPolicyService: vim.ManagedObjectReference{Type: "SsoAdminLockoutPolicyService", Value: "lockoutPolicyService"}, 48 DomainManagementService: vim.ManagedObjectReference{Type: "SsoAdminDomainManagementService", Value: "domainManagementService"}, 49 IdentitySourceManagementService: vim.ManagedObjectReference{Type: "SsoAdminIdentitySourceManagementService", Value: "identitySourceManagementService"}, 50 SystemManagementService: vim.ManagedObjectReference{Type: "SsoAdminSystemManagementService", Value: "systemManagementService"}, 51 DeploymentInformationService: vim.ManagedObjectReference{Type: "SsoAdminDeploymentInformationService", Value: "deploymentInformationService"}, 52 ReplicationService: vim.ManagedObjectReference{Type: "SsoAdminReplicationService", Value: "replicationService"}, 53 } 54 55 var groupcheckContent = types.GroupcheckServiceContent{ 56 SessionManager: vim.ManagedObjectReference{Type: "SsoSessionManager", Value: "ssoSessionManager"}, 57 GroupCheckService: vim.ManagedObjectReference{Type: "SsoGroupcheckGroupCheckService", Value: "groupCheckService"}, 58 } 59 60 var groupcheckServiceInstance = vim.ManagedObjectReference{ 61 Type: "SsoGroupcheckServiceInstance", Value: "ServiceInstance", 62 } 63 64 var IdentitySources = types.IdentitySources{ 65 System: types.IdentitySource{ 66 Name: "vsphere.local", 67 Domains: []types.Domain{{Name: "vsphere.local", Alias: "vmwarem"}}, 68 }, 69 LocalOS: &types.IdentitySource{ 70 Name: "localos", 71 Domains: []types.Domain{{Name: "localos", Alias: ""}}, 72 }, 73 NativeAD: nil, 74 LDAPS: []types.LdapIdentitySource{ 75 { 76 IdentitySource: types.IdentitySource{ 77 Name: "example.com", 78 Domains: []types.Domain{{Name: "example.com", Alias: "examplex"}}, 79 }, 80 Type: "ActiveDirectory", 81 Details: types.LdapIdentitySourceDetails{ 82 FriendlyName: "foo", 83 UserBaseDn: "ou=People,dc=example,dc=org", 84 GroupBaseDn: "ou=People,dc=example,dc=org", 85 PrimaryURL: "ldap://10.168.194.120:389", 86 FailoverURL: "", 87 }, 88 AuthenticationDetails: types.AuthenticationDetails{ 89 AuthenticationType: "PASSWORD", 90 Username: "cn=admin,dc=example,dc=org", 91 }, 92 }, 93 }, 94 } 95 96 type ServiceInstance struct { 97 vim.ManagedObjectReference 98 } 99 100 type GroupcheckServiceInstance struct { 101 vim.ManagedObjectReference 102 } 103 104 type GroupcheckService struct { 105 vim.ManagedObjectReference 106 107 m *PrincipalManagementService 108 } 109 110 type SessionManager struct { 111 vim.ManagedObjectReference 112 } 113 114 type IdentitySourceManagementService struct { 115 vim.ManagedObjectReference 116 } 117 118 type PrincipalManagementService struct { 119 vim.ManagedObjectReference 120 121 dir map[types.PrincipalId]principal 122 } 123 124 type PrincipalDiscoveryService struct { 125 vim.ManagedObjectReference 126 127 m *PrincipalManagementService 128 } 129 130 type principal struct { 131 group *types.AdminGroup 132 members map[types.PrincipalId]principal 133 solution *types.AdminSolutionUser 134 person *types.AdminPersonUser 135 password string 136 } 137 138 func New(vc *simulator.Registry, u *url.URL) *simulator.Registry { 139 r := simulator.NewRegistry() 140 r.Namespace = ssoadmin.Namespace 141 r.Path = ssoadmin.Path 142 143 settings := vc.OptionManager().Setting 144 for i := range settings { 145 setting := settings[i].GetOptionValue() 146 if setting.Key == "config.vpxd.sso.admin.uri" { 147 endpoint, _ := url.Parse(setting.Value.(string)) 148 r.Path = endpoint.Path 149 } 150 } 151 152 r.Put(&ServiceInstance{ 153 ManagedObjectReference: ssoadmin.ServiceInstance, 154 }) 155 156 r.Put(&GroupcheckServiceInstance{ 157 ManagedObjectReference: groupcheckServiceInstance, 158 }) 159 160 r.Put(&SessionManager{ 161 ManagedObjectReference: content.SessionManager, 162 }) 163 164 r.Put(&IdentitySourceManagementService{ 165 ManagedObjectReference: content.IdentitySourceManagementService, 166 }) 167 168 m := &PrincipalManagementService{ 169 ManagedObjectReference: content.PrincipalManagementService, 170 dir: make(map[types.PrincipalId]principal), 171 } 172 r.Put(m) 173 m.createDefaultUser(u.User) 174 vc.SessionManager().ValidLogin = m.validLogin 175 176 r.Put(&PrincipalDiscoveryService{ 177 ManagedObjectReference: content.PrincipalDiscoveryService, 178 m: m, 179 }) 180 181 r.Put(&GroupcheckService{ 182 ManagedObjectReference: groupcheckContent.GroupCheckService, 183 m: m, 184 }) 185 186 return r 187 } 188 189 func (s *ServiceInstance) SsoAdminServiceInstance(ctx *simulator.Context, _ *types.SsoAdminServiceInstance) soap.HasFault { 190 return &methods.SsoAdminServiceInstanceBody{ 191 Res: &types.SsoAdminServiceInstanceResponse{ 192 Returnval: content, 193 }, 194 } 195 } 196 197 func (s *GroupcheckServiceInstance) SsoGroupcheckServiceInstance(ctx *simulator.Context, _ *types.SsoGroupcheckServiceInstance) soap.HasFault { 198 return &methods.SsoGroupcheckServiceInstanceBody{ 199 Res: &types.SsoGroupcheckServiceInstanceResponse{ 200 Returnval: groupcheckContent, 201 }, 202 } 203 } 204 205 func (s *GroupcheckService) FindAllParentGroups(ctx *simulator.Context, req *types.FindAllParentGroups) soap.HasFault { 206 body := new(methods.FindAllParentGroupsBody) 207 208 p, ok := s.m.dir[req.UserId] 209 if !ok || p.person == nil { 210 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 211 return body 212 } 213 214 body.Res = new(types.FindAllParentGroupsResponse) 215 216 for gid, p := range s.m.dir { 217 if p.group == nil { 218 continue 219 } 220 221 for id, m := range p.members { 222 if m.person == nil { 223 continue 224 } 225 if id == req.UserId { 226 body.Res.Returnval = append(body.Res.Returnval, gid) 227 } 228 } 229 } 230 231 return body 232 } 233 234 func (s *SessionManager) Login(ctx *simulator.Context, req *types.Login) soap.HasFault { 235 body := new(methods.LoginBody) 236 237 // TODO: check for Assertion>Subject>NameID as simulator.SessionManager.LoginByToken does 238 body.Res = new(types.LoginResponse) 239 240 return body 241 } 242 243 func (s *SessionManager) Logout(ctx *simulator.Context, req *types.Logout) soap.HasFault { 244 return &methods.LogoutBody{ 245 Res: new(types.LogoutResponse), 246 } 247 } 248 249 func (s *IdentitySourceManagementService) Get(ctx *simulator.Context, _ *types.Get) soap.HasFault { 250 sources := IdentitySources 251 sources.All = nil 252 sources.All = append(sources.All, sources.System) 253 254 if sources.LocalOS != nil { 255 sources.All = append(sources.All, *sources.LocalOS) 256 } 257 258 if sources.NativeAD != nil { 259 sources.All = append(sources.All, *sources.NativeAD) 260 } 261 262 for _, ldap := range sources.LDAPS { 263 sources.All = append(sources.All, ldap.IdentitySource) 264 } 265 266 return &methods.GetBody{ 267 Res: &types.GetResponse{ 268 Returnval: sources, 269 }, 270 } 271 } 272 273 func (s *PrincipalDiscoveryService) FindUser(ctx *simulator.Context, req *types.FindUser) soap.HasFault { 274 body := &methods.FindUserBody{ 275 Res: new(types.FindUserResponse), 276 } 277 278 p, ok := s.m.dir[req.UserId] 279 if !ok { 280 return body 281 } 282 283 var user *types.AdminUser 284 285 switch { 286 case p.person != nil: 287 user = &types.AdminUser{ 288 Kind: "person", 289 Id: p.person.Id, 290 Description: p.person.Details.Description, 291 } 292 case p.solution != nil: 293 user = &types.AdminUser{ 294 Kind: "solution", 295 Id: p.solution.Id, 296 Description: p.solution.Details.Description, 297 } 298 299 } 300 301 body.Res.Returnval = user 302 303 return body 304 } 305 306 func (s *PrincipalDiscoveryService) FindPersonUser(ctx *simulator.Context, req *types.FindPersonUser) soap.HasFault { 307 body := &methods.FindPersonUserBody{ 308 Res: new(types.FindPersonUserResponse), 309 } 310 311 p, ok := s.m.dir[req.UserId] 312 if ok { 313 body.Res.Returnval = p.person 314 } 315 316 return body 317 } 318 319 func (s *PrincipalDiscoveryService) FindPersonUsers(ctx *simulator.Context, req *types.FindPersonUsers) soap.HasFault { 320 body := &methods.FindPersonUsersBody{ 321 Res: &types.FindPersonUsersResponse{}, 322 } 323 324 for _, p := range s.m.dir { 325 if p.person == nil { 326 continue 327 } 328 329 if domain := req.Criteria.Domain; domain != "" { 330 if p.person.Id.Domain != domain { 331 continue 332 } 333 } 334 335 if search := req.Criteria.SearchString; search != "" { 336 if !strings.Contains(p.person.Id.Name, search) { 337 continue 338 } 339 } 340 341 body.Res.Returnval = append(body.Res.Returnval, *p.person) 342 } 343 344 return body 345 } 346 347 func (s *PrincipalManagementService) createDefaultUser(u *url.Userinfo) { 348 user := &types.AdminPersonUser{ 349 Id: types.PrincipalId{ 350 Name: u.Username(), 351 Domain: IdentitySources.System.Name, 352 }, 353 } 354 355 pass, _ := u.Password() 356 s.dir[user.Id] = principal{ 357 person: user, 358 password: pass, 359 } 360 } 361 362 func (s *PrincipalManagementService) validLogin(login *vim.Login) bool { 363 id := parseID(login.UserName) 364 365 if p, ok := s.dir[id]; ok && p.person != nil { 366 return p.password == login.Password 367 } 368 369 return false 370 } 371 372 func (s *PrincipalDiscoveryService) FindSolutionUser(ctx *simulator.Context, req *types.FindSolutionUser) soap.HasFault { 373 body := &methods.FindSolutionUserBody{ 374 Res: new(types.FindSolutionUserResponse), 375 } 376 377 id := parseID(req.UserName) 378 p, ok := s.m.dir[id] 379 if ok { 380 body.Res.Returnval = p.solution 381 } 382 383 return body 384 } 385 386 func (s *PrincipalDiscoveryService) FindSolutionUsers(ctx *simulator.Context, req *types.FindSolutionUsers) soap.HasFault { 387 body := &methods.FindSolutionUsersBody{ 388 Res: new(types.FindSolutionUsersResponse), 389 } 390 391 for _, p := range s.m.dir { 392 if p.solution == nil { 393 continue 394 } 395 396 if search := req.SearchString; search != "" { 397 if !strings.Contains(p.solution.Id.Name, search) { 398 continue 399 } 400 } 401 402 body.Res.Returnval = append(body.Res.Returnval, *p.solution) 403 } 404 405 return body 406 } 407 408 func (s *PrincipalManagementService) CreateLocalPersonUser(ctx *simulator.Context, req *types.CreateLocalPersonUser) soap.HasFault { 409 body := new(methods.CreateLocalPersonUserBody) 410 411 user := &types.AdminPersonUser{ 412 Id: types.PrincipalId{ 413 Name: req.UserName, 414 Domain: IdentitySources.System.Name, 415 }, 416 Details: req.UserDetails, 417 } 418 419 if _, ok := s.dir[user.Id]; ok { 420 body.Fault_ = simulator.Fault("", new(vim.DuplicateName)) 421 return body 422 } 423 424 s.dir[user.Id] = principal{ 425 person: user, 426 password: req.Password, 427 } 428 429 body.Res = new(types.CreateLocalPersonUserResponse) 430 431 return body 432 } 433 434 func (s *PrincipalManagementService) UpdateLocalPersonUserDetails(ctx *simulator.Context, req *types.UpdateLocalPersonUserDetails) soap.HasFault { 435 body := &methods.UpdateLocalPersonUserDetailsBody{ 436 Res: new(types.UpdateLocalPersonUserDetailsResponse), 437 } 438 439 id := parseID(req.UserName) 440 p, ok := s.dir[id] 441 if !ok || p.person == nil { 442 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 443 return body 444 } 445 446 p.person.Details = req.UserDetails 447 448 return body 449 } 450 451 func (s *PrincipalManagementService) ResetLocalPersonUserPassword(ctx *simulator.Context, req *types.ResetLocalPersonUserPassword) soap.HasFault { 452 body := &methods.ResetLocalPersonUserPasswordBody{ 453 Res: new(types.ResetLocalPersonUserPasswordResponse), 454 } 455 456 id := parseID(req.UserName) 457 p, ok := s.dir[id] 458 if !ok || p.person == nil { 459 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 460 return body 461 } 462 463 p.password = req.NewPassword 464 465 return body 466 } 467 468 func (s *PrincipalManagementService) CreateLocalSolutionUser(ctx *simulator.Context, req *types.CreateLocalSolutionUser) soap.HasFault { 469 body := new(methods.CreateLocalSolutionUserBody) 470 471 user := &types.AdminSolutionUser{ 472 Id: types.PrincipalId{ 473 Name: req.UserName, 474 Domain: IdentitySources.System.Name, 475 }, 476 Details: req.UserDetails, 477 } 478 479 if _, ok := s.dir[user.Id]; ok { 480 body.Fault_ = simulator.Fault("", new(vim.DuplicateName)) 481 return body 482 } 483 484 s.dir[user.Id] = principal{ 485 solution: user, 486 } 487 488 body.Res = new(types.CreateLocalSolutionUserResponse) 489 490 return body 491 } 492 493 func (s *PrincipalManagementService) UpdateLocalSolutionUserDetails(ctx *simulator.Context, req *types.UpdateLocalSolutionUserDetails) soap.HasFault { 494 body := &methods.UpdateLocalSolutionUserDetailsBody{ 495 Res: new(types.UpdateLocalSolutionUserDetailsResponse), 496 } 497 498 id := parseID(req.UserName) 499 p, ok := s.dir[id] 500 if !ok || p.solution == nil { 501 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 502 return body 503 } 504 505 p.solution.Details = req.UserDetails 506 507 return body 508 } 509 510 func (s *PrincipalManagementService) DeleteLocalPrincipal(ctx *simulator.Context, req *types.DeleteLocalPrincipal) soap.HasFault { 511 body := new(methods.DeleteLocalPrincipalBody) 512 513 id := parseID(req.PrincipalName) 514 515 if _, ok := s.dir[id]; ok { 516 delete(s.dir, id) 517 for _, p := range s.dir { 518 if p.group != nil { 519 delete(p.members, id) 520 } 521 } 522 body.Res = new(types.DeleteLocalPrincipalResponse) 523 } else { 524 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 525 } 526 527 return body 528 } 529 530 func parseID(name string) types.PrincipalId { 531 p := strings.SplitN(name, "@", 2) 532 id := types.PrincipalId{Name: p[0]} 533 if len(p) == 2 { 534 id.Domain = p[1] 535 } else { 536 id.Domain = IdentitySources.System.Name 537 } 538 return id 539 } 540 541 func (s *PrincipalDiscoveryService) FindGroup(ctx *simulator.Context, req *types.FindGroup) soap.HasFault { 542 body := &methods.FindGroupBody{ 543 Res: new(types.FindGroupResponse), 544 } 545 546 p, ok := s.m.dir[req.GroupId] 547 if !ok { 548 return body 549 } 550 551 body.Res.Returnval = p.group 552 553 return body 554 } 555 556 func (s *PrincipalDiscoveryService) FindGroups(ctx *simulator.Context, req *types.FindGroups) soap.HasFault { 557 body := &methods.FindGroupsBody{ 558 Res: &types.FindGroupsResponse{}, 559 } 560 561 for _, p := range s.m.dir { 562 if p.group == nil { 563 continue 564 } 565 566 if domain := req.Criteria.Domain; domain != "" { 567 if p.group.Id.Domain != domain { 568 continue 569 } 570 } 571 572 if search := req.Criteria.SearchString; search != "" { 573 if !strings.Contains(p.group.Id.Name, search) { 574 continue 575 } 576 } 577 578 body.Res.Returnval = append(body.Res.Returnval, *p.group) 579 } 580 581 return body 582 } 583 584 func (s *PrincipalDiscoveryService) FindGroupsInGroup(ctx *simulator.Context, req *types.FindGroupsInGroup) soap.HasFault { 585 body := &methods.FindGroupsInGroupBody{ 586 Res: &types.FindGroupsInGroupResponse{}, 587 } 588 589 for _, p := range s.m.dir { 590 if p.group == nil { 591 continue 592 } 593 594 for id := range p.members { 595 if search := req.SearchString; search != "" { 596 if !strings.Contains(id.Name, search) { 597 continue 598 } 599 } 600 601 body.Res.Returnval = append(body.Res.Returnval, *p.group) 602 } 603 } 604 605 return body 606 } 607 608 func (s *PrincipalManagementService) CreateLocalGroup(ctx *simulator.Context, req *types.CreateLocalGroup) soap.HasFault { 609 body := new(methods.CreateLocalGroupBody) 610 611 group := &types.AdminGroup{ 612 Id: types.PrincipalId{ 613 Name: req.GroupName, 614 Domain: IdentitySources.System.Name, 615 }, 616 Details: req.GroupDetails, 617 } 618 619 if _, ok := s.dir[group.Id]; ok { 620 body.Fault_ = simulator.Fault("", new(vim.DuplicateName)) 621 return body 622 } 623 624 s.dir[group.Id] = principal{ 625 group: group, 626 members: make(map[types.PrincipalId]principal), 627 } 628 629 body.Res = new(types.CreateLocalGroupResponse) 630 631 return body 632 } 633 634 func (s *PrincipalManagementService) UpdateLocalGroupDetails(ctx *simulator.Context, req *types.UpdateLocalGroupDetails) soap.HasFault { 635 body := &methods.UpdateLocalGroupDetailsBody{ 636 Res: new(types.UpdateLocalGroupDetailsResponse), 637 } 638 639 id := parseID(req.GroupName) 640 p, ok := s.dir[id] 641 if !ok || p.group == nil { 642 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 643 return body 644 } 645 646 p.group.Details = req.GroupDetails 647 648 return body 649 } 650 651 func (s *PrincipalManagementService) AddUsersToLocalGroup(ctx *simulator.Context, req *types.AddUsersToLocalGroup) soap.HasFault { 652 body := &methods.AddUsersToLocalGroupBody{ 653 Res: new(types.AddUsersToLocalGroupResponse), 654 } 655 656 id := parseID(req.GroupName) 657 g, ok := s.dir[id] 658 if !ok || g.group == nil { 659 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 660 return body 661 } 662 663 res := new(types.AddUsersToLocalGroupResponse) 664 665 for _, id := range req.UserIds { 666 p, ok := s.dir[id] 667 if !ok || p.person == nil { 668 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 669 return body 670 } 671 672 added := false 673 if _, ok := g.members[id]; !ok { 674 g.members[id] = p 675 } else { 676 added = true 677 } 678 679 res.Returnval = append(res.Returnval, added) 680 } 681 682 body.Res = res 683 684 return body 685 } 686 687 func (s *PrincipalManagementService) AddGroupsToLocalGroup(ctx *simulator.Context, req *types.AddGroupsToLocalGroup) soap.HasFault { 688 body := &methods.AddGroupsToLocalGroupBody{ 689 Res: new(types.AddGroupsToLocalGroupResponse), 690 } 691 692 id := parseID(req.GroupName) 693 g, ok := s.dir[id] 694 if !ok || g.group == nil { 695 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 696 return body 697 } 698 699 res := new(types.AddGroupsToLocalGroupResponse) 700 701 for _, id := range req.GroupIds { 702 p, ok := s.dir[id] 703 if !ok || p.group == nil { 704 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 705 return body 706 } 707 708 added := false 709 if _, ok := g.members[id]; !ok { 710 g.members[id] = p 711 } else { 712 added = true 713 } 714 715 res.Returnval = append(res.Returnval, added) 716 } 717 718 body.Res = res 719 720 return body 721 } 722 723 func (s *PrincipalManagementService) RemovePrincipalsFromLocalGroup(ctx *simulator.Context, req *types.RemovePrincipalsFromLocalGroup) soap.HasFault { 724 body := &methods.RemovePrincipalsFromLocalGroupBody{ 725 Res: new(types.RemovePrincipalsFromLocalGroupResponse), 726 } 727 728 id := parseID(req.GroupName) 729 g, ok := s.dir[id] 730 if !ok || g.group == nil { 731 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 732 return body 733 } 734 735 res := new(types.RemovePrincipalsFromLocalGroupResponse) 736 737 for _, id := range req.PrincipalsIds { 738 _, ok := s.dir[id] 739 if !ok { 740 body.Fault_ = simulator.Fault("", new(vim.NotFound)) 741 return body 742 } 743 744 removed := false 745 if _, ok := g.members[id]; ok { 746 delete(g.members, id) 747 removed = true 748 } 749 750 res.Returnval = append(res.Returnval, removed) 751 } 752 753 body.Res = res 754 755 return body 756 }