Authentication in EVE-SRP was designed from the start to allow for multiple different authentication systems and to make it easy to integrate it with an existing authentication system.
As an exercise in how to write your own authentication plugin, let’s write one that doesn’t rely on an external service. There are four classes to override for your authentication plugin, User, Group, AuthMethod and AuthForm.
Let’s start with subclassing User. This class is mapped to an SQL table using SQLAlchemy’s declarative extension (more specifically, the Flask-SQLAlchemy plugin to Flask). The parent class automatically sets up the table name and inheritance mapper arguments for you, so all you need to do is provide the id attribute that links your class with the parent class and an attribute to store the password hash. In the example, we’re using the pbkdf2 package to provide the password hashing. We also have a checking method to make life easier for us later.
from evesrp import db
from evesrp.auth import User
from pbkdf2 import pbkdf2_bin
class LocalUser(User):
id = db.Column(db.Integer, db.ForeignKey('user.id', primary_key=True))
password = db.Column(db.LargeBinary(256), nullable=False)
salt = db.Column(db.LargeBinary(256), nullable=False)
def __init__(self, username, password):
self.name = username
self.salt = None
self.password = pbkdf2_bin(password.encode('utf-8'), self.salt,
iterations=100000)
def check_password(self, password):
key = pbkdf2_bin(password.encode('utf-8'), self.salt,
iterations=100000)
matched = 0
for a, b in zip(self.password, key):
matched |= ord(a) ^ ord(b)
return matched == 0
@classmethod
def authmethod(cls):
return LocalAuth
In addition, we override User.authmethod() to tell which authentication method class to use for the actual login process.
AuthMethod subclasses have three and a half methods they can subclass to customize themselves. AuthMethod.form() returns the AuthForm subclass that represents the necessary fields. AuthMethod.login() performs the actual login process. As part of this, it is passed an instance of the class given by AuthMethod.form() with the submitted data via the form argument. Finally, some login methods need a secondary view, for example, OpenID needs a destination to redirect to and process the arguments passed to along with the redirect. The AuthMethod.view() method is an optional method AuthMethod subclasses can implement to process/present a secondary view. It can be accessed at /login/<AuthMethod.__name__.lower()> and accepts the GET and POST HTTP verbs.
from evesrp.auth import AuthForm, AuthMethod
from flask import redirect, url_for
from flask.ext.wtf import Form
from sqlalchemy.orm.exc import NoResultFound
from wtforms.fields import StringField, PasswordField, SubmitField
from wtforms.validators import InputRequired, EqualTo
class LocalLoginForm(AuthForm):
username = StringField('Username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired()])
submit = SubmitField('Log In')
class LocalCreateUserForm(Form):
username = StringField('Username', validators=[InputRequired()])
password = PasswordField('Password', validators=[InputRequired(),
EqualTo('password_repeat', message='Passwords must match')])
password_repeat = PasswordField(
'Repeat Password', validators=[InputRequired()])
submit = SubmitField('Log In')
class LocalAuth(AuthMethod):
def form(self):
return LocalLoginForm()
def login(self, form):
# form has already been validated
try:
user = LocalUser.query.filter_by(name=form.username.data).one()
except NoResultFound:
flash("No user found with that username.", 'error')
return redirect(url_for('login.login'))
if user.check_password(form.password.data):
self.login_user(user)
redirect(request.args.get('next') or url_for('index'))
else:
flash("Incorrect password.", 'error')
redirect(url_for('login.login'))
def view(self):
form = LocalCreateUserForm()
if form.validate_on_submit():
user = LocalUser(form.username.data, form.password.data)
db.session.add(user)
db.session.commit()
self.login_user(user)
return redirect(url_for('index'))
return render_template('form.html', form=form)
Signal to the authentication systems that a new user has logged in.
Handles sending the flask.ext.principal.identity_changed signal and calling flask.ext.login.login_user() for you.
Parameters: | user (User) – The user that has been authenticated and is logging in. |
---|
Normalizes a string to be a valid Python identifier (along with a few other things).
Specifically, all letters are lower cased, only ASCII characters, and whitespace replaced by underscores.
Returns: | The normalized string. |
---|---|
Rtype str: |
Optional method for providing secondary views.
evesrp.views.login.auth_method_login() is configured to allow both GET and POST requests, and will call this method as soon as it is known which auth method is meant to be called. The path for this view is /login/self.safe_name/, and can be generated with url_for('login.auth_method_login', auth_method=self.safe_name).
The default implementation redirects to the main login view.
Private class for shared functionality between User and Group.
This class defines a number of helper methods used indirectly by User and Group subclasses such as automatically defining the table name and mapper arguments.
You should not inherit fomr this class directly, and should instead inherit from either User or Group.
The name of the AuthMethod for this entity.
Permissions associated specifically with this entity.
Returns if this entity has been granted a permission in a division.
If division is None, this method checks if this group has the given permission in any division.
Parameters: |
|
---|---|
Rtype bool: |
The name of the entity. Usually a nickname.
User base class.
Represents users who can submit, review and/or pay out requests. It also supplies a number of convenience methods for subclasses.
If the user is an administrator. This allows the user to create and administer divisions.
Base class for a group of users.
Represents a group of users. Usable for granting permissions to submit, evaluate and pay.
Synonym for entity_permissions
Create a Permission object granting an entity access to a division.
The division this permission is granting access to
The permission being granted.
A reimbursement division.
A division has (possibly non-intersecting) groups of people that can submit requests, review requests, and pay out requests.
All Permissions associated with this division.
The name of this division.
Request s filed under this division.
A mapping of attribute names to Transformer instances.
Represents an in-game character.
The name of the character
The Requests filed with lossmails from this character.
The User this character belongs to.