added basic auth api support, added device name specification in play

This commit is contained in:
aj 2019-09-25 19:28:38 +01:00
parent 5909229d69
commit 215c839210
4 changed files with 131 additions and 72 deletions

View File

@ -25,20 +25,50 @@ task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
logger = logging.getLogger(__name__)
def is_logged_in():
if 'username' in session:
return True
else:
return False
def is_basic_authed():
if request.authorization:
if request.authorization.get('username', None) and request.authorization.get('password', None):
if database.check_user_password(request.authorization.username, request.authorization.password):
return True
return False
def login_required(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if 'username' in session:
return func(*args, **kwargs)
def login_required_wrapper(*args, **kwargs):
if is_logged_in():
return func(username=session['username'], *args, **kwargs)
else:
logger.warning('user not logged in')
return jsonify({'error': 'not logged in'}), 401
return wrapper
return login_required_wrapper
def login_or_basic_auth(func):
@functools.wraps(func)
def login_or_basic_auth_wrapper(*args, **kwargs):
if is_logged_in():
return func(username=session['username'], *args, **kwargs)
elif is_basic_authed():
return func(username=request.authorization.username, *args, **kwargs)
else:
logger.warning('user not logged in')
return jsonify({'error': 'not logged in'}), 401
return login_or_basic_auth_wrapper
def admin_required(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
def admin_required_wrapper(*args, **kwargs):
user_dict = database.get_user_doc_ref(session['username']).get().to_dict()
if user_dict:
@ -51,12 +81,12 @@ def admin_required(func):
logger.warning('user not logged in')
return jsonify({'error': 'not logged in'}), 401
return wrapper
return admin_required_wrapper
def gae_cron(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
def gae_cron_wrapper(*args, **kwargs):
if request.headers.get('X-Appengine-Cron', None):
return func(*args, **kwargs)
@ -64,12 +94,12 @@ def gae_cron(func):
logger.warning('user not logged in')
return jsonify({'status': 'error', 'message': 'unauthorised'}), 401
return wrapper
return gae_cron_wrapper
def cloud_task(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
def cloud_task_wrapper(*args, **kwargs):
if request.headers.get('X-AppEngine-QueueName', None):
return func(*args, **kwargs)
@ -77,14 +107,14 @@ def cloud_task(func):
logger.warning('non tasks request')
return jsonify({'status': 'error', 'message': 'unauthorised'}), 401
return wrapper
return cloud_task_wrapper
@blueprint.route('/playlists', methods=['GET'])
@login_required
def get_playlists():
@login_or_basic_auth
def get_playlists(username=None):
pulled_user = database.get_user_doc_ref(session['username'])
pulled_user = database.get_user_doc_ref(username)
playlists = pulled_user.collection(u'playlists')
@ -102,10 +132,10 @@ def get_playlists():
@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE'])
@login_required
def playlist():
@login_or_basic_auth
def playlist(username=None):
user_ref = database.get_user_doc_ref(session['username'])
user_ref = database.get_user_doc_ref(username)
playlists = user_ref.collection(u'playlists')
if request.method == 'GET' or request.method == 'DELETE':
@ -131,7 +161,7 @@ def playlist():
elif request.method == 'DELETE':
logger.info(f'deleted {session["username"]} / {queried_playlist[0].to_dict()["name"]}')
logger.info(f'deleted {username} / {queried_playlist[0].to_dict()["name"]}')
queried_playlist[0].reference.delete()
return jsonify({"message": 'playlist deleted', "status": "success"}), 200
@ -199,7 +229,7 @@ def playlist():
}
if user_ref.get().to_dict()['spotify_linked']:
new_playlist = create_playlist(session['username'], playlist_name)
new_playlist = create_playlist(username, playlist_name)
to_add['uri'] = str(new_playlist.uri) if new_playlist is not None else None
if playlist_type == 'recents':
@ -208,7 +238,7 @@ def playlist():
to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False
playlists.document().set(to_add)
logger.info(f'added {session["username"]} / {playlist_name}')
logger.info(f'added {username} / {playlist_name}')
return jsonify({"message": 'playlist added', "status": "success"}), 201
@ -261,22 +291,22 @@ def playlist():
dic['type'] = playlist_type
if len(dic) == 0:
logger.warning(f'no changes to make for {session["username"]} / {playlist_name}')
logger.warning(f'no changes to make for {username} / {playlist_name}')
return jsonify({"message": 'no changes to make', "status": "error"}), 400
playlist_doc.update(dic)
logger.info(f'updated {session["username"]} / {playlist_name}')
logger.info(f'updated {username} / {playlist_name}')
return jsonify({"message": 'playlist updated', "status": "success"}), 200
@blueprint.route('/user', methods=['GET', 'POST'])
@login_required
def user():
@login_or_basic_auth
def user(username=None):
if request.method == 'GET':
pulled_user = database.get_user_doc_ref(session['username']).get().to_dict()
pulled_user = database.get_user_doc_ref(username).get().to_dict()
response = {
'username': pulled_user['username'],
@ -289,7 +319,7 @@ def user():
else:
if database.get_user_doc_ref(session['username']).get().to_dict()['type'] != 'admin':
if database.get_user_doc_ref(username).get().to_dict()['type'] != 'admin':
return jsonify({'status': 'error', 'message': 'unauthorized'}), 401
request_json = request.get_json()
@ -383,8 +413,8 @@ def change_password():
@blueprint.route('/playlist/play', methods=['POST'])
@login_required
def play_playlist():
@login_or_basic_auth
def play_playlist(username=None):
request_json = request.get_json()
@ -398,13 +428,14 @@ def play_playlist():
request_add_this_month = request_json.get('add_this_month', False)
request_add_last_month = request_json.get('add_last_month', False)
logger.info(f'playing {session["username"]}')
request_device_name = request_json.get('device_name', None)
if request_parts or request_playlists:
if len(request_parts) > 0 or len(request_playlists) > 0:
logger.info(f'playing {username}')
if (request_parts and len(request_parts) > 0) or (request_playlists and len(request_playlists) > 0):
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_play_user_playlist_task(session['username'],
create_play_user_playlist_task(username,
parts=request_parts,
playlist_type=request_playlist_type,
playlists=request_playlists,
@ -413,9 +444,10 @@ def play_playlist():
recommendation_sample=request_recommendation_sample,
day_boundary=request_day_boundary,
add_this_month=request_add_this_month,
add_last_month=request_add_last_month)
add_last_month=request_add_last_month,
device_name=request_device_name)
else:
play_user_playlist(session['username'],
play_user_playlist(username,
parts=request_parts,
playlist_type=request_playlist_type,
playlists=request_playlists,
@ -424,16 +456,12 @@ def play_playlist():
recommendation_sample=request_recommendation_sample,
day_boundary=request_day_boundary,
add_this_month=request_add_this_month,
add_last_month=request_add_last_month)
add_last_month=request_add_last_month,
device_name=request_device_name)
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
else:
logger.error(f'insufficient playlist/part lengths {session["username"]}')
return jsonify({'error': 'insufficient playlist sources'}), 400
else:
logger.error(f'no playlists/parts {session["username"]}')
logger.error(f'no playlists/parts {username}')
return jsonify({'error': 'insufficient playlist sources'}), 400
@ -454,23 +482,24 @@ def play_playlist_task():
recommendation_sample=payload['recommendation_sample'],
day_boundary=payload['day_boundary'],
add_this_month=payload['add_this_month'],
add_last_month=payload['add_last_month'])
add_last_month=payload['add_last_month'],
device_name=payload['device_name'])
return jsonify({'message': 'executed playlist', 'status': 'success'}), 200
@blueprint.route('/playlist/run', methods=['GET'])
@login_required
def run_playlist():
@login_or_basic_auth
def run_playlist(username=None):
playlist_name = request.args.get('name', None)
if playlist_name:
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
create_run_user_playlist_task(session['username'], playlist_name)
create_run_user_playlist_task(username, playlist_name)
else:
run_user_playlist(session['username'], playlist_name)
run_user_playlist(username, playlist_name)
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
@ -495,13 +524,13 @@ def run_playlist_task():
@blueprint.route('/playlist/run/user', methods=['GET'])
@login_required
def run_user():
@login_or_basic_auth
def run_user(username=None):
if database.get_user_doc_ref(session['username']).get().to_dict()['type'] == 'admin':
user_name = request.args.get('username', session['username'])
if database.get_user_doc_ref(username).get().to_dict()['type'] == 'admin':
user_name = request.args.get('username', username)
else:
user_name = session['username']
user_name = username
execute_user(user_name)
@ -623,7 +652,8 @@ def create_play_user_playlist_task(username,
day_boundary=10,
add_this_month=False,
add_last_month=False,
delay=0):
delay=0,
device_name=None):
task = {
'app_engine_http_request': { # Specify the type of request.
'http_method': 'POST',
@ -638,7 +668,8 @@ def create_play_user_playlist_task(username,
'recommendation_sample': recommendation_sample,
'day_boundary': day_boundary,
'add_this_month': add_this_month,
'add_last_month': add_last_month
'add_last_month': add_last_month,
'device_name': device_name
}).encode()
}
}

