def func(self):
        """
        Uses the Django admin api. Note that unlogged-in commands
        have a unique position in that their func() receives
        a session object instead of a source_object like all
        other types of logged-in commands (this is because
        there is no object yet before the account has logged in)
        """
        session = self.caller
        address = session.address

        args = self.args
        # extract double quote parts
        parts = [
            part.strip() for part in re.split(r"\"", args) if part.strip()
        ]
        if len(parts) <= 1:
            # this was (hopefully) due to no double quotes being found, or a guest login
            # parts = parts[0].split(None, 1)

            # Guest login
            # if len(parts) == 1 and parts[0].lower() == "guest":

            # Get Guest typeclass
            Guest = class_from_module(settings.BASE_GUEST_TYPECLASS)

            account, errors = Guest.authenticate(ip=address, zone=session.zone)
            if account:
                session.sessionhandler.login(session, account)
                return
            else:
                session.msg("|R%s|n" % "\n".join(errors))
                return

        if len(parts) != 2:
            session.msg("\n\r Usage (without <>): connect <name> <password>")
            return

        # Get account class
        Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)

        name, password = parts
        account, errors = Account.authenticate(username=name,
                                               password=password,
                                               ip=address,
                                               session=session)
        if account:
            session.sessionhandler.login(session, account)
        else:
            session.msg("|R%s|n" % "\n".join(errors))
Exemple #2
0
    def setUp(self):
        super(ChannelDetailTest, self).setUp()

        klass = class_from_module(self.channel_typeclass)

        # Create a channel
        klass.create("demo")
Exemple #3
0
class CharacterMixin(TypeclassMixin):
    """
    This is a "mixin", a modifier of sorts.

    Any view class with this in its inheritance list will be modified to work
    with Character objects instead of generic Objects or otherwise.

    """

    # -- Django constructs --
    model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)
    form_class = website_forms.CharacterForm
    success_url = reverse_lazy("character-manage")

    def get_queryset(self):
        """
        This method will override the Django get_queryset method to only
        return a list of characters associated with the current authenticated
        user.

        Returns:
            queryset (QuerySet): Django queryset for use in the given view.

        """
        # Get IDs of characters owned by account
        account = self.request.user
        ids = [getattr(x, "id") for x in account.characters if x]

        # Return a queryset consisting of those characters
        return self.typeclass.objects.filter(id__in=ids).order_by(
            Lower("db_key"))
Exemple #4
0
def create_guest_account(session):
    """
    Creates a guest account/character for this session, if one is available.

    Args:
        session (Session): the session which will use the guest account/character.

    Returns:
        GUEST_ENABLED (boolean), account (Account):
            the boolean is whether guest accounts are enabled at all.
            the Account which was created from an available guest name.
    """
    enabled = settings.GUEST_ENABLED
    address = session.address

    # Get account class
    Guest = class_from_module(settings.BASE_GUEST_TYPECLASS)

    # Get an available guest account
    # authenticate() handles its own throttling
    account, errors = Guest.authenticate(ip=address)
    if account:
        return enabled, account
    else:
        session.msg("|R%s|n" % "\n".join(errors))
        return enabled, None
Exemple #5
0
def create_normal_account(session, name, password):
    """
    Creates an account with the given name and password.

    Args:
        session (Session): the session which is requesting to create an account.
        name (str): the name that the account wants to use for login.
        password (str): the password desired by this account, for login.

    Returns:
        account (Account): the account which was created from the name and password.
    """
    # Get account class
    Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)

    address = session.address

    # Match account name and check password
    # authenticate() handles all its own throttling
    account, errors = Account.authenticate(username=name,
                                           password=password,
                                           ip=address,
                                           session=session)
    if not account:
        # No accountname or password match
        session.msg("|R%s|n" % "\n".join(errors))
        return None

    return account
Exemple #6
0
    def setUp(self):
        super(ChannelDetailTest, self).setUp()

        klass = class_from_module(self.channel_typeclass,
                                  fallback=settings.FALLBACK_CHANNEL_TYPECLASS)

        # Create a channel
        klass.create("demo")
