Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Typeclass-based object creation, validation, and authentication #1669

Merged
merged 25 commits into from Oct 22, 2018
Merged

Typeclass-based object creation, validation, and authentication #1669

merged 25 commits into from Oct 22, 2018

Conversation

strikaco
Copy link
Contributor

@strikaco strikaco commented Oct 2, 2018

Brief overview of PR changes/additions

  • Implements username validation through configurable Django validators.
  • Accounts now handle reflexive actions.
    • Check if a name/IP are banned with a single call to Account.is_banned()
    • Get or create a usable guest account (where enabled) with a single call to Account.authenticate_guest()
    • Authenticate regular accounts with a single call to Account.authenticate(), which also checks login throttles, banlists and emits events to the security log upon success or failure.
    • Usernames have excessive spaces stripped and visually-similar Unicode characters are treated as identical for the purposes of preventing duplicate (or duplicate-looking) usernames via Account.normalize_username().
    • Create a fully-functional default Account or Account+Character pair with Account.create()
  • Cleans up a lot of redundant/migrated/consolidated logic from unloggedin cmdset.
  • Adjusts throttle so that logging of continued auth failures is suppressed while throttle is active; also accepts custom activation message on update/triggering

Motivation for adding to Evennia

Migration of logic out of Commands and into more contextually-agnostic locations.

Other info (issues closed, discussion etc)

Apologies for the torrent of changes in a single PR; once I started messing with one thing, the effects cascaded.

#1383:

  • Authentication failures now logged (limited by throttle)
  • Account creations now logged (guest and normal)
  • Throttle activations now logged
  • Password changes now logged
  • IP address of creator (at time of creation) stored on new Account objects (makes bulk-created/spam accounts easier to identify without having to rely on timestamps)
  • IP address of creator (at time of creation) stored on new Character objects during Account creation. Creator's id (as int) is also stored on Character objects.

#533:

  • Account creation and the security measures surrounding it can now be consistently applied and audited by both terminal commands and Django forms.

Copy link
Member

@Griatch Griatch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the consolidation of resources done here. I'm somewhat in two minds about the practice of storing these resources as class methods on the Account class:

Positives:

  • It gathers those account-specific resources in a consistent way. Using Account.validate_password is pretty intuitive.
  • Storing it on the Account typeclass still allows people to override these classes (now on the Typeclass rather than on the Command as before).

Negatives:

  • Nothing else in the Evennia API works like this from before.
  • We already have a create functions in the Account manager, wrapped by the utils.create.account function - the Account.create is a game-specific wrapper on top of that which confuses the matter; which one should one use?
  • Linking back to the first negative point - this breaks consistency with other typeclasses. You can't do Object.create or Script.create, for example - the Account class becomes a discrepancy. This inconsistency does not an acceptable API do.

I can think of three possible solutions here:

  • Rework this PR by storing these class functions in a new utility module. This has the advantage of minimal API change but means it becomes a lot harder or even impossible for the dev to override those functions without touching core (the reason they were in a Command in the first place)
  • Consolidate all typeclasses to have a .create method on themselves, in the simplest form only as a wrapper of the manager method. This is a big or small API change depending on if we then consider utils.create deprecated or not.
  • Keep this functionality in the Command, adding the new django-related functionality there. This is the smallest API change but pretty much removes the consolidation aspect of this PR.

I'll need to ponder this a bit but am open to discussion.

evennia/accounts/accounts.py Outdated Show resolved Hide resolved
@strikaco
Copy link
Contributor Author

strikaco commented Oct 7, 2018

We already have a create functions in the Account manager, wrapped by the utils.create.account function - the Account.create is a game-specific wrapper on top of that which confuses the matter; which one should one use?

Unfortunately it depends. Whether you use Account.objects.create() or calling utils.create_account(), both are low-level calls-- you ask for an account, you get one, nothing more. It does not perform the post-creation steps currently implemented only in CmdUnconnectedCreate.

As you point out, the benefit of implementing this as a class method is that if one decides they want additional steps taken (like subscribing to additional channels or granting starter bonuses) they can extend the functionality instead of having to reimplement all of it.

Linking back to the first negative point - this breaks consistency with other typeclasses. You can't do Object.create or Script.create, for example - the Account class becomes a discrepancy. This inconsistency does not an acceptable API do.

We could though. I just hadn't thought of that :)

Consolidate all typeclasses to have a .create method on themselves, in the simplest form only as a wrapper of the manager method. This is a big or small API change depending on if we then consider utils.create deprecated or not.

