mirror of
https://github.com/wassname/flask-security.git
synced 2026-07-26 13:18:38 +08:00
Changed token auth a bit, including the use of itsdangerous. Also added JSON authentication feature
This commit is contained in:
+1
-2
@@ -32,8 +32,7 @@ def create_users():
|
||||
('jill@lp.com', 'password', ['author'], True),
|
||||
('tiya@lp.com', 'password', [], False)):
|
||||
current_app.security.datastore.create_user(
|
||||
email=u[0], password=u[1], roles=u[2], active=u[3],
|
||||
authentication_token='123abc')
|
||||
email=u[0], password=u[1], roles=u[2], active=u[3])
|
||||
|
||||
|
||||
def populate_data():
|
||||
|
||||
@@ -26,16 +26,6 @@ _security = LocalProxy(lambda: app.security)
|
||||
_datastore = LocalProxy(lambda: app.security.datastore)
|
||||
|
||||
|
||||
def find_user_by_confirmation_token(token):
|
||||
"""Returns a user with a matching confirmation token.
|
||||
|
||||
:param token: The reset password token
|
||||
"""
|
||||
if not token:
|
||||
raise ConfirmationError('Confirmation token required')
|
||||
return _datastore.find_user(confirmation_token=token)
|
||||
|
||||
|
||||
def send_confirmation_instructions(user, token):
|
||||
"""Sends the confirmation instructions email for the specified user.
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ from passlib.context import CryptContext
|
||||
from werkzeug.datastructures import ImmutableList
|
||||
|
||||
from . import views, exceptions
|
||||
from .confirmable import requires_confirmation, reset_confirmation_token
|
||||
from .confirmable import requires_confirmation
|
||||
from .decorators import login_required
|
||||
from .utils import config_value as cv, get_config
|
||||
|
||||
|
||||
@@ -14,10 +14,15 @@ from functools import wraps
|
||||
from flask import current_app as app, Response, request, abort, redirect
|
||||
from flask.ext.login import login_required, login_url, current_user
|
||||
from flask.ext.principal import RoleNeed, Permission
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from . import utils
|
||||
|
||||
|
||||
# Convenient references
|
||||
_security = LocalProxy(lambda: app.security)
|
||||
|
||||
|
||||
_default_http_auth_msg = """
|
||||
<h1>Unauthorized</h1>
|
||||
<p>The server could not verify that you are authorized to access the URL
|
||||
@@ -29,15 +34,24 @@ _default_http_auth_msg = """
|
||||
|
||||
|
||||
def _check_token():
|
||||
|
||||
header_key = app.security.token_authentication_header
|
||||
args_key = app.security.token_authentication_key
|
||||
|
||||
header_token = request.headers.get(header_key, None)
|
||||
token = request.args.get(args_key, header_token)
|
||||
|
||||
serializer = _security.token_auth_serializer
|
||||
|
||||
try:
|
||||
app.security.datastore.find_user(authentication_token=token)
|
||||
except:
|
||||
data = serializer.loads(token)
|
||||
user = app.security.datastore.find_user(id=data[0],
|
||||
authentication_token=token)
|
||||
|
||||
if data[1] != utils.md5(user.email):
|
||||
raise Exception()
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
@@ -9,51 +9,25 @@
|
||||
:license: MIT, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from flask import current_app as app
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from .exceptions import BadCredentialsError, UserNotFoundError
|
||||
from .utils import generate_token
|
||||
from .utils import md5
|
||||
|
||||
|
||||
# Convenient references
|
||||
_security = LocalProxy(lambda: app.security)
|
||||
|
||||
_datastore = LocalProxy(lambda: app.security.datastore)
|
||||
|
||||
|
||||
def find_user_by_authentication_token(token):
|
||||
"""Returns a user with a matching authentication token.
|
||||
|
||||
:param token: The authentication token
|
||||
"""
|
||||
if not token:
|
||||
raise BadCredentialsError('Authentication token required')
|
||||
return _datastore.find_user(authentication_token=token)
|
||||
|
||||
|
||||
def generate_authentication_token(user):
|
||||
"""Generates a unique authentication token for the specified user.
|
||||
|
||||
:param user: The user to work with
|
||||
"""
|
||||
while True:
|
||||
token = generate_token()
|
||||
try:
|
||||
find_user_by_authentication_token(token)
|
||||
except UserNotFoundError:
|
||||
break
|
||||
|
||||
now = datetime.utcnow()
|
||||
|
||||
try:
|
||||
user['authentication_token'] = token
|
||||
user['authentication_token_created_at'] = now
|
||||
except TypeError:
|
||||
user.authentication_token = token
|
||||
user.authentication_token_created_at = now
|
||||
|
||||
return user
|
||||
data = [str(user.id), md5(user.email)]
|
||||
return _security.token_auth_serializer.dumps(data)
|
||||
|
||||
|
||||
def reset_authentication_token(user):
|
||||
@@ -61,9 +35,10 @@ def reset_authentication_token(user):
|
||||
|
||||
:param user: The user to work with
|
||||
"""
|
||||
user = generate_authentication_token(user)
|
||||
token = generate_authentication_token(user)
|
||||
user.authentication_token = token
|
||||
_datastore._save_model(user)
|
||||
return user.authentication_token
|
||||
return token
|
||||
|
||||
|
||||
def ensure_authentication_token(user):
|
||||
@@ -73,5 +48,4 @@ def ensure_authentication_token(user):
|
||||
:param user: The user to work with
|
||||
"""
|
||||
if not user.authentication_token:
|
||||
reset_authentication_token(user)
|
||||
return user.authentication_token
|
||||
return reset_authentication_token(user)
|
||||
|
||||
@@ -15,7 +15,8 @@ import os
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from flask import url_for, flash, current_app, request, session, render_template
|
||||
from flask import url_for, flash, current_app, request, session, \
|
||||
render_template
|
||||
from flask.ext.login import make_secure_token
|
||||
|
||||
from .signals import user_registered, password_reset_requested
|
||||
|
||||
+39
-4
@@ -12,9 +12,10 @@
|
||||
from datetime import datetime
|
||||
|
||||
from flask import current_app as app, redirect, request, session, \
|
||||
render_template
|
||||
render_template, jsonify
|
||||
from flask.ext.login import login_user, logout_user
|
||||
from flask.ext.principal import Identity, AnonymousIdentity, identity_changed
|
||||
from werkzeug.datastructures import MultiDict
|
||||
from werkzeug.local import LocalProxy
|
||||
|
||||
from .confirmable import confirm_by_token, reset_confirmation_token
|
||||
@@ -25,6 +26,7 @@ from .forms import LoginForm, RegisterForm, ForgotPasswordForm, \
|
||||
from .recoverable import reset_by_token, \
|
||||
reset_password_reset_token
|
||||
from .signals import user_registered
|
||||
from .tokens import generate_authentication_token
|
||||
from .utils import get_post_login_redirect, do_flash, get_remember_token
|
||||
|
||||
|
||||
@@ -42,6 +44,9 @@ def _do_login(user, remember=True):
|
||||
if not login_user(user, remember):
|
||||
return False
|
||||
|
||||
if user.authentication_token is None:
|
||||
user.authentication_token = generate_authentication_token(user)
|
||||
|
||||
if remember:
|
||||
user.remember_token = get_remember_token(user.email, user.password)
|
||||
|
||||
@@ -65,15 +70,45 @@ def _do_login(user, remember=True):
|
||||
return True
|
||||
|
||||
|
||||
def _json_auth_ok(user):
|
||||
return jsonify({
|
||||
"meta": {
|
||||
"code": 200
|
||||
},
|
||||
"response": {
|
||||
"user": {
|
||||
"id": str(user.id),
|
||||
"authentication_token": user.authentication_token
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def _json_auth_error(msg):
|
||||
resp = jsonify({
|
||||
"meta": {
|
||||
"code": 400
|
||||
},
|
||||
"response": {
|
||||
"error": msg
|
||||
}
|
||||
})
|
||||
resp.status_code = 400
|
||||
return resp
|
||||
|
||||
|
||||
def authenticate():
|
||||
"""View function which handles an authentication request."""
|
||||
|
||||
form = LoginForm()
|
||||
form = LoginForm(MultiDict(request.json) if request.json else request.form)
|
||||
|
||||
try:
|
||||
user = _security.auth_provider.authenticate(form)
|
||||
|
||||
if _do_login(user, remember=form.remember.data):
|
||||
if request.json:
|
||||
return _json_auth_ok(user)
|
||||
|
||||
return redirect(get_post_login_redirect())
|
||||
|
||||
raise BadCredentialsError('Inactive user')
|
||||
@@ -81,8 +116,8 @@ def authenticate():
|
||||
except BadCredentialsError, e:
|
||||
msg = str(e)
|
||||
|
||||
except Exception, e:
|
||||
msg = 'Unknown authentication error'
|
||||
if request.json:
|
||||
return _json_auth_error(msg)
|
||||
|
||||
do_flash(msg, 'error')
|
||||
_logger.debug('Unsuccessful authentication attempt: %s' % msg)
|
||||
|
||||
@@ -36,6 +36,17 @@ class SecurityTest(TestCase):
|
||||
data = dict(email=email, password=password)
|
||||
return self._post(endpoint or '/auth', data=data)
|
||||
|
||||
def json_authenticate(self, email="matt@lp.com", password="password", endpoint=None):
|
||||
data = """
|
||||
{
|
||||
"email": "%s",
|
||||
"password": "%s"
|
||||
}
|
||||
"""
|
||||
return self._post(endpoint or '/auth',
|
||||
content_type="application/json",
|
||||
data=data % (email, password))
|
||||
|
||||
def logout(self, endpoint=None):
|
||||
return self._get(endpoint or '/logout', follow_redirects=True)
|
||||
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
from __future__ import with_statement
|
||||
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
try:
|
||||
import simplejson as json
|
||||
except ImportError:
|
||||
import json
|
||||
|
||||
from flask.ext.security.utils import capture_registrations, \
|
||||
capture_reset_password_requests
|
||||
@@ -95,12 +99,27 @@ class DefaultSecurityTests(SecurityTest):
|
||||
r = self._get("/admin_and_editor")
|
||||
self.assertIn('Admin and Editor Page', r.data)
|
||||
|
||||
def test_ok_json_auth(self):
|
||||
r = self.json_authenticate()
|
||||
self.assertIn('"code": 200', r.data)
|
||||
|
||||
def test_invalid_json_auth(self):
|
||||
r = self.json_authenticate(password='junk')
|
||||
self.assertIn('"code": 400', r.data)
|
||||
|
||||
def test_token_auth_via_querystring_valid_token(self):
|
||||
r = self._get('/token?auth_token=123abc')
|
||||
r = self.json_authenticate()
|
||||
data = json.loads(r.data)
|
||||
token = data['response']['user']['authentication_token']
|
||||
r = self._get('/token?auth_token=' + token)
|
||||
self.assertIn('Token Authentication', r.data)
|
||||
|
||||
def test_token_auth_via_header_valid_token(self):
|
||||
r = self._get('/token', headers={"X-Auth-Token": '123abc'})
|
||||
r = self.json_authenticate()
|
||||
data = json.loads(r.data)
|
||||
token = data['response']['user']['authentication_token']
|
||||
headers = {"X-Auth-Token": token}
|
||||
r = self._get('/token', headers=headers)
|
||||
self.assertIn('Token Authentication', r.data)
|
||||
|
||||
def test_token_auth_via_querystring_invalid_token(self):
|
||||
|
||||
Reference in New Issue
Block a user