Skip to content

Commit be1221b

Browse files
committed
api: add override mechanism for SOAP Header.Cookie
Sessions are consumed by API endpoints via one of: - HTTP Cookie (vim25 for example) - HTTP Header (vmodl2 /rest and /api) - SOAP Header (pbm, vslm, sms for example) The soap.Client had always set the SOAP Header cookie regardless if consumed by an API endpoint. Clients can now opt-out of this behavior if they don't need it, such as sts and ssoadmin, to avoid sending the SOAP Header. This also allows clients to change the name of soap.Header.Cookie (default is still "vcSessionCookie") vcsim: add override mechanism for session mapping On the simulator side, API endpoints can specify where a session is sourced from. Default is still the "vmware_soap_session" HTTP Cookie. The pbm simulator now specifies the "vcSessionCookie" SOAP Header, behaving as real pbm/VC does. Signed-off-by: Doug MacEachern <[email protected]>
1 parent ab30b51 commit be1221b

File tree

11 files changed

+105
-31
lines changed

11 files changed

+105
-31
lines changed

Diff for: pbm/client_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package pbm_test
1818

1919
import (
2020
"context"
21+
"encoding/xml"
2122
"os"
2223
"reflect"
2324
"sort"
@@ -346,3 +347,35 @@ func TestSupportsEncryption(t *testing.T) {
346347
})
347348
})
348349
}
350+
351+
func TestClientCookie(t *testing.T) {
352+
simulator.Test(func(ctx context.Context, c *vim25.Client) {
353+
pbmc, err := pbm.NewClient(ctx, c)
354+
assert.NoError(t, err)
355+
assert.NotNil(t, pbmc)
356+
357+
// Using expected Header.Cookie.XMLName = vcSessionCookie
358+
pbmc.Client.Cookie = func() any {
359+
return struct {
360+
XMLName xml.Name `xml:"vcSessionCookie"`
361+
Value string `xml:",chardata"`
362+
}{Value: c.SessionCookie()}
363+
}
364+
365+
ok, err := pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID)
366+
assert.NoError(t, err)
367+
assert.True(t, ok)
368+
369+
// Using invalid Header.Cookie.XMLName = myCookie
370+
pbmc.Client.Cookie = func() any {
371+
return struct {
372+
XMLName xml.Name `xml:"myCookie"`
373+
Value string `xml:",chardata"`
374+
}{Value: c.SessionCookie()}
375+
}
376+
377+
ok, err = pbmc.SupportsEncryption(ctx, pbmsim.DefaultEncryptionProfileID)
378+
assert.EqualError(t, err, "ServerFaultCode: NotAuthenticated")
379+
assert.False(t, ok)
380+
})
381+
}

Diff for: pbm/simulator/simulator.go

