github.com/vmware/govmomi@v0.51.0/ssoadmin/simulator/simulator.go (about)

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