From 49a6a6a7582d52154bb21b2e27345294f03aa1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sybren=20A=2E=20St=C3=BCvel?= Date: Fri, 17 Nov 2017 12:07:53 +0100 Subject: [PATCH] Delete the auth token when logging out. Before this, authentication tokens were kept in the database, even when someone logged out. This is unwanted behaviour, as logging in will create yet another token anyway there is no reason to keep the token around. --- pillar/api/blender_cloud/subscription.py | 8 +++++++- pillar/api/blender_id.py | 13 +++++++++++++ pillar/api/utils/authentication.py | 18 +++++++++++++++--- pillar/auth/__init__.py | 19 +++++++++++++++++-- pillar/web/users/routes.py | 5 ++--- 5 files changed, 54 insertions(+), 9 deletions(-) diff --git a/pillar/api/blender_cloud/subscription.py b/pillar/api/blender_cloud/subscription.py index 63edbe47..9e5aacb9 100644 --- a/pillar/api/blender_cloud/subscription.py +++ b/pillar/api/blender_cloud/subscription.py @@ -83,13 +83,19 @@ def update_subscription(): """ import pprint + from pillar import auth from pillar.api import blender_id, service from pillar.api.utils import authentication my_log: logging.Logger = log.getChild('update_subscription') user_id = authentication.current_user_id() - bid_user = blender_id.fetch_blenderid_user() + try: + bid_user = blender_id.fetch_blenderid_user() + except blender_id.LogoutUser: + auth.logout_user() + return '', 204 + if not bid_user: my_log.warning('Logged in user %s has no BlenderID account! ' 'Unable to update subscription status.', user_id) diff --git a/pillar/api/blender_id.py b/pillar/api/blender_id.py index fa4e0dce..06c8cae2 100644 --- a/pillar/api/blender_id.py +++ b/pillar/api/blender_id.py @@ -20,6 +20,13 @@ blender_id = Blueprint('blender_id', __name__) log = logging.getLogger(__name__) +class LogoutUser(Exception): + """Raised when Blender ID tells us the current user token is invalid. + + This indicates the user should be immediately logged out. + """ + + @blender_id.route('/store_scst', methods=['POST']) def store_subclient_token(): """Verifies & stores a user's subclient-specific token.""" @@ -180,6 +187,8 @@ def fetch_blenderid_user() -> dict: } } + :raises LogoutUser: when Blender ID tells us the current token is + invalid, and the user should be logged out. """ import httplib2 # used by the oauth2 package @@ -201,6 +210,10 @@ def fetch_blenderid_user() -> dict: log.exception('Error getting %s from BlenderID', bid_url) return {} + if bid_resp.status_code == 403: + log.warning('Error %i from BlenderID %s, logging out user', bid_resp.status_code, bid_url) + raise LogoutUser() + if bid_resp.status_code != 200: log.warning('Error %i from BlenderID %s: %s', bid_resp.status_code, bid_url, bid_resp.text) return {} diff --git a/pillar/api/utils/authentication.py b/pillar/api/utils/authentication.py index b84bf468..65a50b79 100644 --- a/pillar/api/utils/authentication.py +++ b/pillar/api/utils/authentication.py @@ -177,11 +177,23 @@ def validate_this_token(token, oauth_subclient=None): return db_user +def remove_token(token: str): + """Removes the token from the database.""" + + + tokens_coll = current_app.db('tokens') + token_hashed = hash_auth_token(token) + + # TODO: remove matching on unhashed tokens once all tokens have been hashed. + lookup = {'$or': [{'token': token}, {'token_hashed': token_hashed}]} + del_res = tokens_coll.delete_many(lookup) + log.debug('Removed token %r, matched %d documents', token, del_res.deleted_count) + + def find_token(token, is_subclient_token=False, **extra_filters): """Returns the token document, or None if it doesn't exist (or is expired).""" - tokens_collection = current_app.data.driver.db['tokens'] - + tokens_coll = current_app.db('tokens') token_hashed = hash_auth_token(token) # TODO: remove matching on unhashed tokens once all tokens have been hashed. @@ -190,7 +202,7 @@ def find_token(token, is_subclient_token=False, **extra_filters): 'expire_time': {"$gt": datetime.datetime.now(tz=tz_util.utc)}} lookup.update(extra_filters) - db_token = tokens_collection.find_one(lookup) + db_token = tokens_coll.find_one(lookup) return db_token diff --git a/pillar/auth/__init__.py b/pillar/auth/__init__.py index 5ae6197a..9f9e3168 100644 --- a/pillar/auth/__init__.py +++ b/pillar/auth/__init__.py @@ -202,8 +202,6 @@ def config_login_manager(app): def login_user(oauth_token: str, *, load_from_db=False): """Log in the user identified by the given token.""" - from flask import g - if load_from_db: user = _load_user(oauth_token) else: @@ -212,6 +210,20 @@ def login_user(oauth_token: str, *, load_from_db=False): g.current_user = user +def logout_user(): + """Forces a logout of the current user.""" + + from ..api.utils import authentication + + token = get_blender_id_oauth_token() + if token: + authentication.remove_token(token) + + session.clear() + flask_login.logout_user() + g.current_user = AnonymousUser() + + def get_blender_id_oauth_token() -> str: """Returns the Blender ID auth token, or an empty string if there is none.""" @@ -231,6 +243,9 @@ def get_blender_id_oauth_token() -> str: if request.authorization and request.authorization.username: return request.authorization.username + if current_user.is_authenticated and current_user.id: + return current_user.id + return '' diff --git a/pillar/web/users/routes.py b/pillar/web/users/routes.py index 3b506b8c..85aa0462 100644 --- a/pillar/web/users/routes.py +++ b/pillar/web/users/routes.py @@ -2,7 +2,7 @@ import logging from flask import abort, Blueprint, redirect, render_template, request, session, \ url_for -from flask_login import login_required, logout_user +from flask_login import login_required from werkzeug import exceptions as wz_exceptions import pillar.api.blender_cloud.subscription @@ -108,8 +108,7 @@ def login_local(): @blueprint.route('/logout') def logout(): - logout_user() - session.clear() + pillar.auth.logout_user() return redirect('/')