added basic auth api support, added device name specification in play
This commit is contained in:
parent
5909229d69
commit
215c839210
@ -25,20 +25,50 @@ task_path = tasker.queue_path('sarsooxyz', 'europe-west2', 'spotify-executions')
|
|||||||
logger = logging.getLogger(__name__)
|
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):
|
def login_required(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def login_required_wrapper(*args, **kwargs):
|
||||||
if 'username' in session:
|
if is_logged_in():
|
||||||
return func(*args, **kwargs)
|
return func(username=session['username'], *args, **kwargs)
|
||||||
else:
|
else:
|
||||||
logger.warning('user not logged in')
|
logger.warning('user not logged in')
|
||||||
return jsonify({'error': 'not logged in'}), 401
|
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):
|
def admin_required(func):
|
||||||
@functools.wraps(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()
|
user_dict = database.get_user_doc_ref(session['username']).get().to_dict()
|
||||||
|
|
||||||
if user_dict:
|
if user_dict:
|
||||||
@ -51,12 +81,12 @@ def admin_required(func):
|
|||||||
logger.warning('user not logged in')
|
logger.warning('user not logged in')
|
||||||
return jsonify({'error': 'not logged in'}), 401
|
return jsonify({'error': 'not logged in'}), 401
|
||||||
|
|
||||||
return wrapper
|
return admin_required_wrapper
|
||||||
|
|
||||||
|
|
||||||
def gae_cron(func):
|
def gae_cron(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def gae_cron_wrapper(*args, **kwargs):
|
||||||
|
|
||||||
if request.headers.get('X-Appengine-Cron', None):
|
if request.headers.get('X-Appengine-Cron', None):
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@ -64,12 +94,12 @@ def gae_cron(func):
|
|||||||
logger.warning('user not logged in')
|
logger.warning('user not logged in')
|
||||||
return jsonify({'status': 'error', 'message': 'unauthorised'}), 401
|
return jsonify({'status': 'error', 'message': 'unauthorised'}), 401
|
||||||
|
|
||||||
return wrapper
|
return gae_cron_wrapper
|
||||||
|
|
||||||
|
|
||||||
def cloud_task(func):
|
def cloud_task(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
def wrapper(*args, **kwargs):
|
def cloud_task_wrapper(*args, **kwargs):
|
||||||
|
|
||||||
if request.headers.get('X-AppEngine-QueueName', None):
|
if request.headers.get('X-AppEngine-QueueName', None):
|
||||||
return func(*args, **kwargs)
|
return func(*args, **kwargs)
|
||||||
@ -77,14 +107,14 @@ def cloud_task(func):
|
|||||||
logger.warning('non tasks request')
|
logger.warning('non tasks request')
|
||||||
return jsonify({'status': 'error', 'message': 'unauthorised'}), 401
|
return jsonify({'status': 'error', 'message': 'unauthorised'}), 401
|
||||||
|
|
||||||
return wrapper
|
return cloud_task_wrapper
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlists', methods=['GET'])
|
@blueprint.route('/playlists', methods=['GET'])
|
||||||
@login_required
|
@login_or_basic_auth
|
||||||
def get_playlists():
|
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')
|
playlists = pulled_user.collection(u'playlists')
|
||||||
|
|
||||||
@ -102,10 +132,10 @@ def get_playlists():
|
|||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT', 'DELETE'])
|
||||||
@login_required
|
@login_or_basic_auth
|
||||||
def playlist():
|
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')
|
playlists = user_ref.collection(u'playlists')
|
||||||
|
|
||||||
if request.method == 'GET' or request.method == 'DELETE':
|
if request.method == 'GET' or request.method == 'DELETE':
|
||||||
@ -131,7 +161,7 @@ def playlist():
|
|||||||
|
|
||||||
elif request.method == 'DELETE':
|
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()
|
queried_playlist[0].reference.delete()
|
||||||
|
|
||||||
return jsonify({"message": 'playlist deleted', "status": "success"}), 200
|
return jsonify({"message": 'playlist deleted', "status": "success"}), 200
|
||||||
@ -199,7 +229,7 @@ def playlist():
|
|||||||
}
|
}
|
||||||
|
|
||||||
if user_ref.get().to_dict()['spotify_linked']:
|
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
|
to_add['uri'] = str(new_playlist.uri) if new_playlist is not None else None
|
||||||
|
|
||||||
if playlist_type == 'recents':
|
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
|
to_add['add_last_month'] = playlist_add_last_month if playlist_add_last_month is not None else False
|
||||||
|
|
||||||
playlists.document().set(to_add)
|
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
|
return jsonify({"message": 'playlist added', "status": "success"}), 201
|
||||||
|
|
||||||
@ -261,22 +291,22 @@ def playlist():
|
|||||||
dic['type'] = playlist_type
|
dic['type'] = playlist_type
|
||||||
|
|
||||||
if len(dic) == 0:
|
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
|
return jsonify({"message": 'no changes to make', "status": "error"}), 400
|
||||||
|
|
||||||
playlist_doc.update(dic)
|
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
|
return jsonify({"message": 'playlist updated', "status": "success"}), 200
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/user', methods=['GET', 'POST'])
|
@blueprint.route('/user', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_or_basic_auth
|
||||||
def user():
|
def user(username=None):
|
||||||
|
|
||||||
if request.method == 'GET':
|
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 = {
|
response = {
|
||||||
'username': pulled_user['username'],
|
'username': pulled_user['username'],
|
||||||
@ -289,7 +319,7 @@ def user():
|
|||||||
|
|
||||||
else:
|
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
|
return jsonify({'status': 'error', 'message': 'unauthorized'}), 401
|
||||||
|
|
||||||
request_json = request.get_json()
|
request_json = request.get_json()
|
||||||
@ -383,8 +413,8 @@ def change_password():
|
|||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlist/play', methods=['POST'])
|
@blueprint.route('/playlist/play', methods=['POST'])
|
||||||
@login_required
|
@login_or_basic_auth
|
||||||
def play_playlist():
|
def play_playlist(username=None):
|
||||||
|
|
||||||
request_json = request.get_json()
|
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_this_month = request_json.get('add_this_month', False)
|
||||||
request_add_last_month = request_json.get('add_last_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:
|
logger.info(f'playing {username}')
|
||||||
if len(request_parts) > 0 or len(request_playlists) > 0:
|
|
||||||
|
if (request_parts and len(request_parts) > 0) or (request_playlists and len(request_playlists) > 0):
|
||||||
|
|
||||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
||||||
create_play_user_playlist_task(session['username'],
|
create_play_user_playlist_task(username,
|
||||||
parts=request_parts,
|
parts=request_parts,
|
||||||
playlist_type=request_playlist_type,
|
playlist_type=request_playlist_type,
|
||||||
playlists=request_playlists,
|
playlists=request_playlists,
|
||||||
@ -413,9 +444,10 @@ def play_playlist():
|
|||||||
recommendation_sample=request_recommendation_sample,
|
recommendation_sample=request_recommendation_sample,
|
||||||
day_boundary=request_day_boundary,
|
day_boundary=request_day_boundary,
|
||||||
add_this_month=request_add_this_month,
|
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:
|
else:
|
||||||
play_user_playlist(session['username'],
|
play_user_playlist(username,
|
||||||
parts=request_parts,
|
parts=request_parts,
|
||||||
playlist_type=request_playlist_type,
|
playlist_type=request_playlist_type,
|
||||||
playlists=request_playlists,
|
playlists=request_playlists,
|
||||||
@ -424,16 +456,12 @@ def play_playlist():
|
|||||||
recommendation_sample=request_recommendation_sample,
|
recommendation_sample=request_recommendation_sample,
|
||||||
day_boundary=request_day_boundary,
|
day_boundary=request_day_boundary,
|
||||||
add_this_month=request_add_this_month,
|
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
|
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error(f'insufficient playlist/part lengths {session["username"]}')
|
logger.error(f'no playlists/parts {username}')
|
||||||
return jsonify({'error': 'insufficient playlist sources'}), 400
|
|
||||||
|
|
||||||
else:
|
|
||||||
logger.error(f'no playlists/parts {session["username"]}')
|
|
||||||
return jsonify({'error': 'insufficient playlist sources'}), 400
|
return jsonify({'error': 'insufficient playlist sources'}), 400
|
||||||
|
|
||||||
|
|
||||||
@ -454,23 +482,24 @@ def play_playlist_task():
|
|||||||
recommendation_sample=payload['recommendation_sample'],
|
recommendation_sample=payload['recommendation_sample'],
|
||||||
day_boundary=payload['day_boundary'],
|
day_boundary=payload['day_boundary'],
|
||||||
add_this_month=payload['add_this_month'],
|
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
|
return jsonify({'message': 'executed playlist', 'status': 'success'}), 200
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlist/run', methods=['GET'])
|
@blueprint.route('/playlist/run', methods=['GET'])
|
||||||
@login_required
|
@login_or_basic_auth
|
||||||
def run_playlist():
|
def run_playlist(username=None):
|
||||||
|
|
||||||
playlist_name = request.args.get('name', None)
|
playlist_name = request.args.get('name', None)
|
||||||
|
|
||||||
if playlist_name:
|
if playlist_name:
|
||||||
|
|
||||||
if os.environ.get('DEPLOY_DESTINATION', None) == 'PROD':
|
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:
|
else:
|
||||||
run_user_playlist(session['username'], playlist_name)
|
run_user_playlist(username, playlist_name)
|
||||||
|
|
||||||
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
return jsonify({'message': 'execution requested', 'status': 'success'}), 200
|
||||||
|
|
||||||
@ -495,13 +524,13 @@ def run_playlist_task():
|
|||||||
|
|
||||||
|
|
||||||
@blueprint.route('/playlist/run/user', methods=['GET'])
|
@blueprint.route('/playlist/run/user', methods=['GET'])
|
||||||
@login_required
|
@login_or_basic_auth
|
||||||
def run_user():
|
def run_user(username=None):
|
||||||
|
|
||||||
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':
|
||||||
user_name = request.args.get('username', session['username'])
|
user_name = request.args.get('username', username)
|
||||||
else:
|
else:
|
||||||
user_name = session['username']
|
user_name = username
|
||||||
|
|
||||||
execute_user(user_name)
|
execute_user(user_name)
|
||||||
|
|
||||||
@ -623,7 +652,8 @@ def create_play_user_playlist_task(username,
|
|||||||
day_boundary=10,
|
day_boundary=10,
|
||||||
add_this_month=False,
|
add_this_month=False,
|
||||||
add_last_month=False,
|
add_last_month=False,
|
||||||
delay=0):
|
delay=0,
|
||||||
|
device_name=None):
|
||||||
task = {
|
task = {
|
||||||
'app_engine_http_request': { # Specify the type of request.
|
'app_engine_http_request': { # Specify the type of request.
|
||||||
'http_method': 'POST',
|
'http_method': 'POST',
|
||||||
@ -638,7 +668,8 @@ def create_play_user_playlist_task(username,
|
|||||||
'recommendation_sample': recommendation_sample,
|
'recommendation_sample': recommendation_sample,
|
||||||
'day_boundary': day_boundary,
|
'day_boundary': day_boundary,
|
||||||
'add_this_month': add_this_month,
|
'add_this_month': add_this_month,
|
||||||
'add_last_month': add_last_month
|
'add_last_month': add_last_month,
|
||||||
|
'device_name': device_name
|
||||||
}).encode()
|
}).encode()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +136,7 @@ def auth():
|
|||||||
{
|
{
|
||||||
'client_id': client_id,
|
'client_id': client_id,
|
||||||
'response_type': 'code',
|
'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'
|
'redirect_uri': 'https://spotify.sarsoo.xyz/auth/spotify/token'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
from google.cloud import firestore
|
from google.cloud import firestore
|
||||||
import logging
|
import logging
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
from werkzeug.security import check_password_hash
|
||||||
|
|
||||||
db = firestore.Client()
|
db = firestore.Client()
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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]:
|
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()]
|
users = [i for i in db.collection(u'spotify_users').where(u'username', u'==', user).stream()]
|
||||||
|
@ -28,7 +28,8 @@ def play_user_playlist(username,
|
|||||||
recommendation_sample=10,
|
recommendation_sample=10,
|
||||||
day_boundary=10,
|
day_boundary=10,
|
||||||
add_this_month=False,
|
add_this_month=False,
|
||||||
add_last_month=False):
|
add_last_month=False,
|
||||||
|
device_name=None):
|
||||||
|
|
||||||
users = database.get_user_query_stream(username)
|
users = database.get_user_query_stream(username)
|
||||||
|
|
||||||
@ -59,6 +60,16 @@ def play_user_playlist(username,
|
|||||||
user_dict['refresh_token'],
|
user_dict['refresh_token'],
|
||||||
user_dict['access_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)
|
engine = PlaylistEngine(net)
|
||||||
|
|
||||||
player = Player(net)
|
player = Player(net)
|
||||||
@ -95,7 +106,7 @@ def play_user_playlist(username,
|
|||||||
else:
|
else:
|
||||||
tracks = engine.make_playlist(params=params)
|
tracks = engine.make_playlist(params=params)
|
||||||
|
|
||||||
player.play(tracks=tracks)
|
player.play(tracks=tracks, device=device)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.critical(f'multiple/no user(s) found ({username})')
|
logger.critical(f'multiple/no user(s) found ({username})')
|
||||||
|
Loading…
Reference in New Issue
Block a user