Implemented shared sessions between webclient and website - logging into either will also log in the player to the other. This is addresses the first point of #613.

This commit is contained in:
Griatch 2016-06-01 00:49:54 +02:00
parent eebd41f46d
commit f04c82fa17
7 changed files with 111 additions and 81 deletions

View file

@ -53,20 +53,27 @@ class WebSocketClient(Protocol, Session):
self.transport.validationMade = self.validationMade self.transport.validationMade = self.validationMade
client_address = self.transport.client client_address = self.transport.client
client_address = client_address[0] if client_address else None 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) self.init_session("websocket", client_address, self.factory.sessionhandler)
# watch for dead links
self.transport.setTcpKeepAlive(1)
self.sessionhandler.connect(self)
def validationMade(self): def validationMade(self):
""" """
This is called from the (modified) txws websocket library when This is called from the (modified) txws websocket library when
the ws handshake and validation has completed fully. 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): 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: if "websocket_close" in kwargs:
self.disconnect() self.disconnect()
return return

View file

@ -30,6 +30,7 @@ from evennia.utils import utils
from evennia.utils.text2html import parse_html from evennia.utils.text2html import parse_html
from evennia.server import session 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) _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE)
_SERVERNAME = settings.SERVERNAME _SERVERNAME = settings.SERVERNAME
_KEEPALIVE = 30 # how often to check keepalive _KEEPALIVE = 30 # how often to check keepalive
@ -149,7 +150,7 @@ class WebClient(resource.Resource):
request (Request): Incoming request. request (Request): Incoming request.
""" """
csessid = request.args.get('csessid') csessid = request.args.get('csessid')[0]
remote_addr = request.getClientIP() remote_addr = request.getClientIP()
host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port) host_string = "%s (%s:%s)" % (_SERVERNAME, request.getRequestHostname(), request.getHost().port)
@ -157,7 +158,15 @@ class WebClient(resource.Resource):
sess = WebClientSession() sess = WebClientSession()
sess.client = self sess.client = self
sess.init_session("ajax/comet", remote_addr, self.sessionhandler) sess.init_session("ajax/comet", remote_addr, self.sessionhandler)
sess.csessid = csessid 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) sess.sessionhandler.connect(sess)
self.last_alive[csessid] = (time(), False) self.last_alive[csessid] = (time(), False)

View file

@ -227,10 +227,10 @@ class ServerSession(Session):
# An existing client sessid is registered, thus a matching # An existing client sessid is registered, thus a matching
# Client Session must also exist. Update it so the website # Client Session must also exist. Update it so the website
# can also see we are logged in. # can also see we are logged in.
csession = ClientSessionStore(session_key=self.browserid) csession = ClientSessionStore(session_key=self.csessid)
if not csession.get("logged_in"):
csession["logged_in"] = player.id csession["logged_in"] = player.id
csession.save() csession.save()
print ("serversession.login:", csession.session_key)
# Update account's last login time. # Update account's last login time.
self.player.last_login = timezone.now() self.player.last_login = timezone.now()

View file

@ -252,14 +252,24 @@ class ServerSessionHandler(SessionHandler):
sess = _ServerSession() sess = _ServerSession()
sess.sessionhandler = self sess.sessionhandler = self
sess.load_sync_data(portalsessiondata) 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() sess.at_sync()
# validate all scripts # validate all scripts
_ScriptDB.objects.validate() _ScriptDB.objects.validate()
self[sess.sessid] = sess 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],{}]) self.data_in(sess, text=[[CMD_LOGINSTART],{}])
def portal_session_sync(self, portalsessiondata): def portal_session_sync(self, portalsessiondata):
@ -355,7 +365,7 @@ class ServerSessionHandler(SessionHandler):
self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION, self.server.amp_protocol.send_AdminServer2Portal(DUMMYSESSION,
operation=SSHUTD) 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 Log in the previously unloggedin session and the player we by
now should know is connected to it. After this point we assume now should know is connected to it. After this point we assume
@ -364,12 +374,14 @@ class ServerSessionHandler(SessionHandler):
Args: Args:
session (Session): The Session to authenticate. session (Session): The Session to authenticate.
player (Player): The Player identified as associated with this Session. 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 testmode (bool, optional): This is used by unittesting for
faking login without any AMP being actually active. 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. # don't log in a session that is already logged in.
return return

