github.com/vmware/govmomi@v0.37.2/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  }