1/* 2Copyright (c) 2017-2018 VMware, Inc. All Rights Reserved. 3 4Licensed under the Apache License, Version 2.0 (the "License"); 5you may not use this file except in compliance with the License. 6You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10Unless required by applicable law or agreed to in writing, software 11distributed under the License is distributed on an "AS IS" BASIS, 12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13See the License for the specific language governing permissions and 14limitations under the License. 15*/ 16 17package simulator 18 19import ( 20 "context" 21 "fmt" 22 "net/http" 23 "strings" 24 "time" 25 26 "github.com/google/uuid" 27 "github.com/vmware/govmomi/object" 28 "github.com/vmware/govmomi/session" 29 "github.com/vmware/govmomi/vim25/methods" 30 "github.com/vmware/govmomi/vim25/mo" 31 "github.com/vmware/govmomi/vim25/soap" 32 "github.com/vmware/govmomi/vim25/types" 33) 34 35type SessionManager struct { 36 mo.SessionManager 37 38 ServiceHostName string 39 40 sessions map[string]Session 41} 42 43func NewSessionManager(ref types.ManagedObjectReference) object.Reference { 44 s := &SessionManager{ 45 sessions: make(map[string]Session), 46 } 47 s.Self = ref 48 return s 49} 50 51func createSession(ctx *Context, name string, locale string) types.UserSession { 52 now := time.Now().UTC() 53 54 if locale == "" { 55 locale = session.Locale 56 } 57 58 session := Session{ 59 UserSession: types.UserSession{ 60 Key: uuid.New().String(), 61 UserName: name, 62 FullName: name, 63 LoginTime: now, 64 LastActiveTime: now, 65 Locale: locale, 66 MessageLocale: locale, 67 }, 68 Registry: NewRegistry(), 69 } 70 71 ctx.SetSession(session, true) 72 73 return session.UserSession 74} 75 76func (s *SessionManager) Login(ctx *Context, req *types.Login) soap.HasFault { 77 body := new(methods.LoginBody) 78 79 if req.UserName == "" || req.Password == "" || ctx.Session != nil { 80 body.Fault_ = invalidLogin 81 } else { 82 body.Res = &types.LoginResponse{ 83 Returnval: createSession(ctx, req.UserName, req.Locale), 84 } 85 } 86 87 return body 88} 89 90func (s *SessionManager) LoginExtensionByCertificate(ctx *Context, req *types.LoginExtensionByCertificate) soap.HasFault { 91 body := new(methods.LoginExtensionByCertificateBody) 92 93 if ctx.req.TLS == nil || len(ctx.req.TLS.PeerCertificates) == 0 { 94 body.Fault_ = Fault("", new(types.NoClientCertificate)) 95 return body 96 } 97 98 if req.ExtensionKey == "" || ctx.Session != nil { 99 body.Fault_ = invalidLogin 100 } else { 101 body.Res = &types.LoginExtensionByCertificateResponse{ 102 Returnval: createSession(ctx, req.ExtensionKey, req.Locale), 103 } 104 } 105 106 return body 107} 108 109func (s *SessionManager) LoginByToken(ctx *Context, req *types.LoginByToken) soap.HasFault { 110 body := new(methods.LoginByTokenBody) 111 112 if ctx.Session != nil { 113 body.Fault_ = invalidLogin 114 } else { 115 var subject struct { 116 ID string `xml:"Assertion>Subject>NameID"` 117 } 118 119 if s, ok := ctx.Header.Security.(*Element); ok { 120 _ = s.Decode(&subject) 121 } 122 123 if subject.ID == "" { 124 body.Fault_ = invalidLogin 125 return body 126 } 127 128 body.Res = &types.LoginByTokenResponse{ 129 Returnval: createSession(ctx, subject.ID, req.Locale), 130 } 131 } 132 133 return body 134} 135 136func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault { 137 session := ctx.Session 138 delete(s.sessions, session.Key) 139 pc := Map.content().PropertyCollector 140 141 for ref, obj := range ctx.Session.Registry.objects { 142 if ref == pc { 143 continue // don't unregister the PropertyCollector singleton 144 } 145 if _, ok := obj.(RegisterObject); ok { 146 ctx.Map.Remove(ref) // Remove RegisterObject handlers 147 } 148 } 149 150 ctx.postEvent(&types.UserLogoutSessionEvent{ 151 IpAddress: session.IpAddress, 152 UserAgent: session.UserAgent, 153 SessionId: session.Key, 154 LoginTime: &session.LoginTime, 155 }) 156 157 return &methods.LogoutBody{Res: new(types.LogoutResponse)} 158} 159 160func (s *SessionManager) TerminateSession(ctx *Context, req *types.TerminateSession) soap.HasFault { 161 body := new(methods.TerminateSessionBody) 162 163 for _, id := range req.SessionId { 164 if id == ctx.Session.Key { 165 body.Fault_ = Fault("", new(types.InvalidArgument)) 166 return body 167 } 168 delete(s.sessions, id) 169 } 170 171 body.Res = new(types.TerminateSessionResponse) 172 return body 173} 174 175func (s *SessionManager) SessionIsActive(ctx *Context, req *types.SessionIsActive) soap.HasFault { 176 body := new(methods.SessionIsActiveBody) 177 178 if ctx.Map.IsESX() { 179 body.Fault_ = Fault("", new(types.NotImplemented)) 180 return body 181 } 182 183 body.Res = new(types.SessionIsActiveResponse) 184 185 if session, exists := s.sessions[req.SessionID]; exists { 186 body.Res.Returnval = session.UserName == req.UserName 187 } 188 189 return body 190} 191 192func (s *SessionManager) AcquireCloneTicket(ctx *Context, _ *types.AcquireCloneTicket) soap.HasFault { 193 session := *ctx.Session 194 session.Key = uuid.New().String() 195 s.sessions[session.Key] = session 196 197 return &methods.AcquireCloneTicketBody{ 198 Res: &types.AcquireCloneTicketResponse{ 199 Returnval: session.Key, 200 }, 201 } 202} 203 204func (s *SessionManager) CloneSession(ctx *Context, ticket *types.CloneSession) soap.HasFault { 205 body := new(methods.CloneSessionBody) 206 207 session, exists := s.sessions[ticket.CloneTicket] 208 209 if exists { 210 delete(s.sessions, ticket.CloneTicket) // A clone ticket can only be used once 211 session.Key = uuid.New().String() 212 ctx.SetSession(session, true) 213 214 body.Res = &types.CloneSessionResponse{ 215 Returnval: session.UserSession, 216 } 217 } else { 218 body.Fault_ = invalidLogin 219 } 220 221 return body 222} 223 224func (s *SessionManager) AcquireGenericServiceTicket(ticket *types.AcquireGenericServiceTicket) soap.HasFault { 225 return &methods.AcquireGenericServiceTicketBody{ 226 Res: &types.AcquireGenericServiceTicketResponse{ 227 Returnval: types.SessionManagerGenericServiceTicket{ 228 Id: uuid.New().String(), 229 HostName: s.ServiceHostName, 230 }, 231 }, 232 } 233} 234 235// internalContext is the session for use by the in-memory client (Service.RoundTrip) 236var internalContext = &Context{ 237 Context: context.Background(), 238 Session: &Session{ 239 UserSession: types.UserSession{ 240 Key: uuid.New().String(), 241 }, 242 Registry: NewRegistry(), 243 }, 244 Map: Map, 245} 246 247var invalidLogin = Fault("Login failure", new(types.InvalidLogin)) 248 249// Context provides per-request Session management. 250type Context struct { 251 req *http.Request 252 res http.ResponseWriter 253 svc *Service 254 255 context.Context 256 Session *Session 257 Header soap.Header 258 Caller *types.ManagedObjectReference 259 Map *Registry 260} 261 262// mapSession maps an HTTP cookie to a Session. 263func (c *Context) mapSession() { 264 if cookie, err := c.req.Cookie(soap.SessionCookieName); err == nil { 265 if val, ok := c.svc.sm.sessions[cookie.Value]; ok { 266 c.SetSession(val, false) 267 } 268 } 269} 270 271// SetSession should be called after successful authentication. 272func (c *Context) SetSession(session Session, login bool) { 273 session.UserAgent = c.req.UserAgent() 274 session.IpAddress = strings.Split(c.req.RemoteAddr, ":")[0] 275 session.LastActiveTime = time.Now() 276 277 c.svc.sm.sessions[session.Key] = session 278 c.Session = &session 279 280 if login { 281 http.SetCookie(c.res, &http.Cookie{ 282 Name: soap.SessionCookieName, 283 Value: session.Key, 284 }) 285 286 c.postEvent(&types.UserLoginSessionEvent{ 287 SessionId: session.Key, 288 IpAddress: session.IpAddress, 289 UserAgent: session.UserAgent, 290 Locale: session.Locale, 291 }) 292 } 293} 294 295// WithLock holds a lock for the given object while then given function is run. 296func (c *Context) WithLock(obj mo.Reference, f func()) { 297 if c.Caller != nil && *c.Caller == obj.Reference() { 298 // Internal method invocation, obj is already locked 299 f() 300 return 301 } 302 Map.WithLock(obj, f) 303} 304 305// postEvent wraps EventManager.PostEvent for internal use, with a lock on the EventManager. 306func (c *Context) postEvent(events ...types.BaseEvent) { 307 m := Map.EventManager() 308 c.WithLock(m, func() { 309 for _, event := range events { 310 m.PostEvent(c, &types.PostEvent{EventToPost: event}) 311 } 312 }) 313} 314 315// Session combines a UserSession and a Registry for per-session managed objects. 316type Session struct { 317 types.UserSession 318 *Registry 319} 320 321// Put wraps Registry.Put, setting the moref value to include the session key. 322func (s *Session) Put(item mo.Reference) mo.Reference { 323 ref := item.Reference() 324 if ref.Value == "" { 325 ref.Value = fmt.Sprintf("session[%s]%s", s.Key, uuid.New()) 326 } 327 s.Registry.setReference(item, ref) 328 return s.Registry.Put(item) 329} 330 331// Get wraps Registry.Get, session-izing singleton objects such as SessionManager and the root PropertyCollector. 332func (s *Session) Get(ref types.ManagedObjectReference) mo.Reference { 333 obj := s.Registry.Get(ref) 334 if obj != nil { 335 return obj 336 } 337 338 // Return a session "view" of certain singleton objects 339 switch ref.Type { 340 case "SessionManager": 341 // Clone SessionManager so the PropertyCollector can properly report CurrentSession 342 m := *Map.SessionManager() 343 m.CurrentSession = &s.UserSession 344 345 // TODO: we could maintain SessionList as part of the SessionManager singleton 346 for _, session := range m.sessions { 347 m.SessionList = append(m.SessionList, session.UserSession) 348 } 349 350 return &m 351 case "PropertyCollector": 352 if ref == Map.content().PropertyCollector { 353 return s.Put(NewPropertyCollector(ref)) 354 } 355 } 356 357 return Map.Get(ref) 358} 359