diff --git a/app/__init__.py b/app/__init__.py index e7a1f3f519..8df1bcca8a 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -10,7 +10,7 @@ from flask_migrate import Migrate, MigrateCommand from flask_script import Manager from flask_login import current_user -from flask_jwt import JWT +from flask_jwt_extended import JWTManager from flask_limiter import Limiter from datetime import timedelta from flask_cors import CORS @@ -26,7 +26,7 @@ import stripe from app.settings import get_settings from app.models import db -from app.api.helpers.jwt import jwt_authenticate, jwt_identity +from app.api.helpers.jwt import jwt_user_loader from app.api.helpers.cache import cache from werkzeug.middleware.profiler import ProfilerMiddleware from app.views import BlueprintsManager @@ -102,10 +102,11 @@ def create_app(): app.logger.setLevel(logging.ERROR) # set up jwt - app.config['JWT_AUTH_USERNAME_KEY'] = 'email' - app.config['JWT_EXPIRATION_DELTA'] = timedelta(seconds=24 * 60 * 60) - app.config['JWT_AUTH_URL_RULE'] = '/auth/session' - _jwt = JWT(app, jwt_authenticate, jwt_identity) + app.config['JWT_HEADER_TYPE'] = 'JWT' + app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(days=1) + app.config['JWT_ERROR_MESSAGE_KEY'] = 'error' + _jwt = JWTManager(app) + _jwt.user_loader_callback_loader(jwt_user_loader) # setup celery app.config['CELERY_BROKER_URL'] = app.config['REDIS_URL'] diff --git a/app/api/admin_statistics_api/events.py b/app/api/admin_statistics_api/events.py index 53f832dd8b..fe2dae28a2 100644 --- a/app/api/admin_statistics_api/events.py +++ b/app/api/admin_statistics_api/events.py @@ -1,6 +1,6 @@ from flask_rest_jsonapi import ResourceDetail from flask import jsonify, Blueprint -from flask_jwt import jwt_required +from flask_jwt_extended import jwt_required from sqlalchemy.sql import text from app.api.bootstrap import api @@ -12,7 +12,7 @@ @event_statistics.route('/event-topics', methods=['GET']) -@jwt_required() +@jwt_required def event_topic_count(): result_set = db.engine.execute(text( "SELECT event_topics.name AS name, event_topics.id AS id, " + @@ -25,7 +25,7 @@ def event_topic_count(): @event_statistics.route('/event-types', methods=['GET']) -@jwt_required() +@jwt_required def event_types_count(): result_set = db.engine.execute(text( "SELECT event_types.name AS name, event_types.id AS id, " + diff --git a/app/api/attendees.py b/app/api/attendees.py index 7629b975ab..e5017e7786 100644 --- a/app/api/attendees.py +++ b/app/api/attendees.py @@ -1,7 +1,7 @@ from datetime import datetime from flask import Blueprint, request, jsonify, abort, make_response -from flask_jwt import current_identity +from flask_jwt_extended import current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from flask_rest_jsonapi.exceptions import ObjectNotFound from sqlalchemy.orm.exc import NoResultFound @@ -146,7 +146,7 @@ def before_get_object(self, view_kwargs): :return: """ attendee = safe_query(self, TicketHolder, 'id', view_kwargs['id'], 'attendee_id') - if not has_access('is_registrar_or_user_itself', user_id=current_identity.id, event_id=attendee.event_id): + if not has_access('is_registrar_or_user_itself', user_id=current_user.id, event_id=attendee.event_id): raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.') def before_delete_object(self, obj, kwargs): @@ -171,7 +171,7 @@ def before_update_object(self, obj, data, kwargs): # raise ForbiddenException({'source': 'User'}, 'You are not authorized to access this.') if 'ticket' in data: - user = safe_query(self, User, 'id', current_identity.id, 'user_id') + user = safe_query(self, User, 'id', current_user.id, 'user_id') ticket = db.session.query(Ticket).filter_by( id=int(data['ticket']), deleted_at=None ).first() @@ -278,7 +278,7 @@ def send_receipt(): except NoResultFound: raise ObjectNotFound({'parameter': '{identifier}'}, "Order not found") - if (order.user_id != current_identity.id) and (not has_access('is_registrar', event_id=order.event_id)): + if (order.user_id != current_user.id) and (not has_access('is_registrar', event_id=order.event_id)): abort( make_response(jsonify(error="You need to be the event organizer or order buyer to send receipts."), 403) ) @@ -287,7 +287,7 @@ def send_receipt(): make_response(jsonify(error="Cannot send receipt for an incomplete order"), 409) ) else: - send_email_to_attendees(order, current_identity.id) + send_email_to_attendees(order, current_user.id) return jsonify(message="receipt sent to attendees") else: abort( diff --git a/app/api/auth.py b/app/api/auth.py index 08f61f0d01..a5be1ea293 100644 --- a/app/api/auth.py +++ b/app/api/auth.py @@ -7,7 +7,7 @@ import requests from flask import request, jsonify, make_response, Blueprint, send_file -from flask_jwt import current_identity as current_user, jwt_required +from flask_jwt_extended import jwt_required, current_user, create_access_token from flask_limiter.util import get_remote_address from healthcheck import EnvironmentDump from flask_rest_jsonapi.exceptions import ObjectNotFound @@ -17,6 +17,7 @@ from app import limiter from app.api.helpers.db import save_to_db, get_count, safe_query from app.api.helpers.auth import AuthManager +from app.api.helpers.jwt import jwt_authenticate from app.api.helpers.errors import ForbiddenError, UnprocessableEntityError, NotFoundError, BadRequestError from app.api.helpers.files import make_frontend_url from app.api.helpers.mail import send_email_to_attendees @@ -44,6 +45,26 @@ auth_routes = Blueprint('auth', __name__, url_prefix='/v1/auth') +@authorised_blueprint.route('/auth/session', methods=['POST']) +@auth_routes.route('/login', methods=['POST']) +def login(): + data = request.get_json() + username = data.get('email', data.get('username')) + password = data.get('password') + criterion = [username, password] + + if not all(criterion): + return jsonify(error='username or password missing'), 400 + + identity = jwt_authenticate(username, password) + + if identity: + access_token = create_access_token(identity.id, fresh=True) + return jsonify(access_token=access_token) + else: + return jsonify(error='Invalid Credentials'), 401 + + @auth_routes.route('/oauth/', methods=['GET']) def redirect_uri(provider): if provider == 'facebook': @@ -266,7 +287,7 @@ def reset_password_patch(): @auth_routes.route('/change-password', methods=['POST']) -@jwt_required() +@jwt_required def change_password(): old_password = request.json['data']['old-password'] new_password = request.json['data']['new-password'] @@ -307,7 +328,7 @@ def return_file(file_name_prefix, file_path, identifier): @ticket_blueprint.route('/tickets/') -@jwt_required() +@jwt_required def ticket_attendee_authorized(order_identifier): if current_user: try: @@ -329,7 +350,7 @@ def ticket_attendee_authorized(order_identifier): @ticket_blueprint.route('/orders/invoices/') -@jwt_required() +@jwt_required def order_invoices(order_identifier): if current_user: try: @@ -351,7 +372,7 @@ def order_invoices(order_identifier): @ticket_blueprint.route('/events/invoices/') -@jwt_required() +@jwt_required def event_invoices(invoice_identifier): if not current_user: return ForbiddenError({'source': ''}, 'Authentication Required to access Invoice').respond() diff --git a/app/api/discount_codes.py b/app/api/discount_codes.py index 10d8869fed..4e7d9e5b0a 100644 --- a/app/api/discount_codes.py +++ b/app/api/discount_codes.py @@ -1,5 +1,6 @@ from datetime import datetime +from flask_jwt_extended import current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from flask_rest_jsonapi.exceptions import ObjectNotFound from sqlalchemy.orm.exc import NoResultFound @@ -7,7 +8,7 @@ from app.api.helpers.db import safe_query from app.api.helpers.exceptions import ConflictException, ForbiddenException, UnprocessableEntity, MethodNotAllowed from app.api.helpers.permission_manager import has_access -from app.api.helpers.permissions import jwt_required, current_identity +from app.api.helpers.permissions import jwt_required from app.api.helpers.utilities import require_relationship from app.api.schema.discount_codes import DiscountCodeSchemaEvent, DiscountCodeSchemaPublic, DiscountCodeSchemaTicket from app.models import db @@ -52,7 +53,7 @@ def before_post(self, args, kwargs, data): elif data['used_for'] == 'event' and not has_access('is_admin') and 'events' in data: raise UnprocessableEntity({'source': ''}, "Please verify your permission or check your relationship") - data['user_id'] = current_identity.id + data['user_id'] = current_user.id def before_create_object(self, data, view_kwargs): if data['used_for'] == 'event': diff --git a/app/api/events.py b/app/api/events.py index ca07989aa4..9e2767271b 100644 --- a/app/api/events.py +++ b/app/api/events.py @@ -1,5 +1,5 @@ from flask import request, current_app -from flask_jwt import current_identity, _jwt_required +from flask_jwt_extended import verify_jwt_in_request, current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from flask_rest_jsonapi.exceptions import ObjectNotFound from marshmallow_jsonapi import fields @@ -105,9 +105,9 @@ def query(self, view_kwargs): """ query_ = self.session.query(Event).filter_by(state='published') if 'Authorization' in request.headers: - _jwt_required(current_app.config['JWT_DEFAULT_REALM']) + verify_jwt_in_request() query2 = self.session.query(Event) - query2 = query2.join(Event.roles).filter_by(user_id=current_identity.id).join(UsersEventsRoles.role). \ + query2 = query2.join(Event.roles).filter_by(user_id=current_user.id).join(UsersEventsRoles.role). \ filter(or_(Role.name == COORGANIZER, Role.name == ORGANIZER, Role.name == OWNER)) query_ = query_.union(query2) @@ -455,7 +455,7 @@ def before_patch(self, args, kwargs, data=None): :param data: :return: """ - user = User.query.filter_by(id=current_identity.id).one() + user = User.query.filter_by(id=current_user.id).one() modules = Module.query.first() validate_event(user, modules, data) diff --git a/app/api/exports.py b/app/api/exports.py index 2949293202..ec7cc1dc82 100644 --- a/app/api/exports.py +++ b/app/api/exports.py @@ -2,7 +2,7 @@ from flask import send_file, make_response, jsonify, url_for, \ current_app, request, Blueprint -from flask_jwt import jwt_required, current_identity +from flask_jwt_extended import jwt_required, current_user from app.api.helpers.export_helpers import export_event_json, create_export_job from app.api.helpers.utilities import TASK_RESULTS @@ -20,7 +20,7 @@ @export_routes.route('/events//export/json', methods=['POST']) -@jwt_required() +@jwt_required def export_event(event_identifier): from .helpers.tasks import export_event_task @@ -37,7 +37,7 @@ def export_event(event_identifier): event_id = event_identifier # queue task task = export_event_task.delay( - current_identity.email, event_id, settings) + current_user.email, event_id, settings) # create Job create_export_job(task.id, event_id) @@ -54,7 +54,7 @@ def export_event(event_identifier): @export_routes.route('/events//exports/') -@jwt_required() +@jwt_required def export_download(event_id, path): if not path.startswith('/'): path = '/' + path @@ -66,7 +66,7 @@ def export_download(event_id, path): @export_routes.route('/events//export/xcal', methods=['GET']) -@jwt_required() +@jwt_required def export_event_xcal(event_identifier): if not event_identifier.isdigit(): @@ -95,7 +95,7 @@ def event_export_task_base(event_id, settings): @export_routes.route('/events//export/ical', methods=['GET']) -@jwt_required() +@jwt_required def export_event_ical(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -115,7 +115,7 @@ def export_event_ical(event_identifier): @export_routes.route('/events//export/pentabarf', methods=['GET']) -@jwt_required() +@jwt_required def export_event_pentabarf(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -135,7 +135,7 @@ def export_event_pentabarf(event_identifier): @export_routes.route('/events//export/orders/csv', methods=['GET']) -@jwt_required() +@jwt_required def export_orders_csv(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -155,7 +155,7 @@ def export_orders_csv(event_identifier): @export_routes.route('/events//export/orders/pdf', methods=['GET']) -@jwt_required() +@jwt_required def export_orders_pdf(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -175,7 +175,7 @@ def export_orders_pdf(event_identifier): @export_routes.route('/events//export/attendees/csv', methods=['GET']) -@jwt_required() +@jwt_required def export_attendees_csv(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -195,7 +195,7 @@ def export_attendees_csv(event_identifier): @export_routes.route('/events//export/attendees/pdf', methods=['GET']) -@jwt_required() +@jwt_required def export_attendees_pdf(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -215,7 +215,7 @@ def export_attendees_pdf(event_identifier): @export_routes.route('/events//export/sessions/csv', methods=['GET']) -@jwt_required() +@jwt_required def export_sessions_csv(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -235,7 +235,7 @@ def export_sessions_csv(event_identifier): @export_routes.route('/events//export/speakers/csv', methods=['GET']) -@jwt_required() +@jwt_required def export_speakers_csv(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -255,7 +255,7 @@ def export_speakers_csv(event_identifier): @export_routes.route('/events//export/sessions/pdf', methods=['GET']) -@jwt_required() +@jwt_required def export_sessions_pdf(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() @@ -275,7 +275,7 @@ def export_sessions_pdf(event_identifier): @export_routes.route('/events//export/speakers/pdf', methods=['GET']) -@jwt_required() +@jwt_required def export_speakers_pdf(event_identifier): if not event_identifier.isdigit(): event = db.session.query(Event).filter_by(identifier=event_identifier).first() diff --git a/app/api/feedbacks.py b/app/api/feedbacks.py index 85c0949b95..14d8f304a9 100644 --- a/app/api/feedbacks.py +++ b/app/api/feedbacks.py @@ -1,6 +1,6 @@ +from flask_jwt_extended import current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from flask_rest_jsonapi.exceptions import ObjectNotFound -from flask_jwt import current_identity as current_user from app.api.bootstrap import api from app.api.helpers.db import safe_query diff --git a/app/api/helpers/export_helpers.py b/app/api/helpers/export_helpers.py index 7876666c19..30ec3de0e9 100644 --- a/app/api/helpers/export_helpers.py +++ b/app/api/helpers/export_helpers.py @@ -8,8 +8,8 @@ import requests from flask import current_app as app from flask import request, url_for -from flask_jwt import current_identity -from flask_login import current_user +from flask_jwt_extended import current_user +from flask_login import current_user as current_logged_user from app.api.helpers.db import save_to_db from app.api.helpers.storage import upload, UPLOAD_PATHS, UploadedFile @@ -231,10 +231,10 @@ def export_event_json(event_id, settings): def get_current_user(): - if current_identity: - return current_identity - else: + if current_user: return current_user + else: + return current_logged_user # HELPERS diff --git a/app/api/helpers/import_helpers.py b/app/api/helpers/import_helpers.py index ded716d3a5..d8554650c3 100644 --- a/app/api/helpers/import_helpers.py +++ b/app/api/helpers/import_helpers.py @@ -8,7 +8,7 @@ import requests from flask import current_app as app from flask import request -from flask_jwt import current_identity +from flask_jwt_extended import current_user from werkzeug import secure_filename from app.api.helpers.db import save_to_db @@ -149,7 +149,7 @@ def _delete_fields(srv, data): def create_import_job(task): """create import record in db""" ij = ImportJob(task=task, - user=current_identity) + user=current_user) save_to_db(ij, 'Import job saved') diff --git a/app/api/helpers/jwt.py b/app/api/helpers/jwt.py index e70bba1049..239f52879f 100644 --- a/app/api/helpers/jwt.py +++ b/app/api/helpers/jwt.py @@ -1,8 +1,12 @@ import base64 import json -from flask_jwt import _default_request_handler +from flask import _app_ctx_stack as ctx_stack +from flask_jwt_extended.view_decorators import _decode_jwt_from_request, _load_user +from flask_jwt_extended.config import config +from flask_jwt_extended.exceptions import JWTExtendedException, UserLoadError from flask_scrypt import check_password_hash +from jwt.exceptions import PyJWTError from app.models.user import User @@ -24,13 +28,8 @@ def jwt_authenticate(email, password): return None -def jwt_identity(payload): - """ - Jwt helper function - :param payload: - :return: - """ - return User.query.get(payload['identity']) +def jwt_user_loader(identity): + return User.query.filter_by(id=identity, deleted_at=None).first() def get_identity(): @@ -38,13 +37,15 @@ def get_identity(): To be used only if identity for expired tokens is required, otherwise use current_identity from flask_jwt :return: """ - token_second_segment = _default_request_handler().split('.')[1] - missing_padding = len(token_second_segment) % 4 - - # ensures the string is correctly padded to be a multiple of 4 - if missing_padding != 0: - token_second_segment += '=' * (4 - missing_padding) - - payload = json.loads(str(base64.b64decode(token_second_segment), 'utf-8')) - user = jwt_identity(payload) - return user + token = None + try: + token = _decode_jwt_from_request('access') + except (JWTExtendedException, PyJWTError): + token = getattr(ctx_stack.top, 'expired_jwt', None) + + if token: + try: + _load_user(token[config.identity_claim_key]) + return getattr(ctx_stack.top, 'jwt_user', None) + except UserLoadError: + pass diff --git a/app/api/helpers/permission_manager.py b/app/api/helpers/permission_manager.py index 924a2e3a5a..05a9d19637 100644 --- a/app/api/helpers/permission_manager.py +++ b/app/api/helpers/permission_manager.py @@ -1,5 +1,5 @@ from flask import current_app as app -from flask_jwt import _jwt_required, current_identity +from flask_jwt_extended import verify_jwt_in_request, current_user from sqlalchemy.orm.exc import NoResultFound from flask import request @@ -24,7 +24,7 @@ def is_super_admin(view, view_args, view_kwargs, *args, **kwargs): Do not use this if the resource is also accessible by a normal admin, use the is_admin decorator instead. :return: """ - user = current_identity + user = current_user if not user.is_super_admin: return ForbiddenError({'source': ''}, 'Super admin access is required').respond() return view(*view_args, **view_kwargs) @@ -32,7 +32,7 @@ def is_super_admin(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def is_admin(view, view_args, view_kwargs, *args, **kwargs): - user = current_identity + user = current_user if not user.is_admin and not user.is_super_admin: return ForbiddenError({'source': ''}, 'Admin access is required').respond() @@ -41,7 +41,7 @@ def is_admin(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def is_owner(view, view_args, view_kwargs, *args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return view(*view_args, **view_kwargs) @@ -54,7 +54,7 @@ def is_owner(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def is_organizer(view, view_args, view_kwargs, *args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return view(*view_args, **view_kwargs) @@ -67,7 +67,7 @@ def is_organizer(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def is_coorganizer(view, view_args, view_kwargs, *args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return view(*view_args, **view_kwargs) @@ -80,7 +80,7 @@ def is_coorganizer(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def is_coorganizer_but_not_admin(view, view_args, view_kwargs, *args, **kwargs): - user = current_identity + user = current_user if user.has_event_access(kwargs['event_id']): return view(*view_args, **view_kwargs) @@ -104,11 +104,11 @@ def is_coorganizer_endpoint_related_to_event(view, view_args, view_kwargs, *args user = get_identity() if user.is_staff: - _jwt_required(app.config['JWT_DEFAULT_REALM']) + verify_jwt_in_request() return view(*view_args, **view_kwargs) if user.has_event_access(kwargs['event_id']): - _jwt_required(app.config['JWT_DEFAULT_REALM']) + verify_jwt_in_request() return view(*view_args, **view_kwargs) return ForbiddenError({'source': ''}, 'Co-organizer access is required.').respond() @@ -120,7 +120,7 @@ def is_user_itself(view, view_args, view_kwargs, *args, **kwargs): Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ - user = current_identity + user = current_user if not user.is_admin and not user.is_super_admin and user.id != kwargs['user_id']: return ForbiddenError({'source': ''}, 'Access Forbidden').respond() return view(*view_args, **view_kwargs) @@ -132,7 +132,7 @@ def is_coorganizer_or_user_itself(view, view_args, view_kwargs, *args, **kwargs) Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ - user = current_identity + user = current_user if user.is_admin or user.is_super_admin or ('user_id' in kwargs and user.id == kwargs['user_id']): return view(*view_args, **view_kwargs) @@ -152,7 +152,7 @@ def is_speaker_for_session(view, view_args, view_kwargs, *args, **kwargs): Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ - user = current_identity + user = current_user if user.is_admin or user.is_super_admin: return view(*view_args, **view_kwargs) @@ -184,7 +184,7 @@ def is_speaker_itself_or_admin(view, view_args, view_kwargs, *args, **kwargs): Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ - user = current_identity + user = current_user if user.is_admin or user.is_super_admin: return view(*view_args, **view_kwargs) @@ -206,7 +206,7 @@ def is_session_self_submitted(view, view_args, view_kwargs, *args, **kwargs): Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ - user = current_identity + user = current_user if user.is_admin or user.is_super_admin: return view(*view_args, **view_kwargs) @@ -232,7 +232,7 @@ def is_registrar(view, view_args, view_kwargs, *args, **kwargs): """ Allows Organizer, Co-organizer and registrar to access the event resources.x` """ - user = current_identity + user = current_user event_id = kwargs['event_id'] if user.is_staff: @@ -248,7 +248,7 @@ def is_registrar_or_user_itself(view, view_args, view_kwargs, *args, **kwargs): Allows admin and super admin access to any resource irrespective of id. Otherwise the user can only access his/her resource. """ - user = current_identity + user = current_user if user.is_admin or user.is_super_admin or user.id == kwargs['user_id']: return view(*view_args, **view_kwargs) @@ -267,7 +267,7 @@ def is_track_organizer(view, view_args, view_kwargs, *args, **kwargs): """ Allows Organizer, Co-organizer and Track Organizer to access the resource(s). """ - user = current_identity + user = current_user event_id = kwargs['event_id'] if user.is_staff: @@ -282,7 +282,7 @@ def is_moderator(view, view_args, view_kwargs, *args, **kwargs): """ Allows Organizer, Co-organizer and Moderator to access the resource(s). """ - user = current_identity + user = current_user event_id = kwargs['event_id'] if user.is_staff: return view(*view_args, **view_kwargs) @@ -293,15 +293,15 @@ def is_moderator(view, view_args, view_kwargs, *args, **kwargs): @jwt_required def user_event(view, view_args, view_kwargs, *args, **kwargs): - user = current_identity + user = current_user view_kwargs['user_id'] = user.id return view(*view_args, **view_kwargs) def accessible_role_based_events(view, view_args, view_kwargs, *args, **kwargs): if 'POST' in request.method or 'withRole' in request.args: - _jwt_required(app.config['JWT_DEFAULT_REALM']) - user = current_identity + verify_jwt_in_request() + user = current_user if 'GET' in request.method and user.is_staff: return view(*view_args, **view_kwargs) @@ -312,8 +312,8 @@ def accessible_role_based_events(view, view_args, view_kwargs, *args, **kwargs): def create_event(view, view_args, view_kwargs, *args, **kwargs): if 'POST' in request.method or 'withRole' in request.args: - _jwt_required(app.config['JWT_DEFAULT_REALM']) - user = current_identity + verify_jwt_in_request() + user = current_user if user.can_create_event is False: return ForbiddenError({'source': ''}, 'Please verify your email').respond() diff --git a/app/api/helpers/permissions.py b/app/api/helpers/permissions.py index 91d4ce8003..1b74b06015 100644 --- a/app/api/helpers/permissions.py +++ b/app/api/helpers/permissions.py @@ -1,6 +1,6 @@ from functools import wraps from flask import current_app as app -from flask_jwt import _jwt_required, current_identity +from flask_jwt_extended import verify_jwt_in_request, current_user from app.api.helpers.db import save_to_db from app.api.helpers.errors import ForbiddenError @@ -39,9 +39,9 @@ def jwt_required(fn, realm=None): """ @wraps(fn) def decorator(*args, **kwargs): - _jwt_required(realm or app.config['JWT_DEFAULT_REALM']) - current_identity.last_accessed_at = datetime.utcnow() - save_to_db(current_identity) + verify_jwt_in_request() + current_user.last_accessed_at = datetime.utcnow() + save_to_db(current_user) return fn(*args, **kwargs) return decorator @@ -58,7 +58,7 @@ def is_super_admin(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if not user.is_super_admin: return ForbiddenError({'source': ''}, 'Super admin access is required').respond() return f(*args, **kwargs) @@ -76,7 +76,7 @@ def is_admin(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if not user.is_admin and not user.is_super_admin: return ForbiddenError({'source': ''}, 'Admin access is required').respond() return f(*args, **kwargs) @@ -95,7 +95,7 @@ def is_user_itself(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if not user.is_admin and not user.is_super_admin and user.id != kwargs['id']: return ForbiddenError({'source': ''}, 'Access Forbidden').respond() return f(*args, **kwargs) @@ -113,7 +113,7 @@ def is_owner(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return f(*args, **kwargs) @@ -134,7 +134,7 @@ def is_organizer(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return f(*args, **kwargs) @@ -155,7 +155,7 @@ def is_coorganizer(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return f(*args, **kwargs) @@ -176,7 +176,7 @@ def is_registrar(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return f(*args, **kwargs) @@ -199,7 +199,7 @@ def is_track_organizer(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return f(*args, **kwargs) @@ -222,7 +222,7 @@ def is_moderator(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if user.is_staff: return f(*args, **kwargs) @@ -247,7 +247,7 @@ def accessible_events(f): @wraps(f) def decorated_function(*args, **kwargs): - user = current_identity + user = current_user if 'POST' in request.method: kwargs['user_id'] = user.id else: diff --git a/app/api/helpers/ticketing.py b/app/api/helpers/ticketing.py index 1ad2112da6..14a02aeb61 100644 --- a/app/api/helpers/ticketing.py +++ b/app/api/helpers/ticketing.py @@ -1,7 +1,5 @@ from datetime import datetime -from flask_jwt import current_identity as current_user - from app.api.helpers.db import save_to_db, get_count from app.api.helpers.exceptions import ConflictException from app.api.helpers.files import make_frontend_url diff --git a/app/api/import_jobs.py b/app/api/import_jobs.py index cb7465f214..05e2b83be3 100644 --- a/app/api/import_jobs.py +++ b/app/api/import_jobs.py @@ -4,7 +4,7 @@ from app.models import db from app.models.import_job import ImportJob from app.api.helpers.permissions import jwt_required -from flask_jwt import current_identity +from flask_jwt_extended import current_user class ImportJobList(ResourceList): @@ -13,7 +13,7 @@ class ImportJobList(ResourceList): """ def query(self, kwargs): query_ = self.session.query(ImportJob) - query_ = query_.filter_by(user_id=current_identity.id) + query_ = query_.filter_by(user_id=current_user.id) return query_ decorators = (jwt_required,) diff --git a/app/api/imports.py b/app/api/imports.py index d45f9d34c2..31d8031814 100644 --- a/app/api/imports.py +++ b/app/api/imports.py @@ -1,5 +1,5 @@ from flask import jsonify, url_for, current_app, Blueprint, abort -from flask_jwt import jwt_required, current_identity +from flask_jwt_extended import jwt_required, current_user from app.api.helpers.files import make_frontend_url from app.api.helpers.import_helpers import get_file_from_request, import_event_json, create_import_job @@ -9,7 +9,7 @@ @import_routes.route('/events/import/', methods=['POST']) -@jwt_required() +@jwt_required def import_event(source_type): if source_type == 'json': file_path = get_file_from_request(['zip']) @@ -17,8 +17,8 @@ def import_event(source_type): file_path = None abort(404) from .helpers.tasks import import_event_task - task = import_event_task.delay(email=current_identity.email, file=file_path, - source_type=source_type, creator_id=current_identity.id) + task = import_event_task.delay(email=current_user.email, file=file_path, + source_type=source_type, creator_id=current_user.id) # create import job create_import_job(task.id) diff --git a/app/api/orders.py b/app/api/orders.py index 2c9737f7f9..4737f9a7c4 100644 --- a/app/api/orders.py +++ b/app/api/orders.py @@ -3,7 +3,7 @@ import omise from flask import request, jsonify, Blueprint, url_for, redirect -from flask_jwt import current_identity as current_user +from flask_jwt_extended import current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from marshmallow_jsonapi import fields from marshmallow_jsonapi.flask import Schema diff --git a/app/api/sessions.py b/app/api/sessions.py index ef1f8cb4b1..8bffa70dde 100644 --- a/app/api/sessions.py +++ b/app/api/sessions.py @@ -1,3 +1,4 @@ +from flask_jwt_extended import current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from app.api.bootstrap import api @@ -6,7 +7,6 @@ from app.api.helpers.exceptions import ForbiddenException from app.api.helpers.mail import send_email_new_session, send_email_session_accept_reject from app.api.helpers.notification import send_notif_new_session_organizer, send_notif_session_accept_reject -from app.api.helpers.permissions import current_identity from app.api.helpers.permission_manager import has_access from app.api.helpers.query import event_query from app.api.helpers.utilities import require_relationship @@ -36,7 +36,7 @@ def before_post(self, args, kwargs, data): :return: """ require_relationship(['event', 'track'], data) - data['creator_id'] = current_identity.id + data['creator_id'] = current_user.id if get_count(db.session.query(Event).filter_by(id=int(data['event']), is_sessions_speakers_enabled=False)) > 0: raise ForbiddenException({'pointer': ''}, "Sessions are disabled for this Event") diff --git a/app/api/settings.py b/app/api/settings.py index 20ed8838d5..b33c166d44 100644 --- a/app/api/settings.py +++ b/app/api/settings.py @@ -1,6 +1,6 @@ from flask import current_app as app from flask import jsonify, request, Blueprint, make_response -from flask_jwt import current_identity as current_user, _jwt_required +from flask_jwt_extended import verify_jwt_in_request, current_user from flask_rest_jsonapi import ResourceDetail from app.api.bootstrap import api @@ -39,7 +39,7 @@ def before_get(self, args, kwargs): kwargs['id'] = 1 if 'Authorization' in request.headers: - _jwt_required(app.config['JWT_DEFAULT_REALM']) + verify_jwt_in_request() if current_user.is_admin or current_user.is_super_admin: self.schema = SettingSchemaAdmin diff --git a/app/api/tickets.py b/app/api/tickets.py index b6c5cebd71..8faac8e15e 100644 --- a/app/api/tickets.py +++ b/app/api/tickets.py @@ -1,7 +1,7 @@ from flask import request, current_app from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from flask_rest_jsonapi.exceptions import ObjectNotFound -from flask_jwt import current_identity as current_user, _jwt_required +from flask_jwt_extended import current_user, verify_jwt_in_request from sqlalchemy.orm.exc import NoResultFound from app.api.bootstrap import api @@ -92,7 +92,7 @@ def query(self, view_kwargs): """ if 'Authorization' in request.headers: - _jwt_required(current_app.config['JWT_DEFAULT_REALM']) + verify_jwt_in_request() if current_user.is_super_admin or current_user.is_admin: query_ = self.session.query(Ticket) elif view_kwargs.get('event_id') and has_access('is_organizer', event_id=view_kwargs['event_id']): diff --git a/app/api/uploads.py b/app/api/uploads.py index 67232fc7ef..3900dbefba 100644 --- a/app/api/uploads.py +++ b/app/api/uploads.py @@ -1,6 +1,6 @@ from flask import Blueprint from flask import make_response, request, jsonify, abort -from flask_jwt import jwt_required +from flask_jwt_extended import jwt_required from app.api.helpers.files import uploaded_image, uploaded_file from app.api.helpers.storage import UPLOAD_PATHS, upload_local, upload import uuid @@ -9,7 +9,7 @@ @upload_routes.route('/image', methods=['POST']) -@jwt_required() +@jwt_required def upload_image(): image = request.json['data'] extension = '.{}'.format(image.split(";")[0].split("/")[1]) @@ -29,7 +29,7 @@ def upload_image(): @upload_routes.route('/files', methods=['POST']) -@jwt_required() +@jwt_required def upload_file(): force_local = request.args.get('force_local', 'false') if 'file' in request.files: diff --git a/app/api/user_favourite_events.py b/app/api/user_favourite_events.py index 36051bc57a..46c5fd6e38 100644 --- a/app/api/user_favourite_events.py +++ b/app/api/user_favourite_events.py @@ -1,5 +1,5 @@ from flask import request, current_app as app -from flask_jwt import current_identity as current_user, _jwt_required, jwt_required +from flask_jwt_extended import current_user, jwt_required, verify_jwt_in_request from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from flask_rest_jsonapi.exceptions import ObjectNotFound from sqlalchemy.orm.exc import NoResultFound @@ -30,7 +30,7 @@ def before_post(self, args, kwargs, data): require_relationship(['event'], data) if 'Authorization' in request.headers: - _jwt_required(app.config['JWT_DEFAULT_REALM']) + verify_jwt_in_request() else: raise ForbiddenException({'source': ''}, 'Only Authorized Users can favourite an event') @@ -81,7 +81,7 @@ class UserFavouriteEventDetail(ResourceDetail): """ User Favourite Events detail by id """ - @jwt_required() + @jwt_required def before_get_object(self, view_kwargs): if view_kwargs.get('id') is not None: diff --git a/app/api/users.py b/app/api/users.py index f86f4e7c85..c6f6e11d64 100644 --- a/app/api/users.py +++ b/app/api/users.py @@ -1,7 +1,7 @@ import base64 from flask import Blueprint, request, jsonify, abort, make_response -from flask_jwt import current_identity as current_user +from flask_jwt_extended import current_user from flask_rest_jsonapi import ResourceDetail, ResourceList, ResourceRelationship from sqlalchemy.orm.exc import NoResultFound import urllib.error diff --git a/requirements/common.txt b/requirements/common.txt index 9221d2a73a..cb4eec4fd0 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -6,7 +6,7 @@ Flask-SQLAlchemy~=2.1 Flask-Migrate~=2.5 Flask-Login~=0.4 Flask-Scrypt~=0.1.3 -Flask-JWT~=0.3.2 +flask-jwt-extended~=3.20.0 flask-celeryext~=0.3 omise~=0.8.1 requests-oauthlib~=0.8 diff --git a/tests/all/integration/api/helpers/test_jwt.py b/tests/all/integration/api/helpers/test_jwt.py index a0fbb1f56b..5459e29fc4 100644 --- a/tests/all/integration/api/helpers/test_jwt.py +++ b/tests/all/integration/api/helpers/test_jwt.py @@ -1,6 +1,6 @@ import unittest -from flask_jwt import _default_jwt_encode_handler +from flask_jwt_extended import create_access_token from app import current_app as app from app.api.helpers.jwt import jwt_authenticate, get_identity @@ -43,7 +43,7 @@ def test_get_identity(self): save_to_db(event) # Authenticate User - self.auth = {'Authorization': "JWT " + str(_default_jwt_encode_handler(user), 'utf-8')} + self.auth = {'Authorization': "JWT " + create_access_token(user.id, fresh=True)} with app.test_request_context(headers=self.auth): self.assertEquals(get_identity().id, user.id) diff --git a/tests/all/integration/api/helpers/test_permission_manager.py b/tests/all/integration/api/helpers/test_permission_manager.py index f2fdb76672..10c8f4c6b7 100644 --- a/tests/all/integration/api/helpers/test_permission_manager.py +++ b/tests/all/integration/api/helpers/test_permission_manager.py @@ -1,7 +1,7 @@ import unittest from flask import Response -from flask_jwt import _default_jwt_encode_handler +from flask_jwt_extended import create_access_token from app import current_app as app from app.api.helpers.db import get_or_create, save_to_db @@ -26,7 +26,7 @@ def setUp(self): save_to_db(event) # Authenticate User - self.auth = {'Authorization': "JWT " + str(_default_jwt_encode_handler(user), 'utf-8')} + self.auth = {'Authorization': "JWT " + create_access_token(user.id, fresh=True)} def test_has_access(self): """Method to test whether user has access to different roles"""