Added "Update from Blender ID" button

Added this button in the /u/ user/embed view, so that admins can easily force a re-check from Blender ID without requiring the user themselves to perform any actions.
This commit is contained in:
Sybren A. Stüvel 2017-12-05 11:45:42 +01:00
parent c8221ea0e4
commit 87fe1887e8
4 changed files with 97 additions and 10 deletions

View File

@ -1,11 +1,14 @@
import collections
import logging
import typing
from flask import Blueprint
from flask import Blueprint, Response
import requests
from requests.adapters import HTTPAdapter
from pillar.auth import UserClass
from pillar.api.utils import authorization
from pillar import auth, current_app
from pillar.api import blender_id
from pillar.api.utils import authorization, jsonify
from pillar.auth import current_user
log = logging.getLogger(__name__)
blueprint = Blueprint('blender_cloud.subscription', __name__)
@ -27,9 +30,6 @@ def update_subscription() -> typing.Tuple[str, int]:
Returns an empty HTTP response.
"""
from pillar import auth
from pillar.api import blender_id
my_log: logging.Logger = log.getChild('update_subscription')
current_user = auth.get_current_user()
@ -48,7 +48,58 @@ def update_subscription() -> typing.Tuple[str, int]:
return '', 204
def do_update_subscription(local_user: UserClass, bid_user: dict):
@blueprint.route('/update-subscription-for/<user_id>', methods=['POST'])
@authorization.require_login(require_cap='admin')
def update_subscription_for(user_id: str):
"""Updates the user based on their info at Blender ID."""
from urllib.parse import urljoin
from pillar.api.utils import str2id
my_log = log.getChild('update_subscription_for')
bid_session = requests.Session()
bid_session.mount('https://', HTTPAdapter(max_retries=5))
bid_session.mount('http://', HTTPAdapter(max_retries=5))
users_coll = current_app.db('users')
db_user = users_coll.find_one({'_id': str2id(user_id)})
if not db_user:
my_log.warning('User %s not found in database', user_id)
return Response(f'User {user_id} not found in our database', status=404)
log.info('Updating user %s from Blender ID on behalf of %s',
db_user['email'], current_user.email)
bid_user_id = blender_id.get_user_blenderid(db_user)
if not bid_user_id:
my_log.info('User %s has no Blender ID', user_id)
return Response('User has no Blender ID', status=404)
# Get the user info from Blender ID, and handle errors.
api_url = current_app.config['BLENDER_ID_USER_INFO_API']
api_token = current_app.config['BLENDER_ID_USER_INFO_TOKEN']
url = urljoin(api_url, bid_user_id)
resp = bid_session.get(url, headers={'Authorization': f'Bearer {api_token}'})
if resp.status_code == 404:
my_log.info('User %s has a Blender ID %s but Blender ID itself does not find it',
user_id, bid_user_id)
return Response(f'User {bid_user_id} does not exist at Blender ID', status=404)
if resp.status_code != 200:
my_log.info('Error code %s getting user %s from Blender ID (resp = %s)',
resp.status_code, user_id, resp.text)
return Response(f'Error code {resp.status_code} from Blender ID', status=resp.status_code)
# Update the user in our database.
local_user = auth.UserClass.construct('', db_user)
bid_user = resp.json()
do_update_subscription(local_user, bid_user)
return '', 204
def do_update_subscription(local_user: auth.UserClass, bid_user: dict):
"""Updates the subscription status of the user given the Blender ID user info.
Uses the badger service to update the user's roles from Blender ID.
@ -106,6 +157,10 @@ def do_update_subscription(local_user: UserClass, bid_user: dict):
user_id, email, ', '.join(sorted(revoke_roles)))
service.do_badger('revoke', roles=revoke_roles, user_id=user_id)
# Re-index the user in the search database.
from pillar.api.users import hooks
hooks.push_updated_user_to_algolia({'_id': user_id}, {})
def setup_app(app, url_prefix):
log.info('Registering blueprint at %s', url_prefix)

View File

@ -168,6 +168,24 @@ def _compute_token_expiry(token_expires_string):
return min(blid_expiry, our_expiry)
def get_user_blenderid(db_user: dict) -> str:
"""Returns the Blender ID user ID for this Pillar user.
Takes the string from 'auth.*.user_id' for the '*' where 'provider'
is 'blender-id'.
:returns the user ID, or the empty string when the user has none.
"""
bid_user_ids = [auth['user_id']
for auth in db_user['auth']
if auth['provider'] == 'blender-id']
try:
return bid_user_ids[0]
except IndexError:
return ''
def fetch_blenderid_user() -> dict:
"""Returns the user info of the currently logged in user from BlenderID.

View File

@ -99,8 +99,9 @@ FULL_FILE_ACCESS_ROLES = {'admin', 'subscriber', 'demo'}
BLENDER_ID_CLIENT_ID = 'SPECIAL-SNOWFLAKE-57'
BLENDER_ID_SUBCLIENT_ID = 'PILLAR'
# Blender ID user info API endpoint URL and auth token, only used for
# reconciling subscribers. The token requires the 'userinfo' scope.
# Blender ID user info API endpoint URL and auth token, used for
# reconciling subscribers and updating their info from /u/.
# The token requires the 'userinfo' scope.
BLENDER_ID_USER_INFO_API = 'http://blender-id:8000/api/user/'
BLENDER_ID_USER_INFO_TOKEN = '-set-in-config-local-'

View File

@ -72,6 +72,7 @@
| none
| {% endif %}
a.btn.btn-default(href="javascript:update_from_bid()") Update from Blender ID
a.btn.btn-default(href="javascript:$('#user-edit-container').html('')") Cancel
input#submit_edit_user.btn.btn-default(
@ -103,4 +104,16 @@ script(type="text/javascript").
new Clipboard('.copy-to-clipboard');
function update_from_bid() {
var url = '{{ url_for("blender_cloud.subscription.update_subscription_for", user_id=user.user_id) }}';
$.post(url)
.done(function(data) {
toastr.info('User updated from Blender ID');
displayUser('{{ user.user_id }}');
})
.fail(function(data) {
toastr.error(data.responseText);
});
}
| {% endblock %}