I think this is the best option, but we don't really need to deprecate utils.create at all. Leave that for power users who don't want any hand-holding, while implementing friendlier and more thorough object.create() methods for the sake of easier adoption and configuration. My opinion (and part of the basis for this PR) is that a naive dev should be able to call something like object.create() and receive a default feature-complete object where possible, unless overridden or extended to do otherwise.

@Griatch
Copy link
Member

Griatch commented Oct 8, 2018

@strikaco After some thinking I agree that this option is the sanest one - I think the way to go would be to add a new .create method on the Typeclass base (defaulting it to be a simple wrapper for the utils.create functions) and then override it in the subclasses where it's needed (like for Account.create in this case).

@strikaco
Copy link
Contributor Author

strikaco commented Oct 9, 2018

@strikaco After some thinking I agree that this option is the sanest one - I think the way to go would be to add a new .create method on the Typeclass base

For additional consideration in support of this course of action, note that these objects already had delete() methods implemented directly on themselves. It seems logical that create() should also be accessible in this manner.

(defaulting it to be a simple wrapper for the utils.create functions)

Looking into it, I notice that DefaultObjects are affected by the same workflow issue that led to this PR for Account objects. CmdCreate implements useful post-creation functionality (like setting locks and default descriptions) that is only available in-game through CmdCreate. I'm sure I'll find the same when I get to Character/Room/Exit objects too.

Wouldn't it make more sense to migrate the effective functionality of CmdCreateWhatever to WhateverObject.create() across the board instead of making them simple wrappers? I think you alluded to this with "in the simplest form only as a wrapper of the manager method" but want to make sure we're on the same page. This would be more consistent in application (not just an exception for Accounts) and much easier for newcomers to grasp.

evennia/accounts/tests.py Outdated Show resolved Hide resolved
@strikaco strikaco changed the title Account validation, creation and authentication consolidation Typeclass-based object creation, validation, and authentication Oct 10, 2018
@strikaco
Copy link
Contributor Author

@Griatch Whenever you get a moment, I'd appreciate a quick review. Thanks!

Copy link
Member

@Griatch Griatch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good. From a consistency perspective I think this will work well. However, since there is still no changes to the typeclass-base, other typeclassed entities like Msg and Comms will still be inconsistent as to the create method. If we are changing the API, we need to do so consistently.


# Username validation plugins
AUTH_USERNAME_VALIDATORS = [
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From a pure formatting perspective, we generally don't format dicts this way (javascript-like, maybe?), but tend to use a more compact form in this settings file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a name for this compact style? It looks pretty but I'm unfamiliar with it and it isn't mentioned in the style guide.

@strikaco
Copy link
Contributor Author

How many typeclassed entities are there? I see "create_object", "create_script", "create_help_entry", "create_message", "create_channel", "create_account" listed in utils.create(). So far it seems I'm overlooking help_entry, message and channel?

What should I have the typeclass base do? I can't account for custom entities or their creation path. Should I just raise a not-implemented exception?

@Griatch
Copy link
Member

Griatch commented Oct 17, 2018

The base typeclasses are DefaultAccount, DefaultObject, DefaultScript and DefaultChannel.

I mis-spoke about the Msg, this is not a typeclass, just an idmapped entity. Same with HelpEntry (although I have toyed with the idea making help entries typeclassed, they are not at this time).

While the TypeclassBase could forward its .create to cls.objects.create, I think it's probably best to not touch it further now that I look at it - keep the create functions only on the parent typeclasses above.

@strikaco
Copy link
Contributor Author

I believe I've addressed the feedback to date; whenever you get a moment this is ready for review.

@Griatch
Copy link
Member

Griatch commented Oct 21, 2018

@strikaco The code changes look good, but you must test your PRs against the entire test suite - it appears to pass when you run your tests in isolation, but fail when ran in combination with the rest of the Evennia test suite (and I've compared with and without this branch - the fault is introduced here). See the results from the Travis integration.
The likely cause of this is because you are making assumptions about the state of your test environment what is not valid when run with other tests. In principle unit tests should be isolated enough that this should not happen (so the problem likely lies in another test that runs before this), but one way or another - the test suite does not pass.

@strikaco
Copy link
Contributor Author

@Griatch All bugs should be squashed; comprehensive tests pass locally but it doesn't look like Travis picked up the last commit. What now?

@strikaco
Copy link
Contributor Author

...Looks like it picked it up after all. This one's ready.

@Griatch Griatch merged commit 52b58db into evennia:develop Oct 22, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants