diff --git a/pbm/client.go b/pbm/client.go index d8d2131cf..131cb65d9 100644 --- a/pbm/client.go +++ b/pbm/client.go @@ -49,6 +49,7 @@ type Client struct { func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) { sc := c.Client.NewServiceClient(Path, Namespace) + sc.Cookie = sc.SessionCookie // vcSessionCookie soap.Header req := types.PbmRetrieveServiceContent{ This: ServiceInstance, diff --git a/pbm/client_test.go b/pbm/client_test.go index 0500e0509..1adef3b7e 100644 --- a/pbm/client_test.go +++ b/pbm/client_test.go @@ -346,3 +346,28 @@ func TestSupportsEncryption(t *testing.T) { }) }) } + +func TestClientCookie(t *testing.T) { + simulator.Test(func(ctx context.Context, c *vim25.Client) { + pbmc, err := pbm.NewClient(ctx, c) + assert.NoError(t, err) + assert.NotNil(t, pbmc) + + // Using default / expected Header.Cookie.XMLName = vcSessionCookie + ok, err := pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID) + assert.NoError(t, err) + assert.True(t, ok) + + // Using invalid Header.Cookie.XMLName = myCookie + myCookie := c.SessionCookie() + myCookie.XMLName.Local = "myCookie" + + pbmc.Client.Cookie = func() *soap.HeaderElement { + return myCookie + } + + ok, err = pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID) + assert.EqualError(t, err, "ServerFaultCode: NotAuthenticated") + assert.False(t, ok) + }) +} diff --git a/pbm/simulator/simulator.go b/pbm/simulator/simulator.go index 7478b44fa..03fde5821 100644 --- a/pbm/simulator/simulator.go +++ b/pbm/simulator/simulator.go @@ -56,6 +56,7 @@ func New() *simulator.Registry { r := simulator.NewRegistry() r.Namespace = pbm.Namespace r.Path = pbm.Path + r.Cookie = simulator.SOAPCookie r.Put(&ServiceInstance{ ManagedObjectReference: pbm.ServiceInstance, diff --git a/simulator/registry.go b/simulator/registry.go index b40b755c3..749efb7b2 100644 --- a/simulator/registry.go +++ b/simulator/registry.go @@ -78,6 +78,7 @@ type Registry struct { Namespace string Path string Handler func(*Context, *Method) (mo.Reference, types.BaseMethodFault) + Cookie func(*Context) string tagManager tagManager } diff --git a/simulator/session_manager.go b/simulator/session_manager.go index 85ed81538..5a80094b8 100644 --- a/simulator/session_manager.go +++ b/simulator/session_manager.go @@ -329,12 +329,29 @@ type Context struct { Map *Registry } +func SOAPCookie(ctx *Context) string { + if cookie := ctx.Header.Cookie; cookie != nil { + return cookie.Value + } + return "" +} + +func HTTPCookie(ctx *Context) string { + if cookie, err := ctx.req.Cookie(soap.SessionCookieName); err == nil { + return cookie.Value + } + return "" +} + // mapSession maps an HTTP cookie to a Session. func (c *Context) mapSession() { - if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil { - if val, ok := c.svc.sm.getSession(cookie.Value); ok { - c.SetSession(val, false) - } + cookie := c.Map.Cookie + if cookie == nil { + cookie = HTTPCookie + } + + if val, ok := c.svc.sm.getSession(cookie(c)); ok { + c.SetSession(val, false) } } diff --git a/simulator/simulator.go b/simulator/simulator.go index 71c0037cd..e40f2fc29 100644 --- a/simulator/simulator.go +++ b/simulator/simulator.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -497,7 +497,6 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) { Map: s.sdk[r.URL.Path], Context: context.Background(), } - ctx.Map.WithLock(ctx, s.sm, ctx.mapSession) var res soap.HasFault var soapBody interface{} @@ -511,6 +510,7 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) { // Redirect any Fetch method calls to the PropertyCollector singleton method.This = ctx.Map.content().PropertyCollector } + ctx.Map.WithLock(ctx, s.sm, ctx.mapSession) res = s.call(ctx, method) } diff --git a/ssoadmin/client.go b/ssoadmin/client.go index 1b178ddb0..58a557bf1 100644 --- a/ssoadmin/client.go +++ b/ssoadmin/client.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/sts/client.go b/sts/client.go index c51ec2a00..5c2eaed73 100644 --- a/sts/client.go +++ b/sts/client.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, diff --git a/vim25/soap/client.go b/vim25/soap/client.go index d3e9e093b..7b4b26ab6 100644 --- a/vim25/soap/client.go +++ b/vim25/soap/client.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -86,7 +86,11 @@ type Client struct { Types types.Func `json:"types"` UserAgent string `json:"userAgent"` - cookie string + // Cookie returns a value for the SOAP Header.Cookie. + // This SOAP request header is used for authentication by + // API endpoints such as pbm, vslm and sms. + // When nil, no SOAP Header.Cookie is set. + Cookie func() *HeaderElement insecureCookies bool useJSON bool @@ -210,6 +214,16 @@ func (c *Client) NewServiceClient(path string, namespace string) *Client { return c.newServiceClientWithTransport(path, namespace, c.t) } +// SessionCookie returns a SessionCookie with value of the vmware_soap_session http.Cookie. +func (c *Client) SessionCookie() *HeaderElement { + for _, cookie := range c.Jar.Cookies(c.URL()) { + if cookie.Name == SessionCookieName { + return &HeaderElement{Value: cookie.Value} + } + } + return nil +} + func (c *Client) newServiceClientWithTransport(path string, namespace string, t *http.Transport) *Client { vc := c.URL() u, err := url.Parse(path) @@ -234,14 +248,6 @@ func (c *Client) newServiceClientWithTransport(path string, namespace string, t // Copy the cookies client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u)) - // Set SOAP Header cookie - for _, cookie := range client.Jar.Cookies(u) { - if cookie.Name == SessionCookieName { - client.cookie = cookie.Value - break - } - } - // Copy any query params (e.g. GOVMOMI_TUNNEL_PROXY_PORT used in testing) client.u.RawQuery = vc.RawQuery @@ -718,8 +724,10 @@ func (c *Client) soapRoundTrip(ctx context.Context, reqBody, resBody HasFault) e h.ID = id } - h.Cookie = c.cookie - if h.Cookie != "" || h.ID != "" || h.Security != nil { + if c.Cookie != nil { + h.Cookie = c.Cookie() + } + if h.Cookie != nil || h.ID != "" || h.Security != nil { reqEnv.Header = &h // XML marshal header only if a field is set } diff --git a/vim25/soap/json_client.go b/vim25/soap/json_client.go index ca5075de3..b5d25b596 100644 --- a/vim25/soap/json_client.go +++ b/vim25/soap/json_client.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2023-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -71,8 +71,10 @@ func (c *Client) invoke(ctx context.Context, this types.ManagedObjectReference, return err } - if len(c.cookie) != 0 { - req.Header.Add(sessionHeader, c.cookie) + if c.Cookie != nil { + if cookie := c.Cookie(); cookie != nil { + req.Header.Add(sessionHeader, cookie.Value) + } } result, err := getSOAPResultPtr(res) @@ -156,8 +158,10 @@ func isError(statusCode int) bool { // session header. func (c *Client) checkForSessionHeader(resp *http.Response) { sessionKey := resp.Header.Get(sessionHeader) - if len(sessionKey) > 0 { - c.cookie = sessionKey + if sessionKey != "" { + c.Cookie = func() *HeaderElement { + return &HeaderElement{Value: sessionKey} + } } } diff --git a/vim25/soap/json_client_test.go b/vim25/soap/json_client_test.go index 7511a23ab..7ef6eec26 100644 --- a/vim25/soap/json_client_test.go +++ b/vim25/soap/json_client_test.go @@ -1,5 +1,5 @@ /* -Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved. +Copyright (c) 2023-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -206,7 +206,9 @@ func TestFullRequestCycle(t *testing.T) { c := NewClient(addr, true) c.Namespace = "urn:vim25" c.Version = "8.0.0.1" - c.cookie = "(original)" + c.Cookie = func() *HeaderElement { + return &HeaderElement{Value: "(original)"} + } c.UseJSON(true) c.Transport = &mockHTTP{ diff --git a/vim25/soap/soap.go b/vim25/soap/soap.go index a8dc121ba..d7703ae8f 100644 --- a/vim25/soap/soap.go +++ b/vim25/soap/soap.go @@ -1,11 +1,11 @@ /* -Copyright (c) 2014-2018 VMware, Inc. All Rights Reserved. +Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 +http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -21,12 +21,18 @@ import ( "github.com/vmware/govmomi/vim25/xml" ) +// HeaderElement allows changing the default XMLName (e.g. Cookie's default of vcSessionCookie) +type HeaderElement struct { + XMLName xml.Name + Value string `xml:",chardata"` +} + // Header includes optional soap Header fields. type Header struct { - Action string `xml:"-"` // Action is the 'SOAPAction' HTTP header value. Defaults to "Client.Namespace/Client.Version". - Cookie string `xml:"vcSessionCookie,omitempty"` // Cookie is a vCenter session cookie that can be used with other SDK endpoints (e.g. pbm). - ID string `xml:"operationID,omitempty"` // ID is the operationID used by ESX/vCenter logging for correlation. - Security interface{} `xml:",omitempty"` // Security is used for SAML token authentication and request signing. + Action string `xml:"-"` // Action is the 'SOAPAction' HTTP header value. Defaults to "Client.Namespace/Client.Version". + Cookie *HeaderElement `xml:"vcSessionCookie,omitempty"` // Cookie is a vCenter session cookie that can be used with other SDK endpoints (e.g. pbm, vslm). + ID string `xml:"operationID,omitempty"` // ID is the operationID used by ESX/vCenter logging for correlation. + Security interface{} `xml:",omitempty"` // Security is used for SAML token authentication and request signing. } type Envelope struct { diff --git a/vslm/client.go b/vslm/client.go index 4bbb70b59..5cfb8a0b7 100644 --- a/vslm/client.go +++ b/vslm/client.go @@ -46,6 +46,8 @@ type Client struct { func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) { sc := c.Client.NewServiceClient(Path, Namespace) + sc.Cookie = sc.SessionCookie // vcSessionCookie soap.Header + req := types.RetrieveContent{ This: ServiceInstance, }