View File

@ -136,7 +136,7 @@ def auth():
{
'client_id': client_id,
'response_type': 'code',
'scope': 'playlist-modify-public playlist-modify-private playlist-read-private user-modify-playback-state user-library-read',
'scope': 'playlist-modify-public playlist-modify-private playlist-read-private user-read-playback-state user-modify-playback-state user-library-read',
'redirect_uri': 'https://spotify.sarsoo.xyz/auth/spotify/token'
}
)

View File

@ -1,12 +1,29 @@
from google.cloud import firestore
import logging
from typing import List, Optional
from werkzeug.security import check_password_hash
db = firestore.Client()
logger = logging.getLogger(__name__)
def check_user_password(username, password):
user = get_user_doc_ref(user=username)
if user:
user_dict = user.get().to_dict()
if check_password_hash(user_dict['password'], password):
return True
else:
logger.error(f'password mismatch {username}')
else:
logger.error(f'user {username} not found')
return False
def get_user_query_stream(user: str) -> List[firestore.DocumentSnapshot]:
users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', user).stream()]

View File

@ -28,7 +28,8 @@ def play_user_playlist(username,
recommendation_sample=10,
day_boundary=10,
add_this_month=False,
add_last_month=False):
add_last_month=False,
device_name=None):
users = database.get_user_query_stream(username)
@ -59,6 +60,16 @@ def play_user_playlist(username,
user_dict['refresh_token'],
user_dict['access_token']))
device = None
if device_name:
devices = net.get_available_devices()
if devices and len(devices) > 0:
device = next((i for i in devices if i.name == device_name), None)
if device is None:
logger.error(f'error selecting device {device_name} to play on')
else:
logger.warning(f'no available devices to play')
engine = PlaylistEngine(net)
player = Player(net)
@ -95,7 +106,7 @@ def play_user_playlist(username,
else:
tracks = engine.make_playlist(params=params)
player.play(tracks=tracks)
player.play(tracks=tracks, device=device)
else:
logger.critical(f'multiple/no user(s) found ({username})')