+1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ func New() *simulator.Registry {
5656
r := simulator.NewRegistry()
5757
r.Namespace = pbm.Namespace
5858
r.Path = pbm.Path
59+
r.Cookie = simulator.SOAPCookie
5960

6061
r.Put(&ServiceInstance{
6162
ManagedObjectReference: pbm.ServiceInstance,

Diff for: simulator/registry.go

+1
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ type Registry struct {
7878
Namespace string
7979
Path string
8080
Handler func(*Context, *Method) (mo.Reference, types.BaseMethodFault)
81+
Cookie func(*Context) string
8182

8283
tagManager tagManager
8384
}

Diff for: simulator/session_manager.go

+20-4
Original file line numberDiff line numberDiff line change
@@ -329,12 +329,28 @@ type Context struct {
329329
Map *Registry
330330
}
331331

332+
func SOAPCookie(ctx *Context) string {
333+
var cookie string
334+
_ = ctx.Header.Cookie.(*Element).Decode(&cookie)
335+
return cookie
336+
}
337+
338+
func HTTPCookie(ctx *Context) string {
339+
if cookie, err := ctx.req.Cookie(soap.SessionCookieName); err == nil {
340+
return cookie.Value
341+
}
342+
return ""
343+
}
344+
332345
// mapSession maps an HTTP cookie to a Session.
333346
func (c *Context) mapSession() {
334-
if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil {
335-
if val, ok := c.svc.sm.getSession(cookie.Value); ok {
336-
c.SetSession(val, false)
337-
}
347+
cookie := c.Map.Cookie
348+
if cookie == nil {
349+
cookie = HTTPCookie
350+
}
351+
352+
if val, ok := c.svc.sm.getSession(cookie(c)); ok {
353+
c.SetSession(val, false)
338354
}
339355
}
340356

Diff for: simulator/simulator.go

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2017-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2017-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
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) {
497497
Map: s.sdk[r.URL.Path],
498498
Context: context.Background(),
499499
}
500-
ctx.Map.WithLock(ctx, s.sm, ctx.mapSession)
501500

502501
var res soap.HasFault
503502
var soapBody interface{}
@@ -511,6 +510,7 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) {
511510
// Redirect any Fetch method calls to the PropertyCollector singleton
512511
method.This = ctx.Map.content().PropertyCollector
513512
}
513+
ctx.Map.WithLock(ctx, s.sm, ctx.mapSession)
514514
res = s.call(ctx, method)
515515
}
516516

@@ -917,6 +917,10 @@ func (e *Element) decoder() *xml.Decoder {
917917
}
918918

919919
func (e *Element) Decode(val interface{}) error {
920+
if s, ok := val.(*string); ok {
921+
*s = e.inner.Content
922+
return nil
923+
}
920924
return e.decoder().DecodeElement(val, &e.start)
921925
}
922926

@@ -926,6 +930,7 @@ func UnmarshalBody(typeFunc func(string) (reflect.Type, bool), data []byte) (*Me
926930
req := soap.Envelope{
927931
Header: &soap.Header{
928932
Security: new(Element),
933+
Cookie: new(Element),
929934
},
930935
Body: body,
931936
}

Diff for: ssoadmin/client.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -95,6 +95,7 @@ func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
9595
url := getEndpointURL(ctx, c)
9696
sc := c.NewServiceClient(url, Namespace)
9797
sc.Version = Version
98+
sc.Cookie = nil // No SOAP Header.Cookie needed
9899

99100
admin := &Client{
100101
Client: sc,

Diff for: sts/client.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2018-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2018-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -78,6 +78,7 @@ func NewClient(ctx context.Context, c *vim25.Client) (*Client, error) {
7878

7979
url := getEndpointURL(ctx, c)
8080
sc := c.Client.NewServiceClient(url, Namespace)
81+
sc.Cookie = nil // No SOAP Header.Cookie needed
8182

8283
return &Client{sc, sc}, nil
8384
}

Diff for: vim25/soap/client.go

+27-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2014-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -86,7 +86,13 @@ type Client struct {
8686
Types types.Func `json:"types"`
8787
UserAgent string `json:"userAgent"`
8888

89-
cookie string
89+
// Cookie returns a value for the SOAP Header.Cookie.
90+
// This SOAP request header is used for authentication by
91+
// API endpoints such as pbm, vslm and sms.
92+
// This field is set to Client.SessionCookie by default
93+
// and uses "vcSessionCookie" for the Header.Cookie name.
94+
// When set to nil, no SOAP Header.Cookie is set.
95+
Cookie func() any
9096
insecureCookies bool
9197

9298
useJSON bool
@@ -210,6 +216,20 @@ func (c *Client) NewServiceClient(path string, namespace string) *Client {
210216
return c.newServiceClientWithTransport(path, namespace, c.t)
211217
}
212218

219+
// SessionCookie returns the value of the vmware_soap_session cookie.
220+
func (c *Client) SessionCookie() string {
221+
for _, cookie := range c.Jar.Cookies(c.URL()) {
222+
if cookie.Name == SessionCookieName {
223+
return cookie.Value
224+
}
225+
}
226+
return ""
227+
}
228+
229+
func (c *Client) anySessionCookie() any {
230+
return c.SessionCookie()
231+
}
232+
213233
func (c *Client) newServiceClientWithTransport(path string, namespace string, t *http.Transport) *Client {
214234
vc := c.URL()
215235
u, err := url.Parse(path)
@@ -234,13 +254,7 @@ func (c *Client) newServiceClientWithTransport(path string, namespace string, t
234254
// Copy the cookies
235255
client.Client.Jar.SetCookies(u, c.Client.Jar.Cookies(u))
236256

237-
// Set SOAP Header cookie
238-
for _, cookie := range client.Jar.Cookies(u) {
239-
if cookie.Name == SessionCookieName {
240-
client.cookie = cookie.Value
241-
break
242-
}
243-
}
257+
client.Cookie = c.anySessionCookie // Set SOAP Header.Cookie by default
244258

245259
// Copy any query params (e.g. GOVMOMI_TUNNEL_PROXY_PORT used in testing)
246260
client.u.RawQuery = vc.RawQuery
@@ -718,8 +732,10 @@ func (c *Client) soapRoundTrip(ctx context.Context, reqBody, resBody HasFault) e
718732
h.ID = id
719733
}
720734

721-
h.Cookie = c.cookie
722-
if h.Cookie != "" || h.ID != "" || h.Security != nil {
735+
if c.Cookie != nil {
736+
h.Cookie = c.Cookie()
737+
}
738+
if h.Cookie != nil || h.ID != "" || h.Security != nil {
723739
reqEnv.Header = &h // XML marshal header only if a field is set
724740
}
725741

Diff for: vim25/soap/json_client.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2023-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -71,8 +71,8 @@ func (c *Client) invoke(ctx context.Context, this types.ManagedObjectReference,
7171
return err
7272
}
7373

74-
if len(c.cookie) != 0 {
75-
req.Header.Add(sessionHeader, c.cookie)
74+
if c.Cookie != nil {
75+
req.Header.Add(sessionHeader, c.Cookie().(string))
7676
}
7777

7878
result, err := getSOAPResultPtr(res)
@@ -156,8 +156,8 @@ func isError(statusCode int) bool {
156156
// session header.
157157
func (c *Client) checkForSessionHeader(resp *http.Response) {
158158
sessionKey := resp.Header.Get(sessionHeader)
159-
if len(sessionKey) > 0 {
160-
c.cookie = sessionKey
159+
if sessionKey != "" {
160+
c.Cookie = func() any { return sessionKey }
161161
}
162162
}
163163

Diff for: vim25/soap/json_client_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2023-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -206,7 +206,7 @@ func TestFullRequestCycle(t *testing.T) {
206206
c := NewClient(addr, true)
207207
c.Namespace = "urn:vim25"
208208
c.Version = "8.0.0.1"
209-
c.cookie = "(original)"
209+
c.Cookie = func() any { return "(original)" }
210210
c.UseJSON(true)
211211

212212
c.Transport = &mockHTTP{

Diff for: vim25/soap/soap.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/*
2-
Copyright (c) 2014-2018 VMware, Inc. All Rights Reserved.
2+
Copyright (c) 2014-2024 VMware, Inc. All Rights Reserved.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
66
You may obtain a copy of the License at
77
8-
http://www.apache.org/licenses/LICENSE-2.0
8+
http://www.apache.org/licenses/LICENSE-2.0
99
1010
Unless required by applicable law or agreed to in writing, software
1111
distributed under the License is distributed on an "AS IS" BASIS,
@@ -24,7 +24,7 @@ import (
2424
// Header includes optional soap Header fields.
2525
type Header struct {
2626
Action string `xml:"-"` // Action is the 'SOAPAction' HTTP header value. Defaults to "Client.Namespace/Client.Version".
27-
Cookie string `xml:"vcSessionCookie,omitempty"` // Cookie is a vCenter session cookie that can be used with other SDK endpoints (e.g. pbm).
27+
Cookie interface{} `xml:"vcSessionCookie,omitempty"` // Cookie is a vCenter session cookie that can be used with other SDK endpoints (e.g. pbm).
2828
ID string `xml:"operationID,omitempty"` // ID is the operationID used by ESX/vCenter logging for correlation.
2929
Security interface{} `xml:",omitempty"` // Security is used for SAML token authentication and request signing.
3030
}

0 commit comments

Comments
 (0)