Add trackable option and make extra features default to off/False to minimize about of application setup to get started

This commit is contained in:
Matt Wright
2012-07-09 18:57:43 -04:00
parent 74e94b2628
commit 2ed0ea48e6
4 changed files with 61 additions and 8 deletions
+12
View File
@@ -129,6 +129,12 @@ def create_sqlalchemy_app(auth_config=None):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(120))
remember_token = db.Column(db.String(255))
last_login_at = db.Column(db.DateTime())
current_login_at = db.Column(db.DateTime())
last_login_ip = db.Column(db.String(100))
current_login_ip = db.Column(db.String(100))
login_count = db.Column(db.Integer)
active = db.Column(db.Boolean())
confirmation_token = db.Column(db.String(255))
confirmation_sent_at = db.Column(db.DateTime())
@@ -166,6 +172,12 @@ def create_mongoengine_app(auth_config=None):
class User(db.Document, UserMixin):
email = db.StringField(unique=True, max_length=255)
password = db.StringField(required=True, max_length=120)
remember_token = db.StringField(max_length=255)
last_login_at = db.DateTimeField()
current_login_at = db.DateTimeField()
last_login_ip = db.StringField(max_length=100)
current_login_ip = db.StringField(max_length=100)
login_count = db.IntField()
active = db.BooleanField(default=True)
confirmation_token = db.StringField(max_length=255)
confirmation_sent_at = db.DateTimeField()
+19 -5
View File
@@ -22,7 +22,6 @@ from . import views, exceptions, utils
from .confirmable import confirmation_token_is_expired, requires_confirmation, \
reset_confirmation_token
from .decorators import login_required
from .forms import Form, LoginForm
#: Default Flask-Security configuration
@@ -50,8 +49,9 @@ _default_config = {
'POST_CONFIRM_VIEW': None,
'DEFAULT_ROLES': [],
'CONFIRMABLE': False,
'REGISTERABLE': True,
'RECOVERABLE': True,
'REGISTERABLE': False,
'RECOVERABLE': False,
'TRACKABLE': False,
'CONFIRM_EMAIL_WITHIN': '5 days',
'RESET_PASSWORD_WITHIN': '2 days',
'LOGIN_WITHOUT_CONFIRMATION': False,
@@ -80,6 +80,10 @@ class UserMixin(BaseUserMixin):
"""Returns `True` if the user is active."""
return self.active
def get_auth_token(self):
"""Returns the user's authentication token."""
self.remember_token
def has_role(self, role):
"""Returns `True` if the user identifies with the specified role.
@@ -103,7 +107,7 @@ class AnonymousUser(AnonymousUserBase):
return False
def _load_user(user_id):
def _user_loader(user_id):
try:
return current_app.security.datastore.with_id(user_id)
except Exception, e:
@@ -111,6 +115,14 @@ def _load_user(user_id):
return None
def _token_loader(token):
try:
return current_app.security.datastore.find_user(remember_token=token)
except Exception, e:
current_app.logger.error('Error getting user: %s' % e)
return None
def _on_identity_loaded(sender, identity):
if hasattr(current_user, 'id'):
identity.provides.add(UserNeed(current_user.id))
@@ -150,7 +162,8 @@ class Security(object):
login_manager = LoginManager()
login_manager.anonymous_user = AnonymousUser
login_manager.login_view = utils.config_value(app, 'LOGIN_VIEW')
login_manager.user_loader(_load_user)
login_manager.user_loader(_user_loader)
login_manager.token_loader(_token_loader)
login_manager.init_app(app)
Provider = utils.get_class_from_string(app, 'AUTH_PROVIDER')
@@ -182,6 +195,7 @@ class Security(object):
self.confirmable = utils.config_value(app, 'CONFIRMABLE')
self.registerable = utils.config_value(app, 'REGISTERABLE')
self.recoverable = utils.config_value(app, 'RECOVERABLE')
self.trackable = utils.config_value(app, 'TRACKABLE')
self.email_sender = utils.config_value(app, 'EMAIL_SENDER')
self.token_authentication_key = utils.config_value(app, 'TOKEN_AUTHENTICATION_KEY')
self.token_authentication_header = utils.config_value(app, 'TOKEN_AUTHENTICATION_HEADER')
+18 -1
View File
@@ -9,9 +9,11 @@
:license: MIT, see LICENSE for more details.
"""
from datetime import datetime
from flask import current_app as app, redirect, request, session, \
render_template
from flask.ext.login import login_user, logout_user
from flask.ext.login import login_user, logout_user, make_secure_token
from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
from werkzeug.local import LocalProxy
@@ -37,6 +39,21 @@ def _do_login(user, remember=True):
"""Performs the login and sends the appropriate signal."""
if login_user(user, remember):
user.remember_token = make_secure_token(user.email, user.password)
if _security.trackable:
old_current, new_current = user.current_login_at, datetime.utcnow()
user.last_login_at = old_current or new_current
user.current_login_at = new_current
old_current, new_current = user.current_login_ip, request.remote_addr
user.last_login_ip = old_current or new_current
user.current_login_ip = new_current
user.login_count = user.login_count + 1 if user.login_count else 0
_datastore._save_model(user)
identity_changed.send(app._get_current_object(),
identity=Identity(user.id))
+12 -2
View File
@@ -114,6 +114,7 @@ class DefaultSecurityTests(SecurityTest):
class ConfiguredURLTests(SecurityTest):
AUTH_CONFIG = {
'SECURITY_REGISTERABLE': True,
'SECURITY_AUTH_URL': '/custom_auth',
'SECURITY_LOGOUT_URL': '/custom_logout',
'SECURITY_LOGIN_VIEW': '/custom_login',
@@ -142,6 +143,9 @@ class ConfiguredURLTests(SecurityTest):
class RegisterableTests(SecurityTest):
AUTH_CONFIG = {
'SECURITY_REGISTERABLE': True
}
def test_register_valid_user(self):
data = dict(email='dude@lp.com', password='password', password_confirm='password')
@@ -152,7 +156,8 @@ class RegisterableTests(SecurityTest):
class ConfirmableTests(SecurityTest):
AUTH_CONFIG = {
'SECURITY_CONFIRMABLE': True
'SECURITY_CONFIRMABLE': True,
'SECURITY_REGISTERABLE': True
}
def test_register_sends_confirmation_email(self):
@@ -216,7 +221,8 @@ class ConfirmableTests(SecurityTest):
class LoginWithoutImmediateConfirmTests(SecurityTest):
AUTH_CONFIG = {
'SECURITY_CONFIRM_EMAIL': True,
'SECURITY_CONFIRMABLE': True,
'SECURITY_REGISTERABLE': True,
'SECURITY_LOGIN_WITHOUT_CONFIRMATION': True
}
@@ -230,6 +236,10 @@ class LoginWithoutImmediateConfirmTests(SecurityTest):
class RecoverableTests(SecurityTest):
AUTH_CONFIG = {
'SECURITY_RECOVERABLE': True
}
def test_forgot_post_sends_email_and_sets_required_fields(self):
with capture_reset_password_requests() as users:
with self.app.mail.record_messages() as outbox: