diff --git a/evennia/commands/default/unloggedin.py b/evennia/commands/default/unloggedin.py index f6f8800ea5..3c45580abc 100644 --- a/evennia/commands/default/unloggedin.py +++ b/evennia/commands/default/unloggedin.py @@ -6,6 +6,7 @@ import time from collections import defaultdict from random import getrandbits from django.conf import settings +from django.contrib.auth import authenticate from evennia.players.models import PlayerDB from evennia.objects.models import ObjectDB from evennia.server.models import ServerConfig @@ -148,17 +149,15 @@ def create_normal_player(session, name, password): return None # Match account name and check password - player = PlayerDB.objects.get_player_from_name(name) - pswd = None - if player: - pswd = player.check_password(password) + player = authenticate(username=name, password=password) - if not (player and pswd): + if not player: # No playername or password match session.msg("Incorrect login information given.") # this just updates the throttle _throttle(session) # calls player hook for a failed login if possible. + player = PlayerDB.objects.get_player_from_name(name) if player: player.at_failed_login(session) return None diff --git a/evennia/server/inputfuncs.py b/evennia/server/inputfuncs.py index ff9701475e..5853bf10e7 100644 --- a/evennia/server/inputfuncs.py +++ b/evennia/server/inputfuncs.py @@ -20,11 +20,16 @@ settings.INPUT_FUNC_MODULES. """ from future.utils import viewkeys +import importlib from django.conf import settings from evennia.commands.cmdhandler import cmdhandler +from evennia.players.models import PlayerDB from evennia.utils.logger import log_err from evennia.utils.utils import to_str, to_unicode +# django browser sessions +BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore + # always let "idle" work since we use this in the webclient _IDLE_COMMAND = settings.IDLE_COMMAND @@ -35,6 +40,7 @@ _NA = lambda o: "N/A" _ERROR_INPUT = "Inputfunc {name}({session}): Wrong/unrecognized input: {inp}" + # All global functions are inputfuncs available to process inputs def text(session, *args, **kwargs): @@ -99,6 +105,35 @@ def default(session, cmdname, *args, **kwargs): log_err(err) +def browser_sessid(session, *args, **kwargs): + """ + This is a utility function for the webclient (only) to communicate its + current browser session hash. This is important in order to link + the browser session to the evennia session. Only the very first + storage request will be accepted, the following ones will be ignored. + + Args: + browserid (str): Browser session hash + + """ + if not session.browserid: + print "stored browserid:", session, args[0] + session.browserid = args[0] + if not session.logged_in: + # automatic log in if the django browser session already authenticated. + browsersession = BrowserSessionStore(session_key=args[0]) + uid = browsersession.get("logged_in", None) + if uid: + try: + player = PlayerDB.objects.get(pk=uid) + except Exception: + return + session.sessionhandler.login(session, player) + + + + + def client_options(session, *args, **kwargs): """ This allows the client an OOB way to inform us about its name and capabilities. diff --git a/evennia/server/portal/webclient.py b/evennia/server/portal/webclient.py index fdb29444ed..6ac3171a0b 100644 --- a/evennia/server/portal/webclient.py +++ b/evennia/server/portal/webclient.py @@ -38,6 +38,7 @@ from evennia.utils.text2html import parse_html _RE_SCREENREADER_REGEX = re.compile(r"%s" % settings.SCREENREADER_REGEX_STRIP, re.DOTALL + re.MULTILINE) + class WebSocketClient(Protocol, Session): """ Implements the server-side of the Websocket connection. diff --git a/evennia/server/serversession.py b/evennia/server/serversession.py index 0711b23256..4a99751ea4 100644 --- a/evennia/server/serversession.py +++ b/evennia/server/serversession.py @@ -10,6 +10,7 @@ from builtins import object import re import weakref +import importlib from time import time from django.utils import timezone from django.conf import settings @@ -19,6 +20,8 @@ from evennia.utils.utils import make_iter, lazy_property from evennia.commands.cmdsethandler import CmdSetHandler from evennia.server.session import Session +BrowserSessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore + _GA = object.__getattribute__ _SA = object.__setattr__ _ObjectDB = None @@ -160,6 +163,7 @@ class ServerSession(Session): "Initiate to avoid AttributeErrors down the line" self.puppet = None self.player = None + self.browserid = None self.cmdset_storage_string = "" self.cmdset = CmdSetHandler(self, True) @@ -220,6 +224,13 @@ class ServerSession(Session): self.puppet = None self.cmdset_storage = settings.CMDSET_SESSION + if self.browserid: + # this is only set by a webclient inputcommand. + bsession = BrowserSessionStore(session_key=self.browserid) + bsession["logged_in"] = player.id # this also saves the bsession + bsession.save() + print ("serversession.login:", bsession.session_key) + # Update account's last login time. self.player.last_login = timezone.now() self.player.save() diff --git a/evennia/server/sessionhandler.py b/evennia/server/sessionhandler.py index fc8c031cc7..9c70540974 100644 --- a/evennia/server/sessionhandler.py +++ b/evennia/server/sessionhandler.py @@ -369,6 +369,10 @@ class ServerSessionHandler(SessionHandler): """ + if session.logged_in: + # don't log in a session that is already logged in. + return + # we have to check this first before uid has been assigned # this session. @@ -589,6 +593,17 @@ class ServerSessionHandler(SessionHandler): return sessions[0] if len(sessions) == 1 else sessions sessions_from_character = sessions_from_puppet + def sessions_from_browserid(self, browserid): + """ + Given a browserid, return all sessions having this id. + + Args + browserid (str): The browserid hash + + """ + return [session for session in self.values() + if session.browserid and session.browserid == browserid] + def announce_all(self, message): """ Send message to all connected sessions diff --git a/evennia/web/webclient/static/webclient/js/evennia.js b/evennia/web/webclient/static/webclient/js/evennia.js index e41493c49b..9deac7aea8 100644 --- a/evennia/web/webclient/static/webclient/js/evennia.js +++ b/evennia/web/webclient/static/webclient/js/evennia.js @@ -230,6 +230,7 @@ An "emitter" object must have a function open = true; ever_open = true; Evennia.emit('connection_open', ["websocket"], event); + Evennia.msg('browser_sessid', [browser_sessid], {}); }; // Handle Websocket close event websocket.onclose = function (event) { @@ -308,6 +309,7 @@ An "emitter" object must have a function success: function(data) { data = JSON.parse(data); log ("connection_open", ["AJAX/COMET"], data); + Evennia.msg("browser_sessid", [browser_sessid], {}); client_hash = data.suid; stop_polling = false; poll(); diff --git a/evennia/web/webclient/templates/webclient/base.html b/evennia/web/webclient/templates/webclient/base.html index 490391f6ed..f9beecc365 100644 --- a/evennia/web/webclient/templates/webclient/base.html +++ b/evennia/web/webclient/templates/webclient/base.html @@ -35,6 +35,12 @@ JQuery available. var wsactive = false; {% endif %} + {% if browser_sessid %} + var browser_sessid = "{{browser_sessid}}"; + {% else %} + var browser_sessid = false; + {% endif %} + {% if websocket_url %} var wsurl = "{{websocket_url}}:{{websocket_port}}"; {% else %} diff --git a/evennia/web/webclient/views.py b/evennia/web/webclient/views.py index 9b011fafe9..26fc72fed8 100644 --- a/evennia/web/webclient/views.py +++ b/evennia/web/webclient/views.py @@ -6,7 +6,9 @@ page and serve it eventual static content. """ 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 @@ -14,9 +16,31 @@ def webclient(request): """ Webclient page template loading. """ + print ("webclient session:", request.session.session_key, request.user, request.user.is_authenticated()) - nsess = len(PlayerDB.objects.get_connected_players()) or "none" - # as an example we send the number of connected players to the template - pagevars = {'num_players_connected': nsess} + browser_session = request.session + browserid = 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_browserid(browserid): + print ("session to connect:", session) + if session.protocol_key in ("websocket", "ajax/comet"): + SESSION_HANDLER.login(session, player) + session.browserid = browser_session.session_key + browser_session["logged_in"] = player.id + elif browser_session.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(browser_session.get("uid")) + login(player, request) + else: + browser_session["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} return render(request, 'webclient.html', pagevars) diff --git a/evennia/web/website/views.py b/evennia/web/website/views.py index 346a90817c..ab6853eb8c 100644 --- a/evennia/web/website/views.py +++ b/evennia/web/website/views.py @@ -14,6 +14,8 @@ from evennia import SESSION_HANDLER from evennia.objects.models import ObjectDB from evennia.players.models import PlayerDB +from django.contrib.auth import login + _BASE_CHAR_TYPECLASS = settings.BASE_CHARACTER_TYPECLASS @@ -21,6 +23,33 @@ def page_index(request): """ Main root page. """ + + # handle webclient-website shared login + + browser_session = request.session + browserid = 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_browserid(browserid): + print "session to connect:", session + if session.protocol_key in ("websocket", "ajax/comet"): + SESSION_HANDLER.login(session, player) + session.browserid = browser_session.session_key + browser_session["logged_in"] = player.id + elif browser_session.get("logged_in"): + # The webclient has previously registered a login to this browser_session + print "website: browser_session logged in, trying to login" + player = PlayerDB.objects.get(id=browser_session.get("logged_in")) + login(request, player) + else: + browser_session["logged_in"] = None + + print ("website session:", request.session.session_key, request.user, request.user.is_authenticated()) + # Some misc. configurable stuff. # TODO: Move this to either SQL or settings.py based configuration. fpage_player_limit = 4