Exemple #7
0
def _gamestats():
    # Some misc. configurable stuff.
    # TODO: Move this to either SQL or settings.py based configuration.
    fpage_account_limit = 4

    # A QuerySet of the most recently connected accounts.
    recent_users = AccountDB.objects.get_recently_connected_accounts(
    )[:fpage_account_limit]
    nplyrs_conn_recent = len(recent_users) or "none"
    nplyrs = AccountDB.objects.num_total_accounts() or "none"
    nplyrs_reg_recent = len(
        AccountDB.objects.get_recently_created_accounts()) or "none"
    nsess = SESSION_HANDLER.account_count()
    # nsess = len(AccountDB.objects.get_connected_accounts()) or "no one"

    nobjs = ObjectDB.objects.count()
    nobjs = nobjs or 1  # fix zero-div error with empty database
    Character = class_from_module(
        settings.BASE_CHARACTER_TYPECLASS,
        fallback=settings.FALLBACK_CHARACTER_TYPECLASS)
    nchars = Character.objects.all_family().count()
    Room = class_from_module(settings.BASE_ROOM_TYPECLASS,
                             fallback=settings.FALLBACK_ROOM_TYPECLASS)
    nrooms = Room.objects.all_family().count()
    Exit = class_from_module(settings.BASE_EXIT_TYPECLASS,
                             fallback=settings.FALLBACK_EXIT_TYPECLASS)
    nexits = Exit.objects.all_family().count()
    nothers = nobjs - nchars - nrooms - nexits

    pagevars = {
        "page_title": "Front Page",
        "accounts_connected_recent": recent_users,
        "num_accounts_connected": nsess or "no one",
        "num_accounts_registered": nplyrs or "no",
        "num_accounts_connected_recent": nplyrs_conn_recent or "no",
        "num_accounts_registered_recent": nplyrs_reg_recent or "no one",
        "num_rooms": nrooms or "none",
        "num_exits": nexits or "no",
        "num_objects": nobjs or "none",
        "num_characters": nchars or "no",
        "num_others": nothers or "no",
    }
    return pagevars
Exemple #8
0
class ObjectCreateView(LoginRequiredMixin, EvenniaCreateView):
    """
    This is an important view.

    Any view you write that deals with creating a specific object will want to
    inherit from this. It provides the mechanisms by which to make sure the user
    requesting creation of an object is authenticated, and provides a sane
    default title for the page.

    """
    model = class_from_module(settings.BASE_OBJECT_TYPECLASS)
Exemple #9
0
class AccountMixin(TypeclassMixin):
    """
    This is a "mixin", a modifier of sorts.

    Any view class with this in its inheritance list will be modified to work
    with Account objects instead of generic Objects or otherwise.

    """
    # -- Django constructs --
    model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
    form_class = website_forms.AccountForm
Exemple #10
0
    class Meta:
        """
        This is a Django construct that provides additional configuration to
        the form.

        """
        # The model/typeclass this form creates
        model = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)

        # The fields to display on the form, in the given order
        fields = ("username", "email")

        # Any overrides of field classes
        field_classes = {'username': UsernameField}
Exemple #11
0
    class Meta:
        """
        This is a Django construct that provides additional configuration to
        the form.

        """

        # Get the correct object model
        model = class_from_module(settings.BASE_CHARACTER_TYPECLASS)

        # Allow entry of the 'key' field
        fields = ("db_key", )

        # Rename 'key' to something more intelligible
        labels = {"db_key": "Name"}
Exemple #12
0
    class Meta:
        """
        This is a Django construct that provides additional configuration to
        the form.

        """

        # The model/typeclass this form creates
        model = class_from_module(settings.BASE_OBJECT_TYPECLASS)

        # The fields to display on the form, in the given order
        fields = ("db_key", )

        # This lets us rename ugly db-specific keys to something more human
        labels = {"db_key": "Name"}
