Repository: vmware/govmomi (go)

Issue:
Implement SessionManager.ImpersonateUser().
It seems like ImpersonateUser() function is missing from SessionManager.

Implement SessionManager.ImpersonateUser().

https://vdc-download.vmware.com/vmwb-repository/dcr-public/8946c1b6-2861-4c12-a45f-f14ae0d3b1b9/a5b8094c-c222-4307-9399-3b606a04af55/vim.SessionManager.html#impersonateUser

Pull request description:
api: add session.Manager.ImpersonateUser method
Helper method for calling [vim.SessionManager.impersonateUser](https://dp-downloads.broadcom.com/api-content/apis/API_VWSA_001/8.0U3/html/ReferenceGuides/vim.SessionManager.html#impersonateUser)

govc: add session.login '-as' flag to impersonate user
vcsim: add SessionManager.ImpersonateUser method

Closes #3507

Patch:
diff --git a/govc/USAGE.md b/govc/USAGE.md
index 8d18b6ed..94932c28 100644
--- a/govc/USAGE.md
+++ b/govc/USAGE.md
@@ -5137,6 +5137,7 @@ The session.login command can be used to:
 - Issue a SAML token
 - Renew a SAML token
 - Login using a SAML token
+- Impersonate a user
 - Avoid passing credentials to other govc commands
 - Send an authenticated raw HTTP request
 
@@ -5149,6 +5150,7 @@ Examples:
   govc session.ls -u root@host # Use the cached session with another command
   ticket=$(govc session.login -u root@host -clone)
   govc session.login -u root@host -ticket $ticket
+  govc session.login -u Administrator@vsphere.local:password@host -as other@vsphere.local
   govc session.login -u host -extension com.vmware.vsan.health -cert rui.crt -key rui.key
   token=$(govc session.login -u host -cert user.crt -key user.key -issue) # HoK token
   bearer=$(govc session.login -u user:pass@host -issue) # Bearer token
@@ -5161,6 +5163,7 @@ Examples:
 
 Options:
   -X=                    HTTP method
+  -as=                   Impersonate user
   -clone=false           Acquire clone ticket
   -cookie=               Set HTTP cookie for an existing session
   -extension=            Extension name
diff --git a/govc/session/login.go b/govc/session/login.go
index 88d52e6e..dce91c3c 100644
--- a/govc/session/login.go
+++ b/govc/session/login.go
@@ -53,6 +53,7 @@ type login struct {
 	cookie string
 	token  string
 	ext    string
+	as     string
 	method string
 }
 
@@ -76,6 +77,7 @@ func (cmd *login) Register(ctx context.Context, f *flag.FlagSet) {
 	f.StringVar(&cmd.cookie, "cookie", "", "Set HTTP cookie for an existing session")
 	f.StringVar(&cmd.token, "token", "", "Use SAML token for login or as issue identity")
 	f.StringVar(&cmd.ext, "extension", "", "Extension name")
+	f.StringVar(&cmd.as, "as", "", "Impersonate user")
 	f.StringVar(&cmd.method, "X", "", "HTTP method")
 }
 
@@ -102,6 +104,7 @@ The session.login command can be used to:
 - Issue a SAML token
 - Renew a SAML token
 - Login using a SAML token
+- Impersonate a user
 - Avoid passing credentials to other govc commands
 - Send an authenticated raw HTTP request
 
@@ -114,6 +117,7 @@ Examples:
   govc session.ls -u root@host # Use the cached session with another command
   ticket=$(govc session.login -u root@host -clone)
   govc session.login -u root@host -ticket $ticket
+  govc session.login -u Administrator@vsphere.local:password@host -as other@vsphere.local
   govc session.login -u host -extension com.vmware.vsan.health -cert rui.crt -key rui.key
   token=$(govc session.login -u host -cert user.crt -key user.key -issue) # HoK token
   bearer=$(govc session.login -u user:pass@host -issue) # Bearer token
@@ -237,6 +241,14 @@ func (cmd *login) loginByExtension(ctx context.Context, c *vim25.Client) error {
 	return session.NewManager(c).LoginExtensionByCertificate(ctx, cmd.ext)
 }
 
+func (cmd *login) impersonateUser(ctx context.Context, c *vim25.Client) error {
+	m := session.NewManager(c)
+	if err := m.Login(ctx, cmd.Session.URL.User); err != nil {
+		return err
+	}
+	return m.ImpersonateUser(ctx, cmd.as)
+}
+
 func (cmd *login) setCookie(ctx context.Context, c *vim25.Client) error {
 	url := c.URL()
 	jar := c.Client.Jar
@@ -323,6 +335,8 @@ func (cmd *login) Run(ctx context.Context, f *flag.FlagSet) error {
 		cmd.Session.LoginREST = cmd.loginRestByToken
 	case cmd.ext != "":
 		cmd.Session.LoginSOAP = cmd.loginByExtension
+	case cmd.as != "":
+		cmd.Session.LoginSOAP = cmd.impersonateUser
 	case cmd.issue:
 		cmd.Session.LoginSOAP = nologinSOAP
 		cmd.Session.LoginREST = nologinREST
diff --git a/session/manager.go b/session/manager.go
index e2d70a2f..6a24fb95 100644
--- a/session/manager.go
+++ b/session/manager.go
@@ -291,3 +291,19 @@ func (sm *Manager) UpdateServiceMessage(ctx context.Context, message string) err
 
 	return err
 }
+
+func (sm *Manager) ImpersonateUser(ctx context.Context, name string) error {
+	req := types.ImpersonateUser{
+		This:     sm.Reference(),
+		UserName: name,
+		Locale:   Locale,
+	}
+
+	res, err := methods.ImpersonateUser(ctx, sm.client, &req)
+	if err != nil {
+		return err
+	}
+
+	sm.userSession = &res.Returnval
+	return nil
+}
diff --git a/simulator/session_manager.go b/simulator/session_manager.go
index e9fbfcb6..02e2212c 100644
--- a/simulator/session_manager.go
+++ b/simulator/session_manager.go
@@ -96,6 +96,17 @@ func (m *SessionManager) getSession(id string) (Session, bool) {
 	return s, ok
 }
 
+func (m *SessionManager) findSession(user string) (Session, bool) {
+	sessionMutex.Lock()
+	defer sessionMutex.Unlock()
+	for _, session := range m.sessions {
+		if session.UserName == user {
+			return session, true
+		}
+	}
+	return Session{}, false
+}
+
 func (m *SessionManager) delSession(id string) {
 	sessionMutex.Lock()
 	defer sessionMutex.Unlock()
@@ -273,6 +284,25 @@ func (s *SessionManager) CloneSession(ctx *Context, ticket *types.CloneSession)
 	return body
 }
 
+func (s *SessionManager) ImpersonateUser(ctx *Context, req *types.ImpersonateUser) soap.HasFault {
+	body := new(methods.ImpersonateUserBody)
+
+	session, exists := s.findSession(req.UserName)
+
+	if exists {
+		session.Key = uuid.New().String()
+		ctx.SetSession(session, true)
+
+		body.Res = &types.ImpersonateUserResponse{
+			Returnval: session.UserSession,
+		}
+	} else {
+		body.Fault_ = invalidLogin
+	}
+
+	return body
+}
+
 func (s *SessionManager) AcquireGenericServiceTicket(ticket *types.AcquireGenericServiceTicket) soap.HasFault {
 	return &methods.AcquireGenericServiceTicketBody{
 		Res: &types.AcquireGenericServiceTicketResponse{


Test patch:
diff --git a/govc/test/session.bats b/govc/test/session.bats
index 4e34aa39..5d96de7d 100755
--- a/govc/test/session.bats
+++ b/govc/test/session.bats
@@ -130,6 +130,17 @@ load test_helper
     run govc tags.ls -u "$user@$host"
     assert_failure # logged out of persisted session
 
+    # ImpersonateUser
+
+    run govc session.login -as vcsim
+    assert_failure # user must have an existing session
+
+    run govc session.login -u vcsim:pass@"$host"
+    assert_success
+
+    run govc session.login -as vcsim
+    assert_success
+
     rm -rf "$dir"
 }