Authentication¶
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. We’ll need to subclass two classes for this; AuthMethod and User
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 for 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 below, we’re using the simple-pbkdf2 package to provide the password hashing. We also have a checking method to make life easier for us later.
import os
from hashlib import sha512
from evesrp import db
from evesrp.auth.models 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(24), nullable=False)
salt = db.Column(db.LargeBinary(24), nullable=False)
def __init__(self, name, password, authmethod, **kwargs):
self.salt = os.urandom(24)
self.password = pbkdf2_bin(password.encode('utf-8'), self.salt,
iterations=10000)
super(LocalUser, self).__init__(name, authmethod, **kwargs)
def check_password(self, password):
key = pbkdf2_bin(password.encode('utf-8'), self.salt,
iterations=10000)
matched = 0
for a, b in zip(self.password, key):
matched |= ord(a) ^ ord(b)
return matched == 0
AuthMethod subclasses have four methods they can implement to customize thier behavior.
- AuthMethod.form() returns a Form 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.
- For those authentication methods that requires a secondary view/route, the AuthMethod.view() method can be implemented to handle requests made to login/safe_name where safe_name is the output of AuthMethod.safe_name.
- Finally, the initializer should be overridden to provide a default name for your AuthMethod other than Base Authentication.
- Finally, the initializer can be overridden to handle specialized configurations.
With these in mind, let’s implement our AuthMethod subclass:
from evesrp.auth import AuthMethod
from flask import redirect, url_for, render_template, request
from flask_wtf import Form
from sqlalchemy.orm.exc import NoResultFound
from wtforms.fields import StringField, PasswordField, SubmitField
from wtforms.validators import InputRequired, EqualTo
class LocalLoginForm(Form):
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, we just need to process it.
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)
return redirect(request.args.get('next') or url_for('index'))
else:
flash("Incorrect password.", 'error')
return 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'))
# form.html is a template included in Eve-SRP that renders all
# elements of a form.
return render_template('form.html', form=form)
That’s all that’s necessary for a very simple AuthMethod. This example cuts some corners, and isn’t ready for production-level use, but it serves as a quick example of what’s necessary to write a custom authentication method. Feel free to look at the sources for the included AuthMethods below to gather ideas on how to use more complicated mechanisms.
Included Authentication Methods¶
Brave Core¶
- class evesrp.auth.bravecore.BraveCore(client_key, server_key, identifier, url='https://core.braveineve.com', **kwargs)[source]¶
Bases: evesrp.auth.AuthMethod
- __init__(client_key, server_key, identifier, url='https://core.braveineve.com', **kwargs)[source]¶
Authentication method using a Brave Core instance.
Uses the native Core API to authenticate users. Currently only supports a single character at a time due to limitations in Core’s API.
Parameters: - client_key (str) – The client’s private key in hex form.
- server_key (str) – The server’s public key for this app in hex form.
- identifier (str) – The identifier for this app in Core.
- url (str) – The URL of the Core instance to authenticate against. Default: ‘https://core.braveineve.com‘
- name (str) – The user-facing name for this authentication method. Default: ‘Brave Core’
TEST Legacy¶
OAuth¶
A number of external authentication services have an OAuth provider for external applications to use with their API. To facilitate usage of thses services, an OAuthMethod class has been provided for easy integration. Subclasses will need to implement the get_user(), get_pilots() and get_groups() methods. Additionally, implementations for JFLP's provider and TEST's provider have been provided as a reference.
- class evesrp.auth.oauth.OAuthMethod(**kwargs)[source]¶
- __init__(**kwargs)[source]¶
Abstract AuthMethod for OAuth-based login methods.
Implementing classes need to implement get_user(), get_pilots(), and get_groups().
In addition to the keyword arguments from AuthMethod, this initializer accepts the following arguments that will be used in the creation of the OAuthMethod.oauth object (See the documentation for OAuthRemoteApp for more details):
- client_id
- client_secret
- scope
- access_token_url
- refresh_token_url
- authorize_url
- access_token_params
- method
As a convenience, the key and secret keyword arguments will be treated as consumer_key and consumer_secret respectively. The name argument is used for both AuthMethod and for OAuthRemoteApp.
Subclasses for providers that may be used by more than one entity are encouraged to provide their own defaults for the above arguments.
The redirect URL for derived classes is based off of the safe_name of the implementing AuthMethod, specifically the URL for view(). For example, the default redirect URL for TestOAuth is similar to https://example.com/login/test_oauth/ (Note the trailing slash, it is significant).
Parameters: default_token_expiry (int) – The default time (in seconds) access tokens are valid for. Defaults to 5 minutes.
- get_groups()[source]¶
Returns a list of Groups for the given token.
Like get_user() and get_pilots(), this method is to be implemented by OAuthMethod subclasses to return a list of Groups associated with the account for the given access token.
Return type: list of Groups.
- get_pilots()[source]¶
Return a list of Pilots for the given token.
Like get_user(), this method is to be implemented by OAuthMethod subclasses to return a list of Pilots associated with the account for the given access token.
Return type: list of Pilots.
- get_user()[source]¶
Returns the OAuthUser instance for the current token.
This method is to be implemented by subclasses of OAuthMethod to use whatever APIs they have access to to get the user account given an access token.
Return type: OAuthUser
- is_admin(user)[source]¶
Returns wether this user should be treated as a site-wide administrator.
The default implementation checks if the user’s name is contained within the list of administrators supplied as an argument to OAuthMethod.
Parameters: user (OAuthUser) – The user to check. Return type: bool
J4OAuth¶
- class evesrp.auth.j4oauth.J4OAuth(base_url='https://j4lp.com/oauth/api/v1/', **kwargs)[source]¶
Bases: evesrp.auth.oauth.OAuthMethod
- __init__(base_url='https://j4lp.com/oauth/api/v1/', **kwargs)[source]¶
AuthMethod for using J4OAuth as an authentication source.
Parameters: - authorize_url (str) – The URL to request OAuth authorization tokens. Default: 'https://j4lp.com/oauth/authorize'.
- access_token_url (str) – The URL for OAuth token exchange. Default: 'https://j4lp.com/oauth/token'.
- base_str (str) – The base URL for API requests. Default: 'https://j4lp.com/oauth/api/v1/'.
- request_token_params (dict) – Additional parameters to include with the authorization token request. Default: {'scope': ['auth_info', 'auth_groups', 'characters']}.
- access_token_method (str) – HTTP Method to use for exchanging authorization tokens for access tokens. Default: 'GET'.
- name (str) – The name for this authentication method. Default: 'J4OAuth'.
TestOAuth¶
- class evesrp.auth.testoauth.TestOAuth(devtest=False, **kwargs)[source]¶
Bases: evesrp.auth.oauth.OAuthMethod
- __init__(devtest=False, **kwargs)[source]¶
AuthMethod using TEST Auth’s OAuth-based API for authentication and authorization.
Parameters: - admins (list) – Two types of values are accepted as values in this list, either a string specifying a user’s primary character’s name, or their Auth ID as an integer.
- devtest (bool) – Testing parameter that changes the default domain for URLs from ‘https://auth.pleaseignore.com‘ to ‘https://auth.devtest.pleaseignore.com`. Default: False.
- authorize_url (str) – The URL to request OAuth authorization tokens. Default: 'https://auth.pleaseignore.com/oauth2/authorize'.
- access_token_url (str) – The URL for OAuth token exchange. Default: 'https://auth.pleaseignore.com/oauth2/access_token'.
- base_str (str) – The base URL for API requests. Default: 'https://auth.pleaseignore.com/api/v3/'.
- request_token_params (dict) – Additional parameters to include with the authorization token request. Default: {'scope': 'private-read'}.
- access_token_method (str) – HTTP Method to use for exchanging authorization tokens for access tokens. Default: 'POST'.
- name (str) – The name for this authentication method. Default: 'Test OAuth'.
Low-Level API¶
- class evesrp.auth.PermissionType[source]¶
Enumerated type for the types of permissions available.
- audit = <audit>¶
A special permission for allowing read-only elevated access
- class evesrp.auth.AuthMethod(admins=None, name='Base Authentication', **kwargs)[source]¶
Represents an authentication mechanism for users.
- static login_user(user)[source]¶
Signal to the authentication systems that a new user has logged in.
Handles calling flask_login.login_user() and any other related housekeeping functions for you.
Parameters: user (User) – The user that has been authenticated and is logging in.
- refresh(user)[source]¶
Refresh a user’s information (if possible).
The AuthMethod should attmept to refresh the given user’s information as if they were logging in for the first time.
Parameters: user (User) – The user to refresh. Returns: Wether or not the refresh attempt succeeded. Return type: bool
- safe_name[source]¶
Normalizes a string to be a valid Python identifier (along with a few other things).
Specifically, all letters are lower cased and non-ASCII and whitespace are replaced by underscores.
Returns: The normalized string. Rtype str:
- view()[source]¶
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.
- class evesrp.auth.models.Entity(name, authmethod, **kwargs)[source]¶
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.
This class should not be inherited from directly, instead either User or Group should be used.
- authmethod¶
The name of the AuthMethod for this entity.
- entity_permissions¶
Permissions associated specifically with this entity.
- has_permission(permissions, division_or_request=None)[source]¶
Returns if this entity has been granted a permission in a division.
If division_or_request is None, this method checks if this group has the given permission in any division.
Parameters: - permissions (iterable) – The series of permissions to check
- division_or_request – The division to check. May also be None or an SRP request.
Return type: bool
- name¶
The name of the entity. Usually a nickname.
- class evesrp.auth.models.User(name, authmethod, **kwargs)[source]¶
Bases: evesrp.auth.models.Entity
User base class.
Represents users who can submit, review and/or pay out requests. It also supplies a number of convenience methods for subclasses.
- admin¶
If the user is an administrator. This allows the user to create and administer divisions.
- class evesrp.auth.models.Pilot(user, name, id_)[source]¶
Represents an in-game character.
- name¶
The name of the character
- requests¶
The Requests filed with lossmails from this character.
- user¶
The User this character belongs to.
- class evesrp.auth.models.APIKey(user)[source]¶
Represents an API key for use with the External API.
- key¶
The raw key data.
- class evesrp.auth.models.Note(user, noter, note)[source]¶
A note about a particular User.
- content¶
The actual contents of this note.
- noter¶
The author of this note.
- class evesrp.auth.models.Group(name, authmethod, **kwargs)[source]¶
Bases: evesrp.auth.models.Entity
Base class for a group of users.
Represents a group of users. Usable for granting permissions to submit, evaluate and pay.
- permissions¶
Synonym for entity_permissions
- class evesrp.auth.models.Permission(division, permission, entity)[source]¶
- __init__(division, permission, entity)[source]¶
Create a Permission object granting an entity access to a division.
- division¶
The division this permission is granting access to
- permission¶
The permission being granted.
- class evesrp.auth.models.Division(name)[source]¶
A reimbursement division.
A division has (possibly non-intersecting) groups of people that can submit requests, review requests, and pay out requests.
- division_permissions¶
All Permissions associated with this division.
- name¶
The name of this division.
- requests¶
Request s filed under this division.
- transformers¶
A mapping of attribute names to Transformer instances.