Exemple #13
0
    def func(self):
        """Do checks and create account"""

        session = self.caller
        args = self.args.strip()

        address = session.address

        # Get account class
        Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)

        # extract double quoted parts
        parts = [part.strip() for part in re.split(r"\"", args) if
                 part.strip()]
        if len(parts) == 1:
            # this was (hopefully) due to no quotes being found
            parts = parts[0].split(None, 1)
        if len(parts) != 2:
            string = (
                "\n Utilisation (sans <>): @inscription <nom d'utilisateur> <mot de passe>"
                "\nSi <nom d'utilisateur> ou <mot de passe> contiennent des espaces, mettez des guillements."
            )
            session.msg(string)
            return

        username, password = parts

        # everything's ok. Create the new account account.
        account, errors = Account.create(
            username=username, password=password, ip=address, session=session
        )

        if not account:
            session.msg("|R{}|n".format("\n".join(errors)))
            return

        new_account_chunk = f"Un nouveau compte {username} a été créé. Bienvenue!"
        double_quotes_chunk = f"\n\nVous pouvez désormais vous connecter avec la commande '|w@connexion \"{username}\" <votre mot de passe>|n'."
        no_double_quotes_chunk = f"\n\nVous pouvez désormais vous connecter avec la commande '|w@connexion {username} <votre mot de passe>|n'."
        chunks = [new_account_chunk, ]
        # tell the caller everything went well.
        if " " in username:
            chunks.append(double_quotes_chunk)
        else:
            chunks.append(no_double_quotes_chunk)
        session.msg("".join(chunks))
Exemple #14
0
class ChannelMixin(TypeclassMixin):
    """
    This is a "mixin", a modifier of sorts.

    Any view class with this in its inheritance list will be modified to work
    with HelpEntry objects instead of generic Objects or otherwise.

    """

    # -- Django constructs --
    model = class_from_module(settings.BASE_CHANNEL_TYPECLASS,
                              fallback=settings.FALLBACK_CHANNEL_TYPECLASS)

    # -- Evennia constructs --
    page_title = "Channels"

    # What lock type to check for the requesting user, authenticated or not.
    # https://github.com/evennia/evennia/wiki/Locks#valid-access_types
    access_type = "listen"

    def get_queryset(self):
        """
        Django hook; here we want to return a list of only those Channels
        and other documentation that the current user is allowed to see.

        Returns:
            queryset (QuerySet): List of Channels available to the user.

        """
        account = self.request.user

        # Get list of all Channels
        channels = self.typeclass.objects.all().iterator()

        # Now figure out which ones the current user is allowed to see
        bucket = [
            channel.id for channel in channels
            if channel.access(account, "listen")
        ]

        # Re-query and set a sorted list
        filtered = self.typeclass.objects.filter(id__in=bucket).order_by(
            Lower("db_key"))

        return filtered
Exemple #15
0
    def func(self):
        """Do checks and create account"""

        session = self.caller
        args = self.args.strip()

        address = session.address

        # Get account class
        Account = class_from_module(settings.BASE_ACCOUNT_TYPECLASS)

        # extract double quoted parts
        parts = [
            part.strip() for part in re.split(r"\"", args) if part.strip()
        ]
        if len(parts) == 1:
            # this was (hopefully) due to no quotes being found
            parts = parts[0].split(None, 1)
        if len(parts) != 2:
            string = (
                "\n Usage (without <>): create <name> <password>"
                "\nIf <name> or <password> contains spaces, enclose it in double quotes."
            )
            session.msg(string)
            return

        username, password = parts

        # everything's ok. Create the new account account.
        account, errors = Account.create(username=username,
                                         password=password,
                                         ip=address,
                                         session=session)
        if account:
            # tell the caller everything went well.
            string = "A new account '%s' was created. Welcome!"
            if " " in username:
                string += (
                    "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
                )
            else:
                string += "\n\nYou can now log with the command 'connect %s <your password>'."
            session.msg(string % (username, username))
        else:
            session.msg("|R%s|n" % "\n".join(errors))
