diff --git a/evennia/server/portal/webclient.py b/evennia/server/portal/webclient.py index 5af4270b35..d6083e136b 100644 --- a/evennia/server/portal/webclient.py +++ b/evennia/server/portal/webclient.py @@ -53,20 +53,27 @@ class WebSocketClient(Protocol, Session): self.transport.validationMade = self.validationMade client_address = self.transport.client client_address = client_address[0] if client_address else None - print ("connectionMade: webclient address", client_address, self.transport.client, self.transport.client.__dict__, self.transport.__dict__) - self.init_session("websocket", client_address, self.factory.sessionhandler) - # watch for dead links - self.transport.setTcpKeepAlive(1) - self.sessionhandler.connect(self) def validationMade(self): """ This is called from the (modified) txws websocket library when the ws handshake and validation has completed fully. + """ - #print "validationMade:", self.transport.location.split("?", 1)[1] - pass + + self.csessid = self.transport.location.split("?", 1)[1] + csession = _CLIENT_SESSIONS(session_key=self.csessid) + uid = csession and csession.get("logged_in", False) + if uid: + # the client session is already logged in. + self.uid = uid + self.logged_in = True + + # watch for dead links + self.transport.setTcpKeepAlive(1) + # actually do the connection + self.sessionhandler.connect(self) def disconnect(self, reason=None): """ @@ -139,19 +146,6 @@ class WebSocketClient(Protocol, Session): """ - if "csessid" in kwargs and self.csessid is None: - # only allow to change csessid on the very first connect - # - this is a safety measure to avoid a clself.transport.client.__dict__, ient to manually - # change its csessid later. - self.csessid = kwargs.pop("csessid") - csession = _CLIENT_SESSIONS(session_key=self.csessid) - uid = csession and csession.get("logged_in", False) - if uid: - # the browser session is already logged in. - self.uid = uid - self.logged_in = True - return - if "websocket_close" in kwargs: self.disconnect() return diff --git a/evennia/server/portal/webclient_ajax.py b/evennia/server/portal/webclient_ajax.py index d5ec8cadaf..65a48fdf55 100644 --- a/evennia/server/portal/webclient_ajax.py +++ b/evennia/server/portal/webclient_ajax.py @@ -30,6 +30,7 @@ from evennia.utils import utils from evennia.utils.text2html import parse_html from evennia.server import session +_CLIENT_SESSIONS = utils.mod_import(settings.SESSION_ENGINE).SessionStore _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) _SERVERNAME = settings.SERVERNAME _KEEPALIVE = 30 # how often to check keepalive @@ -149,7 +150,7 @@ class WebClient(resource.Resource): request (Request): Incoming request. """ - csessid = request.args.get('csessid') + csessid = request.args.get('csessid')[0] remote_addr = request.getClientIP() host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port) @@ -157,7 +158,15 @@ class WebClient(resource.Resource): sess = WebClientSession() sess.client = self sess.init_session("ajax/comet", remote_addr, self.sessionhandler) + sess.csessid = csessid + csession = _CLIENT_SESSIONS(session_key=sess.csessid) + uid = csession and csession.get("logged_in", False) + if uid: + # the client session is already logged in + sess.uid = uid + sess.logged_in = True + sess.sessionhandler.connect(sess) self.last_alive[csessid] = (time(), False) diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index f9eb93fc41..0e728bb544 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -227,10 +227,10 @@ class ServerSession(Session): # An existing client sessid is registered, thus a matching # Client Session must also exist. Update it so the website # can also see we are logged in. - csession = ClientSessionStore(session_key=self.browserid) - csession["logged_in"] = player.id - csession.save() - print ("serversession.login:", csession.session_key) + csession = ClientSessionStore(session_key=self.csessid) + if not csession.get("logged_in"): + csession["logged_in"] = player.id + csession.save() # Update account's last login time. self.player.last_login = timezone.now() diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index 9f650c315e..bda2ab7d9a 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -252,14 +252,24 @@ class ServerSessionHandler(SessionHandler): sess = _ServerSession() sess.sessionhandler = self sess.load_sync_data(portalsessiondata) - if sess.logged_in and sess.uid: - # this can happen in the case of auto-authenticating - # protocols like SSH - sess.player = _PlayerDB.objects.get_player_from_uid(sess.uid) sess.at_sync() # validate all scripts _ScriptDB.objects.validate() self[sess.sessid] = sess + + if sess.logged_in and sess.uid: + # Session is already logged in. This can happen in the + # case of auto-authenticating protocols like SSH or + # webclient's session sharing + player = _PlayerDB.objects.get_player_from_uid(sess.uid) + if player: + self.login(sess, player, force=True) + return + else: + sess.logged_in = False + sess.uid = None + + # show the first login command self.data_in(sess, text=[[CMD_LOGINSTART],{}]) def portal_session_sync(self, portalsessiondata): @@ -355,7 +365,7 @@ class ServerSessionHandler(SessionHandler): self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, operation=SSHUTD) - def login(self, session, player, testmode=False): + def login(self, session, player, force=False, testmode=False): """ Log in the previously unloggedin session and the player we by now should know is connected to it. After this point we assume @@ -364,12 +374,14 @@ class ServerSessionHandler(SessionHandler): Args: session (Session): The Session to authenticate. player (Player): The Player identified as associated with this Session. + force (bool): Login also if the session thinks it's already logged in + (this can happen for auto-authenticating protocols) testmode (bool, optional): This is used by unittesting for faking login without any AMP being actually active. """ - if session.logged_in: + if session.logged_in and not force: # don't log in a session that is already logged in. return diff --git a/evennia/web/webclient/static/webclient/js/evennia.js b/evennia/web/webclient/static/webclient/js/evennia.js index 9a7bf9b0e2..34d2e4bde9 100644 --- a/evennia/web/webclient/static/webclient/js/evennia.js +++ b/evennia/web/webclient/static/webclient/js/evennia.js @@ -82,7 +82,7 @@ An "emitter" object must have a function opts = opts || {}; this.emitter = opts.emitter || new DefaultEmitter(); - if (opts.ckonnection) { + if (opts.connection) { this.connection = opts.connection; } else if (window.WebSocket && wsactive) { @@ -224,6 +224,7 @@ An "emitter" object must have a function // No-op if a connection is already open. return; } + // Important - we pass csessid tacked on the url websocket = new WebSocket(wsurl + '?' + csessid); // Handle Websocket open event @@ -231,7 +232,6 @@ An "emitter" object must have a function open = true; ever_open = true; Evennia.emit('connection_open', ["websocket"], event); - Evennia.msg('csessid', [csessid], {}); }; // Handle Websocket close event websocket.onclose = function (event) { diff --git a/evennia/web/webclient/views.py b/evennia/web/webclient/views.py index 3a877462e0..fadcf87bcd 100644 --- a/evennia/web/webclient/views.py +++ b/evennia/web/webclient/views.py @@ -8,38 +8,45 @@ from __future__ import print_function from django.shortcuts import render from django.contrib.auth import login -from evennia.server.sessionhandler import SESSION_HANDLER from evennia.players.models import PlayerDB +from evennia.utils import logger + + +def _shared_login(request): + """ + Handle the shared login between website and webclient. + + """ + csession = request.session + player = request.user + sesslogin = csession.get("logged_in", None) + + # check if user has authenticated to website + if csession.session_key is None: + # this is necessary to build the sessid key + csession.save() + elif player.is_authenticated(): + if not sesslogin: + # User has already authenticated to website + csession["logged_in"] = player.id + elif sesslogin: + # The webclient has previously registered a login to this browser_session + player = PlayerDB.objects.get(id=sesslogin) + try: + login(request, player) + except AttributeError: + logger.log_trace() def webclient(request): """ Webclient page template loading. + """ - print ("webclient session:", request.session.session_key, request.user, request.user.is_authenticated()) + # handle webclient-website shared login + _shared_login(request) - csession = request.session - csessid = request.session.session_key - player = request.user - # check if user has authenticated to website - if player.is_authenticated(): - print ("webclient: player auth, trying to connect sessions") - # Try to login all the player's webclient sessions - only - # unloggedin ones will actually be logged in. - for session in SESSION_HANDLER.sessions_from_csessid(csessid): - print ("session to connect:", session) - if session.protocol_key in ("websocket", "ajax/comet"): - SESSION_HANDLER.login(session, player) - csession["logged_in"] = player.id - elif csession.get("logged_in"): - # The webclient has previously registered a login to this browser_session - print ("webclient: browser_session logged in, trying to login") - player = PlayerDB.objects.get(csession.get("logged_in")) - login(player, request) - else: - csession["logged_in"] = False - - # make sure to store the browser session's hash so the webclient can get to it + # make sure to store the browser session's hash so the webclient can get to it! pagevars = {'browser_sessid': request.session.session_key} return render(request, 'webclient.html', pagevars) diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index c28d5ea275..2e6af3285e 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -13,43 +13,38 @@ from django.shortcuts import render from evennia import SESSION_HANDLER from evennia.objects.models import ObjectDB from evennia.players.models import PlayerDB +from evennia.utils import logger from django.contrib.auth import login _BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS -def page_index(request): +def _shared_login(request): """ - Main root page. + Handle the shared login between website and webclient. + """ - - # handle webclient-website shared login - csession = request.session - csessid = request.session.session_key player = request.user - # check if user has authenticated to website - if player.is_authenticated(): - # Try to login all the player's webclient sessions - only - # unloggedin ones will actually be logged in. - print "website: player auth, trying to connect sessions" - for session in SESSION_HANDLER.sessions_from_csessid(csessid): - print "session to connect:", session - if session.protocol_key in ("websocket", "ajax/comet"): - SESSION_HANDLER.login(session, player) - session.csessid = csession.session_key - csession["logged_in"] = player.id - elif csession.get("logged_in"): + sesslogin = csession.get("logged_in", None) + + if csession.session_key is None: + # this is necessary to build the sessid key + csession.save() + elif player.is_authenticated(): + if not sesslogin: + csession["logged_in"] = player.id + elif sesslogin: # The webclient has previously registered a login to this csession - print "website: csession logged in, trying to login" - player = PlayerDB.objects.get(id=csession.get("logged_in")) - login(request, player) - else: - csession["logged_in"] = None + player = PlayerDB.objects.get(id=sesslogin) + try: + login(request, player) + except AttributeError: + logger.log_trace() - print ("website session:", request.session.session_key, request.user, request.user.is_authenticated()) +def _gamestats(): # Some misc. configurable stuff. # TODO: Move this to either SQL or settings.py based configuration. fpage_player_limit = 4 @@ -81,6 +76,19 @@ def page_index(request): "num_characters": nchars or "no", "num_others": nothers or "no" } + return pagevars + + +def page_index(request): + """ + Main root page. + """ + + # handle webclient-website shared login + _shared_login(request) + + # get game db stats + pagevars = _gamestats() return render(request, 'index.html', pagevars)