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