Exemple #16
0
class ObjectDeleteView(LoginRequiredMixin, ObjectDetailView,
                       EvenniaDeleteView):
    """
    This is an important view for obvious reasons!

    Any view you write that deals with deleting a specific object will want to
    inherit from this. It provides the mechanisms by which to make sure the user
    requesting deletion of an object is authenticated, and that they have
    permissions to delete the requested object.

    """

    # -- Django constructs --
    model = class_from_module(settings.BASE_OBJECT_TYPECLASS,
                              fallback=settings.FALLBACK_OBJECT_TYPECLASS)
    template_name = "website/object_confirm_delete.html"

    # -- Evennia constructs --
    access_type = "delete"

    def delete(self, request, *args, **kwargs):
        """
        Calls the delete() method on the fetched object and then
        redirects to the success URL.

        We extend this so we can capture the name for the sake of confirmation.

        """
        # Get the object in question. ObjectDetailView.get_object() will also
        # check to make sure the current user (authenticated or not) has
        # permission to delete it!
        obj = str(self.get_object())

        # Perform the actual deletion (the parent class handles this, which will
        # in turn call the delete() method on the object)
        response = super(ObjectDeleteView,
                         self).delete(request, *args, **kwargs)

        # Notify the user of the deletion
        messages.success(request, "Successfully deleted '%s'." % obj)
        return response
Exemple #17
0
"""

Admin commands

"""

import time
import re
from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.server.models import ServerConfig
from evennia.utils import prettytable, search, class_from_module

COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)

PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]

# limit members for API inclusion
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer", "CmdEmit",
           "CmdNewPassword", "CmdPerm", "CmdWall")


