From c4d353ee01c2e85eb415326d9b9f6a230c982eeb Mon Sep 17 00:00:00 2001 From: Griatch Date: Sun, 6 Nov 2011 18:54:08 +0100 Subject: [PATCH] Contrib: Added the contrib/menu_login module. --- contrib/menu_login.py | 316 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 316 insertions(+) create mode 100644 contrib/menu_login.py diff --git a/contrib/menu_login.py b/contrib/menu_login.py new file mode 100644 index 0000000000..c07e183b84 --- /dev/null +++ b/contrib/menu_login.py @@ -0,0 +1,316 @@ +""" +Alternative Login system + +Contribution 2011 - Griatch + +This is an alternative login system for Evennia, using the +contrib.menusystem module. As opposed to the default system it doesn't +use emails for authentication and also don't auto-creates a Character +with the same name as the Player (instead assuming some sort of +character-creation to come next). + + +""" + +import re +import traceback +from django.conf import settings +from django.contrib.auth.models import User +from src.server import sessionhandler +from src.players.models import PlayerDB +from src.objects.models import ObjectDB +from src.server.models import ServerConfig +from src.comms.models import Channel + +from src.utils import create, logger, utils, ansi +from src.commands.command import Command +from src.commands.cmdset import CmdSet +from src.commands.cmdhandler import CMD_LOGINSTART +from contrib.menusystem import MenuNode, MenuTree, CMD_NOINPUT, CMD_NOMATCH + +CONNECTION_SCREEN_MODULE = settings.CONNECTION_SCREEN_MODULE + + +# Commands run on the unloggedin screen. Note that this is not using settings.UNLOGGEDIN_CMDSET but +# the menu system, which is why some are named for the numbers in the menu. +# +# Also note that the menu system will automatically assign all +# commands used in its structure a property "menutree" holding a reference +# back to the menutree. This allows the commands to do direct manipulation +# for example by triggering a conditional jump to another node. +# + +# Menu entry 1a - Entering a Username + +class CmdBackToStart(Command): + """ + Step back to node0 + """ + key = CMD_NOINPUT + locks = "cmd:all()" + def func(self): + "Execute the command" + self.menutree.goto("START") + +class CmdUsernameSelect(Command): + """ + Handles the entering of a username and + checks if it exists. + """ + key = CMD_NOMATCH + locks = "cmd:all()" + def func(self): + "Execute the command" + player = PlayerDB.objects.get_player_from_name(self.args) + if not player: + self.caller.msg("{rThis account name couldn't be found. Did you create it? If you did, make sure you spelled it right (case doesn't matter).{n") + self.menutree.goto("node1a") + else: + self.menutree.player = player # store the player so next step can find it + self.menutree.goto("node1b") + +# Menu entry 1b - Entering a Password + +class CmdPasswordSelectBack(Command): + """ + Steps back from the Password selection + """ + key = CMD_NOINPUT + locks = "cmd:all()" + def func(self): + "Execute the command" + self.menutree.goto("node1a") + +class CmdPasswordSelect(Command): + """ + Handles the entering of a password and logs into the game. + """ + key = CMD_NOMATCH + locks = "cmd:all()" + + def func(self): + "Execute the command" + if not hasattr(self.menutree, "player"): + self.caller.msg("{rSomething went wrong! The player was not remembered from last step!{n") + self.menutree.goto("node1a") + return + player = self.menutree.player + if not player.user.check_password(self.args): + self.caller.msg("{rIncorrect password.{n") + self.menutree.goto("node1b") + return + + # we are ok, log us in. + self.caller.msg("{gWelcome %s! Logging in ...{n" % player.key) + self.caller.session_login(player) + + # abort menu, do cleanup. + self.menutree.goto("END") + + # we are logged in. Look around. + character = player.character + if character: + character.execute_cmd("look") + else: + # we have no character yet; use player's look, if it exists + player.execute_cmd("look") + +# Menu entry 2a - Creating a Username + +class CmdUsernameCreate(Command): + """ + Handle the creation of a valid username + """ + key = CMD_NOMATCH + locks = "cmd:all()" + + def func(self): + "Execute the command" + playername = self.args + + # sanity check on the name + if not re.findall('^[\w. @+-]+$', playername) or not (3 <= len(playername) <= 30): + self.caller.msg("\n\r {rAccount name should be between 3 and 30 characters. Letters, spaces, dig\ +its and @/./+/-/_ only.{n") # this echoes the restrictions made by django's auth module. + self.menutree.goto("node2a") + return + if PlayerDB.objects.get_player_from_name(playername): + self.caller.msg("\n\r {rAccount name %s already exists.{n" % playername) + self.menutree.goto("node2a") + return + # store the name for the next step + self.menutree.playername = playername + self.menutree.goto("node2b") + +# Menu entry 2b - Creating a Password + +class CmdPasswordCreateBack(Command): + "Step back from the password creation" + key = CMD_NOINPUT + locks = "cmd:all()" + def func(self): + "Execute the command" + self.menutree.goto("node2a") + +class CmdPasswordCreate(Command): + "Handle the creation of a password. This also creates the actual Player/User object." + key = CMD_NOMATCH + locks = "cmd:all()" + + def func(self): + "Execute the command" + password = self.args + if not hasattr(self.menutree, 'playername'): + self.caller.msg("{rSomething went wrong! Playername not remembered from previous step!{n") + self.menutree.goto("node2a") + return + playername = self.menutree.playername + if len(password) < 3: + # too short password + string = "{rYour password must be at least 3 characters or longer." + string += "\n\rFor best security, make it at least 8 characters long, " + string += "avoid making it a real word and mix numbers into it.{n" + self.caller.msg(string) + self.menutree.goto("node2b") + return + # everything's ok. Create the new player account. Don't create a Character here. + try: + permissions = settings.PERMISSION_PLAYER_DEFAULT + typeclass = settings.BASE_PLAYER_TYPECLASS + new_player = create.create_player(playername, None, password, + typeclass=typeclass, + permissions=permissions, + create_character=False) + if not new_player: + self.msg("There was an error creating the Player. This error was logged. Contact an admin.") + self.menutree.goto("START") + return + utils.init_new_player(new_player) + + # join the new player to the public channel + pchanneldef = settings.CHANNEL_PUBLIC + if pchanneldef: + pchannel = Channel.objects.get_channel(pchanneldef[0]) + if not pchannel.connect_to(new_player): + string = "New player '%s' could not connect to public channel!" % new_player.key + logger.log_errmsg(string) + + # tell the caller everything went well. + string = "{gA new account '%s' was created. Now go log in from the menu!{n" + self.caller.msg(string % (playername)) + self.menutree.goto("START") + except Exception: + # We are in the middle between logged in and -not, so we have to handle tracebacks + # ourselves at this point. If we don't, we won't see any errors at all. + string = "%s\nThis is a bug. Please e-mail an admin if the problem persists." + self.caller.msg(string % (traceback.format_exc())) + logger.log_errmsg(traceback.format_exc()) + + +# Menu entry 3 - help screen + +LOGIN_SCREEN_HELP = \ + """ + Welcome to %s! + + To login you need to first create an account. This is easy and + free to do: Choose option {w(1){n in the menu and enter an account + name and password when prompted. Obs- the account name is {wnot{n + the name of the Character you will play in the game! + + It's always a good idea (not only here, but everywhere on the net) + to not use a regular word for your password. Make it longer than 3 + characters (ideally 6 or more) and mix numbers and capitalization + into it. The password also handles whitespace, so why not make it + a small sentence - easy to remember, hard for a computer to crack. + + Once you have an account, use option {w(2){n to log in using the + account name and password you specified. + + Use the {whelp{n command once you're logged in to get more + aid. Hope you enjoy your stay! + + + (return to go back)""" % settings.SERVERNAME +# Menu entry 4 + +class CmdUnloggedinQuit(Command): + """ + We maintain a different version of the quit command + here for unconnected players for the sake of simplicity. The logged in + version is a bit more complicated. + """ + key = "4" + aliases = ["quit", "qu", "q"] + locks = "cmd:all()" + + def func(self): + "Simply close the connection." + self.menutree.goto("END") + self.caller.msg("Good bye! Disconnecting ...") + self.caller.session_disconnect() + + +# The login menu tree, using the commands above + +START = MenuNode("START", text=utils.string_from_module(CONNECTION_SCREEN_MODULE) + "\n\rPick one of the following:", + links=["node1a", "node2a", "node3", "END"], + linktexts=["Log in with an existing account", + "Create a new account", + "Help", + "Quit",], + selectcmds=[None, None, None, CmdUnloggedinQuit]) + +node1a = MenuNode("node1a", text="Please enter your account name (empty to abort).", + links=["START", "node1b"], + helptext=["Enter the account name you previously registered with."], + keywords=[CMD_NOINPUT, CMD_NOMATCH], + selectcmds=[CmdBackToStart, CmdUsernameSelect], + nodefaultcmds=True) # if we don't, default help/look will be triggered by names starting with l/h ... +node1b = MenuNode("node1b", text="Please enter your password (empty to go back).", + links=["node1a", "END"], + keywords=[CMD_NOINPUT, CMD_NOMATCH], + selectcmds=[CmdPasswordSelectBack, CmdPasswordSelect], + nodefaultcmds=True) + +node2a = MenuNode("node2a", text="Please enter your desired account name (empty to abort).", + links=["START", "node2b"], + helptext="Account name can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_ only.", + keywords=[CMD_NOINPUT, CMD_NOMATCH], + selectcmds=[CmdBackToStart, CmdUsernameCreate], + nodefaultcmds=True) +node2b = MenuNode("node2b", text="Please enter your password (empty to go back).", + links=["node2a", "START"], + helptext="Your password cannot contain any characters.", + keywords=[CMD_NOINPUT, CMD_NOMATCH], + selectcmds=[CmdPasswordCreateBack, CmdPasswordCreate], + nodefaultcmds=True) +node3 = MenuNode("node3", text=LOGIN_SCREEN_HELP, + links=["START"], + helptext="", + keywords=[CMD_NOINPUT], + selectcmds=[CmdBackToStart]) + + +# access commands + +class UnloggedInCmdSet(CmdSet): + "Cmdset for the unloggedin state" + key = "UnloggedinState" + priority = 0 + def at_cmdset_creation(self): + self.add(CmdUnloggedinLook()) + +class CmdUnloggedinLook(Command): + """ + An unloggedin version of the look command. This is called by the server when the player + first connects. It sets up the menu before handing off to the menu's own look command.. + """ + key = CMD_LOGINSTART + aliases = ["look", "l"] + locks = "cmd:all()" + + def func(self): + "Execute the menu" + menu = MenuTree(self.caller, nodes=(START, node1a, node1b, node2a, node2b, node3), exec_end=None) + menu.start()