View file

@ -82,7 +82,7 @@ An "emitter" object must have a function
opts = opts || {}; opts = opts || {};
this.emitter = opts.emitter || new DefaultEmitter(); this.emitter = opts.emitter || new DefaultEmitter();
if (opts.ckonnection) { if (opts.connection) {
this.connection = opts.connection; this.connection = opts.connection;
} }
else if (window.WebSocket && wsactive) { else if (window.WebSocket && wsactive) {
@ -224,6 +224,7 @@ An "emitter" object must have a function
// No-op if a connection is already open. // No-op if a connection is already open.
return; return;
} }
// Important - we pass csessid tacked on the url
websocket = new WebSocket(wsurl + '?' + csessid); websocket = new WebSocket(wsurl + '?' + csessid);
// Handle Websocket open event // Handle Websocket open event
@ -231,7 +232,6 @@ An "emitter" object must have a function
open = true; open = true;
ever_open = true; ever_open = true;
Evennia.emit('connection_open', ["websocket"], event); Evennia.emit('connection_open', ["websocket"], event);
Evennia.msg('csessid', [csessid], {});
}; };
// Handle Websocket close event // Handle Websocket close event
websocket.onclose = function (event) { websocket.onclose = function (event) {

View file

@ -8,38 +8,45 @@ from __future__ import print_function
from django.shortcuts import render from django.shortcuts import render
from django.contrib.auth import login from django.contrib.auth import login
from evennia.server.sessionhandler import SESSION_HANDLER
from evennia.players.models import PlayerDB 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): def webclient(request):
""" """
Webclient page template loading. 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 # make sure to store the browser session's hash so the webclient can get to it!
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
pagevars = {'browser_sessid': request.session.session_key} pagevars = {'browser_sessid': request.session.session_key}
return render(request, 'webclient.html', pagevars) return render(request, 'webclient.html', pagevars)

View file

@ -13,43 +13,38 @@ from django.shortcuts import render
from evennia import SESSION_HANDLER from evennia import SESSION_HANDLER
from evennia.objects.models import ObjectDB from evennia.objects.models import ObjectDB
from evennia.players.models import PlayerDB from evennia.players.models import PlayerDB
from evennia.utils import logger
from django.contrib.auth import login from django.contrib.auth import login
_BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS _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 csession = request.session
csessid = request.session.session_key
player = request.user player = request.user
# check if user has authenticated to website sesslogin = csession.get("logged_in", None)
if player.is_authenticated():
# Try to login all the player's webclient sessions - only if csession.session_key is None:
# unloggedin ones will actually be logged in. # this is necessary to build the sessid key
print "website: player auth, trying to connect sessions" csession.save()
for session in SESSION_HANDLER.sessions_from_csessid(csessid): elif player.is_authenticated():
print "session to connect:", session if not sesslogin:
if session.protocol_key in ("websocket", "ajax/comet"):
SESSION_HANDLER.login(session, player)
session.csessid = csession.session_key
csession["logged_in"] = player.id csession["logged_in"] = player.id
elif csession.get("logged_in"): elif sesslogin:
# The webclient has previously registered a login to this csession # The webclient has previously registered a login to this csession
print "website: csession logged in, trying to login" player = PlayerDB.objects.get(id=sesslogin)
player = PlayerDB.objects.get(id=csession.get("logged_in")) try:
login(request, player) login(request, player)
else: except AttributeError:
csession["logged_in"] = None logger.log_trace()
print ("website session:", request.session.session_key, request.user, request.user.is_authenticated())
def _gamestats():
# Some misc. configurable stuff. # Some misc. configurable stuff.
# TODO: Move this to either SQL or settings.py based configuration. # TODO: Move this to either SQL or settings.py based configuration.
fpage_player_limit = 4 fpage_player_limit = 4
@ -81,6 +76,19 @@ def page_index(request):
"num_characters": nchars or "no", "num_characters": nchars or "no",
"num_others": nothers 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) return render(request, 'index.html', pagevars)