class CmdBoot(COMMAND_DEFAULT_CLASS):
    """
    kick a player from the server.

    Usage
      @boot[/switches] <player obj> [: reason]

    Switches:
      quiet - Silently boot without informing player
    def func(self):
        """Do checks and create account"""

        session = self.caller
        args = self.args.strip()

        # Rate-limit account creation.
        address = session.address

        if isinstance(address, tuple):
            address = address[0]
        if CREATION_THROTTLE.check(address):
            session.msg(
                "|RYou are creating too many accounts. Try again in a few minutes.|n"
            )
            return

        # extract double quoted parts
        parts = [
            part.strip() for part in re.split(r"\"", args) if part.strip()
        ]
        if len(parts) == 1:
            # this was (hopefully) due to no quotes being found
            parts = parts[0].split(None, 1)
        if len(parts) != 2:
            string = "\n Usage (without <>): create <name> <password>" \
                     "\nIf <name> or <password> contains spaces, enclose it in double quotes."
            session.msg(string)
            return
        accountname, password = parts

        # sanity checks
        if not re.findall(r"^[\w. @+\-']+$",
                          accountname) or not (0 < len(accountname) <= 30):
            # this echoes the restrictions made by django's auth
            # module (except not allowing spaces, for convenience of
            # logging in).
            string = "\n\r Accountname can max be 30 characters or fewer. Letters, spaces, digits and @/./+/-/_/' only."
            session.msg(string)
            return
        # strip excessive spaces in accountname
        accountname = re.sub(r"\s+", " ", accountname).strip()
        if AccountDB.objects.filter(username__iexact=accountname):
            # account already exists (we also ignore capitalization here)
            session.msg(
                "Sorry, there is already an account with the name '%s'." %
                accountname)
            return
        # Reserve accountnames found in GUEST_LIST
        if settings.GUEST_LIST and accountname.lower() in (
                guest.lower() for guest in settings.GUEST_LIST):
            string = "\n\r That name is reserved. Please choose another Accountname."
            session.msg(string)
            return

        # Validate password
        Account = utils.class_from_module(settings.BASE_ACCOUNT_TYPECLASS)
        # Have to create a dummy Account object to check username similarity
        valid, error = Account.validate_password(
            password, account=Account(username=accountname))
        if error:
            errors = [e for suberror in error.messages for e in error.messages]
            string = "\n".join(errors)
            session.msg(string)
            return

        # Check IP and/or name bans
        bans = ServerConfig.objects.conf("server_bans")
        if bans and (any(tup[0] == accountname.lower()
                         for tup in bans) or any(tup[2].match(session.address)
                                                 for tup in bans if tup[2])):
            # this is a banned IP or name!
            string = "|rYou have been banned and cannot continue from here." \
                     "\nIf you feel this ban is in error, please email an admin.|x"
            session.msg(string)
            session.sessionhandler.disconnect(session,
                                              "Good bye! Disconnecting.")
            return

        # everything's ok. Create the new account account.
        try:
            permissions = settings.PERMISSION_ACCOUNT_DEFAULT
            typeclass = settings.BASE_CHARACTER_TYPECLASS
            new_account = _create_account(session, accountname, password,
                                          permissions)
            if new_account:
                if MULTISESSION_MODE < 2:
                    default_home = ObjectDB.objects.get_id(
                        settings.DEFAULT_HOME)
                    _create_character(session, new_account, typeclass,
                                      default_home, permissions)

                # Update the throttle to indicate a new account was created from this IP
                CREATION_THROTTLE.update(address)

                # tell the caller everything went well.
                string = "A new account '%s' was created. Welcome!"
                if " " in accountname:
                    string += "\n\nYou can now log in with the command 'connect \"%s\" <your password>'."
                else:
                    string += "\n\nYou can now log with the command 'connect %s <your password>'."
                session.msg(string % (accountname, accountname))

        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.
            session.msg(
                "An error occurred. Please e-mail an admin if the problem persists."
            )
            logger.log_trace()
Exemple #19
0
class ObjectUpdateView(LoginRequiredMixin, ObjectDetailView,
                       EvenniaUpdateView):
    """
    This is an important view.

    Any view you write that deals with updating a specific object will want to
    inherit from this. It provides the mechanisms by which to make sure the user
    requesting editing of an object is authenticated, and that they have
    permissions to edit the requested object.

    This functions slightly different from default Django UpdateViews in that
    it does not update core model fields, *only* object attributes!

    """

    # -- Django constructs --
    model = class_from_module(settings.BASE_OBJECT_TYPECLASS)

    # -- Evennia constructs --
    access_type = "edit"

    def get_success_url(self):
        """
        Django hook.

        Can be overridden to return any URL you want to redirect the user to
        after the object is successfully updated, but by default it goes to the
        object detail page so the user can see their changes reflected.

        """
        if self.success_url:
            return self.success_url
        return self.object.web_get_detail_url()

    def get_initial(self):
        """
        Django hook, modified for Evennia.

        Prepopulates the update form field values based on object db attributes.

        Returns:
            data (dict): Dictionary of key:value pairs containing initial form
                data.

        """
        # Get the object we want to update
        obj = self.get_object()

        # Get attributes
        data = {k: getattr(obj.db, k, "") for k in self.form_class.base_fields}

        # Get model fields
        data.update(
            {k: getattr(obj, k, "")
             for k in self.form_class.Meta.fields})

        return data

    def form_valid(self, form):
        """
        Override of Django hook.

        Updates object attributes based on values submitted.

        This is run when the form is submitted and the data on it is deemed
        valid-- all values are within expected ranges, all strings contain
        valid characters and lengths, etc.

        This method is only called if all values for the fields submitted
        passed form validation, so at this point we can assume the data is
        validated and sanitized.

        """
        # Get the attributes after they've been cleaned and validated
        data = {
            k: v
            for k, v in form.cleaned_data.items()
            if k not in self.form_class.Meta.fields
        }

        # Update the object attributes
        for key, value in data.items():
            self.object.attributes.add(key, value)
            messages.success(
                self.request,
                "Successfully updated '%s' for %s." % (key, self.object))

        # Do not return super().form_valid; we don't want to update the model
        # instance, just its attributes.
        return HttpResponseRedirect(self.get_success_url())
Exemple #20
0
"""

Admin commands

