github.com/vmware/govmomi@v0.37.1/simulator/session_manager.go (about) 1 /* 2 Copyright (c) 2017-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 "context" 21 "fmt" 22 "net/http" 23 "net/url" 24 "os" 25 "reflect" 26 "strings" 27 "sync" 28 "time" 29 30 "github.com/google/uuid" 31 32 "github.com/vmware/govmomi/session" 33 "github.com/vmware/govmomi/vim25/methods" 34 "github.com/vmware/govmomi/vim25/mo" 35 "github.com/vmware/govmomi/vim25/soap" 36 "github.com/vmware/govmomi/vim25/types" 37 ) 38 39 type SessionManager struct { 40 mo.SessionManager 41 nopLocker 42 43 ServiceHostName string 44 TLSCert func() string 45 ValidLogin func(*types.Login) bool 46 47 sessions map[string]Session 48 } 49 50 func (m *SessionManager) init(*Registry) { 51 m.sessions = make(map[string]Session) 52 } 53 54 var ( 55 // SessionIdleTimeout duration used to expire idle sessions 56 SessionIdleTimeout time.Duration 57 58 sessionMutex sync.Mutex 59 60 // secureCookies enables Set-Cookie.Secure=true 61 // We can't do this by default as simulator.Service defaults to no TLS by default and 62 // Go's cookiejar does not send Secure cookies unless the URL scheme is https. 63 secureCookies = os.Getenv("VCSIM_SECURE_COOKIES") == "true" 64 ) 65 66 func createSession(ctx *Context, name string, locale string) types.UserSession { 67 now := time.Now().UTC() 68 69 if locale == "" { 70 locale = session.Locale 71 } 72 73 session := Session{ 74 UserSession: types.UserSession{ 75 Key: uuid.New().String(), 76 UserName: name, 77 FullName: name, 78 LoginTime: now, 79 LastActiveTime: now, 80 Locale: locale, 81 MessageLocale: locale, 82 ExtensionSession: types.NewBool(false), 83 }, 84 Registry: NewRegistry(), 85 } 86 87 ctx.SetSession(session, true) 88 89 return ctx.Session.UserSession 90 } 91 92 func (m *SessionManager) getSession(id string) (Session, bool) { 93 sessionMutex.Lock() 94 defer sessionMutex.Unlock() 95 s, ok := m.sessions[id] 96 return s, ok 97 } 98 99 func (m *SessionManager) delSession(id string) { 100 sessionMutex.Lock() 101 defer sessionMutex.Unlock() 102 delete(m.sessions, id) 103 } 104 105 func (m *SessionManager) putSession(s Session) { 106 sessionMutex.Lock() 107 defer sessionMutex.Unlock() 108 m.sessions[s.Key] = s 109 } 110 111 func (s *SessionManager) Authenticate(u url.URL, req *types.Login) bool { 112 if u.User == nil || u.User == DefaultLogin { 113 return req.UserName != "" && req.Password != "" 114 } 115 116 if s.ValidLogin != nil { 117 return s.ValidLogin(req) 118 } 119 120 pass, _ := u.User.Password() 121 return req.UserName == u.User.Username() && req.Password == pass 122 } 123 124 func (s *SessionManager) Login(ctx *Context, req *types.Login) soap.HasFault { 125 body := new(methods.LoginBody) 126 127 if ctx.Session == nil && s.Authenticate(*ctx.svc.Listen, req) { 128 body.Res = &types.LoginResponse{ 129 Returnval: createSession(ctx, req.UserName, req.Locale), 130 } 131 } else { 132 body.Fault_ = invalidLogin 133 } 134 135 return body 136 } 137 138 func (s *SessionManager) LoginExtensionByCertificate(ctx *Context, req *types.LoginExtensionByCertificate) soap.HasFault { 139 body := new(methods.LoginExtensionByCertificateBody) 140 141 if ctx.req.TLS == nil || len(ctx.req.TLS.PeerCertificates) == 0 { 142 body.Fault_ = Fault("", new(types.NoClientCertificate)) 143 return body 144 } 145 146 if req.ExtensionKey == "" || ctx.Session != nil { 147 body.Fault_ = invalidLogin 148 } else { 149 body.Res = &types.LoginExtensionByCertificateResponse{ 150 Returnval: createSession(ctx, req.ExtensionKey, req.Locale), 151 } 152 } 153 154 return body 155 } 156 157 func (s *SessionManager) LoginByToken(ctx *Context, req *types.LoginByToken) soap.HasFault { 158 body := new(methods.LoginByTokenBody) 159 160 if ctx.Session != nil { 161 body.Fault_ = invalidLogin 162 } else { 163 var subject struct { 164 ID string `xml:"Assertion>Subject>NameID"` 165 } 166 167 if s, ok := ctx.Header.Security.(*Element); ok { 168 _ = s.Decode(&subject) 169 } 170 171 if subject.ID == "" { 172 body.Fault_ = invalidLogin 173 return body 174 } 175 176 body.Res = &types.LoginByTokenResponse{ 177 Returnval: createSession(ctx, subject.ID, req.Locale), 178 } 179 } 180 181 return body 182 } 183 184 func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault { 185 session := ctx.Session 186 s.delSession(session.Key) 187 pc := ctx.Map.content().PropertyCollector 188 189 for ref, obj := range ctx.Session.Registry.objects { 190 if ref == pc { 191 continue // don't unregister the PropertyCollector singleton 192 } 193 if _, ok := obj.(RegisterObject); ok { 194 ctx.Map.Remove(ctx, ref) // Remove RegisterObject handlers 195 } 196 } 197 198 ctx.postEvent(&types.UserLogoutSessionEvent{ 199 IpAddress: session.IpAddress, 200 UserAgent: session.UserAgent, 201 SessionId: session.Key, 202 LoginTime: &session.LoginTime, 203 }) 204 205 return &methods.LogoutBody{Res: new(types.LogoutResponse)} 206 } 207 208 func (s *SessionManager) TerminateSession(ctx *Context, req *types.TerminateSession) soap.HasFault { 209 body := new(methods.TerminateSessionBody) 210 211 for _, id := range req.SessionId { 212 if id == ctx.Session.Key { 213 body.Fault_ = Fault("", new(types.InvalidArgument)) 214 return body 215 } 216 if _, ok := s.getSession(id); !ok { 217 body.Fault_ = Fault("", new(types.NotFound)) 218 return body 219 } 220 s.delSession(id) 221 } 222 223 body.Res = new(types.TerminateSessionResponse) 224 return body 225 } 226 227 func (s *SessionManager) SessionIsActive(ctx *Context, req *types.SessionIsActive) soap.HasFault { 228 body := new(methods.SessionIsActiveBody) 229 230 if ctx.Map.IsESX() { 231 body.Fault_ = Fault("", new(types.NotImplemented)) 232 return body 233 } 234 235 body.Res = new(types.SessionIsActiveResponse) 236 237 if session, exists := s.getSession(req.SessionID); exists { 238 body.Res.Returnval = session.UserName == req.UserName 239 } 240 241 return body 242 } 243 244 func (s *SessionManager) AcquireCloneTicket(ctx *Context, _ *types.AcquireCloneTicket) soap.HasFault { 245 session := *ctx.Session 246 session.Key = uuid.New().String() 247 s.putSession(session) 248 249 return &methods.AcquireCloneTicketBody{ 250 Res: &types.AcquireCloneTicketResponse{ 251 Returnval: session.Key, 252 }, 253 } 254 } 255 256 func (s *SessionManager) CloneSession(ctx *Context, ticket *types.CloneSession) soap.HasFault { 257 body := new(methods.CloneSessionBody) 258 259 session, exists := s.getSession(ticket.CloneTicket) 260 261 if exists { 262 s.delSession(ticket.CloneTicket) // A clone ticket can only be used once 263 session.Key = uuid.New().String() 264 ctx.SetSession(session, true) 265 266 body.Res = &types.CloneSessionResponse{ 267 Returnval: session.UserSession, 268 } 269 } else { 270 body.Fault_ = invalidLogin 271 } 272 273 return body 274 } 275 276 func (s *SessionManager) AcquireGenericServiceTicket(ticket *types.AcquireGenericServiceTicket) soap.HasFault { 277 return &methods.AcquireGenericServiceTicketBody{ 278 Res: &types.AcquireGenericServiceTicketResponse{ 279 Returnval: types.SessionManagerGenericServiceTicket{ 280 Id: uuid.New().String(), 281 HostName: s.ServiceHostName, 282 }, 283 }, 284 } 285 } 286 287 var invalidLogin = Fault("Login failure", new(types.InvalidLogin)) 288 289 // Context provides per-request Session management. 290 type Context struct { 291 req *http.Request 292 res http.ResponseWriter 293 svc *Service 294 295 context.Context 296 Session *Session 297 Header soap.Header 298 Caller *types.ManagedObjectReference 299 Map *Registry 300 } 301 302 // mapSession maps an HTTP cookie to a Session. 303 func (c *Context) mapSession() { 304 if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil { 305 if val, ok := c.svc.sm.getSession(cookie.Value); ok { 306 c.SetSession(val, false) 307 } 308 } 309 } 310 311 func (m *SessionManager) expiredSession(id string, now time.Time) bool { 312 expired := true 313 314 s, ok := m.getSession(id) 315 if ok { 316 expired = now.Sub(s.LastActiveTime) > SessionIdleTimeout 317 if expired { 318 m.delSession(id) 319 } 320 } 321 322 return expired 323 } 324 325 // SessionIdleWatch starts a goroutine that calls func expired() at SessionIdleTimeout intervals. 326 // The goroutine exits if the func returns true. 327 func SessionIdleWatch(ctx context.Context, id string, expired func(string, time.Time) bool) { 328 if SessionIdleTimeout == 0 { 329 return 330 } 331 332 go func() { 333 for t := time.NewTimer(SessionIdleTimeout); ; { 334 select { 335 case <-ctx.Done(): 336 return 337 case now := <-t.C: 338 if expired(id, now) { 339 return 340 } 341 t.Reset(SessionIdleTimeout) 342 } 343 } 344 }() 345 } 346 347 // SetSession should be called after successful authentication. 348 func (c *Context) SetSession(session Session, login bool) { 349 session.UserAgent = c.req.UserAgent() 350 session.IpAddress = strings.Split(c.req.RemoteAddr, ":")[0] 351 session.LastActiveTime = time.Now() 352 session.CallCount++ 353 354 c.svc.sm.putSession(session) 355 c.Session = &session 356 357 if login { 358 http.SetCookie(c.res, &http.Cookie{ 359 Name: soap.SessionCookieName, 360 Value: session.Key, 361 Secure: secureCookies, 362 HttpOnly: true, 363 }) 364 365 c.postEvent(&types.UserLoginSessionEvent{ 366 SessionId: session.Key, 367 IpAddress: session.IpAddress, 368 UserAgent: session.UserAgent, 369 Locale: session.Locale, 370 }) 371 372 SessionIdleWatch(c.Context, session.Key, c.svc.sm.expiredSession) 373 } 374 } 375 376 // WithLock holds a lock for the given object while the given function is run. 377 // It will skip locking if this context already holds the given object's lock. 378 func (c *Context) WithLock(obj mo.Reference, f func()) { 379 // TODO: This is not always going to be correct. An object should 380 // really be locked by the registry that "owns it", which is not always 381 // Map. This function will need to take the Registry as an additional 382 // argument to accomplish this. 383 // Basic mutex locking will work even if obj doesn't belong to Map, but 384 // if obj implements sync.Locker, that custom locking will not be used. 385 c.Map.WithLock(c, obj, f) 386 } 387 388 // postEvent wraps EventManager.PostEvent for internal use, with a lock on the EventManager. 389 func (c *Context) postEvent(events ...types.BaseEvent) { 390 m := c.Map.EventManager() 391 c.WithLock(m, func() { 392 for _, event := range events { 393 m.PostEvent(c, &types.PostEvent{EventToPost: event}) 394 } 395 }) 396 } 397 398 // Session combines a UserSession and a Registry for per-session managed objects. 399 type Session struct { 400 types.UserSession 401 *Registry 402 } 403 404 func (s *Session) setReference(item mo.Reference) { 405 ref := item.Reference() 406 if ref.Value == "" { 407 ref.Value = fmt.Sprintf("session[%s]%s", s.Key, uuid.New()) 408 } 409 if ref.Type == "" { 410 ref.Type = typeName(item) 411 } 412 s.Registry.setReference(item, ref) 413 } 414 415 // Put wraps Registry.Put, setting the moref value to include the session key. 416 func (s *Session) Put(item mo.Reference) mo.Reference { 417 s.setReference(item) 418 return s.Registry.Put(item) 419 } 420 421 // Get wraps Registry.Get, session-izing singleton objects such as SessionManager and the root PropertyCollector. 422 func (s *Session) Get(ref types.ManagedObjectReference) mo.Reference { 423 obj := s.Registry.Get(ref) 424 if obj != nil { 425 return obj 426 } 427 428 // Return a session "view" of certain singleton objects 429 switch ref.Type { 430 case "SessionManager": 431 // Clone SessionManager so the PropertyCollector can properly report CurrentSession 432 m := *Map.SessionManager() 433 m.CurrentSession = &s.UserSession 434 435 // TODO: we could maintain SessionList as part of the SessionManager singleton 436 sessionMutex.Lock() 437 for _, session := range m.sessions { 438 m.SessionList = append(m.SessionList, session.UserSession) 439 } 440 sessionMutex.Unlock() 441 442 return &m 443 case "PropertyCollector": 444 if ref == Map.content().PropertyCollector { 445 // Per-session instance of the PropertyCollector singleton. 446 // Using reflection here as PropertyCollector might be wrapped with a custom type. 447 obj = Map.Get(ref) 448 pc := reflect.New(reflect.TypeOf(obj).Elem()) 449 obj = pc.Interface().(mo.Reference) 450 s.Registry.setReference(obj, ref) 451 return s.Put(obj) 452 } 453 } 454 455 return Map.Get(ref) 456 }