"""

import time
import re
from django.conf import settings
from evennia.server.sessionhandler import SESSIONS
from evennia.server.models import ServerConfig
from evennia.utils import evtable, search, class_from_module

COMMAND_DEFAULT_CLASS = class_from_module(settings.COMMAND_DEFAULT_CLASS)

PERMISSION_HIERARCHY = [p.lower() for p in settings.PERMISSION_HIERARCHY]

# limit members for API inclusion
__all__ = ("CmdBoot", "CmdBan", "CmdUnban", "CmdDelPlayer",
           "CmdEmit", "CmdNewPassword", "CmdPerm", "CmdWall")


class CmdBoot(COMMAND_DEFAULT_CLASS):
    """
    kick a player from the server.

    Usage
      @boot[/switches] <player obj> [: reason]

    Switches:
      quiet - Silently boot without informing player
Exemple #21
0
class ObjectDetailView(EvenniaDetailView):
    """
    This is an important view.

    Any view you write that deals with displaying, updating or deleting a
    specific object will want to inherit from this. It provides the mechanisms
    by which to retrieve the object and make sure the user requesting it has
    permissions to actually *do* things to it.

    """

    # -- Django constructs --
    #
    # Choose what class of object this view will display. Note that this should
    # be an actual Python class (i.e. do `from typeclasses.characters import
    # Character`, then put `Character`), not an Evennia typeclass path
    # (i.e. `typeclasses.characters.Character`).
    #
    # So when you extend it, this line should look simple, like:
    # model = Object
    model = class_from_module(settings.BASE_OBJECT_TYPECLASS)

    # What HTML template you wish to use to display this page.
    template_name = "website/object_detail.html"

    # -- Evennia constructs --
    #
    # What lock type to check for the requesting user, authenticated or not.
    # https://github.com/evennia/evennia/wiki/Locks#valid-access_types
    access_type = "view"

    # What attributes of the object you wish to display on the page. Model-level
    # attributes will take precedence over identically-named db.attributes!
    # The order you specify here will be followed.
    attributes = ["name", "desc"]

    def get_context_data(self, **kwargs):
        """
        Adds an 'attributes' list to the request context consisting of the
        attributes specified at the class level, and in the order provided.

        Django views do not provide a way to reference dynamic attributes, so
        we have to grab them all before we render the template.

        Returns:
            context (dict): Django context object

        """
        # Get the base Django context object
        context = super(ObjectDetailView, self).get_context_data(**kwargs)

        # Get the object in question
        obj = self.get_object()

        # Create an ordered dictionary to contain the attribute map
        attribute_list = OrderedDict()

        for attribute in self.attributes:
            # Check if the attribute is a core fieldname (name, desc)
            if attribute in self.typeclass._meta._property_names:
                attribute_list[attribute.title()] = getattr(obj, attribute, "")

            # Check if the attribute is a db attribute (char1.db.favorite_color)
            else:
                attribute_list[attribute.title()] = getattr(
                    obj.db, attribute, "")

        # Add our attribute map to the Django request context, so it gets
        # displayed on the template
        context["attribute_list"] = attribute_list

        # Return the comprehensive context object
        return context

    def get_object(self, queryset=None):
        """
        Override of Django hook that provides some important Evennia-specific
        functionality.

        Evennia does not natively store slugs, so where a slug is provided,
        calculate the same for the object and make sure it matches.

        This also checks to make sure the user has access to view/edit/delete
        this object!

        """
        # A queryset can be provided to pre-emptively limit what objects can
        # possibly be returned. For example, you can supply a queryset that
        # only returns objects whose name begins with "a".
        if not queryset:
            queryset = self.get_queryset()

        # Get the object, ignoring all checks and filters for now
        obj = self.typeclass.objects.get(pk=self.kwargs.get("pk"))

        # Check if this object was requested in a valid manner
        if slugify(obj.name) != self.kwargs.get(self.slug_url_kwarg):
            raise HttpResponseBadRequest(
                "No %(verbose_name)s found matching the query" %
                {"verbose_name": queryset.model._meta.verbose_name})

        # Check if the requestor account has permissions to access object
        account = self.request.user
        if not obj.access(account, self.access_type):
            raise PermissionDenied(
                "You are not authorized to %s this object." % self.access_type)

        # Get the object, if it is in the specified queryset
        obj = super(ObjectDetailView, self).get_object(queryset)

        return obj